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