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