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