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