001/*
002 * Copyright 2007-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk;
022
023
024
025import java.math.BigInteger;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Date;
031import java.util.HashSet;
032import java.util.Iterator;
033import java.util.LinkedHashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037import java.util.StringTokenizer;
038
039import com.unboundid.asn1.ASN1OctetString;
040import com.unboundid.ldap.matchingrules.MatchingRule;
041import com.unboundid.ldap.matchingrules.OctetStringMatchingRule;
042import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
043import com.unboundid.ldap.sdk.schema.Schema;
044import com.unboundid.ldif.LDIFException;
045import com.unboundid.ldif.LDIFReader;
046import com.unboundid.ldif.LDIFRecord;
047import com.unboundid.ldif.LDIFWriter;
048import com.unboundid.util.ByteStringBuffer;
049import com.unboundid.util.Debug;
050import com.unboundid.util.Mutable;
051import com.unboundid.util.NotExtensible;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056
057import static com.unboundid.ldap.sdk.LDAPMessages.*;
058
059
060
061/**
062 * This class provides a data structure for holding information about an LDAP
063 * entry.  An entry contains a distinguished name (DN) and a set of attributes.
064 * An entry can be created from these components, and it can also be created
065 * from its LDIF representation as described in
066 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>.  For example:
067 * <BR><BR>
068 * <PRE>
069 *   Entry entry = new Entry(
070 *     "dn: dc=example,dc=com",
071 *     "objectClass: top",
072 *     "objectClass: domain",
073 *     "dc: example");
074 * </PRE>
075 * <BR><BR>
076 * This class also provides methods for retrieving the LDIF representation of
077 * an entry, either as a single string or as an array of strings that make up
078 * the LDIF lines.
079 * <BR><BR>
080 * The {@link Entry#diff} method may be used to obtain the set of differences
081 * between two entries, and to retrieve a list of {@link Modification} objects
082 * that can be used to modify one entry so that it contains the same set of
083 * data as another.  The {@link Entry#applyModifications} method may be used to
084 * apply a set of modifications to an entry.
085 * <BR><BR>
086 * Entry objects are mutable, and the DN, set of attributes, and individual
087 * attribute values can be altered.
088 */
089@Mutable()
090@NotExtensible()
091@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
092public class Entry
093       implements LDIFRecord
094{
095  /**
096   * An empty octet string that will be used as the value for an attribute that
097   * doesn't have any values.
098   */
099  private static final ASN1OctetString EMPTY_OCTET_STRING =
100       new ASN1OctetString();
101
102
103
104  /**
105   * The serial version UID for this serializable class.
106   */
107  private static final long serialVersionUID = -4438809025903729197L;
108
109
110
111  // The parsed DN for this entry.
112  private volatile DN parsedDN;
113
114  // The set of attributes for this entry.
115  private final LinkedHashMap<String,Attribute> attributes;
116
117  // The schema to use for this entry.
118  private final Schema schema;
119
120  // The DN for this entry.
121  private String dn;
122
123
124
125  /**
126   * Creates a new entry that wraps the provided entry.
127   *
128   * @param  e  The entry to be wrapped.
129   */
130  protected Entry(final Entry e)
131  {
132    parsedDN = e.parsedDN;
133    attributes = e.attributes;
134    schema = e.schema;
135    dn = e.dn;
136  }
137
138
139
140  /**
141   * Creates a new entry with the provided DN and no attributes.
142   *
143   * @param  dn  The DN for this entry.  It must not be {@code null}.
144   */
145  public Entry(final String dn)
146  {
147    this(dn, (Schema) null);
148  }
149
150
151
152  /**
153   * Creates a new entry with the provided DN and no attributes.
154   *
155   * @param  dn      The DN for this entry.  It must not be {@code null}.
156   * @param  schema  The schema to use for operations involving this entry.  It
157   *                 may be {@code null} if no schema is available.
158   */
159  public Entry(final String dn, final Schema schema)
160  {
161    Validator.ensureNotNull(dn);
162
163    this.dn     = dn;
164    this.schema = schema;
165
166    attributes = new LinkedHashMap<>(20);
167  }
168
169
170
171  /**
172   * Creates a new entry with the provided DN and no attributes.
173   *
174   * @param  dn  The DN for this entry.  It must not be {@code null}.
175   */
176  public Entry(final DN dn)
177  {
178    this(dn, (Schema) null);
179  }
180
181
182
183  /**
184   * Creates a new entry with the provided DN and no attributes.
185   *
186   * @param  dn      The DN for this entry.  It must not be {@code null}.
187   * @param  schema  The schema to use for operations involving this entry.  It
188   *                 may be {@code null} if no schema is available.
189   */
190  public Entry(final DN dn, final Schema schema)
191  {
192    Validator.ensureNotNull(dn);
193
194    parsedDN    = dn;
195    this.dn     = parsedDN.toString();
196    this.schema = schema;
197
198    attributes = new LinkedHashMap<>(20);
199  }
200
201
202
203  /**
204   * Creates a new entry with the provided DN and set of attributes.
205   *
206   * @param  dn          The DN for this entry.  It must not be {@code null}.
207   * @param  attributes  The set of attributes for this entry.  It must not be
208   *                     {@code null}.
209   */
210  public Entry(final String dn, final Attribute... attributes)
211  {
212    this(dn, null, attributes);
213  }
214
215
216
217  /**
218   * Creates a new entry with the provided DN and set of attributes.
219   *
220   * @param  dn          The DN for this entry.  It must not be {@code null}.
221   * @param  schema      The schema to use for operations involving this entry.
222   *                     It may be {@code null} if no schema is available.
223   * @param  attributes  The set of attributes for this entry.  It must not be
224   *                     {@code null}.
225   */
226  public Entry(final String dn, final Schema schema,
227               final Attribute... attributes)
228  {
229    Validator.ensureNotNull(dn, attributes);
230
231    this.dn     = dn;
232    this.schema = schema;
233
234    this.attributes = new LinkedHashMap<>(attributes.length);
235    for (final Attribute a : attributes)
236    {
237      final String name = StaticUtils.toLowerCase(a.getName());
238      final Attribute attr = this.attributes.get(name);
239      if (attr == null)
240      {
241        this.attributes.put(name, a);
242      }
243      else
244      {
245        this.attributes.put(name, Attribute.mergeAttributes(attr, a));
246      }
247    }
248  }
249
250
251
252  /**
253   * Creates a new entry with the provided DN and set of attributes.
254   *
255   * @param  dn          The DN for this entry.  It must not be {@code null}.
256   * @param  attributes  The set of attributes for this entry.  It must not be
257   *                     {@code null}.
258   */
259  public Entry(final DN dn, final Attribute... attributes)
260  {
261    this(dn, null, attributes);
262  }
263
264
265
266  /**
267   * Creates a new entry with the provided DN and set of attributes.
268   *
269   * @param  dn          The DN for this entry.  It must not be {@code null}.
270   * @param  schema      The schema to use for operations involving this entry.
271   *                     It may be {@code null} if no schema is available.
272   * @param  attributes  The set of attributes for this entry.  It must not be
273   *                     {@code null}.
274   */
275  public Entry(final DN dn, final Schema schema, final Attribute... attributes)
276  {
277    Validator.ensureNotNull(dn, attributes);
278
279    parsedDN    = dn;
280    this.dn     = parsedDN.toString();
281    this.schema = schema;
282
283    this.attributes = new LinkedHashMap<>(attributes.length);
284    for (final Attribute a : attributes)
285    {
286      final String name = StaticUtils.toLowerCase(a.getName());
287      final Attribute attr = this.attributes.get(name);
288      if (attr == null)
289      {
290        this.attributes.put(name, a);
291      }
292      else
293      {
294        this.attributes.put(name, Attribute.mergeAttributes(attr, a));
295      }
296    }
297  }
298
299
300
301  /**
302   * Creates a new entry with the provided DN and set of attributes.
303   *
304   * @param  dn          The DN for this entry.  It must not be {@code null}.
305   * @param  attributes  The set of attributes for this entry.  It must not be
306   *                     {@code null}.
307   */
308  public Entry(final String dn, final Collection<Attribute> attributes)
309  {
310    this(dn, null, attributes);
311  }
312
313
314
315  /**
316   * Creates a new entry with the provided DN and set of attributes.
317   *
318   * @param  dn          The DN for this entry.  It must not be {@code null}.
319   * @param  schema      The schema to use for operations involving this entry.
320   *                     It may be {@code null} if no schema is available.
321   * @param  attributes  The set of attributes for this entry.  It must not be
322   *                     {@code null}.
323   */
324  public Entry(final String dn, final Schema schema,
325               final Collection<Attribute> attributes)
326  {
327    Validator.ensureNotNull(dn, attributes);
328
329    this.dn     = dn;
330    this.schema = schema;
331
332    this.attributes = new LinkedHashMap<>(attributes.size());
333    for (final Attribute a : attributes)
334    {
335      final String name = StaticUtils.toLowerCase(a.getName());
336      final Attribute attr = this.attributes.get(name);
337      if (attr == null)
338      {
339        this.attributes.put(name, a);
340      }
341      else
342      {
343        this.attributes.put(name, Attribute.mergeAttributes(attr, a));
344      }
345    }
346  }
347
348
349
350  /**
351   * Creates a new entry with the provided DN and set of attributes.
352   *
353   * @param  dn          The DN for this entry.  It must not be {@code null}.
354   * @param  attributes  The set of attributes for this entry.  It must not be
355   *                     {@code null}.
356   */
357  public Entry(final DN dn, final Collection<Attribute> attributes)
358  {
359    this(dn, null, attributes);
360  }
361
362
363
364  /**
365   * Creates a new entry with the provided DN and set of attributes.
366   *
367   * @param  dn          The DN for this entry.  It must not be {@code null}.
368   * @param  schema      The schema to use for operations involving this entry.
369   *                     It may be {@code null} if no schema is available.
370   * @param  attributes  The set of attributes for this entry.  It must not be
371   *                     {@code null}.
372   */
373  public Entry(final DN dn, final Schema schema,
374               final Collection<Attribute> attributes)
375  {
376    Validator.ensureNotNull(dn, attributes);
377
378    parsedDN    = dn;
379    this.dn     = parsedDN.toString();
380    this.schema = schema;
381
382    this.attributes = new LinkedHashMap<>(attributes.size());
383    for (final Attribute a : attributes)
384    {
385      final String name = StaticUtils.toLowerCase(a.getName());
386      final Attribute attr = this.attributes.get(name);
387      if (attr == null)
388      {
389        this.attributes.put(name, a);
390      }
391      else
392      {
393        this.attributes.put(name, Attribute.mergeAttributes(attr, a));
394      }
395    }
396  }
397
398
399
400  /**
401   * Creates a new entry from the provided LDIF representation.
402   *
403   * @param  entryLines  The set of lines that comprise an LDIF representation
404   *                     of the entry.  It must not be {@code null} or empty.
405   *
406   * @throws  LDIFException  If the provided lines cannot be decoded as an entry
407   *                         in LDIF format.
408   */
409  public Entry(final String... entryLines)
410         throws LDIFException
411  {
412    this(null, entryLines);
413  }
414
415
416
417  /**
418   * Creates a new entry from the provided LDIF representation.
419   *
420   * @param  schema      The schema to use for operations involving this entry.
421   *                     It may be {@code null} if no schema is available.
422   * @param  entryLines  The set of lines that comprise an LDIF representation
423   *                     of the entry.  It must not be {@code null} or empty.
424   *
425   * @throws  LDIFException  If the provided lines cannot be decoded as an entry
426   *                         in LDIF format.
427   */
428  public Entry(final Schema schema, final String... entryLines)
429         throws LDIFException
430  {
431    final Entry e = LDIFReader.decodeEntry(false, schema, entryLines);
432
433    this.schema = schema;
434
435    dn         = e.dn;
436    parsedDN   = e.parsedDN;
437    attributes = e.attributes;
438  }
439
440
441
442  /**
443   * Retrieves the DN for this entry.
444   *
445   * @return  The DN for this entry.
446   */
447  @Override()
448  public final String getDN()
449  {
450    return dn;
451  }
452
453
454
455  /**
456   * Specifies the DN for this entry.
457   *
458   * @param  dn  The DN for this entry.  It must not be {@code null}.
459   */
460  public void setDN(final String dn)
461  {
462    Validator.ensureNotNull(dn);
463
464    this.dn = dn;
465    parsedDN = null;
466  }
467
468
469
470  /**
471   * Specifies the DN for this entry.
472   *
473   * @param  dn  The DN for this entry.  It must not be {@code null}.
474   */
475  public void setDN(final DN dn)
476  {
477    Validator.ensureNotNull(dn);
478
479    parsedDN = dn;
480    this.dn  = parsedDN.toString();
481  }
482
483
484
485  /**
486   * Retrieves the parsed DN for this entry.
487   *
488   * @return  The parsed DN for this entry.
489   *
490   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
491   */
492  @Override()
493  public final DN getParsedDN()
494         throws LDAPException
495  {
496    if (parsedDN == null)
497    {
498      parsedDN = new DN(dn, schema);
499    }
500
501    return parsedDN;
502  }
503
504
505
506  /**
507   * Retrieves the RDN for this entry.
508   *
509   * @return  The RDN for this entry, or {@code null} if the DN is the null DN.
510   *
511   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
512   */
513  public final RDN getRDN()
514         throws LDAPException
515  {
516    return getParsedDN().getRDN();
517  }
518
519
520
521  /**
522   * Retrieves the parent DN for this entry.
523   *
524   * @return  The parent DN for this entry, or {@code null} if there is no
525   *          parent.
526   *
527   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
528   */
529  public final DN getParentDN()
530         throws LDAPException
531  {
532    if (parsedDN == null)
533    {
534      parsedDN = new DN(dn, schema);
535    }
536
537    return parsedDN.getParent();
538  }
539
540
541
542  /**
543   * Retrieves the parent DN for this entry as a string.
544   *
545   * @return  The parent DN for this entry as a string, or {@code null} if there
546   *          is no parent.
547   *
548   * @throws  LDAPException  If the DN string cannot be parsed as a valid DN.
549   */
550  public final String getParentDNString()
551         throws LDAPException
552  {
553    if (parsedDN == null)
554    {
555      parsedDN = new DN(dn, schema);
556    }
557
558    final DN parentDN = parsedDN.getParent();
559    if (parentDN == null)
560    {
561      return null;
562    }
563    else
564    {
565      return parentDN.toString();
566    }
567  }
568
569
570
571  /**
572   * Retrieves the schema that will be used for this entry, if any.
573   *
574   * @return  The schema that will be used for this entry, or {@code null} if
575   *          no schema was provided.
576   */
577  protected Schema getSchema()
578  {
579    return schema;
580  }
581
582
583
584  /**
585   * Indicates whether this entry contains the specified attribute.
586   *
587   * @param  attributeName  The name of the attribute for which to make the
588   *                        determination.  It must not be {@code null}.
589   *
590   * @return  {@code true} if this entry contains the specified attribute, or
591   *          {@code false} if not.
592   */
593  public final boolean hasAttribute(final String attributeName)
594  {
595    return hasAttribute(attributeName, schema);
596  }
597
598
599
600  /**
601   * Indicates whether this entry contains the specified attribute.
602   *
603   * @param  attributeName  The name of the attribute for which to make the
604   *                        determination.  It must not be {@code null}.
605   * @param  schema         The schema to use to determine whether there may be
606   *                        alternate names for the specified attribute.  It may
607   *                        be {@code null} if no schema is available.
608   *
609   * @return  {@code true} if this entry contains the specified attribute, or
610   *          {@code false} if not.
611   */
612  public final boolean hasAttribute(final String attributeName,
613                                    final Schema schema)
614  {
615    Validator.ensureNotNull(attributeName);
616
617    if (attributes.containsKey(StaticUtils.toLowerCase(attributeName)))
618    {
619      return true;
620    }
621
622    if (schema != null)
623    {
624      final String baseName;
625      final String options;
626      final int semicolonPos = attributeName.indexOf(';');
627      if (semicolonPos > 0)
628      {
629        baseName = attributeName.substring(0, semicolonPos);
630        options =
631             StaticUtils.toLowerCase(attributeName.substring(semicolonPos));
632      }
633      else
634      {
635        baseName = attributeName;
636        options  = "";
637      }
638
639      final AttributeTypeDefinition at = schema.getAttributeType(baseName);
640      if (at != null)
641      {
642        if (attributes.containsKey(
643             StaticUtils.toLowerCase(at.getOID()) + options))
644        {
645          return true;
646        }
647
648        for (final String name : at.getNames())
649        {
650          if (attributes.containsKey(
651               StaticUtils.toLowerCase(name) + options))
652          {
653            return true;
654          }
655        }
656      }
657    }
658
659    return false;
660  }
661
662
663
664  /**
665   * Indicates whether this entry contains the specified attribute.  It will
666   * only return {@code true} if this entry contains an attribute with the same
667   * name and exact set of values.
668   *
669   * @param  attribute  The attribute for which to make the determination.  It
670   *                    must not be {@code null}.
671   *
672   * @return  {@code true} if this entry contains the specified attribute, or
673   *          {@code false} if not.
674   */
675  public final boolean hasAttribute(final Attribute attribute)
676  {
677    Validator.ensureNotNull(attribute);
678
679    final String lowerName = StaticUtils.toLowerCase(attribute.getName());
680    final Attribute attr = attributes.get(lowerName);
681    return ((attr != null) && attr.equals(attribute));
682  }
683
684
685
686  /**
687   * Indicates whether this entry contains an attribute with the given name and
688   * value.
689   *
690   * @param  attributeName   The name of the attribute for which to make the
691   *                         determination.  It must not be {@code null}.
692   * @param  attributeValue  The value for which to make the determination.  It
693   *                         must not be {@code null}.
694   *
695   * @return  {@code true} if this entry contains an attribute with the
696   *          specified name and value, or {@code false} if not.
697   */
698  public final boolean hasAttributeValue(final String attributeName,
699                                         final String attributeValue)
700  {
701    Validator.ensureNotNull(attributeName, attributeValue);
702
703    final Attribute attr =
704         attributes.get(StaticUtils.toLowerCase(attributeName));
705    return ((attr != null) && attr.hasValue(attributeValue));
706  }
707
708
709
710  /**
711   * Indicates whether this entry contains an attribute with the given name and
712   * value.
713   *
714   * @param  attributeName   The name of the attribute for which to make the
715   *                         determination.  It must not be {@code null}.
716   * @param  attributeValue  The value for which to make the determination.  It
717   *                         must not be {@code null}.
718   * @param  matchingRule    The matching rule to use to make the determination.
719   *                         It must not be {@code null}.
720   *
721   * @return  {@code true} if this entry contains an attribute with the
722   *          specified name and value, or {@code false} if not.
723   */
724  public final boolean hasAttributeValue(final String attributeName,
725                                         final String attributeValue,
726                                         final MatchingRule matchingRule)
727  {
728    Validator.ensureNotNull(attributeName, attributeValue);
729
730    final Attribute attr =
731         attributes.get(StaticUtils.toLowerCase(attributeName));
732    return ((attr != null) && attr.hasValue(attributeValue, matchingRule));
733  }
734
735
736
737  /**
738   * Indicates whether this entry contains an attribute with the given name and
739   * value.
740   *
741   * @param  attributeName   The name of the attribute for which to make the
742   *                         determination.  It must not be {@code null}.
743   * @param  attributeValue  The value for which to make the determination.  It
744   *                         must not be {@code null}.
745   *
746   * @return  {@code true} if this entry contains an attribute with the
747   *          specified name and value, or {@code false} if not.
748   */
749  public final boolean hasAttributeValue(final String attributeName,
750                                         final byte[] attributeValue)
751  {
752    Validator.ensureNotNull(attributeName, attributeValue);
753
754    final Attribute attr =
755         attributes.get(StaticUtils.toLowerCase(attributeName));
756    return ((attr != null) && attr.hasValue(attributeValue));
757  }
758
759
760
761  /**
762   * Indicates whether this entry contains an attribute with the given name and
763   * value.
764   *
765   * @param  attributeName   The name of the attribute for which to make the
766   *                         determination.  It must not be {@code null}.
767   * @param  attributeValue  The value for which to make the determination.  It
768   *                         must not be {@code null}.
769   * @param  matchingRule    The matching rule to use to make the determination.
770   *                         It must not be {@code null}.
771   *
772   * @return  {@code true} if this entry contains an attribute with the
773   *          specified name and value, or {@code false} if not.
774   */
775  public final boolean hasAttributeValue(final String attributeName,
776                                         final byte[] attributeValue,
777                                         final MatchingRule matchingRule)
778  {
779    Validator.ensureNotNull(attributeName, attributeValue);
780
781    final Attribute attr =
782         attributes.get(StaticUtils.toLowerCase(attributeName));
783    return ((attr != null) && attr.hasValue(attributeValue, matchingRule));
784  }
785
786
787
788  /**
789   * Indicates whether this entry contains the specified object class.
790   *
791   * @param  objectClassName  The name of the object class for which to make the
792   *                          determination.  It must not be {@code null}.
793   *
794   * @return  {@code true} if this entry contains the specified object class, or
795   *          {@code false} if not.
796   */
797  public final boolean hasObjectClass(final String objectClassName)
798  {
799    return hasAttributeValue("objectClass", objectClassName);
800  }
801
802
803
804  /**
805   * Retrieves the set of attributes contained in this entry.
806   *
807   * @return  The set of attributes contained in this entry.
808   */
809  public final Collection<Attribute> getAttributes()
810  {
811    return Collections.unmodifiableCollection(attributes.values());
812  }
813
814
815
816  /**
817   * Retrieves the attribute with the specified name.
818   *
819   * @param  attributeName  The name of the attribute to retrieve.  It must not
820   *                        be {@code null}.
821   *
822   * @return  The requested attribute from this entry, or {@code null} if the
823   *          specified attribute is not present in this entry.
824   */
825  public final Attribute getAttribute(final String attributeName)
826  {
827    return getAttribute(attributeName, schema);
828  }
829
830
831
832  /**
833   * Retrieves the attribute with the specified name.
834   *
835   * @param  attributeName  The name of the attribute to retrieve.  It must not
836   *                        be {@code null}.
837   * @param  schema         The schema to use to determine whether there may be
838   *                        alternate names for the specified attribute.  It may
839   *                        be {@code null} if no schema is available.
840   *
841   * @return  The requested attribute from this entry, or {@code null} if the
842   *          specified attribute is not present in this entry.
843   */
844  public final Attribute getAttribute(final String attributeName,
845                                      final Schema schema)
846  {
847    Validator.ensureNotNull(attributeName);
848
849    Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
850    if ((a == null) && (schema != null))
851    {
852      final String baseName;
853      final String options;
854      final int semicolonPos = attributeName.indexOf(';');
855      if (semicolonPos > 0)
856      {
857        baseName = attributeName.substring(0, semicolonPos);
858        options =
859             StaticUtils.toLowerCase(attributeName.substring(semicolonPos));
860      }
861      else
862      {
863        baseName = attributeName;
864        options  = "";
865      }
866
867      final AttributeTypeDefinition at = schema.getAttributeType(baseName);
868      if (at == null)
869      {
870        return null;
871      }
872
873      a = attributes.get(StaticUtils.toLowerCase(at.getOID() + options));
874      if (a == null)
875      {
876        for (final String name : at.getNames())
877        {
878          a = attributes.get(StaticUtils.toLowerCase(name) + options);
879          if (a != null)
880          {
881            return a;
882          }
883        }
884      }
885
886      return a;
887    }
888    else
889    {
890      return a;
891    }
892  }
893
894
895
896  /**
897   * Retrieves the list of attributes with the given base name and all of the
898   * specified options.
899   *
900   * @param  baseName  The base name (without any options) for the attribute to
901   *                   retrieve.  It must not be {@code null}.
902   * @param  options   The set of options that should be included in the
903   *                   attributes that are returned.  It may be empty or
904   *                   {@code null} if all attributes with the specified base
905   *                   name should be returned, regardless of the options that
906   *                   they contain (if any).
907   *
908   * @return  The list of attributes with the given base name and all of the
909   *          specified options.  It may be empty if there are no attributes
910   *          with the specified base name and set of options.
911   */
912  public final List<Attribute> getAttributesWithOptions(final String baseName,
913                                    final Set<String> options)
914  {
915    Validator.ensureNotNull(baseName);
916
917    final ArrayList<Attribute> attrList = new ArrayList<>(10);
918
919    for (final Attribute a : attributes.values())
920    {
921      if (a.getBaseName().equalsIgnoreCase(baseName))
922      {
923        if ((options == null) || options.isEmpty())
924        {
925          attrList.add(a);
926        }
927        else
928        {
929          boolean allFound = true;
930          for (final String option : options)
931          {
932            if (! a.hasOption(option))
933            {
934              allFound = false;
935              break;
936            }
937          }
938
939          if (allFound)
940          {
941            attrList.add(a);
942          }
943        }
944      }
945    }
946
947    return Collections.unmodifiableList(attrList);
948  }
949
950
951
952  /**
953   * Retrieves the value for the specified attribute, if available.  If the
954   * attribute has more than one value, then the first value will be returned.
955   *
956   * @param  attributeName  The name of the attribute for which to retrieve the
957   *                        value.  It must not be {@code null}.
958   *
959   * @return  The value for the specified attribute, or {@code null} if that
960   *          attribute is not available.
961   */
962  public String getAttributeValue(final String attributeName)
963  {
964    Validator.ensureNotNull(attributeName);
965
966    final Attribute a =
967         attributes.get(StaticUtils.toLowerCase(attributeName));
968    if (a == null)
969    {
970      return null;
971    }
972    else
973    {
974      return a.getValue();
975    }
976  }
977
978
979
980  /**
981   * Retrieves the value for the specified attribute as a byte array, if
982   * available.  If the attribute has more than one value, then the first value
983   * will be returned.
984   *
985   * @param  attributeName  The name of the attribute for which to retrieve the
986   *                        value.  It must not be {@code null}.
987   *
988   * @return  The value for the specified attribute as a byte array, or
989   *          {@code null} if that attribute is not available.
990   */
991  public byte[] getAttributeValueBytes(final String attributeName)
992  {
993    Validator.ensureNotNull(attributeName);
994
995    final Attribute a =
996         attributes.get(StaticUtils.toLowerCase(attributeName));
997    if (a == null)
998    {
999      return null;
1000    }
1001    else
1002    {
1003      return a.getValueByteArray();
1004    }
1005  }
1006
1007
1008
1009  /**
1010   * Retrieves the value for the specified attribute as a Boolean, if available.
1011   * If the attribute has more than one value, then the first value will be
1012   * returned.  Values of "true", "t", "yes", "y", "on", and "1" will be
1013   * interpreted as {@code TRUE}.  Values of "false", "f", "no", "n", "off", and
1014   * "0" will be interpreted as {@code FALSE}.
1015   *
1016   * @param  attributeName  The name of the attribute for which to retrieve the
1017   *                        value.  It must not be {@code null}.
1018   *
1019   * @return  The Boolean value parsed from the specified attribute, or
1020   *          {@code null} if that attribute is not available or the value
1021   *          cannot be parsed as a Boolean.
1022   */
1023  public Boolean getAttributeValueAsBoolean(final String attributeName)
1024  {
1025    Validator.ensureNotNull(attributeName);
1026
1027    final Attribute a =
1028         attributes.get(StaticUtils.toLowerCase(attributeName));
1029    if (a == null)
1030    {
1031      return null;
1032    }
1033    else
1034    {
1035      return a.getValueAsBoolean();
1036    }
1037  }
1038
1039
1040
1041  /**
1042   * Retrieves the value for the specified attribute as a Date, formatted using
1043   * the generalized time syntax, if available.  If the attribute has more than
1044   * one value, then the first value will be returned.
1045   *
1046   * @param  attributeName  The name of the attribute for which to retrieve the
1047   *                        value.  It must not be {@code null}.
1048   *
1049   * @return  The Date value parsed from the specified attribute, or
1050   *           {@code null} if that attribute is not available or the value
1051   *           cannot be parsed as a Date.
1052   */
1053  public Date getAttributeValueAsDate(final String attributeName)
1054  {
1055    Validator.ensureNotNull(attributeName);
1056
1057    final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
1058    if (a == null)
1059    {
1060      return null;
1061    }
1062    else
1063    {
1064      return a.getValueAsDate();
1065    }
1066  }
1067
1068
1069
1070  /**
1071   * Retrieves the value for the specified attribute as a DN, if available.  If
1072   * the attribute has more than one value, then the first value will be
1073   * returned.
1074   *
1075   * @param  attributeName  The name of the attribute for which to retrieve the
1076   *                        value.  It must not be {@code null}.
1077   *
1078   * @return  The DN value parsed from the specified attribute, or {@code null}
1079   *          if that attribute is not available or the value cannot be parsed
1080   *          as a DN.
1081   */
1082  public DN getAttributeValueAsDN(final String attributeName)
1083  {
1084    Validator.ensureNotNull(attributeName);
1085
1086    final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
1087    if (a == null)
1088    {
1089      return null;
1090    }
1091    else
1092    {
1093      return a.getValueAsDN();
1094    }
1095  }
1096
1097
1098
1099  /**
1100   * Retrieves the value for the specified attribute as an Integer, if
1101   * available.  If the attribute has more than one value, then the first value
1102   * will be returned.
1103   *
1104   * @param  attributeName  The name of the attribute for which to retrieve the
1105   *                        value.  It must not be {@code null}.
1106   *
1107   * @return  The Integer value parsed from the specified attribute, or
1108   *          {@code null} if that attribute is not available or the value
1109   *          cannot be parsed as an Integer.
1110   */
1111  public Integer getAttributeValueAsInteger(final String attributeName)
1112  {
1113    Validator.ensureNotNull(attributeName);
1114
1115    final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
1116    if (a == null)
1117    {
1118      return null;
1119    }
1120    else
1121    {
1122      return a.getValueAsInteger();
1123    }
1124  }
1125
1126
1127
1128  /**
1129   * Retrieves the value for the specified attribute as a Long, if available.
1130   * If the attribute has more than one value, then the first value will be
1131   * returned.
1132   *
1133   * @param  attributeName  The name of the attribute for which to retrieve the
1134   *                        value.  It must not be {@code null}.
1135   *
1136   * @return  The Long value parsed from the specified attribute, or
1137   *          {@code null} if that attribute is not available or the value
1138   *          cannot be parsed as a Long.
1139   */
1140  public Long getAttributeValueAsLong(final String attributeName)
1141  {
1142    Validator.ensureNotNull(attributeName);
1143
1144    final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
1145    if (a == null)
1146    {
1147      return null;
1148    }
1149    else
1150    {
1151      return a.getValueAsLong();
1152    }
1153  }
1154
1155
1156
1157  /**
1158   * Retrieves the set of values for the specified attribute, if available.
1159   *
1160   * @param  attributeName  The name of the attribute for which to retrieve the
1161   *                        values.  It must not be {@code null}.
1162   *
1163   * @return  The set of values for the specified attribute, or {@code null} if
1164   *          that attribute is not available.
1165   */
1166  public String[] getAttributeValues(final String attributeName)
1167  {
1168    Validator.ensureNotNull(attributeName);
1169
1170    final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
1171    if (a == null)
1172    {
1173      return null;
1174    }
1175    else
1176    {
1177      return a.getValues();
1178    }
1179  }
1180
1181
1182
1183  /**
1184   * Retrieves the set of values for the specified attribute as byte arrays, if
1185   * available.
1186   *
1187   * @param  attributeName  The name of the attribute for which to retrieve the
1188   *                        values.  It must not be {@code null}.
1189   *
1190   * @return  The set of values for the specified attribute as byte arrays, or
1191   *          {@code null} if that attribute is not available.
1192   */
1193  public byte[][] getAttributeValueByteArrays(final String attributeName)
1194  {
1195    Validator.ensureNotNull(attributeName);
1196
1197    final Attribute a = attributes.get(StaticUtils.toLowerCase(attributeName));
1198    if (a == null)
1199    {
1200      return null;
1201    }
1202    else
1203    {
1204      return a.getValueByteArrays();
1205    }
1206  }
1207
1208
1209
1210  /**
1211   * Retrieves the "objectClass" attribute from the entry, if available.
1212   *
1213   * @return  The "objectClass" attribute from the entry, or {@code null} if
1214   *          that attribute not available.
1215   */
1216  public final Attribute getObjectClassAttribute()
1217  {
1218    return getAttribute("objectClass");
1219  }
1220
1221
1222
1223  /**
1224   * Retrieves the values of the "objectClass" attribute from the entry, if
1225   * available.
1226   *
1227   * @return  The values of the "objectClass" attribute from the entry, or
1228   *          {@code null} if that attribute is not available.
1229   */
1230  public final String[] getObjectClassValues()
1231  {
1232    return getAttributeValues("objectClass");
1233  }
1234
1235
1236
1237  /**
1238   * Adds the provided attribute to this entry.  If this entry already contains
1239   * an attribute with the same name, then their values will be merged.
1240   *
1241   * @param  attribute  The attribute to be added.  It must not be {@code null}.
1242   *
1243   * @return  {@code true} if the entry was updated, or {@code false} because
1244   *          the specified attribute already existed with all provided values.
1245   */
1246  public boolean addAttribute(final Attribute attribute)
1247  {
1248    Validator.ensureNotNull(attribute);
1249
1250    final String lowerName = StaticUtils.toLowerCase(attribute.getName());
1251    final Attribute attr = attributes.get(lowerName);
1252    if (attr == null)
1253    {
1254      attributes.put(lowerName, attribute);
1255      return true;
1256    }
1257    else
1258    {
1259      final Attribute newAttr = Attribute.mergeAttributes(attr, attribute);
1260      attributes.put(lowerName, newAttr);
1261      return (attr.getRawValues().length != newAttr.getRawValues().length);
1262    }
1263  }
1264
1265
1266
1267  /**
1268   * Adds the specified attribute value to this entry, if it is not already
1269   * present.
1270   *
1271   * @param  attributeName   The name for the attribute to be added.  It must
1272   *                         not be {@code null}.
1273   * @param  attributeValue  The value for the attribute to be added.  It must
1274   *                         not be {@code null}.
1275   *
1276   * @return  {@code true} if the entry was updated, or {@code false} because
1277   *          the specified attribute already existed with the given value.
1278   */
1279  public boolean addAttribute(final String attributeName,
1280                              final String attributeValue)
1281  {
1282    Validator.ensureNotNull(attributeName, attributeValue);
1283    return addAttribute(new Attribute(attributeName, schema, attributeValue));
1284  }
1285
1286
1287
1288  /**
1289   * Adds the specified attribute value to this entry, if it is not already
1290   * present.
1291   *
1292   * @param  attributeName   The name for the attribute to be added.  It must
1293   *                         not be {@code null}.
1294   * @param  attributeValue  The value for the attribute to be added.  It must
1295   *                         not be {@code null}.
1296   *
1297   * @return  {@code true} if the entry was updated, or {@code false} because
1298   *          the specified attribute already existed with the given value.
1299   */
1300  public boolean addAttribute(final String attributeName,
1301                              final byte[] attributeValue)
1302  {
1303    Validator.ensureNotNull(attributeName, attributeValue);
1304    return addAttribute(new Attribute(attributeName, schema, attributeValue));
1305  }
1306
1307
1308
1309  /**
1310   * Adds the provided attribute to this entry.  If this entry already contains
1311   * an attribute with the same name, then their values will be merged.
1312   *
1313   * @param  attributeName    The name for the attribute to be added.  It must
1314   *                          not be {@code null}.
1315   * @param  attributeValues  The value for the attribute to be added.  It must
1316   *                          not be {@code null}.
1317   *
1318   * @return  {@code true} if the entry was updated, or {@code false} because
1319   *          the specified attribute already existed with all provided values.
1320   */
1321  public boolean addAttribute(final String attributeName,
1322                              final String... attributeValues)
1323  {
1324    Validator.ensureNotNull(attributeName, attributeValues);
1325    return addAttribute(new Attribute(attributeName, schema, attributeValues));
1326  }
1327
1328
1329
1330  /**
1331   * Adds the provided attribute to this entry.  If this entry already contains
1332   * an attribute with the same name, then their values will be merged.
1333   *
1334   * @param  attributeName    The name for the attribute to be added.  It must
1335   *                          not be {@code null}.
1336   * @param  attributeValues  The value for the attribute to be added.  It must
1337   *                          not be {@code null}.
1338   *
1339   * @return  {@code true} if the entry was updated, or {@code false} because
1340   *          the specified attribute already existed with all provided values.
1341   */
1342  public boolean addAttribute(final String attributeName,
1343                              final byte[]... attributeValues)
1344  {
1345    Validator.ensureNotNull(attributeName, attributeValues);
1346    return addAttribute(new Attribute(attributeName, schema, attributeValues));
1347  }
1348
1349
1350
1351  /**
1352   * Adds the provided attribute to this entry.  If this entry already contains
1353   * an attribute with the same name, then their values will be merged.
1354   *
1355   * @param  attributeName    The name for the attribute to be added.  It must
1356   *                          not be {@code null}.
1357   * @param  attributeValues  The value for the attribute to be added.  It must
1358   *                          not be {@code null}.
1359   *
1360   * @return  {@code true} if the entry was updated, or {@code false} because
1361   *          the specified attribute already existed with all provided values.
1362   */
1363  public boolean addAttribute(final String attributeName,
1364                              final Collection<String> attributeValues)
1365  {
1366    Validator.ensureNotNull(attributeName, attributeValues);
1367    return addAttribute(new Attribute(attributeName, schema, attributeValues));
1368  }
1369
1370
1371
1372  /**
1373   * Removes the specified attribute from this entry.
1374   *
1375   * @param  attributeName  The name of the attribute to remove.  It must not be
1376   *                        {@code null}.
1377   *
1378   * @return  {@code true} if the attribute was removed from the entry, or
1379   *          {@code false} if it was not present.
1380   */
1381  public boolean removeAttribute(final String attributeName)
1382  {
1383    Validator.ensureNotNull(attributeName);
1384
1385    if (schema == null)
1386    {
1387      return
1388           (attributes.remove(StaticUtils.toLowerCase(attributeName)) != null);
1389    }
1390    else
1391    {
1392      final Attribute a = getAttribute(attributeName,  schema);
1393      if (a == null)
1394      {
1395        return false;
1396      }
1397      else
1398      {
1399        attributes.remove(StaticUtils.toLowerCase(a.getName()));
1400        return true;
1401      }
1402    }
1403  }
1404
1405
1406
1407  /**
1408   * Removes the specified attribute value from this entry if it is present.  If
1409   * it is the last value for the attribute, then the entire attribute will be
1410   * removed.  If the specified value is not present, then no change will be
1411   * made.
1412   *
1413   * @param  attributeName   The name of the attribute from which to remove the
1414   *                         value.  It must not be {@code null}.
1415   * @param  attributeValue  The value to remove from the attribute.  It must
1416   *                         not be {@code null}.
1417   *
1418   * @return  {@code true} if the attribute value was removed from the entry, or
1419   *          {@code false} if it was not present.
1420   */
1421  public boolean removeAttributeValue(final String attributeName,
1422                                      final String attributeValue)
1423  {
1424    return removeAttributeValue(attributeName, attributeValue, null);
1425  }
1426
1427
1428
1429  /**
1430   * Removes the specified attribute value from this entry if it is present.  If
1431   * it is the last value for the attribute, then the entire attribute will be
1432   * removed.  If the specified value is not present, then no change will be
1433   * made.
1434   *
1435   * @param  attributeName   The name of the attribute from which to remove the
1436   *                         value.  It must not be {@code null}.
1437   * @param  attributeValue  The value to remove from the attribute.  It must
1438   *                         not be {@code null}.
1439   * @param  matchingRule    The matching rule to use for the attribute.  It may
1440   *                         be {@code null} to use the matching rule associated
1441   *                         with the attribute.
1442   *
1443   * @return  {@code true} if the attribute value was removed from the entry, or
1444   *          {@code false} if it was not present.
1445   */
1446  public boolean removeAttributeValue(final String attributeName,
1447                                      final String attributeValue,
1448                                      final MatchingRule matchingRule)
1449  {
1450    Validator.ensureNotNull(attributeName, attributeValue);
1451
1452    final Attribute attr = getAttribute(attributeName, schema);
1453    if (attr == null)
1454    {
1455      return false;
1456    }
1457    else
1458    {
1459      final String lowerName = StaticUtils.toLowerCase(attr.getName());
1460      final Attribute newAttr = Attribute.removeValues(attr,
1461           new Attribute(attributeName, attributeValue), matchingRule);
1462      if (newAttr.hasValue())
1463      {
1464        attributes.put(lowerName, newAttr);
1465      }
1466      else
1467      {
1468        attributes.remove(lowerName);
1469      }
1470
1471      return (attr.getRawValues().length != newAttr.getRawValues().length);
1472    }
1473  }
1474
1475
1476
1477  /**
1478   * Removes the specified attribute value from this entry if it is present.  If
1479   * it is the last value for the attribute, then the entire attribute will be
1480   * removed.  If the specified value is not present, then no change will be
1481   * made.
1482   *
1483   * @param  attributeName   The name of the attribute from which to remove the
1484   *                         value.  It must not be {@code null}.
1485   * @param  attributeValue  The value to remove from the attribute.  It must
1486   *                         not be {@code null}.
1487   *
1488   * @return  {@code true} if the attribute value was removed from the entry, or
1489   *          {@code false} if it was not present.
1490   */
1491  public boolean removeAttributeValue(final String attributeName,
1492                                      final byte[] attributeValue)
1493  {
1494    return removeAttributeValue(attributeName, attributeValue, null);
1495  }
1496
1497
1498
1499  /**
1500   * Removes the specified attribute value from this entry if it is present.  If
1501   * it is the last value for the attribute, then the entire attribute will be
1502   * removed.  If the specified value is not present, then no change will be
1503   * made.
1504   *
1505   * @param  attributeName   The name of the attribute from which to remove the
1506   *                         value.  It must not be {@code null}.
1507   * @param  attributeValue  The value to remove from the attribute.  It must
1508   *                         not be {@code null}.
1509   * @param  matchingRule    The matching rule to use for the attribute.  It may
1510   *                         be {@code null} to use the matching rule associated
1511   *                         with the attribute.
1512   *
1513   * @return  {@code true} if the attribute value was removed from the entry, or
1514   *          {@code false} if it was not present.
1515   */
1516  public boolean removeAttributeValue(final String attributeName,
1517                                      final byte[] attributeValue,
1518                                      final MatchingRule matchingRule)
1519  {
1520    Validator.ensureNotNull(attributeName, attributeValue);
1521
1522    final Attribute attr = getAttribute(attributeName, schema);
1523    if (attr == null)
1524    {
1525      return false;
1526    }
1527    else
1528    {
1529      final String lowerName = StaticUtils.toLowerCase(attr.getName());
1530      final Attribute newAttr = Attribute.removeValues(attr,
1531           new Attribute(attributeName, attributeValue), matchingRule);
1532      if (newAttr.hasValue())
1533      {
1534        attributes.put(lowerName, newAttr);
1535      }
1536      else
1537      {
1538        attributes.remove(lowerName);
1539      }
1540
1541      return (attr.getRawValues().length != newAttr.getRawValues().length);
1542    }
1543  }
1544
1545
1546
1547  /**
1548   * Removes the specified attribute values from this entry if they are present.
1549   * If the attribute does not have any remaining values, then the entire
1550   * attribute will be removed.  If any of the provided values are not present,
1551   * then they will be ignored.
1552   *
1553   * @param  attributeName    The name of the attribute from which to remove the
1554   *                          values.  It must not be {@code null}.
1555   * @param  attributeValues  The set of values to remove from the attribute.
1556   *                          It must not be {@code null}.
1557   *
1558   * @return  {@code true} if any attribute values were removed from the entry,
1559   *          or {@code false} none of them were present.
1560   */
1561  public boolean removeAttributeValues(final String attributeName,
1562                                       final String... attributeValues)
1563  {
1564    Validator.ensureNotNull(attributeName, attributeValues);
1565
1566    final Attribute attr = getAttribute(attributeName, schema);
1567    if (attr == null)
1568    {
1569      return false;
1570    }
1571    else
1572    {
1573      final String lowerName = StaticUtils.toLowerCase(attr.getName());
1574      final Attribute newAttr = Attribute.removeValues(attr,
1575           new Attribute(attributeName, attributeValues));
1576      if (newAttr.hasValue())
1577      {
1578        attributes.put(lowerName, newAttr);
1579      }
1580      else
1581      {
1582        attributes.remove(lowerName);
1583      }
1584
1585      return (attr.getRawValues().length != newAttr.getRawValues().length);
1586    }
1587  }
1588
1589
1590
1591  /**
1592   * Removes the specified attribute values from this entry if they are present.
1593   * If the attribute does not have any remaining values, then the entire
1594   * attribute will be removed.  If any of the provided values are not present,
1595   * then they will be ignored.
1596   *
1597   * @param  attributeName    The name of the attribute from which to remove the
1598   *                          values.  It must not be {@code null}.
1599   * @param  attributeValues  The set of values to remove from the attribute.
1600   *                          It must not be {@code null}.
1601   *
1602   * @return  {@code true} if any attribute values were removed from the entry,
1603   *          or {@code false} none of them were present.
1604   */
1605  public boolean removeAttributeValues(final String attributeName,
1606                                       final byte[]... attributeValues)
1607  {
1608    Validator.ensureNotNull(attributeName, attributeValues);
1609
1610    final Attribute attr = getAttribute(attributeName, schema);
1611    if (attr == null)
1612    {
1613      return false;
1614    }
1615    else
1616    {
1617      final String lowerName = StaticUtils.toLowerCase(attr.getName());
1618      final Attribute newAttr = Attribute.removeValues(attr,
1619           new Attribute(attributeName, attributeValues));
1620      if (newAttr.hasValue())
1621      {
1622        attributes.put(lowerName, newAttr);
1623      }
1624      else
1625      {
1626        attributes.remove(lowerName);
1627      }
1628
1629      return (attr.getRawValues().length != newAttr.getRawValues().length);
1630    }
1631  }
1632
1633
1634
1635  /**
1636   * Adds the provided attribute to this entry, replacing any existing set of
1637   * values for the associated attribute.
1638   *
1639   * @param  attribute  The attribute to be included in this entry.  It must not
1640   *                    be {@code null}.
1641   */
1642  public void setAttribute(final Attribute attribute)
1643  {
1644    Validator.ensureNotNull(attribute);
1645
1646    final String lowerName;
1647    final Attribute a = getAttribute(attribute.getName(), schema);
1648    if (a == null)
1649    {
1650      lowerName = StaticUtils.toLowerCase(attribute.getName());
1651    }
1652    else
1653    {
1654      lowerName = StaticUtils.toLowerCase(a.getName());
1655    }
1656
1657    attributes.put(lowerName, attribute);
1658  }
1659
1660
1661
1662  /**
1663   * Adds the provided attribute to this entry, replacing any existing set of
1664   * values for the associated attribute.
1665   *
1666   * @param  attributeName   The name to use for the attribute.  It must not be
1667   *                         {@code null}.
1668   * @param  attributeValue  The value to use for the attribute.  It must not be
1669   *                         {@code null}.
1670   */
1671  public void setAttribute(final String attributeName,
1672                           final String attributeValue)
1673  {
1674    Validator.ensureNotNull(attributeName, attributeValue);
1675    setAttribute(new Attribute(attributeName, schema, attributeValue));
1676  }
1677
1678
1679
1680  /**
1681   * Adds the provided attribute to this entry, replacing any existing set of
1682   * values for the associated attribute.
1683   *
1684   * @param  attributeName   The name to use for the attribute.  It must not be
1685   *                         {@code null}.
1686   * @param  attributeValue  The value to use for the attribute.  It must not be
1687   *                         {@code null}.
1688   */
1689  public void setAttribute(final String attributeName,
1690                           final byte[] attributeValue)
1691  {
1692    Validator.ensureNotNull(attributeName, attributeValue);
1693    setAttribute(new Attribute(attributeName, schema, attributeValue));
1694  }
1695
1696
1697
1698  /**
1699   * Adds the provided attribute to this entry, replacing any existing set of
1700   * values for the associated attribute.
1701   *
1702   * @param  attributeName    The name to use for the attribute.  It must not be
1703   *                          {@code null}.
1704   * @param  attributeValues  The set of values to use for the attribute.  It
1705   *                          must not be {@code null}.
1706   */
1707  public void setAttribute(final String attributeName,
1708                           final String... attributeValues)
1709  {
1710    Validator.ensureNotNull(attributeName, attributeValues);
1711    setAttribute(new Attribute(attributeName, schema, attributeValues));
1712  }
1713
1714
1715
1716  /**
1717   * Adds the provided attribute to this entry, replacing any existing set of
1718   * values for the associated attribute.
1719   *
1720   * @param  attributeName    The name to use for the attribute.  It must not be
1721   *                          {@code null}.
1722   * @param  attributeValues  The set of values to use for the attribute.  It
1723   *                          must not be {@code null}.
1724   */
1725  public void setAttribute(final String attributeName,
1726                           final byte[]... attributeValues)
1727  {
1728    Validator.ensureNotNull(attributeName, attributeValues);
1729    setAttribute(new Attribute(attributeName, schema, attributeValues));
1730  }
1731
1732
1733
1734  /**
1735   * Adds the provided attribute to this entry, replacing any existing set of
1736   * values for the associated attribute.
1737   *
1738   * @param  attributeName    The name to use for the attribute.  It must not be
1739   *                          {@code null}.
1740   * @param  attributeValues  The set of values to use for the attribute.  It
1741   *                          must not be {@code null}.
1742   */
1743  public void setAttribute(final String attributeName,
1744                           final Collection<String> attributeValues)
1745  {
1746    Validator.ensureNotNull(attributeName, attributeValues);
1747    setAttribute(new Attribute(attributeName, schema, attributeValues));
1748  }
1749
1750
1751
1752  /**
1753   * Indicates whether this entry falls within the range of the provided search
1754   * base DN and scope.
1755   *
1756   * @param  baseDN  The base DN for which to make the determination.  It must
1757   *                 not be {@code null}.
1758   * @param  scope   The scope for which to make the determination.  It must not
1759   *                 be {@code null}.
1760   *
1761   * @return  {@code true} if this entry is within the range of the provided
1762   *          base and scope, or {@code false} if not.
1763   *
1764   * @throws  LDAPException  If a problem occurs while making the determination.
1765   */
1766  public boolean matchesBaseAndScope(final String baseDN,
1767                                     final SearchScope scope)
1768         throws LDAPException
1769  {
1770    return getParsedDN().matchesBaseAndScope(new DN(baseDN), scope);
1771  }
1772
1773
1774
1775  /**
1776   * Indicates whether this entry falls within the range of the provided search
1777   * base DN and scope.
1778   *
1779   * @param  baseDN  The base DN for which to make the determination.  It must
1780   *                 not be {@code null}.
1781   * @param  scope   The scope for which to make the determination.  It must not
1782   *                 be {@code null}.
1783   *
1784   * @return  {@code true} if this entry is within the range of the provided
1785   *          base and scope, or {@code false} if not.
1786   *
1787   * @throws  LDAPException  If a problem occurs while making the determination.
1788   */
1789  public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope)
1790         throws LDAPException
1791  {
1792    return getParsedDN().matchesBaseAndScope(baseDN, scope);
1793  }
1794
1795
1796
1797  /**
1798   * Retrieves a set of modifications that can be applied to the source entry in
1799   * order to make it match the target entry.  The diff will be generated in
1800   * reversible form (i.e., the same as calling
1801   * {@code diff(sourceEntry, targetEntry, ignoreRDN, true, attributes)}.
1802   *
1803   * @param  sourceEntry  The source entry for which the set of modifications
1804   *                      should be generated.
1805   * @param  targetEntry  The target entry, which is what the source entry
1806   *                      should look like if the returned modifications are
1807   *                      applied.
1808   * @param  ignoreRDN    Indicates whether to ignore differences in the RDNs
1809   *                      of the provided entries.  If this is {@code false},
1810   *                      then the resulting set of modifications may include
1811   *                      changes to the RDN attribute.  If it is {@code true},
1812   *                      then differences in the entry DNs will be ignored.
1813   * @param  attributes   The set of attributes to be compared.  If this is
1814   *                      {@code null} or empty, then all attributes will be
1815   *                      compared.  Note that if a list of attributes is
1816   *                      specified, then matching will be performed only
1817   *                      against the attribute base name and any differences in
1818   *                      attribute options will be ignored.
1819   *
1820   * @return  A set of modifications that can be applied to the source entry in
1821   *          order to make it match the target entry.
1822   */
1823  public static List<Modification> diff(final Entry sourceEntry,
1824                                        final Entry targetEntry,
1825                                        final boolean ignoreRDN,
1826                                        final String... attributes)
1827  {
1828    return diff(sourceEntry, targetEntry, ignoreRDN, true, attributes);
1829  }
1830
1831
1832
1833  /**
1834   * Retrieves a set of modifications that can be applied to the source entry in
1835   * order to make it match the target entry.
1836   *
1837   * @param  sourceEntry  The source entry for which the set of modifications
1838   *                      should be generated.
1839   * @param  targetEntry  The target entry, which is what the source entry
1840   *                      should look like if the returned modifications are
1841   *                      applied.
1842   * @param  ignoreRDN    Indicates whether to ignore differences in the RDNs
1843   *                      of the provided entries.  If this is {@code false},
1844   *                      then the resulting set of modifications may include
1845   *                      changes to the RDN attribute.  If it is {@code true},
1846   *                      then differences in the entry DNs will be ignored.
1847   * @param  reversible   Indicates whether to generate the diff in reversible
1848   *                      form.  In reversible form, only the ADD or DELETE
1849   *                      modification types will be used so that source entry
1850   *                      could be reconstructed from the target and the
1851   *                      resulting modifications.  In non-reversible form, only
1852   *                      the REPLACE modification type will be used.  Attempts
1853   *                      to apply the modifications obtained when using
1854   *                      reversible form are more likely to fail if the entry
1855   *                      has been modified since the source and target forms
1856   *                      were obtained.
1857   * @param  attributes   The set of attributes to be compared.  If this is
1858   *                      {@code null} or empty, then all attributes will be
1859   *                      compared.  Note that if a list of attributes is
1860   *                      specified, then matching will be performed only
1861   *                      against the attribute base name and any differences in
1862   *                      attribute options will be ignored.
1863   *
1864   * @return  A set of modifications that can be applied to the source entry in
1865   *          order to make it match the target entry.
1866   */
1867  public static List<Modification> diff(final Entry sourceEntry,
1868                                        final Entry targetEntry,
1869                                        final boolean ignoreRDN,
1870                                        final boolean reversible,
1871                                        final String... attributes)
1872  {
1873    return diff(sourceEntry, targetEntry, ignoreRDN, reversible, false,
1874         attributes);
1875  }
1876
1877
1878
1879  /**
1880   * Retrieves a set of modifications that can be applied to the source entry in
1881   * order to make it match the target entry.
1882   *
1883   * @param  sourceEntry  The source entry for which the set of modifications
1884   *                      should be generated.
1885   * @param  targetEntry  The target entry, which is what the source entry
1886   *                      should look like if the returned modifications are
1887   *                      applied.
1888   * @param  ignoreRDN    Indicates whether to ignore differences in the RDNs
1889   *                      of the provided entries.  If this is {@code false},
1890   *                      then the resulting set of modifications may include
1891   *                      changes to the RDN attribute.  If it is {@code true},
1892   *                      then differences in the entry DNs will be ignored.
1893   * @param  reversible   Indicates whether to generate the diff in reversible
1894   *                      form.  In reversible form, only the ADD or DELETE
1895   *                      modification types will be used so that source entry
1896   *                      could be reconstructed from the target and the
1897   *                      resulting modifications.  In non-reversible form, only
1898   *                      the REPLACE modification type will be used.  Attempts
1899   *                      to apply the modifications obtained when using
1900   *                      reversible form are more likely to fail if the entry
1901   *                      has been modified since the source and target forms
1902   *                      were obtained.
1903   * @param  byteForByte  Indicates whether to use a byte-for-byte comparison to
1904   *                      identify which attribute values have changed.  Using
1905   *                      byte-for-byte comparison requires additional
1906   *                      processing over using each attribute's associated
1907   *                      matching rule, but it can detect changes that would
1908   *                      otherwise be considered logically equivalent (e.g.,
1909   *                      changing the capitalization of a value that uses a
1910   *                      case-insensitive matching rule).
1911   * @param  attributes   The set of attributes to be compared.  If this is
1912   *                      {@code null} or empty, then all attributes will be
1913   *                      compared.  Note that if a list of attributes is
1914   *                      specified, then matching will be performed only
1915   *                      against the attribute base name and any differences in
1916   *                      attribute options will be ignored.
1917   *
1918   * @return  A set of modifications that can be applied to the source entry in
1919   *          order to make it match the target entry.
1920   */
1921  public static List<Modification> diff(final Entry sourceEntry,
1922                                        final Entry targetEntry,
1923                                        final boolean ignoreRDN,
1924                                        final boolean reversible,
1925                                        final boolean byteForByte,
1926                                        final String... attributes)
1927  {
1928    HashSet<String> compareAttrs = null;
1929    if ((attributes != null) && (attributes.length > 0))
1930    {
1931      compareAttrs = new HashSet<>(attributes.length);
1932      for (final String s : attributes)
1933      {
1934        compareAttrs.add(StaticUtils.toLowerCase(Attribute.getBaseName(s)));
1935      }
1936    }
1937
1938    final LinkedHashMap<String,Attribute> sourceOnlyAttrs =
1939         new LinkedHashMap<>(20);
1940    final LinkedHashMap<String,Attribute> targetOnlyAttrs =
1941         new LinkedHashMap<>(20);
1942    final LinkedHashMap<String,Attribute> commonAttrs = new LinkedHashMap<>(20);
1943
1944    for (final Map.Entry<String,Attribute> e :
1945         sourceEntry.attributes.entrySet())
1946    {
1947      final String lowerName = StaticUtils.toLowerCase(e.getKey());
1948      if ((compareAttrs != null) &&
1949          (! compareAttrs.contains(Attribute.getBaseName(lowerName))))
1950      {
1951        continue;
1952      }
1953
1954      final Attribute attr;
1955      if (byteForByte)
1956      {
1957        final Attribute a = e.getValue();
1958        attr = new Attribute(a.getName(),
1959             OctetStringMatchingRule.getInstance(), a.getRawValues());
1960      }
1961      else
1962      {
1963        attr = e.getValue();
1964      }
1965
1966      sourceOnlyAttrs.put(lowerName, attr);
1967      commonAttrs.put(lowerName, attr);
1968    }
1969
1970    for (final Map.Entry<String,Attribute> e :
1971         targetEntry.attributes.entrySet())
1972    {
1973      final String lowerName = StaticUtils.toLowerCase(e.getKey());
1974      if ((compareAttrs != null) &&
1975          (! compareAttrs.contains(Attribute.getBaseName(lowerName))))
1976      {
1977        continue;
1978      }
1979
1980
1981      if (sourceOnlyAttrs.remove(lowerName) == null)
1982      {
1983        // It wasn't in the set of source attributes, so it must be a
1984        // target-only attribute.
1985        final Attribute attr;
1986        if (byteForByte)
1987        {
1988          final Attribute a = e.getValue();
1989          attr = new Attribute(a.getName(),
1990               OctetStringMatchingRule.getInstance(), a.getRawValues());
1991        }
1992        else
1993        {
1994          attr = e.getValue();
1995        }
1996
1997        targetOnlyAttrs.put(lowerName, attr);
1998      }
1999    }
2000
2001    for (final String lowerName : sourceOnlyAttrs.keySet())
2002    {
2003      commonAttrs.remove(lowerName);
2004    }
2005
2006    RDN sourceRDN = null;
2007    RDN targetRDN = null;
2008    if (ignoreRDN)
2009    {
2010      try
2011      {
2012        sourceRDN = sourceEntry.getRDN();
2013      }
2014      catch (final Exception e)
2015      {
2016        Debug.debugException(e);
2017      }
2018
2019      try
2020      {
2021        targetRDN = targetEntry.getRDN();
2022      }
2023      catch (final Exception e)
2024      {
2025        Debug.debugException(e);
2026      }
2027    }
2028
2029    final ArrayList<Modification> mods = new ArrayList<>(10);
2030
2031    for (final Attribute a : sourceOnlyAttrs.values())
2032    {
2033      if (reversible)
2034      {
2035        ASN1OctetString[] values = a.getRawValues();
2036        if ((sourceRDN != null) && (sourceRDN.hasAttribute(a.getName())))
2037        {
2038          final ArrayList<ASN1OctetString> newValues =
2039               new ArrayList<>(values.length);
2040          for (final ASN1OctetString value : values)
2041          {
2042            if (! sourceRDN.hasAttributeValue(a.getName(), value.getValue()))
2043            {
2044              newValues.add(value);
2045            }
2046          }
2047
2048          if (newValues.isEmpty())
2049          {
2050            continue;
2051          }
2052          else
2053          {
2054            values = new ASN1OctetString[newValues.size()];
2055            newValues.toArray(values);
2056          }
2057        }
2058
2059        mods.add(new Modification(ModificationType.DELETE, a.getName(),
2060             values));
2061      }
2062      else
2063      {
2064        mods.add(new Modification(ModificationType.REPLACE, a.getName()));
2065      }
2066    }
2067
2068    for (final Attribute a : targetOnlyAttrs.values())
2069    {
2070      ASN1OctetString[] values = a.getRawValues();
2071      if ((targetRDN != null) && (targetRDN.hasAttribute(a.getName())))
2072      {
2073        final ArrayList<ASN1OctetString> newValues =
2074             new ArrayList<>(values.length);
2075        for (final ASN1OctetString value : values)
2076        {
2077          if (! targetRDN.hasAttributeValue(a.getName(), value.getValue()))
2078          {
2079            newValues.add(value);
2080          }
2081        }
2082
2083        if (newValues.isEmpty())
2084        {
2085          continue;
2086        }
2087        else
2088        {
2089          values = new ASN1OctetString[newValues.size()];
2090          newValues.toArray(values);
2091        }
2092      }
2093
2094      if (reversible)
2095      {
2096        mods.add(new Modification(ModificationType.ADD, a.getName(), values));
2097      }
2098      else
2099      {
2100        mods.add(new Modification(ModificationType.REPLACE, a.getName(),
2101             values));
2102      }
2103    }
2104
2105    for (final Attribute sourceAttr : commonAttrs.values())
2106    {
2107      Attribute targetAttr = targetEntry.getAttribute(sourceAttr.getName());
2108      if ((byteForByte) && (targetAttr != null))
2109      {
2110        targetAttr = new Attribute(targetAttr.getName(),
2111             OctetStringMatchingRule.getInstance(), targetAttr.getRawValues());
2112      }
2113
2114      if (sourceAttr.equals(targetAttr))
2115      {
2116        continue;
2117      }
2118
2119      if (reversible ||
2120          ((targetRDN != null) && targetRDN.hasAttribute(targetAttr.getName())))
2121      {
2122        final ASN1OctetString[] sourceValueArray = sourceAttr.getRawValues();
2123        final LinkedHashMap<ASN1OctetString,ASN1OctetString> sourceValues =
2124             new LinkedHashMap<>(sourceValueArray.length);
2125        for (final ASN1OctetString s : sourceValueArray)
2126        {
2127          try
2128          {
2129            sourceValues.put(sourceAttr.getMatchingRule().normalize(s), s);
2130          }
2131          catch (final Exception e)
2132          {
2133            Debug.debugException(e);
2134            sourceValues.put(s, s);
2135          }
2136        }
2137
2138        final ASN1OctetString[] targetValueArray = targetAttr.getRawValues();
2139        final LinkedHashMap<ASN1OctetString,ASN1OctetString> targetValues =
2140             new LinkedHashMap<>(targetValueArray.length);
2141        for (final ASN1OctetString s : targetValueArray)
2142        {
2143          try
2144          {
2145            targetValues.put(sourceAttr.getMatchingRule().normalize(s), s);
2146          }
2147          catch (final Exception e)
2148          {
2149            Debug.debugException(e);
2150            targetValues.put(s, s);
2151          }
2152        }
2153
2154        final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>>
2155             sourceIterator = sourceValues.entrySet().iterator();
2156        while (sourceIterator.hasNext())
2157        {
2158          final Map.Entry<ASN1OctetString,ASN1OctetString> e =
2159               sourceIterator.next();
2160          if (targetValues.remove(e.getKey()) != null)
2161          {
2162            sourceIterator.remove();
2163          }
2164          else if ((sourceRDN != null) &&
2165                   sourceRDN.hasAttributeValue(sourceAttr.getName(),
2166                        e.getValue().getValue()))
2167          {
2168            sourceIterator.remove();
2169          }
2170        }
2171
2172        final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>>
2173             targetIterator = targetValues.entrySet().iterator();
2174        while (targetIterator.hasNext())
2175        {
2176          final Map.Entry<ASN1OctetString,ASN1OctetString> e =
2177               targetIterator.next();
2178          if ((targetRDN != null) &&
2179              targetRDN.hasAttributeValue(targetAttr.getName(),
2180                   e.getValue().getValue()))
2181          {
2182            targetIterator.remove();
2183          }
2184        }
2185
2186        final ArrayList<ASN1OctetString> delValues =
2187             new ArrayList<>(sourceValues.values());
2188        if (! delValues.isEmpty())
2189        {
2190          final ASN1OctetString[] delArray =
2191               new ASN1OctetString[delValues.size()];
2192          mods.add(new Modification(ModificationType.DELETE,
2193               sourceAttr.getName(), delValues.toArray(delArray)));
2194        }
2195
2196        final ArrayList<ASN1OctetString> addValues =
2197             new ArrayList<>(targetValues.values());
2198        if (! addValues.isEmpty())
2199        {
2200          final ASN1OctetString[] addArray =
2201               new ASN1OctetString[addValues.size()];
2202          mods.add(new Modification(ModificationType.ADD, targetAttr.getName(),
2203               addValues.toArray(addArray)));
2204        }
2205      }
2206      else
2207      {
2208        mods.add(new Modification(ModificationType.REPLACE,
2209             targetAttr.getName(), targetAttr.getRawValues()));
2210      }
2211    }
2212
2213    return mods;
2214  }
2215
2216
2217
2218  /**
2219   * Merges the contents of all provided entries so that the resulting entry
2220   * will contain all attribute values present in at least one of the entries.
2221   *
2222   * @param  entries  The set of entries to be merged.  At least one entry must
2223   *                  be provided.
2224   *
2225   * @return  An entry containing all attribute values present in at least one
2226   *          of the entries.
2227   */
2228  public static Entry mergeEntries(final Entry... entries)
2229  {
2230    Validator.ensureNotNull(entries);
2231    Validator.ensureTrue(entries.length > 0);
2232
2233    final Entry newEntry = entries[0].duplicate();
2234
2235    for (int i=1; i < entries.length; i++)
2236    {
2237      for (final Attribute a : entries[i].attributes.values())
2238      {
2239        newEntry.addAttribute(a);
2240      }
2241    }
2242
2243    return newEntry;
2244  }
2245
2246
2247
2248  /**
2249   * Intersects the contents of all provided entries so that the resulting
2250   * entry will contain only attribute values present in all of the provided
2251   * entries.
2252   *
2253   * @param  entries  The set of entries to be intersected.  At least one entry
2254   *                  must be provided.
2255   *
2256   * @return  An entry containing only attribute values contained in all of the
2257   *          provided entries.
2258   */
2259  public static Entry intersectEntries(final Entry... entries)
2260  {
2261    Validator.ensureNotNull(entries);
2262    Validator.ensureTrue(entries.length > 0);
2263
2264    final Entry newEntry = entries[0].duplicate();
2265
2266    for (final Attribute a : entries[0].attributes.values())
2267    {
2268      final String name = a.getName();
2269      for (final byte[] v : a.getValueByteArrays())
2270      {
2271        for (int i=1; i < entries.length; i++)
2272        {
2273          if (! entries[i].hasAttributeValue(name, v))
2274          {
2275            newEntry.removeAttributeValue(name, v);
2276            break;
2277          }
2278        }
2279      }
2280    }
2281
2282    return newEntry;
2283  }
2284
2285
2286
2287  /**
2288   * Creates a duplicate of the provided entry with the given set of
2289   * modifications applied to it.
2290   *
2291   * @param  entry          The entry to be modified.  It must not be
2292   *                        {@code null}.
2293   * @param  lenient        Indicates whether to exhibit a lenient behavior for
2294   *                        the modifications, which will cause it to ignore
2295   *                        problems like trying to add values that already
2296   *                        exist or to remove nonexistent attributes or values.
2297   * @param  modifications  The set of modifications to apply to the entry.  It
2298   *                        must not be {@code null} or empty.
2299   *
2300   * @return  An updated version of the entry with the requested modifications
2301   *          applied.
2302   *
2303   * @throws  LDAPException  If a problem occurs while attempting to apply the
2304   *                         modifications.
2305   */
2306  public static Entry applyModifications(final Entry entry,
2307                                         final boolean lenient,
2308                                         final Modification... modifications)
2309         throws LDAPException
2310  {
2311    Validator.ensureNotNull(entry, modifications);
2312    Validator.ensureFalse(modifications.length == 0);
2313
2314    return applyModifications(entry, lenient, Arrays.asList(modifications));
2315  }
2316
2317
2318
2319  /**
2320   * Creates a duplicate of the provided entry with the given set of
2321   * modifications applied to it.
2322   *
2323   * @param  entry          The entry to be modified.  It must not be
2324   *                        {@code null}.
2325   * @param  lenient        Indicates whether to exhibit a lenient behavior for
2326   *                        the modifications, which will cause it to ignore
2327   *                        problems like trying to add values that already
2328   *                        exist or to remove nonexistent attributes or values.
2329   * @param  modifications  The set of modifications to apply to the entry.  It
2330   *                        must not be {@code null} or empty.
2331   *
2332   * @return  An updated version of the entry with the requested modifications
2333   *          applied.
2334   *
2335   * @throws  LDAPException  If a problem occurs while attempting to apply the
2336   *                         modifications.
2337   */
2338  public static Entry applyModifications(final Entry entry,
2339                                         final boolean lenient,
2340                                         final List<Modification> modifications)
2341         throws LDAPException
2342  {
2343    Validator.ensureNotNull(entry, modifications);
2344    Validator.ensureFalse(modifications.isEmpty());
2345
2346    final Entry e = entry.duplicate();
2347    final ArrayList<String> errors = new ArrayList<>(modifications.size());
2348    ResultCode resultCode = null;
2349
2350    // Get the RDN for the entry to ensure that RDN modifications are not
2351    // allowed.
2352    RDN rdn = null;
2353    try
2354    {
2355      rdn = entry.getRDN();
2356    }
2357    catch (final LDAPException le)
2358    {
2359      Debug.debugException(le);
2360    }
2361
2362    for (final Modification m : modifications)
2363    {
2364      final String   name   = m.getAttributeName();
2365      final byte[][] values = m.getValueByteArrays();
2366      switch (m.getModificationType().intValue())
2367      {
2368        case ModificationType.ADD_INT_VALUE:
2369          if (lenient)
2370          {
2371            e.addAttribute(m.getAttribute());
2372          }
2373          else
2374          {
2375            if (values.length == 0)
2376            {
2377              errors.add(ERR_ENTRY_APPLY_MODS_ADD_NO_VALUES.get(name));
2378            }
2379
2380            for (int i=0; i < values.length; i++)
2381            {
2382              if (! e.addAttribute(name, values[i]))
2383              {
2384                if (resultCode == null)
2385                {
2386                  resultCode = ResultCode.ATTRIBUTE_OR_VALUE_EXISTS;
2387                }
2388                errors.add(ERR_ENTRY_APPLY_MODS_ADD_EXISTING.get(
2389                     m.getValues()[i], name));
2390              }
2391            }
2392          }
2393          break;
2394
2395        case ModificationType.DELETE_INT_VALUE:
2396          if (values.length == 0)
2397          {
2398            final boolean removed = e.removeAttribute(name);
2399            if (! (lenient || removed))
2400            {
2401              if (resultCode == null)
2402              {
2403                resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
2404              }
2405              errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_ATTR.get(
2406                   name));
2407            }
2408          }
2409          else
2410          {
2411            for (int i=0; i < values.length; i++)
2412            {
2413              final boolean removed = e.removeAttributeValue(name, values[i]);
2414              if (! (lenient || removed))
2415              {
2416                if (resultCode == null)
2417                {
2418                  resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
2419                }
2420                errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_VALUE.get(
2421                     m.getValues()[i], name));
2422              }
2423            }
2424          }
2425          break;
2426
2427        case ModificationType.REPLACE_INT_VALUE:
2428          if (values.length == 0)
2429          {
2430            e.removeAttribute(name);
2431          }
2432          else
2433          {
2434            e.setAttribute(m.getAttribute());
2435          }
2436          break;
2437
2438        case ModificationType.INCREMENT_INT_VALUE:
2439          final Attribute a = e.getAttribute(name);
2440          if ((a == null) || (! a.hasValue()))
2441          {
2442            errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_SUCH_ATTR.get(name));
2443            continue;
2444          }
2445
2446          if (a.size() > 1)
2447          {
2448            errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NOT_SINGLE_VALUED.get(
2449                 name));
2450            continue;
2451          }
2452
2453          if ((rdn != null) && rdn.hasAttribute(name))
2454          {
2455            final String msg =
2456                 ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2457            if (! errors.contains(msg))
2458            {
2459              errors.add(msg);
2460            }
2461
2462            if (resultCode == null)
2463            {
2464              resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2465            }
2466            continue;
2467          }
2468
2469          final BigInteger currentValue;
2470          try
2471          {
2472            currentValue = new BigInteger(a.getValue());
2473          }
2474          catch (final NumberFormatException nfe)
2475          {
2476            Debug.debugException(nfe);
2477            errors.add(
2478                 ERR_ENTRY_APPLY_MODS_INCREMENT_ENTRY_VALUE_NOT_INTEGER.get(
2479                      name, a.getValue()));
2480            continue;
2481          }
2482
2483          if (values.length == 0)
2484          {
2485            errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_MOD_VALUES.get(name));
2486            continue;
2487          }
2488          else if (values.length > 1)
2489          {
2490            errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MULTIPLE_MOD_VALUES.get(
2491                 name));
2492            continue;
2493          }
2494
2495          final BigInteger incrementValue;
2496          final String incrementValueStr = m.getValues()[0];
2497          try
2498          {
2499            incrementValue = new BigInteger(incrementValueStr);
2500          }
2501          catch (final NumberFormatException nfe)
2502          {
2503            Debug.debugException(nfe);
2504            errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MOD_VALUE_NOT_INTEGER.get(
2505                 name, incrementValueStr));
2506            continue;
2507          }
2508
2509          final BigInteger newValue = currentValue.add(incrementValue);
2510          e.setAttribute(name, newValue.toString());
2511          break;
2512
2513        default:
2514          errors.add(ERR_ENTRY_APPLY_MODS_UNKNOWN_TYPE.get(
2515               String.valueOf(m.getModificationType())));
2516          break;
2517      }
2518    }
2519
2520
2521    // Make sure that the entry still has all of the RDN attribute values.
2522    if (rdn != null)
2523    {
2524      final String[] rdnAttrs  = rdn.getAttributeNames();
2525      final byte[][] rdnValues = rdn.getByteArrayAttributeValues();
2526      for (int i=0; i < rdnAttrs.length; i++)
2527      {
2528        if (! e.hasAttributeValue(rdnAttrs[i], rdnValues[i]))
2529        {
2530          errors.add(ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN()));
2531          if (resultCode == null)
2532          {
2533            resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2534          }
2535          break;
2536        }
2537      }
2538    }
2539
2540
2541    if (errors.isEmpty())
2542    {
2543      return e;
2544    }
2545
2546    if (resultCode == null)
2547    {
2548      resultCode = ResultCode.CONSTRAINT_VIOLATION;
2549    }
2550
2551    throw new LDAPException(resultCode,
2552         ERR_ENTRY_APPLY_MODS_FAILURE.get(e.getDN(),
2553              StaticUtils.concatenateStrings(errors)));
2554  }
2555
2556
2557
2558  /**
2559   * Creates a duplicate of the provided entry with the appropriate changes for
2560   * a modify DN operation.  Any corresponding changes to the set of attribute
2561   * values (to ensure that the new RDN values are present in the entry, and
2562   * optionally to remove the old RDN values from the entry) will also be
2563   * applied.
2564   *
2565   * @param  entry         The entry to be renamed.  It must not be
2566   *                       {@code null}.
2567   * @param  newRDN        The new RDN to use for the entry.  It must not be
2568   *                       {@code null}.
2569   * @param  deleteOldRDN  Indicates whether attribute values that were present
2570   *                       in the old RDN but are no longer present in the new
2571   *                       DN should be removed from the entry.
2572   *
2573   * @return  A new entry that is a duplicate of the provided entry, except with
2574   *          any necessary changes for the modify DN.
2575   *
2576   * @throws  LDAPException  If a problem is encountered during modify DN
2577   *                         processing.
2578   */
2579  public static Entry applyModifyDN(final Entry entry, final String newRDN,
2580                                    final boolean deleteOldRDN)
2581         throws LDAPException
2582  {
2583    return applyModifyDN(entry, newRDN, deleteOldRDN, null);
2584  }
2585
2586
2587
2588  /**
2589   * Creates a duplicate of the provided entry with the appropriate changes for
2590   * a modify DN operation.  Any corresponding changes to the set of attribute
2591   * values (to ensure that the new RDN values are present in the entry, and
2592   * optionally to remove the old RDN values from the entry) will also be
2593   * applied.
2594   *
2595   * @param  entry          The entry to be renamed.  It must not be
2596   *                        {@code null}.
2597   * @param  newRDN         The new RDN to use for the entry.  It must not be
2598   *                        {@code null}.
2599   * @param  deleteOldRDN   Indicates whether attribute values that were present
2600   *                        in the old RDN but are no longer present in the new
2601   *                        DN should be removed from the entry.
2602   * @param  newSuperiorDN  The new superior DN for the entry.  If this is
2603   *                        {@code null}, then the entry will remain below its
2604   *                        existing parent.  If it is non-{@code null}, then
2605   *                        the resulting DN will be a concatenation of the new
2606   *                        RDN and the new superior DN.
2607   *
2608   * @return  A new entry that is a duplicate of the provided entry, except with
2609   *          any necessary changes for the modify DN.
2610   *
2611   * @throws  LDAPException  If a problem is encountered during modify DN
2612   *                         processing.
2613   */
2614  public static Entry applyModifyDN(final Entry entry, final String newRDN,
2615                                    final boolean deleteOldRDN,
2616                                    final String newSuperiorDN)
2617         throws LDAPException
2618  {
2619    Validator.ensureNotNull(entry);
2620    Validator.ensureNotNull(newRDN);
2621
2622    // Parse all of the necessary elements from the request.
2623    final DN  parsedOldDN         = entry.getParsedDN();
2624    final RDN parsedOldRDN        = parsedOldDN.getRDN();
2625    final DN  parsedOldSuperiorDN = parsedOldDN.getParent();
2626
2627    final RDN parsedNewRDN = new RDN(newRDN);
2628
2629    final DN  parsedNewSuperiorDN;
2630    if (newSuperiorDN == null)
2631    {
2632      parsedNewSuperiorDN = parsedOldSuperiorDN;
2633    }
2634    else
2635    {
2636      parsedNewSuperiorDN = new DN(newSuperiorDN);
2637    }
2638
2639    // Duplicate the provided entry and update it with the new DN.
2640    final Entry newEntry = entry.duplicate();
2641    if (parsedNewSuperiorDN == null)
2642    {
2643      // This should only happen if the provided entry has a zero-length DN.
2644      // It's extremely unlikely that a directory server would permit this
2645      // change, but we'll go ahead and process it.
2646      newEntry.setDN(new DN(parsedNewRDN));
2647    }
2648    else
2649    {
2650      newEntry.setDN(new DN(parsedNewRDN, parsedNewSuperiorDN));
2651    }
2652
2653    // If deleteOldRDN is true, then remove any values present in the old RDN
2654    // that are not present in the new RDN.
2655    if (deleteOldRDN && (parsedOldRDN != null))
2656    {
2657      final String[] oldNames  = parsedOldRDN.getAttributeNames();
2658      final byte[][] oldValues = parsedOldRDN.getByteArrayAttributeValues();
2659      for (int i=0; i < oldNames.length; i++)
2660      {
2661        if (! parsedNewRDN.hasAttributeValue(oldNames[i], oldValues[i]))
2662        {
2663          newEntry.removeAttributeValue(oldNames[i], oldValues[i]);
2664        }
2665      }
2666    }
2667
2668    // Add any values present in the new RDN that were not present in the old
2669    // RDN.
2670    final String[] newNames  = parsedNewRDN.getAttributeNames();
2671    final byte[][] newValues = parsedNewRDN.getByteArrayAttributeValues();
2672    for (int i=0; i < newNames.length; i++)
2673    {
2674      if ((parsedOldRDN == null) ||
2675          (! parsedOldRDN.hasAttributeValue(newNames[i], newValues[i])))
2676      {
2677        newEntry.addAttribute(newNames[i], newValues[i]);
2678      }
2679    }
2680
2681    return newEntry;
2682  }
2683
2684
2685
2686  /**
2687   * Generates a hash code for this entry.
2688   *
2689   * @return  The generated hash code for this entry.
2690   */
2691  @Override()
2692  public int hashCode()
2693  {
2694    int hashCode = 0;
2695    try
2696    {
2697      hashCode += getParsedDN().hashCode();
2698    }
2699    catch (final LDAPException le)
2700    {
2701      Debug.debugException(le);
2702      hashCode += dn.hashCode();
2703    }
2704
2705    for (final Attribute a : attributes.values())
2706    {
2707      hashCode += a.hashCode();
2708    }
2709
2710    return hashCode;
2711  }
2712
2713
2714
2715  /**
2716   * Indicates whether the provided object is equal to this entry.  The provided
2717   * object will only be considered equal to this entry if it is an entry with
2718   * the same DN and set of attributes.
2719   *
2720   * @param  o  The object for which to make the determination.
2721   *
2722   * @return  {@code true} if the provided object is considered equal to this
2723   *          entry, or {@code false} if not.
2724   */
2725  @Override()
2726  public boolean equals(final Object o)
2727  {
2728    if (o == null)
2729    {
2730      return false;
2731    }
2732
2733    if (o == this)
2734    {
2735      return true;
2736    }
2737
2738    if (! (o instanceof Entry))
2739    {
2740      return false;
2741    }
2742
2743    final Entry e = (Entry) o;
2744
2745    try
2746    {
2747      final DN thisDN = getParsedDN();
2748      final DN thatDN = e.getParsedDN();
2749      if (! thisDN.equals(thatDN))
2750      {
2751        return false;
2752      }
2753    }
2754    catch (final LDAPException le)
2755    {
2756      Debug.debugException(le);
2757      if (! dn.equals(e.dn))
2758      {
2759        return false;
2760      }
2761    }
2762
2763    if (attributes.size() != e.attributes.size())
2764    {
2765      return false;
2766    }
2767
2768    for (final Attribute a : attributes.values())
2769    {
2770      if (! e.hasAttribute(a))
2771      {
2772        return false;
2773      }
2774    }
2775
2776    return true;
2777  }
2778
2779
2780
2781  /**
2782   * Creates a new entry that is a duplicate of this entry.
2783   *
2784   * @return  A new entry that is a duplicate of this entry.
2785   */
2786  public Entry duplicate()
2787  {
2788    return new Entry(dn, schema, attributes.values());
2789  }
2790
2791
2792
2793  /**
2794   * Retrieves an LDIF representation of this entry, with each attribute value
2795   * on a separate line.  Long lines will not be wrapped.
2796   *
2797   * @return  An LDIF representation of this entry.
2798   */
2799  @Override()
2800  public final String[] toLDIF()
2801  {
2802    return toLDIF(0);
2803  }
2804
2805
2806
2807  /**
2808   * Retrieves an LDIF representation of this entry, with each attribute value
2809   * on a separate line.  Long lines will be wrapped at the specified column.
2810   *
2811   * @param  wrapColumn  The column at which long lines should be wrapped.  A
2812   *                     value less than or equal to two indicates that no
2813   *                     wrapping should be performed.
2814   *
2815   * @return  An LDIF representation of this entry.
2816   */
2817  @Override()
2818  public final String[] toLDIF(final int wrapColumn)
2819  {
2820    List<String> ldifLines = new ArrayList<>(2*attributes.size());
2821    encodeNameAndValue("dn", new ASN1OctetString(dn), ldifLines);
2822
2823    for (final Attribute a : attributes.values())
2824    {
2825      final String name = a.getName();
2826      if (a.hasValue())
2827      {
2828        for (final ASN1OctetString value : a.getRawValues())
2829        {
2830          encodeNameAndValue(name, value, ldifLines);
2831        }
2832      }
2833      else
2834      {
2835        encodeNameAndValue(name, EMPTY_OCTET_STRING, ldifLines);
2836      }
2837    }
2838
2839    if (wrapColumn > 2)
2840    {
2841      ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
2842    }
2843
2844    final String[] lineArray = new String[ldifLines.size()];
2845    ldifLines.toArray(lineArray);
2846    return lineArray;
2847  }
2848
2849
2850
2851  /**
2852   * Encodes the provided name and value and adds the result to the provided
2853   * list of lines.  This will handle the case in which the encoded name and
2854   * value includes comments about the base64-decoded representation of the
2855   * provided value.
2856   *
2857   * @param  name   The attribute name to be encoded.
2858   * @param  value  The attribute value to be encoded.
2859   * @param  lines  The list of lines to be updated.
2860   */
2861  private static void encodeNameAndValue(final String name,
2862                                         final ASN1OctetString value,
2863                                         final List<String> lines)
2864  {
2865    final String line = LDIFWriter.encodeNameAndValue(name, value);
2866    if (LDIFWriter.commentAboutBase64EncodedValues() &&
2867        line.startsWith(name + "::"))
2868    {
2869      final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
2870      while (tokenizer.hasMoreTokens())
2871      {
2872        lines.add(tokenizer.nextToken());
2873      }
2874    }
2875    else
2876    {
2877      lines.add(line);
2878    }
2879  }
2880
2881
2882
2883  /**
2884   * Appends an LDIF representation of this entry to the provided buffer.  Long
2885   * lines will not be wrapped.
2886   *
2887   * @param  buffer The buffer to which the LDIF representation of this entry
2888   *                should be written.
2889   */
2890  @Override()
2891  public final void toLDIF(final ByteStringBuffer buffer)
2892  {
2893    toLDIF(buffer, 0);
2894  }
2895
2896
2897
2898  /**
2899   * Appends an LDIF representation of this entry to the provided buffer.
2900   *
2901   * @param  buffer      The buffer to which the LDIF representation of this
2902   *                     entry should be written.
2903   * @param  wrapColumn  The column at which long lines should be wrapped.  A
2904   *                     value less than or equal to two indicates that no
2905   *                     wrapping should be performed.
2906   */
2907  @Override()
2908  public final void toLDIF(final ByteStringBuffer buffer, final int wrapColumn)
2909  {
2910    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
2911                       wrapColumn);
2912    buffer.append(StaticUtils.EOL_BYTES);
2913
2914    for (final Attribute a : attributes.values())
2915    {
2916      final String name = a.getName();
2917      if (a.hasValue())
2918      {
2919        for (final ASN1OctetString value : a.getRawValues())
2920        {
2921          LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
2922          buffer.append(StaticUtils.EOL_BYTES);
2923        }
2924      }
2925      else
2926      {
2927        LDIFWriter.encodeNameAndValue(name, EMPTY_OCTET_STRING, buffer,
2928             wrapColumn);
2929        buffer.append(StaticUtils.EOL_BYTES);
2930      }
2931    }
2932  }
2933
2934
2935
2936  /**
2937   * Retrieves an LDIF-formatted string representation of this entry.  No
2938   * wrapping will be performed, and no extra blank lines will be added.
2939   *
2940   * @return  An LDIF-formatted string representation of this entry.
2941   */
2942  @Override()
2943  public final String toLDIFString()
2944  {
2945    final StringBuilder buffer = new StringBuilder();
2946    toLDIFString(buffer, 0);
2947    return buffer.toString();
2948  }
2949
2950
2951
2952  /**
2953   * Retrieves an LDIF-formatted string representation of this entry.  No
2954   * extra blank lines will be added.
2955   *
2956   * @param  wrapColumn  The column at which long lines should be wrapped.  A
2957   *                     value less than or equal to two indicates that no
2958   *                     wrapping should be performed.
2959   *
2960   * @return  An LDIF-formatted string representation of this entry.
2961   */
2962  @Override()
2963  public final String toLDIFString(final int wrapColumn)
2964  {
2965    final StringBuilder buffer = new StringBuilder();
2966    toLDIFString(buffer, wrapColumn);
2967    return buffer.toString();
2968  }
2969
2970
2971
2972  /**
2973   * Appends an LDIF-formatted string representation of this entry to the
2974   * provided buffer.  No wrapping will be performed, and no extra blank lines
2975   * will be added.
2976   *
2977   * @param  buffer  The buffer to which to append the LDIF representation of
2978   *                 this entry.
2979   */
2980  @Override()
2981  public final void toLDIFString(final StringBuilder buffer)
2982  {
2983    toLDIFString(buffer, 0);
2984  }
2985
2986
2987
2988  /**
2989   * Appends an LDIF-formatted string representation of this entry to the
2990   * provided buffer.  No extra blank lines will be added.
2991   *
2992   * @param  buffer      The buffer to which to append the LDIF representation
2993   *                     of this entry.
2994   * @param  wrapColumn  The column at which long lines should be wrapped.  A
2995   *                     value less than or equal to two indicates that no
2996   *                     wrapping should be performed.
2997   */
2998  @Override()
2999  public final void toLDIFString(final StringBuilder buffer,
3000                                 final int wrapColumn)
3001  {
3002    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
3003                                  wrapColumn);
3004    buffer.append(StaticUtils.EOL);
3005
3006    for (final Attribute a : attributes.values())
3007    {
3008      final String name = a.getName();
3009      if (a.hasValue())
3010      {
3011        for (final ASN1OctetString value : a.getRawValues())
3012        {
3013          LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
3014          buffer.append(StaticUtils.EOL);
3015        }
3016      }
3017      else
3018      {
3019        LDIFWriter.encodeNameAndValue(name, EMPTY_OCTET_STRING, buffer,
3020             wrapColumn);
3021        buffer.append(StaticUtils.EOL);
3022      }
3023    }
3024  }
3025
3026
3027
3028  /**
3029   * Retrieves a string representation of this entry.
3030   *
3031   * @return  A string representation of this entry.
3032   */
3033  @Override()
3034  public final String toString()
3035  {
3036    final StringBuilder buffer = new StringBuilder();
3037    toString(buffer);
3038    return buffer.toString();
3039  }
3040
3041
3042
3043  /**
3044   * Appends a string representation of this entry to the provided buffer.
3045   *
3046   * @param  buffer  The buffer to which to append the string representation of
3047   *                 this entry.
3048   */
3049  @Override()
3050  public void toString(final StringBuilder buffer)
3051  {
3052    buffer.append("Entry(dn='");
3053    buffer.append(dn);
3054    buffer.append("', attributes={");
3055
3056    final Iterator<Attribute> iterator = attributes.values().iterator();
3057
3058    while (iterator.hasNext())
3059    {
3060      iterator.next().toString(buffer);
3061      if (iterator.hasNext())
3062      {
3063        buffer.append(", ");
3064      }
3065    }
3066
3067    buffer.append("})");
3068  }
3069}