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