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