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