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