001    /*
002     * Copyright 2007-2013 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2013 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       * Removes the specified attribute from this entry.
1314       *
1315       * @param  attributeName  The name of the attribute to remove.  It must not be
1316       *                        {@code null}.
1317       *
1318       * @return  {@code true} if the attribute was removed from the entry, or
1319       *          {@code false} if it was not present.
1320       */
1321      public boolean removeAttribute(final String attributeName)
1322      {
1323        ensureNotNull(attributeName);
1324    
1325        if (schema == null)
1326        {
1327          return (attributes.remove(toLowerCase(attributeName)) != null);
1328        }
1329        else
1330        {
1331          final Attribute a = getAttribute(attributeName,  schema);
1332          if (a == null)
1333          {
1334            return false;
1335          }
1336          else
1337          {
1338            attributes.remove(toLowerCase(a.getName()));
1339            return true;
1340          }
1341        }
1342      }
1343    
1344    
1345    
1346      /**
1347       * Removes the specified attribute value from this entry if it is present.  If
1348       * it is the last value for the attribute, then the entire attribute will be
1349       * removed.  If the specified value is not present, then no change will be
1350       * made.
1351       *
1352       * @param  attributeName   The name of the attribute from which to remove the
1353       *                         value.  It must not be {@code null}.
1354       * @param  attributeValue  The value to remove from the attribute.  It must
1355       *                         not be {@code null}.
1356       *
1357       * @return  {@code true} if the attribute value was removed from the entry, or
1358       *          {@code false} if it was not present.
1359       */
1360      public boolean removeAttributeValue(final String attributeName,
1361                                          final String attributeValue)
1362      {
1363        return removeAttributeValue(attributeName, attributeValue, null);
1364      }
1365    
1366    
1367    
1368      /**
1369       * Removes the specified attribute value from this entry if it is present.  If
1370       * it is the last value for the attribute, then the entire attribute will be
1371       * removed.  If the specified value is not present, then no change will be
1372       * made.
1373       *
1374       * @param  attributeName   The name of the attribute from which to remove the
1375       *                         value.  It must not be {@code null}.
1376       * @param  attributeValue  The value to remove from the attribute.  It must
1377       *                         not be {@code null}.
1378       * @param  matchingRule    The matching rule to use for the attribute.  It may
1379       *                         be {@code null} to use the matching rule associated
1380       *                         with the attribute.
1381       *
1382       * @return  {@code true} if the attribute value was removed from the entry, or
1383       *          {@code false} if it was not present.
1384       */
1385      public boolean removeAttributeValue(final String attributeName,
1386                                          final String attributeValue,
1387                                          final MatchingRule matchingRule)
1388      {
1389        ensureNotNull(attributeName, attributeValue);
1390    
1391        final Attribute attr = getAttribute(attributeName, schema);
1392        if (attr == null)
1393        {
1394          return false;
1395        }
1396        else
1397        {
1398          final String lowerName = toLowerCase(attr.getName());
1399          final Attribute newAttr = Attribute.removeValues(attr,
1400               new Attribute(attributeName, attributeValue), matchingRule);
1401          if (newAttr.hasValue())
1402          {
1403            attributes.put(lowerName, newAttr);
1404          }
1405          else
1406          {
1407            attributes.remove(lowerName);
1408          }
1409    
1410          return (attr.getRawValues().length != newAttr.getRawValues().length);
1411        }
1412      }
1413    
1414    
1415    
1416      /**
1417       * Removes the specified attribute value from this entry if it is present.  If
1418       * it is the last value for the attribute, then the entire attribute will be
1419       * removed.  If the specified value is not present, then no change will be
1420       * made.
1421       *
1422       * @param  attributeName   The name of the attribute from which to remove the
1423       *                         value.  It must not be {@code null}.
1424       * @param  attributeValue  The value to remove from the attribute.  It must
1425       *                         not be {@code null}.
1426       *
1427       * @return  {@code true} if the attribute value was removed from the entry, or
1428       *          {@code false} if it was not present.
1429       */
1430      public boolean removeAttributeValue(final String attributeName,
1431                                          final byte[] attributeValue)
1432      {
1433        return removeAttributeValue(attributeName, attributeValue, null);
1434      }
1435    
1436    
1437    
1438      /**
1439       * Removes the specified attribute value from this entry if it is present.  If
1440       * it is the last value for the attribute, then the entire attribute will be
1441       * removed.  If the specified value is not present, then no change will be
1442       * made.
1443       *
1444       * @param  attributeName   The name of the attribute from which to remove the
1445       *                         value.  It must not be {@code null}.
1446       * @param  attributeValue  The value to remove from the attribute.  It must
1447       *                         not be {@code null}.
1448       * @param  matchingRule    The matching rule to use for the attribute.  It may
1449       *                         be {@code null} to use the matching rule associated
1450       *                         with the attribute.
1451       *
1452       * @return  {@code true} if the attribute value was removed from the entry, or
1453       *          {@code false} if it was not present.
1454       */
1455      public boolean removeAttributeValue(final String attributeName,
1456                                          final byte[] attributeValue,
1457                                          final MatchingRule matchingRule)
1458      {
1459        ensureNotNull(attributeName, attributeValue);
1460    
1461        final Attribute attr = getAttribute(attributeName, schema);
1462        if (attr == null)
1463        {
1464          return false;
1465        }
1466        else
1467        {
1468          final String lowerName = toLowerCase(attr.getName());
1469          final Attribute newAttr = Attribute.removeValues(attr,
1470               new Attribute(attributeName, attributeValue), matchingRule);
1471          if (newAttr.hasValue())
1472          {
1473            attributes.put(lowerName, newAttr);
1474          }
1475          else
1476          {
1477            attributes.remove(lowerName);
1478          }
1479    
1480          return (attr.getRawValues().length != newAttr.getRawValues().length);
1481        }
1482      }
1483    
1484    
1485    
1486      /**
1487       * Removes the specified attribute values from this entry if they are present.
1488       * If the attribute does not have any remaining values, then the entire
1489       * attribute will be removed.  If any of the provided values are not present,
1490       * then they will be ignored.
1491       *
1492       * @param  attributeName    The name of the attribute from which to remove the
1493       *                          values.  It must not be {@code null}.
1494       * @param  attributeValues  The set of values to remove from the attribute.
1495       *                          It must not be {@code null}.
1496       *
1497       * @return  {@code true} if any attribute values were removed from the entry,
1498       *          or {@code false} none of them were present.
1499       */
1500      public boolean removeAttributeValues(final String attributeName,
1501                                           final String... attributeValues)
1502      {
1503        ensureNotNull(attributeName, attributeValues);
1504    
1505        final Attribute attr = getAttribute(attributeName, schema);
1506        if (attr == null)
1507        {
1508          return false;
1509        }
1510        else
1511        {
1512          final String lowerName = toLowerCase(attr.getName());
1513          final Attribute newAttr = Attribute.removeValues(attr,
1514               new Attribute(attributeName, attributeValues));
1515          if (newAttr.hasValue())
1516          {
1517            attributes.put(lowerName, newAttr);
1518          }
1519          else
1520          {
1521            attributes.remove(lowerName);
1522          }
1523    
1524          return (attr.getRawValues().length != newAttr.getRawValues().length);
1525        }
1526      }
1527    
1528    
1529    
1530      /**
1531       * Removes the specified attribute values from this entry if they are present.
1532       * If the attribute does not have any remaining values, then the entire
1533       * attribute will be removed.  If any of the provided values are not present,
1534       * then they will be ignored.
1535       *
1536       * @param  attributeName    The name of the attribute from which to remove the
1537       *                          values.  It must not be {@code null}.
1538       * @param  attributeValues  The set of values to remove from the attribute.
1539       *                          It must not be {@code null}.
1540       *
1541       * @return  {@code true} if any attribute values were removed from the entry,
1542       *          or {@code false} none of them were present.
1543       */
1544      public boolean removeAttributeValues(final String attributeName,
1545                                           final byte[]... attributeValues)
1546      {
1547        ensureNotNull(attributeName, attributeValues);
1548    
1549        final Attribute attr = getAttribute(attributeName, schema);
1550        if (attr == null)
1551        {
1552          return false;
1553        }
1554        else
1555        {
1556          final String lowerName = toLowerCase(attr.getName());
1557          final Attribute newAttr = Attribute.removeValues(attr,
1558               new Attribute(attributeName, attributeValues));
1559          if (newAttr.hasValue())
1560          {
1561            attributes.put(lowerName, newAttr);
1562          }
1563          else
1564          {
1565            attributes.remove(lowerName);
1566          }
1567    
1568          return (attr.getRawValues().length != newAttr.getRawValues().length);
1569        }
1570      }
1571    
1572    
1573    
1574      /**
1575       * Adds the provided attribute to this entry, replacing any existing set of
1576       * values for the associated attribute.
1577       *
1578       * @param  attribute  The attribute to be included in this entry.  It must not
1579       *                    be {@code null}.
1580       */
1581      public void setAttribute(final Attribute attribute)
1582      {
1583        ensureNotNull(attribute);
1584    
1585        final String lowerName;
1586        final Attribute a = getAttribute(attribute.getName(), schema);
1587        if (a == null)
1588        {
1589          lowerName = toLowerCase(attribute.getName());
1590        }
1591        else
1592        {
1593          lowerName = toLowerCase(a.getName());
1594        }
1595    
1596        attributes.put(lowerName, attribute);
1597      }
1598    
1599    
1600    
1601      /**
1602       * Adds the provided attribute to this entry, replacing any existing set of
1603       * values for the associated attribute.
1604       *
1605       * @param  attributeName   The name to use for the attribute.  It must not be
1606       *                         {@code null}.
1607       * @param  attributeValue  The value to use for the attribute.  It must not be
1608       *                         {@code null}.
1609       */
1610      public void setAttribute(final String attributeName,
1611                               final String attributeValue)
1612      {
1613        ensureNotNull(attributeName, attributeValue);
1614        setAttribute(new Attribute(attributeName, schema, attributeValue));
1615      }
1616    
1617    
1618    
1619      /**
1620       * Adds the provided attribute to this entry, replacing any existing set of
1621       * values for the associated attribute.
1622       *
1623       * @param  attributeName   The name to use for the attribute.  It must not be
1624       *                         {@code null}.
1625       * @param  attributeValue  The value to use for the attribute.  It must not be
1626       *                         {@code null}.
1627       */
1628      public void setAttribute(final String attributeName,
1629                               final byte[] attributeValue)
1630      {
1631        ensureNotNull(attributeName, attributeValue);
1632        setAttribute(new Attribute(attributeName, schema, attributeValue));
1633      }
1634    
1635    
1636    
1637      /**
1638       * Adds the provided attribute to this entry, replacing any existing set of
1639       * values for the associated attribute.
1640       *
1641       * @param  attributeName    The name to use for the attribute.  It must not be
1642       *                          {@code null}.
1643       * @param  attributeValues  The set of values to use for the attribute.  It
1644       *                          must not be {@code null}.
1645       */
1646      public void setAttribute(final String attributeName,
1647                               final String... attributeValues)
1648      {
1649        ensureNotNull(attributeName, attributeValues);
1650        setAttribute(new Attribute(attributeName, schema, attributeValues));
1651      }
1652    
1653    
1654    
1655      /**
1656       * Adds the provided attribute to this entry, replacing any existing set of
1657       * values for the associated attribute.
1658       *
1659       * @param  attributeName    The name to use for the attribute.  It must not be
1660       *                          {@code null}.
1661       * @param  attributeValues  The set of values to use for the attribute.  It
1662       *                          must not be {@code null}.
1663       */
1664      public void setAttribute(final String attributeName,
1665                               final byte[]... attributeValues)
1666      {
1667        ensureNotNull(attributeName, attributeValues);
1668        setAttribute(new Attribute(attributeName, schema, attributeValues));
1669      }
1670    
1671    
1672    
1673      /**
1674       * Indicates whether this entry falls within the range of the provided search
1675       * base DN and scope.
1676       *
1677       * @param  baseDN  The base DN for which to make the determination.  It must
1678       *                 not be {@code null}.
1679       * @param  scope   The scope for which to make the determination.  It must not
1680       *                 be {@code null}.
1681       *
1682       * @return  {@code true} if this entry is within the range of the provided
1683       *          base and scope, or {@code false} if not.
1684       *
1685       * @throws  LDAPException  If a problem occurs while making the determination.
1686       */
1687      public boolean matchesBaseAndScope(final String baseDN,
1688                                         final SearchScope scope)
1689             throws LDAPException
1690      {
1691        return getParsedDN().matchesBaseAndScope(new DN(baseDN), scope);
1692      }
1693    
1694    
1695    
1696      /**
1697       * Indicates whether this entry falls within the range of the provided search
1698       * base DN and scope.
1699       *
1700       * @param  baseDN  The base DN for which to make the determination.  It must
1701       *                 not be {@code null}.
1702       * @param  scope   The scope for which to make the determination.  It must not
1703       *                 be {@code null}.
1704       *
1705       * @return  {@code true} if this entry is within the range of the provided
1706       *          base and scope, or {@code false} if not.
1707       *
1708       * @throws  LDAPException  If a problem occurs while making the determination.
1709       */
1710      public boolean matchesBaseAndScope(final DN baseDN, final SearchScope scope)
1711             throws LDAPException
1712      {
1713        return getParsedDN().matchesBaseAndScope(baseDN, scope);
1714      }
1715    
1716    
1717    
1718      /**
1719       * Retrieves a set of modifications that can be applied to the source entry in
1720       * order to make it match the target entry.  The diff will be generated in
1721       * reversible form (i.e., the same as calling
1722       * {@code diff(sourceEntry, targetEntry, ignoreRDN, true, attributes)}.
1723       *
1724       * @param  sourceEntry  The source entry for which the set of modifications
1725       *                      should be generated.
1726       * @param  targetEntry  The target entry, which is what the source entry
1727       *                      should look like if the returned modifications are
1728       *                      applied.
1729       * @param  ignoreRDN    Indicates whether to ignore differences in the RDNs
1730       *                      of the provided entries.  If this is {@code false},
1731       *                      then the resulting set of modifications may include
1732       *                      changes to the RDN attribute.  If it is {@code true},
1733       *                      then differences in the entry DNs will be ignored.
1734       * @param  attributes   The set of attributes to be compared.  If this is
1735       *                      {@code null} or empty, then all attributes will be
1736       *                      compared.
1737       *
1738       * @return  A set of modifications that can be applied to the source entry in
1739       *          order to make it match the target entry.
1740       */
1741      public static List<Modification> diff(final Entry sourceEntry,
1742                                            final Entry targetEntry,
1743                                            final boolean ignoreRDN,
1744                                            final String... attributes)
1745      {
1746        return diff(sourceEntry, targetEntry, ignoreRDN, true, attributes);
1747      }
1748    
1749    
1750    
1751      /**
1752       * Retrieves a set of modifications that can be applied to the source entry in
1753       * order to make it match the target entry.
1754       *
1755       * @param  sourceEntry  The source entry for which the set of modifications
1756       *                      should be generated.
1757       * @param  targetEntry  The target entry, which is what the source entry
1758       *                      should look like if the returned modifications are
1759       *                      applied.
1760       * @param  ignoreRDN    Indicates whether to ignore differences in the RDNs
1761       *                      of the provided entries.  If this is {@code false},
1762       *                      then the resulting set of modifications may include
1763       *                      changes to the RDN attribute.  If it is {@code true},
1764       *                      then differences in the entry DNs will be ignored.
1765       * @param  reversible   Indicates whether to generate the diff in reversible
1766       *                      form.  In reversible form, only the ADD or DELETE
1767       *                      modification types will be used so that source entry
1768       *                      could be reconstructed from the target and the
1769       *                      resulting modifications.  In non-reversible form, only
1770       *                      the REPLACE modification type will be used.  Attempts
1771       *                      to apply the modifications obtained when using
1772       *                      reversible form are more likely to fail if the entry
1773       *                      has been modified since the source and target forms
1774       *                      were obtained.
1775       * @param  attributes   The set of attributes to be compared.  If this is
1776       *                      {@code null} or empty, then all attributes will be
1777       *                      compared.
1778       *
1779       * @return  A set of modifications that can be applied to the source entry in
1780       *          order to make it match the target entry.
1781       */
1782      public static List<Modification> diff(final Entry sourceEntry,
1783                                            final Entry targetEntry,
1784                                            final boolean ignoreRDN,
1785                                            final boolean reversible,
1786                                            final String... attributes)
1787      {
1788        HashSet<String> compareAttrs = null;
1789        if ((attributes != null) && (attributes.length > 0))
1790        {
1791          compareAttrs = new HashSet<String>(attributes.length);
1792          for (final String s : attributes)
1793          {
1794            compareAttrs.add(toLowerCase(s));
1795          }
1796        }
1797    
1798        final LinkedHashMap<String,Attribute> sourceOnlyAttrs =
1799             new LinkedHashMap<String,Attribute>();
1800        final LinkedHashMap<String,Attribute> targetOnlyAttrs =
1801             new LinkedHashMap<String,Attribute>();
1802        final LinkedHashMap<String,Attribute> commonAttrs =
1803             new LinkedHashMap<String,Attribute>();
1804    
1805        for (final Map.Entry<String,Attribute> e :
1806             sourceEntry.attributes.entrySet())
1807        {
1808          final String lowerName = toLowerCase(e.getKey());
1809          if ((compareAttrs != null) && (! compareAttrs.contains(lowerName)))
1810          {
1811            continue;
1812          }
1813    
1814          sourceOnlyAttrs.put(lowerName, e.getValue());
1815          commonAttrs.put(lowerName, e.getValue());
1816        }
1817    
1818        for (final Map.Entry<String,Attribute> e :
1819             targetEntry.attributes.entrySet())
1820        {
1821          final String lowerName = toLowerCase(e.getKey());
1822          if ((compareAttrs != null) && (! compareAttrs.contains(lowerName)))
1823          {
1824            continue;
1825          }
1826    
1827    
1828          if (sourceOnlyAttrs.remove(lowerName) == null)
1829          {
1830            // It wasn't in the set of source attributes, so it must be a
1831            // target-only attribute.
1832            targetOnlyAttrs.put(lowerName,e.getValue());
1833          }
1834        }
1835    
1836        for (final String lowerName : sourceOnlyAttrs.keySet())
1837        {
1838          commonAttrs.remove(lowerName);
1839        }
1840    
1841        RDN sourceRDN = null;
1842        RDN targetRDN = null;
1843        if (ignoreRDN)
1844        {
1845          try
1846          {
1847            sourceRDN = sourceEntry.getRDN();
1848          }
1849          catch (Exception e)
1850          {
1851            debugException(e);
1852          }
1853    
1854          try
1855          {
1856            targetRDN = targetEntry.getRDN();
1857          }
1858          catch (Exception e)
1859          {
1860            debugException(e);
1861          }
1862        }
1863    
1864        final ArrayList<Modification> mods = new ArrayList<Modification>(10);
1865    
1866        for (final Attribute a : sourceOnlyAttrs.values())
1867        {
1868          if (reversible)
1869          {
1870            ASN1OctetString[] values = a.getRawValues();
1871            if ((sourceRDN != null) && (sourceRDN.hasAttribute(a.getName())))
1872            {
1873              final ArrayList<ASN1OctetString> newValues =
1874                   new ArrayList<ASN1OctetString>(values.length);
1875              for (final ASN1OctetString value : values)
1876              {
1877                if (! sourceRDN.hasAttributeValue(a.getName(), value.getValue()))
1878                {
1879                  newValues.add(value);
1880                }
1881              }
1882    
1883              if (newValues.isEmpty())
1884              {
1885                continue;
1886              }
1887              else
1888              {
1889                values = new ASN1OctetString[newValues.size()];
1890                newValues.toArray(values);
1891              }
1892            }
1893    
1894            mods.add(new Modification(ModificationType.DELETE, a.getName(),
1895                 values));
1896          }
1897          else
1898          {
1899            mods.add(new Modification(ModificationType.REPLACE, a.getName()));
1900          }
1901        }
1902    
1903        for (final Attribute a : targetOnlyAttrs.values())
1904        {
1905          ASN1OctetString[] values = a.getRawValues();
1906          if ((targetRDN != null) && (targetRDN.hasAttribute(a.getName())))
1907          {
1908            final ArrayList<ASN1OctetString> newValues =
1909                 new ArrayList<ASN1OctetString>(values.length);
1910            for (final ASN1OctetString value : values)
1911            {
1912              if (! targetRDN.hasAttributeValue(a.getName(), value.getValue()))
1913              {
1914                newValues.add(value);
1915              }
1916            }
1917    
1918            if (newValues.isEmpty())
1919            {
1920              continue;
1921            }
1922            else
1923            {
1924              values = new ASN1OctetString[newValues.size()];
1925              newValues.toArray(values);
1926            }
1927          }
1928    
1929          if (reversible)
1930          {
1931            mods.add(new Modification(ModificationType.ADD, a.getName(), values));
1932          }
1933          else
1934          {
1935            mods.add(new Modification(ModificationType.REPLACE, a.getName(),
1936                 values));
1937          }
1938        }
1939    
1940        for (final Attribute sourceAttr : commonAttrs.values())
1941        {
1942          final Attribute targetAttr =
1943               targetEntry.getAttribute(sourceAttr.getName());
1944          if (sourceAttr.equals(targetAttr))
1945          {
1946            continue;
1947          }
1948    
1949          if (reversible ||
1950              ((targetRDN != null) && targetRDN.hasAttribute(targetAttr.getName())))
1951          {
1952            final ASN1OctetString[] sourceValueArray = sourceAttr.getRawValues();
1953            final LinkedHashMap<ASN1OctetString,ASN1OctetString> sourceValues =
1954                 new LinkedHashMap<ASN1OctetString,ASN1OctetString>(
1955                      sourceValueArray.length);
1956            for (final ASN1OctetString s : sourceValueArray)
1957            {
1958              try
1959              {
1960                sourceValues.put(sourceAttr.getMatchingRule().normalize(s), s);
1961              }
1962              catch (final Exception e)
1963              {
1964                debugException(e);
1965                sourceValues.put(s, s);
1966              }
1967            }
1968    
1969            final ASN1OctetString[] targetValueArray = targetAttr.getRawValues();
1970            final LinkedHashMap<ASN1OctetString,ASN1OctetString> targetValues =
1971                 new LinkedHashMap<ASN1OctetString,ASN1OctetString>(
1972                      targetValueArray.length);
1973            for (final ASN1OctetString s : targetValueArray)
1974            {
1975              try
1976              {
1977                targetValues.put(sourceAttr.getMatchingRule().normalize(s), s);
1978              }
1979              catch (final Exception e)
1980              {
1981                debugException(e);
1982                targetValues.put(s, s);
1983              }
1984            }
1985    
1986            final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>>
1987                 sourceIterator = sourceValues.entrySet().iterator();
1988            while (sourceIterator.hasNext())
1989            {
1990              final Map.Entry<ASN1OctetString,ASN1OctetString> e =
1991                   sourceIterator.next();
1992              if (targetValues.remove(e.getKey()) != null)
1993              {
1994                sourceIterator.remove();
1995              }
1996              else if ((sourceRDN != null) &&
1997                       sourceRDN.hasAttributeValue(sourceAttr.getName(),
1998                            e.getValue().getValue()))
1999              {
2000                sourceIterator.remove();
2001              }
2002            }
2003    
2004            final Iterator<Map.Entry<ASN1OctetString,ASN1OctetString>>
2005                 targetIterator = targetValues.entrySet().iterator();
2006            while (targetIterator.hasNext())
2007            {
2008              final Map.Entry<ASN1OctetString,ASN1OctetString> e =
2009                   targetIterator.next();
2010              if ((targetRDN != null) &&
2011                  targetRDN.hasAttributeValue(targetAttr.getName(),
2012                       e.getValue().getValue()))
2013              {
2014                targetIterator.remove();
2015              }
2016            }
2017    
2018            final ArrayList<ASN1OctetString> addValues =
2019                 new ArrayList<ASN1OctetString>(targetValues.values());
2020            final ArrayList<ASN1OctetString> delValues =
2021                 new ArrayList<ASN1OctetString>(sourceValues.values());
2022    
2023            if (! addValues.isEmpty())
2024            {
2025              final ASN1OctetString[] addArray =
2026                   new ASN1OctetString[addValues.size()];
2027              mods.add(new Modification(ModificationType.ADD, targetAttr.getName(),
2028                   addValues.toArray(addArray)));
2029            }
2030    
2031            if (! delValues.isEmpty())
2032            {
2033              final ASN1OctetString[] delArray =
2034                   new ASN1OctetString[delValues.size()];
2035              mods.add(new Modification(ModificationType.DELETE,
2036                   sourceAttr.getName(), delValues.toArray(delArray)));
2037            }
2038          }
2039          else
2040          {
2041            mods.add(new Modification(ModificationType.REPLACE,
2042                 targetAttr.getName(), targetAttr.getRawValues()));
2043          }
2044        }
2045    
2046        return mods;
2047      }
2048    
2049    
2050    
2051      /**
2052       * Merges the contents of all provided entries so that the resulting entry
2053       * will contain all attribute values present in at least one of the entries.
2054       *
2055       * @param  entries  The set of entries to be merged.  At least one entry must
2056       *                  be provided.
2057       *
2058       * @return  An entry containing all attribute values present in at least one
2059       *          of the entries.
2060       */
2061      public static Entry mergeEntries(final Entry... entries)
2062      {
2063        ensureNotNull(entries);
2064        ensureTrue(entries.length > 0);
2065    
2066        final Entry newEntry = entries[0].duplicate();
2067    
2068        for (int i=1; i < entries.length; i++)
2069        {
2070          for (final Attribute a : entries[i].attributes.values())
2071          {
2072            newEntry.addAttribute(a);
2073          }
2074        }
2075    
2076        return newEntry;
2077      }
2078    
2079    
2080    
2081      /**
2082       * Intersects the contents of all provided entries so that the resulting
2083       * entry will contain only attribute values present in all of the provided
2084       * entries.
2085       *
2086       * @param  entries  The set of entries to be intersected.  At least one entry
2087       *                  must be provided.
2088       *
2089       * @return  An entry containing only attribute values contained in all of the
2090       *          provided entries.
2091       */
2092      public static Entry intersectEntries(final Entry... entries)
2093      {
2094        ensureNotNull(entries);
2095        ensureTrue(entries.length > 0);
2096    
2097        final Entry newEntry = entries[0].duplicate();
2098    
2099        for (final Attribute a : entries[0].attributes.values())
2100        {
2101          final String name = a.getName();
2102          for (final byte[] v : a.getValueByteArrays())
2103          {
2104            for (int i=1; i < entries.length; i++)
2105            {
2106              if (! entries[i].hasAttributeValue(name, v))
2107              {
2108                newEntry.removeAttributeValue(name, v);
2109                break;
2110              }
2111            }
2112          }
2113        }
2114    
2115        return newEntry;
2116      }
2117    
2118    
2119    
2120      /**
2121       * Creates a duplicate of the provided entry with the given set of
2122       * modifications applied to it.
2123       *
2124       * @param  entry          The entry to be modified.  It must not be
2125       *                        {@code null}.
2126       * @param  lenient        Indicates whether to exhibit a lenient behavior for
2127       *                        the modifications, which will cause it to ignore
2128       *                        problems like trying to add values that already
2129       *                        exist or to remove nonexistent attributes or values.
2130       * @param  modifications  The set of modifications to apply to the entry.  It
2131       *                        must not be {@code null} or empty.
2132       *
2133       * @return  An updated version of the entry with the requested modifications
2134       *          applied.
2135       *
2136       * @throws  LDAPException  If a problem occurs while attempting to apply the
2137       *                         modifications.
2138       */
2139      public static Entry applyModifications(final Entry entry,
2140                                             final boolean lenient,
2141                                             final Modification... modifications)
2142             throws LDAPException
2143      {
2144        ensureNotNull(entry, modifications);
2145        ensureFalse(modifications.length == 0);
2146    
2147        return applyModifications(entry, lenient, Arrays.asList(modifications));
2148      }
2149    
2150    
2151    
2152      /**
2153       * Creates a duplicate of the provided entry with the given set of
2154       * modifications applied to it.
2155       *
2156       * @param  entry          The entry to be modified.  It must not be
2157       *                        {@code null}.
2158       * @param  lenient        Indicates whether to exhibit a lenient behavior for
2159       *                        the modifications, which will cause it to ignore
2160       *                        problems like trying to add values that already
2161       *                        exist or to remove nonexistent attributes or values.
2162       * @param  modifications  The set of modifications to apply to the entry.  It
2163       *                        must not be {@code null} or empty.
2164       *
2165       * @return  An updated version of the entry with the requested modifications
2166       *          applied.
2167       *
2168       * @throws  LDAPException  If a problem occurs while attempting to apply the
2169       *                         modifications.
2170       */
2171      public static Entry applyModifications(final Entry entry,
2172                                             final boolean lenient,
2173                                             final List<Modification> modifications)
2174             throws LDAPException
2175      {
2176        ensureNotNull(entry, modifications);
2177        ensureFalse(modifications.isEmpty());
2178    
2179        final Entry e = entry.duplicate();
2180        final ArrayList<String> errors =
2181             new ArrayList<String>(modifications.size());
2182        ResultCode resultCode = null;
2183    
2184        // Get the RDN for the entry to ensure that RDN modifications are not
2185        // allowed.
2186        RDN rdn = null;
2187        try
2188        {
2189          rdn = entry.getRDN();
2190        }
2191        catch (final LDAPException le)
2192        {
2193          debugException(le);
2194        }
2195    
2196        for (final Modification m : modifications)
2197        {
2198          final String   name   = m.getAttributeName();
2199          final byte[][] values = m.getValueByteArrays();
2200          switch (m.getModificationType().intValue())
2201          {
2202            case ModificationType.ADD_INT_VALUE:
2203              if (lenient)
2204              {
2205                e.addAttribute(m.getAttribute());
2206              }
2207              else
2208              {
2209                if (values.length == 0)
2210                {
2211                  errors.add(ERR_ENTRY_APPLY_MODS_ADD_NO_VALUES.get(name));
2212                }
2213    
2214                for (int i=0; i < values.length; i++)
2215                {
2216                  if (! e.addAttribute(name, values[i]))
2217                  {
2218                    if (resultCode == null)
2219                    {
2220                      resultCode = ResultCode.ATTRIBUTE_OR_VALUE_EXISTS;
2221                    }
2222                    errors.add(ERR_ENTRY_APPLY_MODS_ADD_EXISTING.get(
2223                         m.getValues()[i], name));
2224                  }
2225                }
2226              }
2227              break;
2228    
2229            case ModificationType.DELETE_INT_VALUE:
2230              if (values.length == 0)
2231              {
2232                if ((rdn != null) && rdn.hasAttribute(name))
2233                {
2234                  final String msg =
2235                       ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2236                  if (! errors.contains(msg))
2237                  {
2238                    errors.add(msg);
2239                  }
2240    
2241                  if (resultCode == null)
2242                  {
2243                    resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2244                  }
2245                  break;
2246                }
2247    
2248                final boolean removed = e.removeAttribute(name);
2249                if (! (lenient || removed))
2250                {
2251                  if (resultCode == null)
2252                  {
2253                    resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
2254                  }
2255                  errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_ATTR.get(
2256                       name));
2257                }
2258              }
2259              else
2260              {
2261    deleteValueLoop:
2262                for (int i=0; i < values.length; i++)
2263                {
2264                  if ((rdn != null) && rdn.hasAttributeValue(name, values[i]))
2265                  {
2266                    final String msg =
2267                         ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2268                    if (! errors.contains(msg))
2269                    {
2270                      errors.add(msg);
2271                    }
2272    
2273                    if (resultCode == null)
2274                    {
2275                      resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2276                    }
2277                    break deleteValueLoop;
2278                  }
2279    
2280                  final boolean removed = e.removeAttributeValue(name, values[i]);
2281                  if (! (lenient || removed))
2282                  {
2283                    if (resultCode == null)
2284                    {
2285                      resultCode = ResultCode.NO_SUCH_ATTRIBUTE;
2286                    }
2287                    errors.add(ERR_ENTRY_APPLY_MODS_DELETE_NONEXISTENT_VALUE.get(
2288                         m.getValues()[i], name));
2289                  }
2290                }
2291              }
2292              break;
2293    
2294            case ModificationType.REPLACE_INT_VALUE:
2295              if ((rdn != null) && rdn.hasAttribute(name))
2296              {
2297                final String msg =
2298                     ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2299                if (! errors.contains(msg))
2300                {
2301                  errors.add(msg);
2302                }
2303    
2304                if (resultCode == null)
2305                {
2306                  resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2307                }
2308                continue;
2309              }
2310    
2311              if (values.length == 0)
2312              {
2313                e.removeAttribute(name);
2314              }
2315              else
2316              {
2317                e.setAttribute(m.getAttribute());
2318              }
2319              break;
2320    
2321            case ModificationType.INCREMENT_INT_VALUE:
2322              final Attribute a = e.getAttribute(name);
2323              if ((a == null) || (! a.hasValue()))
2324              {
2325                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_SUCH_ATTR.get(name));
2326                continue;
2327              }
2328    
2329              if (a.size() > 1)
2330              {
2331                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NOT_SINGLE_VALUED.get(
2332                     name));
2333                continue;
2334              }
2335    
2336              if ((rdn != null) && rdn.hasAttribute(name))
2337              {
2338                final String msg =
2339                     ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN());
2340                if (! errors.contains(msg))
2341                {
2342                  errors.add(msg);
2343                }
2344    
2345                if (resultCode == null)
2346                {
2347                  resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2348                }
2349                continue;
2350              }
2351    
2352              final BigInteger currentValue;
2353              try
2354              {
2355                currentValue = new BigInteger(a.getValue());
2356              }
2357              catch (NumberFormatException nfe)
2358              {
2359                debugException(nfe);
2360                errors.add(
2361                     ERR_ENTRY_APPLY_MODS_INCREMENT_ENTRY_VALUE_NOT_INTEGER.get(
2362                          name, a.getValue()));
2363                continue;
2364              }
2365    
2366              if (values.length == 0)
2367              {
2368                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_NO_MOD_VALUES.get(name));
2369                continue;
2370              }
2371              else if (values.length > 1)
2372              {
2373                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MULTIPLE_MOD_VALUES.get(
2374                     name));
2375                continue;
2376              }
2377    
2378              final BigInteger incrementValue;
2379              final String incrementValueStr = m.getValues()[0];
2380              try
2381              {
2382                incrementValue = new BigInteger(incrementValueStr);
2383              }
2384              catch (NumberFormatException nfe)
2385              {
2386                debugException(nfe);
2387                errors.add(ERR_ENTRY_APPLY_MODS_INCREMENT_MOD_VALUE_NOT_INTEGER.get(
2388                     name, incrementValueStr));
2389                continue;
2390              }
2391    
2392              final BigInteger newValue = currentValue.add(incrementValue);
2393              e.setAttribute(name, newValue.toString());
2394              break;
2395    
2396            default:
2397              errors.add(ERR_ENTRY_APPLY_MODS_UNKNOWN_TYPE.get(
2398                   String.valueOf(m.getModificationType())));
2399              break;
2400          }
2401        }
2402    
2403        if (errors.isEmpty())
2404        {
2405          return e;
2406        }
2407    
2408        if (resultCode == null)
2409        {
2410          resultCode = ResultCode.CONSTRAINT_VIOLATION;
2411        }
2412    
2413        throw new LDAPException(resultCode,
2414             ERR_ENTRY_APPLY_MODS_FAILURE.get(e.getDN(),
2415                  concatenateStrings(errors)));
2416      }
2417    
2418    
2419    
2420      /**
2421       * Generates a hash code for this entry.
2422       *
2423       * @return  The generated hash code for this entry.
2424       */
2425      @Override()
2426      public int hashCode()
2427      {
2428        int hashCode = 0;
2429        try
2430        {
2431          hashCode += getParsedDN().hashCode();
2432        }
2433        catch (LDAPException le)
2434        {
2435          debugException(le);
2436          hashCode += dn.hashCode();
2437        }
2438    
2439        for (final Attribute a : attributes.values())
2440        {
2441          hashCode += a.hashCode();
2442        }
2443    
2444        return hashCode;
2445      }
2446    
2447    
2448    
2449      /**
2450       * Indicates whether the provided object is equal to this entry.  The provided
2451       * object will only be considered equal to this entry if it is an entry with
2452       * the same DN and set of attributes.
2453       *
2454       * @param  o  The object for which to make the determination.
2455       *
2456       * @return  {@code true} if the provided object is considered equal to this
2457       *          entry, or {@code false} if not.
2458       */
2459      @Override()
2460      public boolean equals(final Object o)
2461      {
2462        if (o == null)
2463        {
2464          return false;
2465        }
2466    
2467        if (o == this)
2468        {
2469          return true;
2470        }
2471    
2472        if (! (o instanceof Entry))
2473        {
2474          return false;
2475        }
2476    
2477        final Entry e = (Entry) o;
2478    
2479        try
2480        {
2481          final DN thisDN = getParsedDN();
2482          final DN thatDN = e.getParsedDN();
2483          if (! thisDN.equals(thatDN))
2484          {
2485            return false;
2486          }
2487        }
2488        catch (LDAPException le)
2489        {
2490          debugException(le);
2491          if (! dn.equals(e.dn))
2492          {
2493            return false;
2494          }
2495        }
2496    
2497        if (attributes.size() != e.attributes.size())
2498        {
2499          return false;
2500        }
2501    
2502        for (final Attribute a : attributes.values())
2503        {
2504          if (! e.hasAttribute(a))
2505          {
2506            return false;
2507          }
2508        }
2509    
2510        return true;
2511      }
2512    
2513    
2514    
2515      /**
2516       * Creates a new entry that is a duplicate of this entry.
2517       *
2518       * @return  A new entry that is a duplicate of this entry.
2519       */
2520      public Entry duplicate()
2521      {
2522        return new Entry(dn, schema, attributes.values());
2523      }
2524    
2525    
2526    
2527      /**
2528       * Retrieves an LDIF representation of this entry, with each attribute value
2529       * on a separate line.  Long lines will not be wrapped.
2530       *
2531       * @return  An LDIF representation of this entry.
2532       */
2533      public final String[] toLDIF()
2534      {
2535        return toLDIF(0);
2536      }
2537    
2538    
2539    
2540      /**
2541       * Retrieves an LDIF representation of this entry, with each attribute value
2542       * on a separate line.  Long lines will be wrapped at the specified column.
2543       *
2544       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2545       *                     value less than or equal to two indicates that no
2546       *                     wrapping should be performed.
2547       *
2548       * @return  An LDIF representation of this entry.
2549       */
2550      public final String[] toLDIF(final int wrapColumn)
2551      {
2552        List<String> ldifLines = new ArrayList<String>(2*attributes.size());
2553        ldifLines.add(LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn)));
2554    
2555        for (final Attribute a : attributes.values())
2556        {
2557          final String name = a.getName();
2558          for (final ASN1OctetString value : a.getRawValues())
2559          {
2560            ldifLines.add(LDIFWriter.encodeNameAndValue(name, value));
2561          }
2562        }
2563    
2564        if (wrapColumn > 2)
2565        {
2566          ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
2567        }
2568    
2569        final String[] lineArray = new String[ldifLines.size()];
2570        ldifLines.toArray(lineArray);
2571        return lineArray;
2572      }
2573    
2574    
2575    
2576      /**
2577       * Appends an LDIF representation of this entry to the provided buffer.  Long
2578       * lines will not be wrapped.
2579       *
2580       * @param  buffer The buffer to which the LDIF representation of this entry
2581       *                should be written.
2582       */
2583      public final void toLDIF(final ByteStringBuffer buffer)
2584      {
2585        toLDIF(buffer, 0);
2586      }
2587    
2588    
2589    
2590      /**
2591       * Appends an LDIF representation of this entry to the provided buffer.
2592       *
2593       * @param  buffer      The buffer to which the LDIF representation of this
2594       *                     entry should be written.
2595       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2596       *                     value less than or equal to two indicates that no
2597       *                     wrapping should be performed.
2598       */
2599      public final void toLDIF(final ByteStringBuffer buffer, final int wrapColumn)
2600      {
2601        LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
2602                           wrapColumn);
2603        buffer.append(EOL_BYTES);
2604    
2605        for (final Attribute a : attributes.values())
2606        {
2607          final String name = a.getName();
2608          for (final ASN1OctetString value : a.getRawValues())
2609          {
2610            LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
2611            buffer.append(EOL_BYTES);
2612          }
2613        }
2614      }
2615    
2616    
2617    
2618      /**
2619       * Retrieves an LDIF-formatted string representation of this entry.  No
2620       * wrapping will be performed, and no extra blank lines will be added.
2621       *
2622       * @return  An LDIF-formatted string representation of this entry.
2623       */
2624      public final String toLDIFString()
2625      {
2626        final StringBuilder buffer = new StringBuilder();
2627        toLDIFString(buffer, 0);
2628        return buffer.toString();
2629      }
2630    
2631    
2632    
2633      /**
2634       * Retrieves an LDIF-formatted string representation of this entry.  No
2635       * extra blank lines will be added.
2636       *
2637       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2638       *                     value less than or equal to two indicates that no
2639       *                     wrapping should be performed.
2640       *
2641       * @return  An LDIF-formatted string representation of this entry.
2642       */
2643      public final String toLDIFString(final int wrapColumn)
2644      {
2645        final StringBuilder buffer = new StringBuilder();
2646        toLDIFString(buffer, wrapColumn);
2647        return buffer.toString();
2648      }
2649    
2650    
2651    
2652      /**
2653       * Appends an LDIF-formatted string representation of this entry to the
2654       * provided buffer.  No wrapping will be performed, and no extra blank lines
2655       * will be added.
2656       *
2657       * @param  buffer  The buffer to which to append the LDIF representation of
2658       *                 this entry.
2659       */
2660      public final void toLDIFString(final StringBuilder buffer)
2661      {
2662        toLDIFString(buffer, 0);
2663      }
2664    
2665    
2666    
2667      /**
2668       * Appends an LDIF-formatted string representation of this entry to the
2669       * provided buffer.  No extra blank lines will be added.
2670       *
2671       * @param  buffer      The buffer to which to append the LDIF representation
2672       *                     of this entry.
2673       * @param  wrapColumn  The column at which long lines should be wrapped.  A
2674       *                     value less than or equal to two indicates that no
2675       *                     wrapping should be performed.
2676       */
2677      public final void toLDIFString(final StringBuilder buffer,
2678                                     final int wrapColumn)
2679      {
2680        LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
2681                                      wrapColumn);
2682        buffer.append(EOL);
2683    
2684        for (final Attribute a : attributes.values())
2685        {
2686          final String name = a.getName();
2687          for (final ASN1OctetString value : a.getRawValues())
2688          {
2689            LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
2690            buffer.append(EOL);
2691          }
2692        }
2693      }
2694    
2695    
2696    
2697      /**
2698       * Retrieves a string representation of this entry.
2699       *
2700       * @return  A string representation of this entry.
2701       */
2702      @Override()
2703      public final String toString()
2704      {
2705        final StringBuilder buffer = new StringBuilder();
2706        toString(buffer);
2707        return buffer.toString();
2708      }
2709    
2710    
2711    
2712      /**
2713       * Appends a string representation of this entry to the provided buffer.
2714       *
2715       * @param  buffer  The buffer to which to append the string representation of
2716       *                 this entry.
2717       */
2718      public void toString(final StringBuilder buffer)
2719      {
2720        buffer.append("Entry(dn='");
2721        buffer.append(dn);
2722        buffer.append("', attributes={");
2723    
2724        final Iterator<Attribute> iterator = attributes.values().iterator();
2725    
2726        while (iterator.hasNext())
2727        {
2728          iterator.next().toString(buffer);
2729          if (iterator.hasNext())
2730          {
2731            buffer.append(", ");
2732          }
2733        }
2734    
2735        buffer.append("})");
2736      }
2737    }