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