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