001/*
002 * Copyright 2007-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2022 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-2022 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          errors.add(ERR_ENTRY_APPLY_MODS_TARGETS_RDN.get(entry.getDN()));
2591          if (resultCode == null)
2592          {
2593            resultCode = ResultCode.NOT_ALLOWED_ON_RDN;
2594          }
2595          break;
2596        }
2597      }
2598    }
2599
2600
2601    if (errors.isEmpty())
2602    {
2603      return e;
2604    }
2605
2606    if (resultCode == null)
2607    {
2608      resultCode = ResultCode.CONSTRAINT_VIOLATION;
2609    }
2610
2611    throw new LDAPException(resultCode,
2612         ERR_ENTRY_APPLY_MODS_FAILURE.get(e.getDN(),
2613              StaticUtils.concatenateStrings(errors)));
2614  }
2615
2616
2617
2618  /**
2619   * Creates a duplicate of the provided entry with the appropriate changes for
2620   * a modify DN operation.  Any corresponding changes to the set of attribute
2621   * values (to ensure that the new RDN values are present in the entry, and
2622   * optionally to remove the old RDN values from the entry) will also be
2623   * applied.
2624   *
2625   * @param  entry         The entry to be renamed.  It must not be
2626   *                       {@code null}.
2627   * @param  newRDN        The new RDN to use for the entry.  It must not be
2628   *                       {@code null}.
2629   * @param  deleteOldRDN  Indicates whether attribute values that were present
2630   *                       in the old RDN but are no longer present in the new
2631   *                       DN should be removed from the entry.
2632   *
2633   * @return  A new entry that is a duplicate of the provided entry, except with
2634   *          any necessary changes for the modify DN.
2635   *
2636   * @throws  LDAPException  If a problem is encountered during modify DN
2637   *                         processing.
2638   */
2639  @NotNull()
2640  public static Entry applyModifyDN(@NotNull final Entry entry,
2641                                    @NotNull final String newRDN,
2642                                    final boolean deleteOldRDN)
2643         throws LDAPException
2644  {
2645    return applyModifyDN(entry, newRDN, deleteOldRDN, null);
2646  }
2647
2648
2649
2650  /**
2651   * Creates a duplicate of the provided entry with the appropriate changes for
2652   * a modify DN operation.  Any corresponding changes to the set of attribute
2653   * values (to ensure that the new RDN values are present in the entry, and
2654   * optionally to remove the old RDN values from the entry) will also be
2655   * applied.
2656   *
2657   * @param  entry          The entry to be renamed.  It must not be
2658   *                        {@code null}.
2659   * @param  newRDN         The new RDN to use for the entry.  It must not be
2660   *                        {@code null}.
2661   * @param  deleteOldRDN   Indicates whether attribute values that were present
2662   *                        in the old RDN but are no longer present in the new
2663   *                        DN should be removed from the entry.
2664   * @param  newSuperiorDN  The new superior DN for the entry.  If this is
2665   *                        {@code null}, then the entry will remain below its
2666   *                        existing parent.  If it is non-{@code null}, then
2667   *                        the resulting DN will be a concatenation of the new
2668   *                        RDN and the new superior DN.
2669   *
2670   * @return  A new entry that is a duplicate of the provided entry, except with
2671   *          any necessary changes for the modify DN.
2672   *
2673   * @throws  LDAPException  If a problem is encountered during modify DN
2674   *                         processing.
2675   */
2676  @NotNull()
2677  public static Entry applyModifyDN(@NotNull final Entry entry,
2678                                    @NotNull final String newRDN,
2679                                    final boolean deleteOldRDN,
2680                                    @Nullable final String newSuperiorDN)
2681         throws LDAPException
2682  {
2683    Validator.ensureNotNull(entry);
2684    Validator.ensureNotNull(newRDN);
2685
2686    // Parse all of the necessary elements from the request.
2687    final DN  parsedOldDN         = entry.getParsedDN();
2688    final RDN parsedOldRDN        = parsedOldDN.getRDN();
2689    final DN  parsedOldSuperiorDN = parsedOldDN.getParent();
2690
2691    final RDN parsedNewRDN = new RDN(newRDN);
2692
2693    final DN  parsedNewSuperiorDN;
2694    if (newSuperiorDN == null)
2695    {
2696      parsedNewSuperiorDN = parsedOldSuperiorDN;
2697    }
2698    else
2699    {
2700      parsedNewSuperiorDN = new DN(newSuperiorDN);
2701    }
2702
2703    // Duplicate the provided entry and update it with the new DN.
2704    final Entry newEntry = entry.duplicate();
2705    if (parsedNewSuperiorDN == null)
2706    {
2707      // This should only happen if the provided entry has a zero-length DN.
2708      // It's extremely unlikely that a directory server would permit this
2709      // change, but we'll go ahead and process it.
2710      newEntry.setDN(new DN(parsedNewRDN));
2711    }
2712    else
2713    {
2714      newEntry.setDN(new DN(parsedNewRDN, parsedNewSuperiorDN));
2715    }
2716
2717    // If deleteOldRDN is true, then remove any values present in the old RDN
2718    // that are not present in the new RDN.
2719    if (deleteOldRDN && (parsedOldRDN != null))
2720    {
2721      final String[] oldNames  = parsedOldRDN.getAttributeNames();
2722      final byte[][] oldValues = parsedOldRDN.getByteArrayAttributeValues();
2723      for (int i=0; i < oldNames.length; i++)
2724      {
2725        if (! parsedNewRDN.hasAttributeValue(oldNames[i], oldValues[i]))
2726        {
2727          newEntry.removeAttributeValue(oldNames[i], oldValues[i]);
2728        }
2729      }
2730    }
2731
2732    // Add any values present in the new RDN that were not present in the old
2733    // RDN.
2734    final String[] newNames  = parsedNewRDN.getAttributeNames();
2735    final byte[][] newValues = parsedNewRDN.getByteArrayAttributeValues();
2736    for (int i=0; i < newNames.length; i++)
2737    {
2738      if ((parsedOldRDN == null) ||
2739          (! parsedOldRDN.hasAttributeValue(newNames[i], newValues[i])))
2740      {
2741        newEntry.addAttribute(newNames[i], newValues[i]);
2742      }
2743    }
2744
2745    return newEntry;
2746  }
2747
2748
2749
2750  /**
2751   * Generates a hash code for this entry.
2752   *
2753   * @return  The generated hash code for this entry.
2754   */
2755  @Override()
2756  public int hashCode()
2757  {
2758    int hashCode = 0;
2759    try
2760    {
2761      hashCode += getParsedDN().hashCode();
2762    }
2763    catch (final LDAPException le)
2764    {
2765      Debug.debugException(le);
2766      hashCode += dn.hashCode();
2767    }
2768
2769    for (final Attribute a : attributes.values())
2770    {
2771      hashCode += a.hashCode();
2772    }
2773
2774    return hashCode;
2775  }
2776
2777
2778
2779  /**
2780   * Indicates whether the provided object is equal to this entry.  The provided
2781   * object will only be considered equal to this entry if it is an entry with
2782   * the same DN and set of attributes.
2783   *
2784   * @param  o  The object for which to make the determination.
2785   *
2786   * @return  {@code true} if the provided object is considered equal to this
2787   *          entry, or {@code false} if not.
2788   */
2789  @Override()
2790  public boolean equals(@Nullable final Object o)
2791  {
2792    if (o == null)
2793    {
2794      return false;
2795    }
2796
2797    if (o == this)
2798    {
2799      return true;
2800    }
2801
2802    if (! (o instanceof Entry))
2803    {
2804      return false;
2805    }
2806
2807    final Entry e = (Entry) o;
2808
2809    try
2810    {
2811      final DN thisDN = getParsedDN();
2812      final DN thatDN = e.getParsedDN();
2813      if (! thisDN.equals(thatDN))
2814      {
2815        return false;
2816      }
2817    }
2818    catch (final LDAPException le)
2819    {
2820      Debug.debugException(le);
2821      if (! dn.equals(e.dn))
2822      {
2823        return false;
2824      }
2825    }
2826
2827    if (attributes.size() != e.attributes.size())
2828    {
2829      return false;
2830    }
2831
2832    for (final Attribute a : attributes.values())
2833    {
2834      if (! e.hasAttribute(a))
2835      {
2836        return false;
2837      }
2838    }
2839
2840    return true;
2841  }
2842
2843
2844
2845  /**
2846   * Creates a new entry that is a duplicate of this entry.
2847   *
2848   * @return  A new entry that is a duplicate of this entry.
2849   */
2850  @NotNull()
2851  public Entry duplicate()
2852  {
2853    return new Entry(dn, schema, attributes.values());
2854  }
2855
2856
2857
2858  /**
2859   * Retrieves an LDIF representation of this entry, with each attribute value
2860   * on a separate line.  Long lines will not be wrapped.
2861   *
2862   * @return  An LDIF representation of this entry.
2863   */
2864  @Override()
2865  @NotNull()
2866  public final String[] toLDIF()
2867  {
2868    return toLDIF(0);
2869  }
2870
2871
2872
2873  /**
2874   * Retrieves an LDIF representation of this entry, with each attribute value
2875   * on a separate line.  Long lines will be wrapped at the specified column.
2876   *
2877   * @param  wrapColumn  The column at which long lines should be wrapped.  A
2878   *                     value less than or equal to two indicates that no
2879   *                     wrapping should be performed.
2880   *
2881   * @return  An LDIF representation of this entry.
2882   */
2883  @Override()
2884  @NotNull()
2885  public final String[] toLDIF(final int wrapColumn)
2886  {
2887    List<String> ldifLines = new ArrayList<>(2*attributes.size());
2888    encodeNameAndValue("dn", new ASN1OctetString(dn), ldifLines);
2889
2890    for (final Attribute a : attributes.values())
2891    {
2892      final String name = a.getName();
2893      if (a.hasValue())
2894      {
2895        for (final ASN1OctetString value : a.getRawValues())
2896        {
2897          encodeNameAndValue(name, value, ldifLines);
2898        }
2899      }
2900      else
2901      {
2902        encodeNameAndValue(name, EMPTY_OCTET_STRING, ldifLines);
2903      }
2904    }
2905
2906    if (wrapColumn > 2)
2907    {
2908      ldifLines = LDIFWriter.wrapLines(wrapColumn, ldifLines);
2909    }
2910
2911    final String[] lineArray = new String[ldifLines.size()];
2912    ldifLines.toArray(lineArray);
2913    return lineArray;
2914  }
2915
2916
2917
2918  /**
2919   * Encodes the provided name and value and adds the result to the provided
2920   * list of lines.  This will handle the case in which the encoded name and
2921   * value includes comments about the base64-decoded representation of the
2922   * provided value.
2923   *
2924   * @param  name   The attribute name to be encoded.
2925   * @param  value  The attribute value to be encoded.
2926   * @param  lines  The list of lines to be updated.
2927   */
2928  private static void encodeNameAndValue(@NotNull final String name,
2929                                         @NotNull final ASN1OctetString value,
2930                                         @NotNull final List<String> lines)
2931  {
2932    final String line = LDIFWriter.encodeNameAndValue(name, value);
2933    if (LDIFWriter.commentAboutBase64EncodedValues() &&
2934        line.startsWith(name + "::"))
2935    {
2936      final StringTokenizer tokenizer = new StringTokenizer(line, "\r\n");
2937      while (tokenizer.hasMoreTokens())
2938      {
2939        lines.add(tokenizer.nextToken());
2940      }
2941    }
2942    else
2943    {
2944      lines.add(line);
2945    }
2946  }
2947
2948
2949
2950  /**
2951   * Appends an LDIF representation of this entry to the provided buffer.  Long
2952   * lines will not be wrapped.
2953   *
2954   * @param  buffer The buffer to which the LDIF representation of this entry
2955   *                should be written.
2956   */
2957  @Override()
2958  public final void toLDIF(@NotNull final ByteStringBuffer buffer)
2959  {
2960    toLDIF(buffer, 0);
2961  }
2962
2963
2964
2965  /**
2966   * Appends an LDIF representation of this entry to the provided buffer.
2967   *
2968   * @param  buffer      The buffer to which the LDIF representation of this
2969   *                     entry should be written.
2970   * @param  wrapColumn  The column at which long lines should be wrapped.  A
2971   *                     value less than or equal to two indicates that no
2972   *                     wrapping should be performed.
2973   */
2974  @Override()
2975  public final void toLDIF(@NotNull final ByteStringBuffer buffer,
2976                           final int wrapColumn)
2977  {
2978    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
2979                       wrapColumn);
2980    buffer.append(StaticUtils.EOL_BYTES);
2981
2982    for (final Attribute a : attributes.values())
2983    {
2984      final String name = a.getName();
2985      if (a.hasValue())
2986      {
2987        for (final ASN1OctetString value : a.getRawValues())
2988        {
2989          LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
2990          buffer.append(StaticUtils.EOL_BYTES);
2991        }
2992      }
2993      else
2994      {
2995        LDIFWriter.encodeNameAndValue(name, EMPTY_OCTET_STRING, buffer,
2996             wrapColumn);
2997        buffer.append(StaticUtils.EOL_BYTES);
2998      }
2999    }
3000  }
3001
3002
3003
3004  /**
3005   * Retrieves an LDIF-formatted string representation of this entry.  No
3006   * wrapping will be performed, and no extra blank lines will be added.
3007   *
3008   * @return  An LDIF-formatted string representation of this entry.
3009   */
3010  @Override()
3011  @NotNull()
3012  public final String toLDIFString()
3013  {
3014    final StringBuilder buffer = new StringBuilder();
3015    toLDIFString(buffer, 0);
3016    return buffer.toString();
3017  }
3018
3019
3020
3021  /**
3022   * Retrieves an LDIF-formatted string representation of this entry.  No
3023   * extra blank lines will be added.
3024   *
3025   * @param  wrapColumn  The column at which long lines should be wrapped.  A
3026   *                     value less than or equal to two indicates that no
3027   *                     wrapping should be performed.
3028   *
3029   * @return  An LDIF-formatted string representation of this entry.
3030   */
3031  @Override()
3032  @NotNull()
3033  public final String toLDIFString(final int wrapColumn)
3034  {
3035    final StringBuilder buffer = new StringBuilder();
3036    toLDIFString(buffer, wrapColumn);
3037    return buffer.toString();
3038  }
3039
3040
3041
3042  /**
3043   * Appends an LDIF-formatted string representation of this entry to the
3044   * provided buffer.  No wrapping will be performed, and no extra blank lines
3045   * will be added.
3046   *
3047   * @param  buffer  The buffer to which to append the LDIF representation of
3048   *                 this entry.
3049   */
3050  @Override()
3051  public final void toLDIFString(@NotNull final StringBuilder buffer)
3052  {
3053    toLDIFString(buffer, 0);
3054  }
3055
3056
3057
3058  /**
3059   * Appends an LDIF-formatted string representation of this entry to the
3060   * provided buffer.  No extra blank lines will be added.
3061   *
3062   * @param  buffer      The buffer to which to append the LDIF representation
3063   *                     of this entry.
3064   * @param  wrapColumn  The column at which long lines should be wrapped.  A
3065   *                     value less than or equal to two indicates that no
3066   *                     wrapping should be performed.
3067   */
3068  @Override()
3069  public final void toLDIFString(@NotNull final StringBuilder buffer,
3070                                 final int wrapColumn)
3071  {
3072    LDIFWriter.encodeNameAndValue("dn", new ASN1OctetString(dn), buffer,
3073                                  wrapColumn);
3074    buffer.append(StaticUtils.EOL);
3075
3076    for (final Attribute a : attributes.values())
3077    {
3078      final String name = a.getName();
3079      if (a.hasValue())
3080      {
3081        for (final ASN1OctetString value : a.getRawValues())
3082        {
3083          LDIFWriter.encodeNameAndValue(name, value, buffer, wrapColumn);
3084          buffer.append(StaticUtils.EOL);
3085        }
3086      }
3087      else
3088      {
3089        LDIFWriter.encodeNameAndValue(name, EMPTY_OCTET_STRING, buffer,
3090             wrapColumn);
3091        buffer.append(StaticUtils.EOL);
3092      }
3093    }
3094  }
3095
3096
3097
3098  /**
3099   * Retrieves a string representation of this entry.
3100   *
3101   * @return  A string representation of this entry.
3102   */
3103  @Override()
3104  @NotNull()
3105  public final String toString()
3106  {
3107    final StringBuilder buffer = new StringBuilder();
3108    toString(buffer);
3109    return buffer.toString();
3110  }
3111
3112
3113
3114  /**
3115   * Appends a string representation of this entry to the provided buffer.
3116   *
3117   * @param  buffer  The buffer to which to append the string representation of
3118   *                 this entry.
3119   */
3120  @Override()
3121  public void toString(@NotNull final StringBuilder buffer)
3122  {
3123    buffer.append("Entry(dn='");
3124    buffer.append(dn);
3125    buffer.append("', attributes={");
3126
3127    final Iterator<Attribute> iterator = attributes.values().iterator();
3128
3129    while (iterator.hasNext())
3130    {
3131      iterator.next().toString(buffer);
3132      if (iterator.hasNext())
3133      {
3134        buffer.append(", ");
3135      }
3136    }
3137
3138    buffer.append("})");
3139  }
3140}