001    /*
002     * Copyright 2007-2014 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2014 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(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.
1776       *
1777       * @return  A set of modifications that can be applied to the source entry in
1778       *          order to make it match the target entry.
1779       */
1780      public static List<Modification> diff(final Entry sourceEntry,
1781                                            final Entry targetEntry,
1782                                            final boolean ignoreRDN,
1783                                            final String... attributes)
1784      {
1785        return diff(sourceEntry, targetEntry, ignoreRDN, true, attributes);
1786      }
1787    
1788    
1789    
1790      /**
1791       * Retrieves a set of modifications that can be applied to the source entry in
1792       * order to make it match the target entry.
1793       *
1794       * @param  sourceEntry  The source entry for which the set of modifications
1795       *                      should be generated.
1796       * @param  targetEntry  The target entry, which is what the source entry
1797       *                      should look like if the returned modifications are
1798       *                      applied.
1799       * @param  ignoreRDN    Indicates whether to ignore differences in the RDNs
1800       *                      of the provided entries.  If this is {@code false},
1801       *                      then the resulting set of modifications may include
1802       *                      changes to the RDN attribute.  If it is {@code true},
1803       *                      then differences in the entry DNs will be ignored.
1804       * @param  reversible   Indicates whether to generate the diff in reversible
1805       *                      form.  In reversible form, only the ADD or DELETE
1806       *                      modification types will be used so that source entry
1807       *                      could be reconstructed from the target and the
1808       *                      resulting modifications.  In non-reversible form, only
1809       *                      the REPLACE modification type will be used.  Attempts
1810       *                      to apply the modifications obtained when using
1811       *                      reversible form are more likely to fail if the entry
1812       *                      has been modified since the source and target forms
1813       *                      were obtained.
1814       * @param  attributes   The set of attributes to be compared.  If this is
1815       *                      {@code null} or empty, then all attributes will be
1816       *                      compared.
1817       *
1818       * @return  A set of modifications that can be applied to the source entry in
1819       *          order to make it match the target entry.
1820       */
1821      public static List<Modification> diff(final Entry sourceEntry,
1822                                            final Entry targetEntry,
1823                                            final boolean ignoreRDN,
1824                                            final boolean reversible,
1825                                            final String... attributes)
1826      {
1827        HashSet<String> compareAttrs = null;
1828        if ((attributes != null) && (attributes.length > 0))
1829        {
1830          compareAttrs = new HashSet<String>(attributes.length);
1831          for (final String s : attributes)
1832          {
1833            compareAttrs.add(toLowerCase(s));
1834          }
1835        }
1836    
1837        final LinkedHashMap<String,Attribute> sourceOnlyAttrs =
1838             new LinkedHashMap<String,Attribute>();
1839        final LinkedHashMap<String,Attribute> targetOnlyAttrs =
1840             new LinkedHashMap<String,Attribute>();
1841        final LinkedHashMap<String,Attribute> commonAttrs =
1842             new LinkedHashMap<String,Attribute>();
1843    
1844        for (final Map.Entry<String,Attribute> e :
1845             sourceEntry.attributes.entrySet())
1846        {
1847          final String lowerName = toLowerCase(e.getKey());
1848          if ((compareAttrs != null) && (! compareAttrs.contains(lowerName)))
1849          {
1850            continue;
1851          }
1852    
1853          sourceOnlyAttrs.put(lowerName, e.getValue());
1854          commonAttrs.put(lowerName, e.getValue());
1855        }
1856    
1857        for (final Map.Entry<String,Attribute> e :
1858             targetEntry.attributes.entrySet())
1859        {
1860          final String lowerName = toLowerCase(e.getKey());
1861          if ((compareAttrs != null) && (! compareAttrs.contains(lowerName)))
1862          {
1863            continue;
1864          }
1865    
1866    
1867          if (sourceOnlyAttrs.remove(lowerName) == null)
1868          {
1869            // It wasn't in the set of source attributes, so it must be a
1870            // target-only attribute.
1871            targetOnlyAttrs.put(lowerName,e.getValue());
1872          }
1873        }
1874    
1875        for (final String lowerName : sourceOnlyAttrs.keySet())
1876        {
1877          commonAttrs.remove(lowerName);
1878        }
1879    
1880        RDN sourceRDN = null;
1881        RDN targetRDN = null;
1882        if (ignoreRDN)
1883        {
1884          try
1885          {
1886            sourceRDN = sourceEntry.getRDN();
1887          }
1888          catch (Exception e)
1889          {
1890            debugException(e);
1891          }
1892    
1893          try
1894          {
1895            targetRDN = targetEntry.getRDN();
1896          }
1897          catch (Exception e)
1898          {
1899            debugException(e);
1900          }
1901        }
1902    
1903        final ArrayList<Modification> mods = new ArrayList<Modification>(10);
1904    
1905        for (final Attribute a : sourceOnlyAttrs.values())
1906        {
1907          if (reversible)
1908          {
1909            ASN1OctetString[] values = a.getRawValues();
1910            if ((sourceRDN != null) && (sourceRDN.hasAttribute(a.getName())))
1911            {
1912              final ArrayList<ASN1OctetString> newValues =
1913                   new ArrayList<ASN1OctetString>(values.length);
1914              for (final ASN1OctetString value : values)
1915              {
1916                if (! sourceRDN.hasAttributeValue(a.getName(), value.getValue()))
1917                {
1918                  newValues.add(value);
1919                }
1920              }
1921    
1922              if (newValues.isEmpty())
1923              {
1924                continue;
1925              }
1926              else
1927              {
1928                values = new ASN1OctetString[newValues.size()];
1929                newValues.toArray(values);
1930              }
1931            }
1932    
1933            mods.add(new Modification(ModificationType.DELETE, a.getName(),
1934                 values));
1935          }
1936          else
1937          {
1938            mods.add(new Modification(ModificationType.REPLACE, a.getName()));
1939          }
1940        }
1941    
1942        for (final Attribute a : targetOnlyAttrs.values())
1943        {
1944          ASN1OctetString[] values = a.getRawValues();
1945          if ((targetRDN != null) && (targetRDN.hasAttribute(a.getName())))
1946          {
1947            final ArrayList<ASN1OctetString> newValues =
1948                 new ArrayList<ASN1OctetString>(values.length);
1949            for (final ASN1OctetString value : values)
1950            {
1951              if (! targetRDN.hasAttributeValue(a.getName(), value.getValue()))
1952              {
1953                newValues.add(value);
1954              }
1955            }
1956    
1957            if (newValues.isEmpty())
1958            {
1959              continue;
1960            }
1961            else
1962            {
1963              values = new ASN1OctetString[newValues.size()];
1964              newValues.toArray(values);
1965            }
1966          }
1967    
1968          if (reversible)
1969          {
1970            mods.add(new Modification(ModificationType.ADD, a.getName(), values));
1971          }
1972          else
1973          {
1974            mods.add(new Modification(ModificationType.REPLACE, a.getName(),
1975                 values));
1976          }
1977        }
1978    
1979        for (final Attribute sourceAttr : commonAttrs.values())
1980        {
1981          final Attribute targetAttr =
1982               targetEntry.getAttribute(sourceAttr.getName());
1983          if (sourceAttr.equals(targetAttr))
1984          {
1985            continue;
1986          }
1987    
1988          if (reversible ||
1989              ((targetRDN != null) && targetRDN.hasAttribute(targetAttr.getName())))
1990          {
1991            final ASN1OctetString[] sourceValueArray = sourceAttr.getRawValues();
1992            final LinkedHashMap<ASN1OctetString,ASN1OctetString> sourceValues =
1993                 new LinkedHashMap<ASN1OctetString,ASN1OctetString>(
1994                      sourceValueArray.length);
1995            for (final ASN1OctetString s : sourceValueArray)
1996            {
1997              try
1998              {
1999                sourceValues.put(sourceAttr.getMatchingRule().normalize(s), s);
2000              }
2001              catch (final Exception e)
2002              {
2003                debugException(e);
2004                sourceValues.put(s, s);
2005              }
2006            }
2007    
2008            final ASN1OctetString[] targetValueArray = targetAttr.getRawValues();
2009            final LinkedHashMap<ASN1OctetString,ASN1OctetString> targetValues =
2010                 new LinkedHashMap<ASN1OctetString,ASN1OctetString>(
2011                      targetValueArray.length);
2012            for (final ASN1OctetString s : targetValueArray)
2013            {
2014              try
2015              {
2016                targetValues.put(sourceAttr.getMatchingRule().normalize(s), s);
2017              }
2018              catch (final Exception e)
2019              {
2020                debugException(e);
2021                targetValues.put(s, s);
2022              }
2023            }
2024    
2025            final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>>
2026                 sourceIterator = sourceValues.entrySet().iterator();
2027            while (sourceIterator.hasNext())
2028            {
2029              final Map.Entry<ASN1OctetString,ASN1OctetString> e =
2030                   sourceIterator.next();
2031              if (targetValues.remove(e.getKey()) != null)
2032              {
2033                sourceIterator.remove();
2034              }
2035              else if ((sourceRDN != null) &&
2036                       sourceRDN.hasAttributeValue(sourceAttr.getName(),
2037                            e.getValue().getValue()))
2038              {
2039                sourceIterator.remove();
2040              }
2041            }
2042    
2043            final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>>
2044                 targetIterator = targetValues.entrySet().iterator();
2045            while (targetIterator.hasNext())
2046            {
2047              final Map.Entry<ASN1OctetString,ASN1OctetString> e =
2048                   targetIterator.next();
2049              if ((targetRDN != null) &&
2050                  targetRDN.hasAttributeValue(targetAttr.getName(),
2051                       e.getValue().getValue()))
2052              {
2053                targetIterator.remove();
2054              }
2055            }
2056    
2057            final ArrayList<ASN1OctetString> addValues =
2058                 new ArrayList<ASN1OctetString>(targetValues.values());
2059            final ArrayList<ASN1OctetString> delValues =
2060                 new ArrayList<ASN1OctetString>(sourceValues.values());
2061    
2062            if (! addValues.isEmpty())
2063            {
2064              final ASN1OctetString[] addArray =
2065                   new ASN1OctetString[addValues.size()];
2066              mods.add(new Modification(ModificationType.ADD, targetAttr.getName(),
2067                   addValues.toArray(addArray)));
2068            }
2069    
2070            if (! delValues.isEmpty())
2071            {
2072              final ASN1OctetString[] delArray =
2073                   new ASN1OctetString[delValues.size()];
2074              mods.add(new Modification(ModificationType.DELETE,
2075                   sourceAttr.getName(), delValues.toArray(delArray)));
2076            }
2077          }
2078          else
2079          {
2080            mods.add(new Modification(ModificationType.REPLACE,
2081                 targetAttr.getName(), targetAttr.getRawValues()));
2082          }
2083        }
2084    
2085        return mods;
2086      }
2087    
2088    
2089    
2090      /**
2091       * Merges the contents of all provided entries so that the resulting entry
2092       * will contain all attribute values present in at least one of the entries.
2093       *
2094       * @param  entries  The set of entries to be merged.  At least one entry must
2095       *                  be provided.
2096       *
2097       * @return  An entry containing all attribute values present in at least one
2098       *          of the entries.
2099       */
2100      public static Entry mergeEntries(final Entry... entries)
2101      {
2102        ensureNotNull(entries);
2103        ensureTrue(entries.length > 0);
2104    
2105        final Entry newEntry = entries[0].duplicate();
2106    
2107        for (int i=1; i < entries.length; i++)
2108        {
2109          for (final Attribute a : entries[i].attributes.values())
2110          {
2111            newEntry.addAttribute(a);
2112          }
2113        }
2114    
2115        return newEntry;
2116      }
2117    
2118    
2119    
2120      /**
2121       * Intersects the contents of all provided entries so that the resulting
2122       * entry will contain only attribute values present in all of the provided
2123       * entries.
2124       *
2125       * @param  entries  The set of entries to be intersected.  At least one entry
2126       *                  must be provided.
2127       *
2128       * @return  An entry containing only attribute values contained in all of the
2129       *          provided entries.
2130       */
2131      public static Entry intersectEntries(final Entry... entries)
2132      {
2133        ensureNotNull(entries);
2134        ensureTrue(entries.length > 0);
2135    
2136        final Entry newEntry = entries[0].duplicate();
2137    
2138        for (final Attribute a : entries[0].attributes.values())
2139        {
2140          final String name = a.getName();
2141          for (final byte[] v : a.getValueByteArrays())
2142          {
2143            for (int i=1; i < entries.length; i++)
2144            {
2145              if (! entries[i].hasAttributeValue(name, v))
2146              {
2147                newEntry.removeAttributeValue(name, v);
2148                break;
2149              }
2150            }
2151          }
2152        }
2153    
2154        return newEntry;
2155      }
2156    
2157    
2158    
2159      /**
2160       * Creates a duplicate of the provided entry with the given set of
2161       * modifications applied to it.
2162       *
2163       * @param  entry          The entry to be modified.  It must not be
2164       *                        {@code null}.
2165       * @param  lenient        Indicates whether to exhibit a lenient behavior for
2166       *                        the modifications, which will cause it to ignore
2167       *                        problems like trying to add values that already
2168       *                        exist or to remove nonexistent attributes or values.
2169       * @param  modifications  The set of modifications to apply to the entry.  It
2170       *                        must not be {@code null} or empty.
2171       *
2172       * @return  An updated version of the entry with the requested modifications
2173       *          applied.
2174       *
2175       * @throws  LDAPException  If a problem occurs while attempting to apply the
2176       *                         modifications.
2177       */
2178      public static Entry applyModifications(final Entry entry,
2179                                             final boolean lenient,
2180                                             final Modification... modifications)
2181             throws LDAPException
2182      {
2183        ensureNotNull(entry, modifications);
2184        ensureFalse(modifications.length == 0);
2185    
2186        return applyModifications(entry, lenient, Arrays.asList(modifications));
2187      }
2188    
2189    
2190    
2191      /**
2192       * Creates a duplicate of the provided entry with the given set of
2193       * modifications applied to it.
2194       *
2195       * @param  entry          The entry to be modified.  It must not be
2196       *                        {@code null}.
2197       * @param  lenient        Indicates whether to exhibit a lenient behavior for
2198       *                        the modifications, which will cause it to ignore
2199       *                        problems like trying to add values that already
2200       *                        exist or to remove nonexistent attributes or values.
2201       * @param  modifications  The set of modifications to apply to the entry.  It
2202       *                        must not be {@code null} or empty.
2203       *
2204       * @return  An updated version of the entry with the requested modifications
2205       *          applied.
2206       *
2207       * @throws  LDAPException  If a problem occurs while attempting to apply the
2208       *                         modifications.
2209       */
2210      public static Entry applyModifications(final Entry entry,
2211                                             final boolean lenient,
2212                                             final List<Modification> modifications)
2213             throws LDAPException
2214      {
2215        ensureNotNull(entry, modifications);
2216        ensureFalse(modifications.isEmpty());
2217    
2218        final Entry e = entry.duplicate();
2219        final ArrayList<String> errors =
2220             new ArrayList<String>(modifications.size());
2221        ResultCode resultCode = null;
2222    
2223        // Get the RDN for the entry to ensure that RDN modifications are not
2224        // allowed.
2225        RDN rdn = null;
2226        try
2227        {
2228          rdn = entry.getRDN();
2229        }
2230        catch (final LDAPException le)
2231        {
2232          debugException(le);
2233        }
2234    
2235        for (final Modification m : modifications)
2236        {
2237          final String   name   = m.getAttributeName();
2238          final byte[][] values = m.getValueByteArrays();
2239          switch (m.getModificationType().intValue())
2240          {
2241            case ModificationType.ADD_INT_VALUE:
2242              if (lenient)
2243              {
2244                e.addAttribute(m.getAttribute());
2245              }
2246              else
2247              {
2248                if (values.length == 0)
2249                {
2250                  errors.add(ERR_ENTRY_APPLY_MODS_ADD_NO_VALUES.get(name));
2251                }
2252    
2253                for (int i=0; i < values.length; i++)
2254                {
2255                  if (! e.addAttribute(name, values[i]))
2256                  {
2257                    if (resultCode == null)
2258                    {
2259                      resultCode = ResultCode.ATTRIBUTE_OR_VALUE_EXISTS;
2260                    }
2261                    errors.add(ERR_ENTRY_APPLY_MODS_ADD_EXISTING.get(
2262                         m.getValues()[i], name));
2263                  }
2264                }
2265              }
2266              break;
2267    
2268            case ModificationType.DELETE_INT_VALUE:
2269              if (values.length == 0)
2270              {
2271                if ((rdn != null) && rdn.hasAttribute(name))
2272                {
2273                  final String msg =
2274                       ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2275                  if (! errors.contains(msg))
2276                  {
2277                    errors.add(msg);
2278                  }
2279    
2280                  if (resultCode == null)
2281                  {
2282                    resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2283                  }
2284                  break;
2285                }
2286    
2287                final boolean removed = e.removeAttribute(name);
2288                if (! (lenient || removed))
2289                {
2290                  if (resultCode == null)
2291                  {
2292                    resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
2293                  }
2294                  errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_ATTR.get(
2295                       name));
2296                }
2297              }
2298              else
2299              {
2300    deleteValueLoop:
2301                for (int i=0; i < values.length; i++)
2302                {
2303                  if ((rdn != null) && rdn.hasAttributeValue(name, values[i]))
2304                  {
2305                    final String msg =
2306                         ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2307                    if (! errors.contains(msg))
2308                    {
2309                      errors.add(msg);
2310                    }
2311    
2312                    if (resultCode == null)
2313                    {
2314                      resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2315                    }
2316                    break deleteValueLoop;
2317                  }
2318    
2319                  final boolean removed = e.removeAttributeValue(name, values[i]);
2320                  if (! (lenient || removed))
2321                  {
2322                    if (resultCode == null)
2323                    {
2324                      resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
2325                    }
2326                    errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_VALUE.get(
2327                         m.getValues()[i], name));
2328                  }
2329                }
2330              }
2331              break;
2332    
2333            case ModificationType.REPLACE_INT_VALUE:
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              if (values.length == 0)
2351              {
2352                e.removeAttribute(name);
2353              }
2354              else
2355              {
2356                e.setAttribute(m.getAttribute());
2357              }
2358              break;
2359    
2360            case ModificationType.INCREMENT_INT_VALUE:
2361              final Attribute a = e.getAttribute(name);
2362              if ((a == null) || (! a.hasValue()))
2363              {
2364                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_SUCH_ATTR.get(name));
2365                continue;
2366              }
2367    
2368              if (a.size() > 1)
2369              {
2370                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NOT_SINGLE_VALUED.get(
2371                     name));
2372                continue;
2373              }
2374    
2375              if ((rdn != null) && rdn.hasAttribute(name))
2376              {
2377                final String msg =
2378                     ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2379                if (! errors.contains(msg))
2380                {
2381                  errors.add(msg);
2382                }
2383    
2384                if (resultCode == null)
2385                {
2386                  resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2387                }
2388                continue;
2389              }
2390    
2391              final BigInteger currentValue;
2392              try
2393              {
2394                currentValue = new BigInteger(a.getValue());
2395              }
2396              catch (NumberFormatException nfe)
2397              {
2398                debugException(nfe);
2399                errors.add(
2400                     ERR_ENTRY_APPLY_MODS_INCREMENT_ENTRY_VALUE_NOT_INTEGER.get(
2401                          name, a.getValue()));
2402                continue;
2403              }
2404    
2405              if (values.length == 0)
2406              {
2407                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_MOD_VALUES.get(name));
2408                continue;
2409              }
2410              else if (values.length > 1)
2411              {
2412                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MULTIPLE_MOD_VALUES.get(
2413                     name));
2414                continue;
2415              }
2416    
2417              final BigInteger incrementValue;
2418              final String incrementValueStr = m.getValues()[0];
2419              try
2420              {
2421                incrementValue = new BigInteger(incrementValueStr);
2422              }
2423              catch (NumberFormatException nfe)
2424              {
2425                debugException(nfe);
2426                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MOD_VALUE_NOT_INTEGER.get(
2427                     name, incrementValueStr));
2428                continue;
2429              }
2430    
2431              final BigInteger newValue = currentValue.add(incrementValue);
2432              e.setAttribute(name, newValue.toString());
2433              break;
2434    
2435            default:
2436              errors.add(ERR_ENTRY_APPLY_MODS_UNKNOWN_TYPE.get(
2437                   String.valueOf(m.getModificationType())));
2438              break;
2439          }
2440        }
2441    
2442        if (errors.isEmpty())
2443        {
2444          return e;
2445        }
2446    
2447        if (resultCode == null)
2448        {
2449          resultCode = ResultCode.CONSTRAINT_VIOLATION;
2450        }
2451    
2452        throw new LDAPException(resultCode,
2453             ERR_ENTRY_APPLY_MODS_FAILURE.get(e.getDN(),
2454                  concatenateStrings(errors)));
2455      }
2456    
2457    
2458    
2459      /**
2460       * Generates a hash code for this entry.
2461       *
2462       * @return  The generated hash code for this entry.
2463       */
2464      @Override()
2465      public int hashCode()
2466      {
2467        int hashCode = 0;
2468        try
2469        {
2470          hashCode += getParsedDN().hashCode();
2471        }
2472        catch (LDAPException le)
2473        {
2474          debugException(le);
2475          hashCode += dn.hashCode();
2476        }
2477    
2478        for (final Attribute a : attributes.values())
2479        {
2480          hashCode += a.hashCode();
2481        }
2482    
2483        return hashCode;
2484      }
2485    
2486    
2487    
2488      /**
2489       * Indicates whether the provided object is equal to this entry.  The provided
2490       * object will only be considered equal to this entry if it is an entry with
2491       * the same DN and set of attributes.
2492       *
2493       * @param  o  The object for which to make the determination.
2494       *
2495       * @return  {@code true} if the provided object is considered equal to this
2496       *          entry, or {@code false} if not.
2497       */
2498      @Override()
2499      public boolean equals(final Object o)
2500      {
2501        if (o == null)
2502        {
2503          return false;
2504        }
2505    
2506        if (o == this)
2507        {
2508          return true;
2509        }
2510    
2511        if (! (o instanceof Entry))
2512        {
2513          return false;
2514        }
2515    
2516        final Entry e = (Entry) o;
2517    
2518        try
2519        {
2520          final DN thisDN = getParsedDN();
2521          final DN thatDN = e.getParsedDN();
2522          if (! thisDN.equals(thatDN))
2523          {
2524            return false;
2525          }
2526        }
2527        catch (LDAPException le)
2528        {
2529          debugException(le);
2530          if (! dn.equals(e.dn))
2531          {
2532            return false;
2533          }
2534        }
2535    
2536        if (attributes.size() != e.attributes.size())
2537        {
2538          return false;
2539        }
2540    
2541        for (final Attribute a : attributes.values())
2542        {
2543          if (! e.hasAttribute(a))
2544          {
2545            return false;
2546          }
2547        }
2548    
2549        return true;
2550      }
2551    
2552    
2553    
2554      /**
2555       * Creates a new entry that is a duplicate of this entry.
2556       *
2557       * @return  A new entry that is a duplicate of this entry.
2558       */
2559      public Entry duplicate()
2560      {
2561        return new Entry(dn, schema, attributes.values());
2562      }
2563    
2564    
2565    
2566      /**
2567       * Retrieves an LDIF representation of this entry, with each attribute value
2568       * on a separate line.  Long lines will not be wrapped.
2569       *
2570       * @return  An LDIF representation of this entry.
2571       */
2572      public final String[] toLDIF()
2573      {
2574        return toLDIF(0);
2575      }
2576    
2577    
2578    
2579      /**
2580       * Retrieves an LDIF representation of this entry, with each attribute value
2581       * on a separate line.  Long lines will be wrapped at the specified column.
2582       *
2583       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2584       *                     value less than or equal to two indicates that no
2585       *                     wrapping should be performed.
2586       *
2587       * @return  An LDIF representation of this entry.
2588       */
2589      public final String[] toLDIF(final int wrapColumn)
2590      {
2591        List<String> ldifLines = new ArrayList<String>(2*attributes.size());
2592        ldifLines.add(LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn)));
2593    
2594        for (final Attribute a : attributes.values())
2595        {
2596          final String name = a.getName();
2597          for (final ASN1OctetString value : a.getRawValues())
2598          {
2599            ldifLines.add(LDIFWriter.encodeNameAndValue(name, value));
2600          }
2601        }
2602    
2603        if (wrapColumn > 2)
2604        {
2605          ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
2606        }
2607    
2608        final String[] lineArray = new String[ldifLines.size()];
2609        ldifLines.toArray(lineArray);
2610        return lineArray;
2611      }
2612    
2613    
2614    
2615      /**
2616       * Appends an LDIF representation of this entry to the provided buffer.  Long
2617       * lines will not be wrapped.
2618       *
2619       * @param  buffer The buffer to which the LDIF representation of this entry
2620       *                should be written.
2621       */
2622      public final void toLDIF(final ByteStringBuffer buffer)
2623      {
2624        toLDIF(buffer, 0);
2625      }
2626    
2627    
2628    
2629      /**
2630       * Appends an LDIF representation of this entry to the provided buffer.
2631       *
2632       * @param  buffer      The buffer to which the LDIF representation of this
2633       *                     entry should be written.
2634       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2635       *                     value less than or equal to two indicates that no
2636       *                     wrapping should be performed.
2637       */
2638      public final void toLDIF(final ByteStringBuffer buffer, final int wrapColumn)
2639      {
2640        LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
2641                           wrapColumn);
2642        buffer.append(EOL_BYTES);
2643    
2644        for (final Attribute a : attributes.values())
2645        {
2646          final String name = a.getName();
2647          for (final ASN1OctetString value : a.getRawValues())
2648          {
2649            LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
2650            buffer.append(EOL_BYTES);
2651          }
2652        }
2653      }
2654    
2655    
2656    
2657      /**
2658       * Retrieves an LDIF-formatted string representation of this entry.  No
2659       * wrapping will be performed, and no extra blank lines will be added.
2660       *
2661       * @return  An LDIF-formatted string representation of this entry.
2662       */
2663      public final String toLDIFString()
2664      {
2665        final StringBuilder buffer = new StringBuilder();
2666        toLDIFString(buffer, 0);
2667        return buffer.toString();
2668      }
2669    
2670    
2671    
2672      /**
2673       * Retrieves an LDIF-formatted string representation of this entry.  No
2674       * extra blank lines will be added.
2675       *
2676       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2677       *                     value less than or equal to two indicates that no
2678       *                     wrapping should be performed.
2679       *
2680       * @return  An LDIF-formatted string representation of this entry.
2681       */
2682      public final String toLDIFString(final int wrapColumn)
2683      {
2684        final StringBuilder buffer = new StringBuilder();
2685        toLDIFString(buffer, wrapColumn);
2686        return buffer.toString();
2687      }
2688    
2689    
2690    
2691      /**
2692       * Appends an LDIF-formatted string representation of this entry to the
2693       * provided buffer.  No wrapping will be performed, and no extra blank lines
2694       * will be added.
2695       *
2696       * @param  buffer  The buffer to which to append the LDIF representation of
2697       *                 this entry.
2698       */
2699      public final void toLDIFString(final StringBuilder buffer)
2700      {
2701        toLDIFString(buffer, 0);
2702      }
2703    
2704    
2705    
2706      /**
2707       * Appends an LDIF-formatted string representation of this entry to the
2708       * provided buffer.  No extra blank lines will be added.
2709       *
2710       * @param  buffer      The buffer to which to append the LDIF representation
2711       *                     of this entry.
2712       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2713       *                     value less than or equal to two indicates that no
2714       *                     wrapping should be performed.
2715       */
2716      public final void toLDIFString(final StringBuilder buffer,
2717                                     final int wrapColumn)
2718      {
2719        LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
2720                                      wrapColumn);
2721        buffer.append(EOL);
2722    
2723        for (final Attribute a : attributes.values())
2724        {
2725          final String name = a.getName();
2726          for (final ASN1OctetString value : a.getRawValues())
2727          {
2728            LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
2729            buffer.append(EOL);
2730          }
2731        }
2732      }
2733    
2734    
2735    
2736      /**
2737       * Retrieves a string representation of this entry.
2738       *
2739       * @return  A string representation of this entry.
2740       */
2741      @Override()
2742      public final String toString()
2743      {
2744        final StringBuilder buffer = new StringBuilder();
2745        toString(buffer);
2746        return buffer.toString();
2747      }
2748    
2749    
2750    
2751      /**
2752       * Appends a string representation of this entry to the provided buffer.
2753       *
2754       * @param  buffer  The buffer to which to append the string representation of
2755       *                 this entry.
2756       */
2757      public void toString(final StringBuilder buffer)
2758      {
2759        buffer.append("Entry(dn='");
2760        buffer.append(dn);
2761        buffer.append("', attributes={");
2762    
2763        final Iterator<Attribute> iterator = attributes.values().iterator();
2764    
2765        while (iterator.hasNext())
2766        {
2767          iterator.next().toString(buffer);
2768          if (iterator.hasNext())
2769          {
2770            buffer.append(", ");
2771          }
2772        }
2773    
2774        buffer.append("})");
2775      }
2776    }