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