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