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.io.Serializable;
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.LinkedHashSet;
034import java.util.Set;
035
036import com.unboundid.asn1.ASN1Buffer;
037import com.unboundid.asn1.ASN1BufferSequence;
038import com.unboundid.asn1.ASN1BufferSet;
039import com.unboundid.asn1.ASN1Element;
040import com.unboundid.asn1.ASN1Exception;
041import com.unboundid.asn1.ASN1OctetString;
042import com.unboundid.asn1.ASN1Sequence;
043import com.unboundid.asn1.ASN1Set;
044import com.unboundid.asn1.ASN1StreamReader;
045import com.unboundid.asn1.ASN1StreamReaderSet;
046import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
047import com.unboundid.ldap.matchingrules.MatchingRule;
048import com.unboundid.ldap.sdk.schema.Schema;
049import com.unboundid.util.Base64;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053
054import static com.unboundid.ldap.sdk.LDAPMessages.*;
055import static com.unboundid.util.Debug.*;
056import static com.unboundid.util.StaticUtils.*;
057import static com.unboundid.util.Validator.*;
058
059
060
061/**
062 * This class provides a data structure for holding information about an LDAP
063 * attribute, which includes an attribute name (which may include a set of
064 * attribute options) and zero or more values.  Attribute objects are immutable
065 * and cannot be altered.  However, if an attribute is included in an
066 * {@link Entry} object, then it is possible to add and remove attribute values
067 * from the entry (which will actually create new Attribute object instances),
068 * although this is not allowed for instances of {@link ReadOnlyEntry} and its
069 * subclasses.
070 * <BR><BR>
071 * This class uses the term "attribute name" as an equivalent of what the LDAP
072 * specification refers to as an "attribute description".  An attribute
073 * description consists of an attribute type name or object identifier (which
074 * this class refers to as the "base name") followed by zero or more attribute
075 * options, each of which should be prefixed by a semicolon.  Attribute options
076 * may be used to provide additional metadata for the attribute and/or its
077 * values, or to indicate special handling for the values.  For example,
078 * <A HREF="http://www.ietf.org/rfc/rfc3866.txt">RFC 3866</A> describes the use
079 * of attribute options to indicate that a value may be associated with a
080 * particular language (e.g., "cn;lang-en-US" indicates that the values of that
081 * cn attribute should be treated as U.S. English values), and
082 * <A HREF="http://www.ietf.org/rfc/rfc4522.txt">RFC 4522</A> describes a binary
083 * encoding option that indicates that the server should only attempt to
084 * interact with the values as binary data (e.g., "userCertificate;binary") and
085 * should not treat them as strings.  An attribute name (which is technically
086 * referred to as an "attribute description" in the protocol specification) may
087 * have zero, one, or multiple attribute options.  If there are any attribute
088 * options, then a semicolon is used to separate the first option from the base
089 * attribute name, and to separate each subsequent attribute option from the
090 * previous option.
091 * <BR><BR>
092 * Attribute values can be treated as either strings or byte arrays.  In LDAP,
093 * they are always transferred using a binary encoding, but applications
094 * frequently treat them as strings and it is often more convenient to do so.
095 * However, for some kinds of data (e.g., certificates, images, audio clips, and
096 * other "blobs") it may be desirable to only treat them as binary data and only
097 * interact with the values as byte arrays.  If you do intend to interact with
098 * string values as byte arrays, then it is important to ensure that you use a
099 * UTF-8 representation for those values unless you are confident that the
100 * directory server will not attempt to treat the value as a string.
101 */
102@NotMutable()
103@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
104public final class Attribute
105       implements Serializable
106{
107  /**
108   * The array to use as the set of values when there are no values.
109   */
110  private static final ASN1OctetString[] NO_VALUES = new ASN1OctetString[0];
111
112
113
114  /**
115   * The array to use as the set of byte array values when there are no values.
116   */
117  private static final byte[][] NO_BYTE_VALUES = new byte[0][];
118
119
120
121  /**
122   * The serial version UID for this serializable class.
123   */
124  private static final long serialVersionUID = 5867076498293567612L;
125
126
127
128  // The set of values for this attribute.
129  private final ASN1OctetString[] values;
130
131  // The hash code for this attribute.
132  private int hashCode = -1;
133
134  // The matching rule that should be used for equality determinations.
135  private final MatchingRule matchingRule;
136
137  // The attribute description for this attribute.
138  private final String name;
139
140
141
142  /**
143   * Creates a new LDAP attribute with the specified name and no values.
144   *
145   * @param  name  The name for this attribute.  It must not be {@code null}.
146   */
147  public Attribute(final String name)
148  {
149    ensureNotNull(name);
150
151    this.name = name;
152
153    values = NO_VALUES;
154    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
155  }
156
157
158
159  /**
160   * Creates a new LDAP attribute with the specified name and value.
161   *
162   * @param  name   The name for this attribute.  It must not be {@code null}.
163   * @param  value  The value for this attribute.  It must not be {@code null}.
164   */
165  public Attribute(final String name, final String value)
166  {
167    ensureNotNull(name, value);
168
169    this.name = name;
170
171    values = new ASN1OctetString[] { new ASN1OctetString(value) };
172    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
173  }
174
175
176
177  /**
178   * Creates a new LDAP attribute with the specified name and value.
179   *
180   * @param  name   The name for this attribute.  It must not be {@code null}.
181   * @param  value  The value for this attribute.  It must not be {@code null}.
182   */
183  public Attribute(final String name, final byte[] value)
184  {
185    ensureNotNull(name, value);
186
187    this.name = name;
188    values = new ASN1OctetString[] { new ASN1OctetString(value) };
189    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
190  }
191
192
193
194  /**
195   * Creates a new LDAP attribute with the specified name and set of values.
196   *
197   * @param  name    The name for this attribute.  It must not be {@code null}.
198   * @param  values  The set of values for this attribute.  It must not be
199   *                 {@code null}.
200   */
201  public Attribute(final String name, final String... values)
202  {
203    ensureNotNull(name, values);
204
205    this.name = name;
206
207    this.values = new ASN1OctetString[values.length];
208    for (int i=0; i < values.length; i++)
209    {
210      this.values[i] = new ASN1OctetString(values[i]);
211    }
212    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
213  }
214
215
216
217  /**
218   * Creates a new LDAP attribute with the specified name and set of values.
219   *
220   * @param  name    The name for this attribute.  It must not be {@code null}.
221   * @param  values  The set of values for this attribute.  It must not be
222   *                 {@code null}.
223   */
224  public Attribute(final String name, final byte[]... values)
225  {
226    ensureNotNull(name, values);
227
228    this.name = name;
229
230    this.values = new ASN1OctetString[values.length];
231    for (int i=0; i < values.length; i++)
232    {
233      this.values[i] = new ASN1OctetString(values[i]);
234    }
235    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
236  }
237
238
239
240  /**
241   * Creates a new LDAP attribute with the specified name and set of values.
242   *
243   * @param  name    The name for this attribute.  It must not be {@code null}.
244   * @param  values  The set of raw values for this attribute.  It must not be
245   *                 {@code null}.
246   */
247  public Attribute(final String name, final ASN1OctetString... values)
248  {
249    ensureNotNull(name, values);
250
251    this.name   = name;
252    this.values = values;
253
254    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
255  }
256
257
258
259  /**
260   * Creates a new LDAP attribute with the specified name and set of values.
261   *
262   * @param  name    The name for this attribute.  It must not be {@code null}.
263   * @param  values  The set of values for this attribute.  It must not be
264   *                 {@code null}.
265   */
266  public Attribute(final String name, final Collection<String> values)
267  {
268    ensureNotNull(name, values);
269
270    this.name = name;
271
272    this.values = new ASN1OctetString[values.size()];
273
274    int i=0;
275    for (final String s : values)
276    {
277      this.values[i++] = new ASN1OctetString(s);
278    }
279    matchingRule = CaseIgnoreStringMatchingRule.getInstance();
280  }
281
282
283
284  /**
285   * Creates a new LDAP attribute with the specified name and no values.
286   *
287   * @param  name          The name for this attribute.  It must not be
288   *                       {@code null}.
289   * @param  matchingRule  The matching rule to use when comparing values.  It
290   *                       must not be {@code null}.
291   */
292  public Attribute(final String name, final MatchingRule matchingRule)
293  {
294    ensureNotNull(name, matchingRule);
295
296    this.name         = name;
297    this.matchingRule = matchingRule;
298
299    values = NO_VALUES;
300  }
301
302
303
304  /**
305   * Creates a new LDAP attribute with the specified name and value.
306   *
307   * @param  name          The name for this attribute.  It must not be
308   *                       {@code null}.
309   * @param  matchingRule  The matching rule to use when comparing values.  It
310   *                       must not be {@code null}.
311   * @param  value         The value for this attribute.  It must not be
312   *                       {@code null}.
313   */
314  public Attribute(final String name, final MatchingRule matchingRule,
315                   final String value)
316  {
317    ensureNotNull(name, matchingRule, value);
318
319    this.name         = name;
320    this.matchingRule = matchingRule;
321
322    values = new ASN1OctetString[] { new ASN1OctetString(value) };
323  }
324
325
326
327  /**
328   * Creates a new LDAP attribute with the specified name and value.
329   *
330   * @param  name          The name for this attribute.  It must not be
331   *                       {@code null}.
332   * @param  matchingRule  The matching rule to use when comparing values.  It
333   *                       must not be {@code null}.
334   * @param  value         The value for this attribute.  It must not be
335   *                       {@code null}.
336   */
337  public Attribute(final String name, final MatchingRule matchingRule,
338                   final byte[] value)
339  {
340    ensureNotNull(name, matchingRule, value);
341
342    this.name         = name;
343    this.matchingRule = matchingRule;
344
345    values = new ASN1OctetString[] { new ASN1OctetString(value) };
346  }
347
348
349
350  /**
351   * Creates a new LDAP attribute with the specified name and set of values.
352   *
353   * @param  name          The name for this attribute.  It must not be
354   *                       {@code null}.
355   * @param  matchingRule  The matching rule to use when comparing values.  It
356   *                       must not be {@code null}.
357   * @param  values        The set of values for this attribute.  It must not be
358   *                       {@code null}.
359   */
360  public Attribute(final String name, final MatchingRule matchingRule,
361                   final String... values)
362  {
363    ensureNotNull(name, matchingRule, values);
364
365    this.name         = name;
366    this.matchingRule = matchingRule;
367
368    this.values = new ASN1OctetString[values.length];
369    for (int i=0; i < values.length; i++)
370    {
371      this.values[i] = new ASN1OctetString(values[i]);
372    }
373  }
374
375
376
377  /**
378   * Creates a new LDAP attribute with the specified name and set of values.
379   *
380   * @param  name          The name for this attribute.  It must not be
381   *                       {@code null}.
382   * @param  matchingRule  The matching rule to use when comparing values.  It
383   *                       must not be {@code null}.
384   * @param  values        The set of values for this attribute.  It must not be
385   *                       {@code null}.
386   */
387  public Attribute(final String name, final MatchingRule matchingRule,
388                   final byte[]... values)
389  {
390    ensureNotNull(name, matchingRule, values);
391
392    this.name         = name;
393    this.matchingRule = matchingRule;
394
395    this.values = new ASN1OctetString[values.length];
396    for (int i=0; i < values.length; i++)
397    {
398      this.values[i] = new ASN1OctetString(values[i]);
399    }
400  }
401
402
403
404  /**
405   * Creates a new LDAP attribute with the specified name and set of values.
406   *
407   * @param  name          The name for this attribute.  It must not be
408   *                       {@code null}.
409   * @param  matchingRule  The matching rule to use when comparing values.  It
410   *                       must not be {@code null}.
411   * @param  values        The set of values for this attribute.  It must not be
412   *                       {@code null}.
413   */
414  public Attribute(final String name, final MatchingRule matchingRule,
415                   final Collection<String> values)
416  {
417    ensureNotNull(name, matchingRule, values);
418
419    this.name         = name;
420    this.matchingRule = matchingRule;
421
422    this.values = new ASN1OctetString[values.size()];
423
424    int i=0;
425    for (final String s : values)
426    {
427      this.values[i++] = new ASN1OctetString(s);
428    }
429  }
430
431
432
433  /**
434   * Creates a new LDAP attribute with the specified name and set of values.
435   *
436   * @param  name          The name for this attribute.
437   * @param  matchingRule  The matching rule for this attribute.
438   * @param  values        The set of values for this attribute.
439   */
440  public Attribute(final String name, final MatchingRule matchingRule,
441                   final ASN1OctetString[] values)
442  {
443    this.name         = name;
444    this.matchingRule = matchingRule;
445    this.values       = values;
446  }
447
448
449
450  /**
451   * Creates a new LDAP attribute with the specified name and set of values.
452   *
453   * @param  name    The name for this attribute.  It must not be {@code null}.
454   * @param  schema  The schema to use to select the matching rule for this
455   *                 attribute.  It may be {@code null} if the default matching
456   *                 rule should be used.
457   * @param  values  The set of values for this attribute.  It must not be
458   *                 {@code null}.
459   */
460  public Attribute(final String name, final Schema schema,
461                   final String... values)
462  {
463    this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
464  }
465
466
467
468  /**
469   * Creates a new LDAP attribute with the specified name and set of values.
470   *
471   * @param  name    The name for this attribute.  It must not be {@code null}.
472   * @param  schema  The schema to use to select the matching rule for this
473   *                 attribute.  It may be {@code null} if the default matching
474   *                 rule should be used.
475   * @param  values  The set of values for this attribute.  It must not be
476   *                 {@code null}.
477   */
478  public Attribute(final String name, final Schema schema,
479                   final byte[]... values)
480  {
481    this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
482  }
483
484
485
486  /**
487   * Creates a new LDAP attribute with the specified name and set of values.
488   *
489   * @param  name    The name for this attribute.  It must not be {@code null}.
490   * @param  schema  The schema to use to select the matching rule for this
491   *                 attribute.  It may be {@code null} if the default matching
492   *                 rule should be used.
493   * @param  values  The set of values for this attribute.  It must not be
494   *                 {@code null}.
495   */
496  public Attribute(final String name, final Schema schema,
497                   final Collection<String> values)
498  {
499    this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
500  }
501
502
503
504  /**
505   * Creates a new LDAP attribute with the specified name and set of values.
506   *
507   * @param  name    The name for this attribute.  It must not be {@code null}.
508   * @param  schema  The schema to use to select the matching rule for this
509   *                 attribute.  It may be {@code null} if the default matching
510   *                 rule should be used.
511   * @param  values  The set of values for this attribute.  It must not be
512   *                 {@code null}.
513   */
514  public Attribute(final String name, final Schema schema,
515                   final ASN1OctetString[] values)
516  {
517    this(name, MatchingRule.selectEqualityMatchingRule(name, schema), values);
518  }
519
520
521
522  /**
523   * Creates a new attribute containing the merged values of the provided
524   * attributes.  Any duplicate values will only be present once in the
525   * resulting attribute.  The names of the provided attributes must be the
526   * same.
527   *
528   * @param  attr1  The first attribute containing the values to merge.  It must
529   *                not be {@code null}.
530   * @param  attr2  The second attribute containing the values to merge.  It
531   *                must not be {@code null}.
532   *
533   * @return  The new attribute containing the values of both of the
534   *          provided attributes.
535   */
536  public static Attribute mergeAttributes(final Attribute attr1,
537                                          final Attribute attr2)
538  {
539    return mergeAttributes(attr1, attr2, attr1.matchingRule);
540  }
541
542
543
544  /**
545   * Creates a new attribute containing the merged values of the provided
546   * attributes.  Any duplicate values will only be present once in the
547   * resulting attribute.  The names of the provided attributes must be the
548   * same.
549   *
550   * @param  attr1         The first attribute containing the values to merge.
551   *                       It must not be {@code null}.
552   * @param  attr2         The second attribute containing the values to merge.
553   *                       It must not be {@code null}.
554   * @param  matchingRule  The matching rule to use to locate matching values.
555   *                       It may be {@code null} if the matching rule
556   *                       associated with the first attribute should be used.
557   *
558   * @return  The new attribute containing the values of both of the
559   *          provided attributes.
560   */
561  public static Attribute mergeAttributes(final Attribute attr1,
562                                          final Attribute attr2,
563                                          final MatchingRule matchingRule)
564  {
565    ensureNotNull(attr1, attr2);
566
567    final String name = attr1.name;
568    ensureTrue(name.equalsIgnoreCase(attr2.name));
569
570    final MatchingRule mr;
571    if (matchingRule == null)
572    {
573      mr = attr1.matchingRule;
574    }
575    else
576    {
577      mr = matchingRule;
578    }
579
580    ASN1OctetString[] mergedValues =
581         new ASN1OctetString[attr1.values.length + attr2.values.length];
582    System.arraycopy(attr1.values, 0, mergedValues, 0, attr1.values.length);
583
584    int pos = attr1.values.length;
585    for (final ASN1OctetString s2 : attr2.values)
586    {
587      boolean found = false;
588      for (final ASN1OctetString s1 : attr1.values)
589      {
590        try
591        {
592          if (mr.valuesMatch(s1, s2))
593          {
594            found = true;
595            break;
596          }
597        }
598        catch (final Exception e)
599        {
600          debugException(e);
601        }
602      }
603
604      if (! found)
605      {
606        mergedValues[pos++] = s2;
607      }
608    }
609
610    if (pos != mergedValues.length)
611    {
612      // This indicates that there were duplicate values.
613      final ASN1OctetString[] newMergedValues = new ASN1OctetString[pos];
614      System.arraycopy(mergedValues, 0, newMergedValues, 0, pos);
615      mergedValues = newMergedValues;
616    }
617
618    return new Attribute(name, mr, mergedValues);
619  }
620
621
622
623  /**
624   * Creates a new attribute containing all of the values of the first attribute
625   * that are not contained in the second attribute.  Any values contained in
626   * the second attribute that are not contained in the first will be ignored.
627   * The names of the provided attributes must be the same.
628   *
629   * @param  attr1  The attribute from which to remove the values.  It must not
630   *                be {@code null}.
631   * @param  attr2  The attribute containing the values to remove.  It must not
632   *                be {@code null}.
633   *
634   * @return  A new attribute containing all of the values of the first
635   *          attribute not contained in the second.  It may contain zero values
636   *          if all the values of the first attribute were also contained in
637   *          the second.
638   */
639  public static Attribute removeValues(final Attribute attr1,
640                                       final Attribute attr2)
641  {
642    return removeValues(attr1, attr2, attr1.matchingRule);
643  }
644
645
646
647  /**
648   * Creates a new attribute containing all of the values of the first attribute
649   * that are not contained in the second attribute.  Any values contained in
650   * the second attribute that are not contained in the first will be ignored.
651   * The names of the provided attributes must be the same.
652   *
653   * @param  attr1         The attribute from which to remove the values.  It
654   *                       must not be {@code null}.
655   * @param  attr2         The attribute containing the values to remove.  It
656   *                       must not be {@code null}.
657   * @param  matchingRule  The matching rule to use to locate matching values.
658   *                       It may be {@code null} if the matching rule
659   *                       associated with the first attribute should be used.
660   *
661   * @return  A new attribute containing all of the values of the first
662   *          attribute not contained in the second.  It may contain zero values
663   *          if all the values of the first attribute were also contained in
664   *          the second.
665   */
666  public static Attribute removeValues(final Attribute attr1,
667                                       final Attribute attr2,
668                                       final MatchingRule matchingRule)
669  {
670    ensureNotNull(attr1, attr2);
671
672    final String name = attr1.name;
673    ensureTrue(name.equalsIgnoreCase(attr2.name));
674
675    final MatchingRule mr;
676    if (matchingRule == null)
677    {
678      mr = attr1.matchingRule;
679    }
680    else
681    {
682      mr = matchingRule;
683    }
684
685    final ArrayList<ASN1OctetString> newValues =
686         new ArrayList<ASN1OctetString>(Arrays.asList(attr1.values));
687
688    final Iterator<ASN1OctetString> iterator = newValues.iterator();
689    while (iterator.hasNext())
690    {
691      if (attr2.hasValue(iterator.next(), mr))
692      {
693        iterator.remove();
694      }
695    }
696
697    final ASN1OctetString[] newValueArray =
698         new ASN1OctetString[newValues.size()];
699    newValues.toArray(newValueArray);
700
701    return new Attribute(name, mr, newValueArray);
702  }
703
704
705
706  /**
707   * Retrieves the name for this attribute (i.e., the attribute description),
708   * which may include zero or more attribute options.
709   *
710   * @return  The name for this attribute.
711   */
712  public String getName()
713  {
714    return name;
715  }
716
717
718
719  /**
720   * Retrieves the base name for this attribute, which is the name or OID of the
721   * attribute type, without any attribute options.  For an attribute without
722   * any options, the value returned by this method will be identical the value
723   * returned by the {@link #getName} method.
724   *
725   * @return  The base name for this attribute.
726   */
727  public String getBaseName()
728  {
729    return getBaseName(name);
730  }
731
732
733
734  /**
735   * Retrieves the base name for an attribute with the given name, which will be
736   * the provided name without any attribute options.  If the given name does
737   * not include any attribute options, then it will be returned unaltered.  If
738   * it does contain one or more attribute options, then the name will be
739   * returned without those options.
740   *
741   * @param  name  The name to be processed.
742   *
743   * @return  The base name determined from the provided attribute name.
744   */
745  public static String getBaseName(final String name)
746  {
747    final int semicolonPos = name.indexOf(';');
748    if (semicolonPos > 0)
749    {
750      return name.substring(0, semicolonPos);
751    }
752    else
753    {
754      return name;
755    }
756  }
757
758
759
760  /**
761   * Indicates whether the name of this attribute is valid as per RFC 4512.  The
762   * name will be considered valid only if it starts with an ASCII alphabetic
763   * character ('a' through 'z', or 'A' through 'Z'), and contains only ASCII
764   * alphabetic characters, ASCII numeric digits ('0' through '9'), and the
765   * ASCII hyphen character ('-').  It will also be allowed to include zero or
766   * more attribute options, in which the option must be separate from the base
767   * name by a semicolon and has the same naming constraints as the base name.
768   *
769   * @return  {@code true} if this attribute has a valid name, or {@code false}
770   *          if not.
771   */
772  public boolean nameIsValid()
773  {
774    return nameIsValid(name, true);
775  }
776
777
778
779  /**
780   * Indicates whether the provided string represents a valid attribute name as
781   * per RFC 4512.  It will be considered valid only if it starts with an ASCII
782   * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains
783   * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'),
784   * and the ASCII hyphen character ('-').  It will also be allowed to include
785   * zero or more attribute options, in which the option must be separate from
786   * the base name by a semicolon and has the same naming constraints as the
787   * base name.
788   *
789   * @param  s  The name for which to make the determination.
790   *
791   * @return  {@code true} if this attribute has a valid name, or {@code false}
792   *          if not.
793   */
794  public static boolean nameIsValid(final String s)
795  {
796    return nameIsValid(s, true);
797  }
798
799
800
801  /**
802   * Indicates whether the provided string represents a valid attribute name as
803   * per RFC 4512.  It will be considered valid only if it starts with an ASCII
804   * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains
805   * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'),
806   * and the ASCII hyphen character ('-').  It may optionally be allowed to
807   * include zero or more attribute options, in which the option must be
808   * separate from the base name by a semicolon and has the same naming
809   * constraints as the base name.
810   *
811   * @param  s             The name for which to make the determination.
812   * @param  allowOptions  Indicates whether the provided name will be allowed
813   *                       to contain attribute options.
814   *
815   * @return  {@code true} if this attribute has a valid name, or {@code false}
816   *          if not.
817   */
818  public static boolean nameIsValid(final String s, final boolean allowOptions)
819  {
820    final int length;
821    if ((s == null) || ((length = s.length()) == 0))
822    {
823      return false;
824    }
825
826    final char firstChar = s.charAt(0);
827    if (! (((firstChar >= 'a') && (firstChar <= 'z')) ||
828          ((firstChar >= 'A') && (firstChar <= 'Z'))))
829    {
830      return false;
831    }
832
833    boolean lastWasSemiColon = false;
834    for (int i=1; i < length; i++)
835    {
836      final char c = s.charAt(i);
837      if (((c >= 'a') && (c <= 'z')) ||
838          ((c >= 'A') && (c <= 'Z')))
839      {
840        // This will always be acceptable.
841        lastWasSemiColon = false;
842      }
843      else if (((c >= '0') && (c <= '9')) ||
844               (c == '-'))
845      {
846        // These will only be acceptable if the last character was not a
847        // semicolon.
848        if (lastWasSemiColon)
849        {
850          return false;
851        }
852
853        lastWasSemiColon = false;
854      }
855      else if (c == ';')
856      {
857        // This will only be acceptable if attribute options are allowed and the
858        // last character was not a semicolon.
859        if (lastWasSemiColon || (! allowOptions))
860        {
861          return false;
862        }
863
864        lastWasSemiColon = true;
865      }
866      else
867      {
868        return false;
869      }
870    }
871
872    return (! lastWasSemiColon);
873  }
874
875
876
877  /**
878   * Indicates whether this attribute has any attribute options.
879   *
880   * @return  {@code true} if this attribute has at least one attribute option,
881   *          or {@code false} if not.
882   */
883  public boolean hasOptions()
884  {
885    return hasOptions(name);
886  }
887
888
889
890  /**
891   * Indicates whether the provided attribute name contains any options.
892   *
893   * @param  name  The name for which to make the determination.
894   *
895   * @return  {@code true} if the provided attribute name has at least one
896   *          attribute option, or {@code false} if not.
897   */
898  public static boolean hasOptions(final String name)
899  {
900    return (name.indexOf(';') > 0);
901  }
902
903
904
905  /**
906   * Indicates whether this attribute has the specified attribute option.
907   *
908   * @param  option  The attribute option for which to make the determination.
909   *
910   * @return  {@code true} if this attribute has the specified attribute option,
911   *          or {@code false} if not.
912   */
913  public boolean hasOption(final String option)
914  {
915    return hasOption(name, option);
916  }
917
918
919
920  /**
921   * Indicates whether the provided attribute name has the specified attribute
922   * option.
923   *
924   * @param  name    The name to be examined.
925   * @param  option  The attribute option for which to make the determination.
926   *
927   * @return  {@code true} if the provided attribute name has the specified
928   *          attribute option, or {@code false} if not.
929   */
930  public static boolean hasOption(final String name, final String option)
931  {
932    final Set<String> options = getOptions(name);
933    for (final String s : options)
934    {
935      if (s.equalsIgnoreCase(option))
936      {
937        return true;
938      }
939    }
940
941    return false;
942  }
943
944
945
946  /**
947   * Retrieves the set of options for this attribute.
948   *
949   * @return  The set of options for this attribute, or an empty set if there
950   *          are none.
951   */
952  public Set<String> getOptions()
953  {
954    return getOptions(name);
955  }
956
957
958
959  /**
960   * Retrieves the set of options for the provided attribute name.
961   *
962   * @param  name  The name to be examined.
963   *
964   * @return  The set of options for the provided attribute name, or an empty
965   *          set if there are none.
966   */
967  public static Set<String> getOptions(final String name)
968  {
969    int semicolonPos = name.indexOf(';');
970    if (semicolonPos > 0)
971    {
972      final LinkedHashSet<String> options = new LinkedHashSet<String>();
973      while (true)
974      {
975        final int nextSemicolonPos = name.indexOf(';', semicolonPos+1);
976        if (nextSemicolonPos > 0)
977        {
978          options.add(name.substring(semicolonPos+1, nextSemicolonPos));
979          semicolonPos = nextSemicolonPos;
980        }
981        else
982        {
983          options.add(name.substring(semicolonPos+1));
984          break;
985        }
986      }
987
988      return Collections.unmodifiableSet(options);
989    }
990    else
991    {
992      return Collections.emptySet();
993    }
994  }
995
996
997
998  /**
999   * Retrieves the matching rule instance used by this attribute.
1000   *
1001   * @return  The matching rule instance used by this attribute.
1002   */
1003  public MatchingRule getMatchingRule()
1004  {
1005    return matchingRule;
1006  }
1007
1008
1009
1010  /**
1011   * Retrieves the value for this attribute as a string.  If this attribute has
1012   * multiple values, then the first value will be returned.
1013   *
1014   * @return  The value for this attribute, or {@code null} if this attribute
1015   *          does not have any values.
1016   */
1017  public String getValue()
1018  {
1019    if (values.length == 0)
1020    {
1021      return null;
1022    }
1023
1024    return values[0].stringValue();
1025  }
1026
1027
1028
1029  /**
1030   * Retrieves the value for this attribute as a byte array.  If this attribute
1031   * has multiple values, then the first value will be returned.  The returned
1032   * array must not be altered by the caller.
1033   *
1034   * @return  The value for this attribute, or {@code null} if this attribute
1035   *          does not have any values.
1036   */
1037  public byte[] getValueByteArray()
1038  {
1039    if (values.length == 0)
1040    {
1041      return null;
1042    }
1043
1044    return values[0].getValue();
1045  }
1046
1047
1048
1049  /**
1050   * Retrieves the value for this attribute as a Boolean.  If this attribute has
1051   * multiple values, then the first value will be examined.  Values of "true",
1052   * "t", "yes", "y", "on", and "1" will be interpreted as {@code TRUE}.  Values
1053   * of "false", "f", "no", "n", "off", and "0" will be interpreted as
1054   * {@code FALSE}.
1055   *
1056   * @return  The Boolean value for this attribute, or {@code null} if this
1057   *          attribute does not have any values or the value cannot be parsed
1058   *          as a Boolean.
1059   */
1060  public Boolean getValueAsBoolean()
1061  {
1062    if (values.length == 0)
1063    {
1064      return null;
1065    }
1066
1067    final String lowerValue = toLowerCase(values[0].stringValue());
1068    if (lowerValue.equals("true") || lowerValue.equals("t") ||
1069        lowerValue.equals("yes") || lowerValue.equals("y") ||
1070        lowerValue.equals("on") || lowerValue.equals("1"))
1071    {
1072      return Boolean.TRUE;
1073    }
1074    else if (lowerValue.equals("false") || lowerValue.equals("f") ||
1075             lowerValue.equals("no") || lowerValue.equals("n") ||
1076             lowerValue.equals("off") || lowerValue.equals("0"))
1077    {
1078      return Boolean.FALSE;
1079    }
1080    else
1081    {
1082      return null;
1083    }
1084  }
1085
1086
1087
1088  /**
1089   * Retrieves the value for this attribute as a Date, formatted using the
1090   * generalized time syntax.  If this attribute has multiple values, then the
1091   * first value will be examined.
1092   *
1093   * @return  The Date value for this attribute, or {@code null} if this
1094   *          attribute does not have any values or the value cannot be parsed
1095   *          as a Date.
1096   */
1097  public Date getValueAsDate()
1098  {
1099    if (values.length == 0)
1100    {
1101      return null;
1102    }
1103
1104    try
1105    {
1106      return decodeGeneralizedTime(values[0].stringValue());
1107    }
1108    catch (final Exception e)
1109    {
1110      debugException(e);
1111      return null;
1112    }
1113  }
1114
1115
1116
1117  /**
1118   * Retrieves the value for this attribute as a DN.  If this attribute has
1119   * multiple values, then the first value will be examined.
1120   *
1121   * @return  The DN value for this attribute, or {@code null} if this attribute
1122   *          does not have any values or the value cannot be parsed as a DN.
1123   */
1124  public DN getValueAsDN()
1125  {
1126    if (values.length == 0)
1127    {
1128      return null;
1129    }
1130
1131    try
1132    {
1133      return new DN(values[0].stringValue());
1134    }
1135    catch (final Exception e)
1136    {
1137      debugException(e);
1138      return null;
1139    }
1140  }
1141
1142
1143
1144  /**
1145   * Retrieves the value for this attribute as an Integer.  If this attribute
1146   * has multiple values, then the first value will be examined.
1147   *
1148   * @return  The Integer value for this attribute, or {@code null} if this
1149   *          attribute does not have any values or the value cannot be parsed
1150   *          as an Integer.
1151   */
1152  public Integer getValueAsInteger()
1153  {
1154    if (values.length == 0)
1155    {
1156      return null;
1157    }
1158
1159    try
1160    {
1161      return Integer.valueOf(values[0].stringValue());
1162    }
1163    catch (final NumberFormatException nfe)
1164    {
1165      debugException(nfe);
1166      return null;
1167    }
1168  }
1169
1170
1171
1172  /**
1173   * Retrieves the value for this attribute as a Long.  If this attribute has
1174   * multiple values, then the first value will be examined.
1175   *
1176   * @return  The Long value for this attribute, or {@code null} if this
1177   *          attribute does not have any values or the value cannot be parsed
1178   *          as a Long.
1179   */
1180  public Long getValueAsLong()
1181  {
1182    if (values.length == 0)
1183    {
1184      return null;
1185    }
1186
1187    try
1188    {
1189      return Long.valueOf(values[0].stringValue());
1190    }
1191    catch (final NumberFormatException nfe)
1192    {
1193      debugException(nfe);
1194      return null;
1195    }
1196  }
1197
1198
1199
1200  /**
1201   * Retrieves the set of values for this attribute as strings.  The returned
1202   * array must not be altered by the caller.
1203   *
1204   * @return  The set of values for this attribute, or an empty array if it does
1205   *          not have any values.
1206   */
1207  public String[] getValues()
1208  {
1209    if (values.length == 0)
1210    {
1211      return NO_STRINGS;
1212    }
1213
1214    final String[] stringValues = new String[values.length];
1215    for (int i=0; i < values.length; i++)
1216    {
1217      stringValues[i] = values[i].stringValue();
1218    }
1219
1220    return stringValues;
1221  }
1222
1223
1224
1225  /**
1226   * Retrieves the set of values for this attribute as byte arrays.  The
1227   * returned array must not be altered by the caller.
1228   *
1229   * @return  The set of values for this attribute, or an empty array if it does
1230   *          not have any values.
1231   */
1232  public byte[][] getValueByteArrays()
1233  {
1234    if (values.length == 0)
1235    {
1236      return NO_BYTE_VALUES;
1237    }
1238
1239    final byte[][] byteValues = new byte[values.length][];
1240    for (int i=0; i < values.length; i++)
1241    {
1242      byteValues[i] = values[i].getValue();
1243    }
1244
1245    return byteValues;
1246  }
1247
1248
1249
1250  /**
1251   * Retrieves the set of values for this attribute as an array of ASN.1 octet
1252   * strings.  The returned array must not be altered by the caller.
1253   *
1254   * @return  The set of values for this attribute as an array of ASN.1 octet
1255   *          strings.
1256   */
1257  public ASN1OctetString[] getRawValues()
1258  {
1259    return values;
1260  }
1261
1262
1263
1264  /**
1265   * Indicates whether this attribute contains at least one value.
1266   *
1267   * @return  {@code true} if this attribute has at least one value, or
1268   *          {@code false} if not.
1269   */
1270  public boolean hasValue()
1271  {
1272    return (values.length > 0);
1273  }
1274
1275
1276
1277  /**
1278   * Indicates whether this attribute contains the specified value.
1279   *
1280   * @param  value  The value for which to make the determination.  It must not
1281   *                be {@code null}.
1282   *
1283   * @return  {@code true} if this attribute has the specified value, or
1284   *          {@code false} if not.
1285   */
1286  public boolean hasValue(final String value)
1287  {
1288    ensureNotNull(value);
1289
1290    return hasValue(new ASN1OctetString(value), matchingRule);
1291  }
1292
1293
1294
1295  /**
1296   * Indicates whether this attribute contains the specified value.
1297   *
1298   * @param  value         The value for which to make the determination.  It
1299   *                       must not be {@code null}.
1300   * @param  matchingRule  The matching rule to use when making the
1301   *                       determination.  It must not be {@code null}.
1302   *
1303   * @return  {@code true} if this attribute has the specified value, or
1304   *          {@code false} if not.
1305   */
1306  public boolean hasValue(final String value, final MatchingRule matchingRule)
1307  {
1308    ensureNotNull(value);
1309
1310    return hasValue(new ASN1OctetString(value), matchingRule);
1311  }
1312
1313
1314
1315  /**
1316   * Indicates whether this attribute contains the specified value.
1317   *
1318   * @param  value  The value for which to make the determination.  It must not
1319   *                be {@code null}.
1320   *
1321   * @return  {@code true} if this attribute has the specified value, or
1322   *          {@code false} if not.
1323   */
1324  public boolean hasValue(final byte[] value)
1325  {
1326    ensureNotNull(value);
1327
1328    return hasValue(new ASN1OctetString(value), matchingRule);
1329  }
1330
1331
1332
1333  /**
1334   * Indicates whether this attribute contains the specified value.
1335   *
1336   * @param  value         The value for which to make the determination.  It
1337   *                       must not be {@code null}.
1338   * @param  matchingRule  The matching rule to use when making the
1339   *                       determination.  It must not be {@code null}.
1340   *
1341   * @return  {@code true} if this attribute has the specified value, or
1342   *          {@code false} if not.
1343   */
1344  public boolean hasValue(final byte[] value, final MatchingRule matchingRule)
1345  {
1346    ensureNotNull(value);
1347
1348    return hasValue(new ASN1OctetString(value), matchingRule);
1349  }
1350
1351
1352
1353  /**
1354   * Indicates whether this attribute contains the specified value.
1355   *
1356   * @param  value  The value for which to make the determination.
1357   *
1358   * @return  {@code true} if this attribute has the specified value, or
1359   *          {@code false} if not.
1360   */
1361  boolean hasValue(final ASN1OctetString value)
1362  {
1363    return hasValue(value, matchingRule);
1364  }
1365
1366
1367
1368  /**
1369   * Indicates whether this attribute contains the specified value.
1370   *
1371   * @param  value         The value for which to make the determination.  It
1372   *                       must not be {@code null}.
1373   * @param  matchingRule  The matching rule to use when making the
1374   *                       determination.  It must not be {@code null}.
1375   *
1376   * @return  {@code true} if this attribute has the specified value, or
1377   *          {@code false} if not.
1378   */
1379  boolean hasValue(final ASN1OctetString value, final MatchingRule matchingRule)
1380  {
1381    for (final ASN1OctetString existingValue : values)
1382    {
1383      try
1384      {
1385        if (matchingRule.valuesMatch(existingValue, value))
1386        {
1387          return true;
1388        }
1389      }
1390      catch (final LDAPException le)
1391      {
1392        debugException(le);
1393
1394        // The value cannot be normalized, but we'll still consider it a match
1395        // if the values are exactly the same.
1396        if (existingValue.equals(value))
1397        {
1398          return true;
1399        }
1400      }
1401    }
1402
1403    // If we've gotten here, then we didn't find a match.
1404    return false;
1405  }
1406
1407
1408
1409  /**
1410   * Retrieves the number of values for this attribute.
1411   *
1412   * @return  The number of values for this attribute.
1413   */
1414  public int size()
1415  {
1416    return values.length;
1417  }
1418
1419
1420
1421  /**
1422   * Writes an ASN.1-encoded representation of this attribute to the provided
1423   * ASN.1 buffer.
1424   *
1425   * @param  buffer  The ASN.1 buffer to which the encoded representation should
1426   *                 be written.
1427   */
1428  public void writeTo(final ASN1Buffer buffer)
1429  {
1430    final ASN1BufferSequence attrSequence = buffer.beginSequence();
1431    buffer.addOctetString(name);
1432
1433    final ASN1BufferSet valueSet = buffer.beginSet();
1434    for (final ASN1OctetString value : values)
1435    {
1436      buffer.addElement(value);
1437    }
1438    valueSet.end();
1439    attrSequence.end();
1440  }
1441
1442
1443
1444  /**
1445   * Encodes this attribute into a form suitable for use in the LDAP protocol.
1446   * It will be encoded as a sequence containing the attribute name (as an octet
1447   * string) and a set of values.
1448   *
1449   * @return  An ASN.1 sequence containing the encoded attribute.
1450   */
1451  public ASN1Sequence encode()
1452  {
1453    final ASN1Element[] elements =
1454    {
1455      new ASN1OctetString(name),
1456      new ASN1Set(values)
1457    };
1458
1459    return new ASN1Sequence(elements);
1460  }
1461
1462
1463
1464  /**
1465   * Reads and decodes an attribute from the provided ASN.1 stream reader.
1466   *
1467   * @param  reader  The ASN.1 stream reader from which to read the attribute.
1468   *
1469   * @return  The decoded attribute.
1470   *
1471   * @throws  LDAPException  If a problem occurs while trying to read or decode
1472   *                         the attribute.
1473   */
1474  public static Attribute readFrom(final ASN1StreamReader reader)
1475         throws LDAPException
1476  {
1477    return readFrom(reader, null);
1478  }
1479
1480
1481
1482  /**
1483   * Reads and decodes an attribute from the provided ASN.1 stream reader.
1484   *
1485   * @param  reader  The ASN.1 stream reader from which to read the attribute.
1486   * @param  schema  The schema to use to select the appropriate matching rule
1487   *                 for this attribute.  It may be {@code null} if the default
1488   *                 matching rule should be selected.
1489   *
1490   * @return  The decoded attribute.
1491   *
1492   * @throws  LDAPException  If a problem occurs while trying to read or decode
1493   *                         the attribute.
1494   */
1495  public static Attribute readFrom(final ASN1StreamReader reader,
1496                                   final Schema schema)
1497         throws LDAPException
1498  {
1499    try
1500    {
1501      ensureNotNull(reader.beginSequence());
1502      final String attrName = reader.readString();
1503      ensureNotNull(attrName);
1504
1505      final MatchingRule matchingRule =
1506           MatchingRule.selectEqualityMatchingRule(attrName, schema);
1507
1508      final ArrayList<ASN1OctetString> valueList =
1509           new ArrayList<ASN1OctetString>();
1510      final ASN1StreamReaderSet valueSet = reader.beginSet();
1511      while (valueSet.hasMoreElements())
1512      {
1513        valueList.add(new ASN1OctetString(reader.readBytes()));
1514      }
1515
1516      final ASN1OctetString[] values = new ASN1OctetString[valueList.size()];
1517      valueList.toArray(values);
1518
1519      return new Attribute(attrName, matchingRule, values);
1520    }
1521    catch (final Exception e)
1522    {
1523      debugException(e);
1524      throw new LDAPException(ResultCode.DECODING_ERROR,
1525           ERR_ATTR_CANNOT_DECODE.get(getExceptionMessage(e)), e);
1526    }
1527  }
1528
1529
1530
1531  /**
1532   * Decodes the provided ASN.1 sequence as an LDAP attribute.
1533   *
1534   * @param  encodedAttribute  The ASN.1 sequence to be decoded as an LDAP
1535   *                           attribute.  It must not be {@code null}.
1536   *
1537   * @return  The decoded LDAP attribute.
1538   *
1539   * @throws  LDAPException  If a problem occurs while attempting to decode the
1540   *                         provided ASN.1 sequence as an LDAP attribute.
1541   */
1542  public static Attribute decode(final ASN1Sequence encodedAttribute)
1543         throws LDAPException
1544  {
1545    ensureNotNull(encodedAttribute);
1546
1547    final ASN1Element[] elements = encodedAttribute.elements();
1548    if (elements.length != 2)
1549    {
1550      throw new LDAPException(ResultCode.DECODING_ERROR,
1551                     ERR_ATTR_DECODE_INVALID_COUNT.get(elements.length));
1552    }
1553
1554    final String name =
1555         ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
1556
1557    final ASN1Set valueSet;
1558    try
1559    {
1560      valueSet = ASN1Set.decodeAsSet(elements[1]);
1561    }
1562    catch (final ASN1Exception ae)
1563    {
1564      debugException(ae);
1565      throw new LDAPException(ResultCode.DECODING_ERROR,
1566           ERR_ATTR_DECODE_VALUE_SET.get(getExceptionMessage(ae)), ae);
1567    }
1568
1569    final ASN1OctetString[] values =
1570         new ASN1OctetString[valueSet.elements().length];
1571    for (int i=0; i < values.length; i++)
1572    {
1573      values[i] = ASN1OctetString.decodeAsOctetString(valueSet.elements()[i]);
1574    }
1575
1576    return new Attribute(name, CaseIgnoreStringMatchingRule.getInstance(),
1577                         values);
1578  }
1579
1580
1581
1582  /**
1583   * Indicates whether any of the values of this attribute need to be
1584   * base64-encoded when represented as LDIF.
1585   *
1586   * @return  {@code true} if any of the values of this attribute need to be
1587   *          base64-encoded when represented as LDIF, or {@code false} if not.
1588   */
1589  public boolean needsBase64Encoding()
1590  {
1591    for (final ASN1OctetString v : values)
1592    {
1593      if (needsBase64Encoding(v.getValue()))
1594      {
1595        return true;
1596      }
1597    }
1598
1599    return false;
1600  }
1601
1602
1603
1604  /**
1605   * Indicates whether the provided value needs to be base64-encoded when
1606   * represented as LDIF.
1607   *
1608   * @param  v  The value for which to make the determination.  It must not be
1609   *            {@code null}.
1610   *
1611   * @return  {@code true} if the provided value needs to be base64-encoded when
1612   *          represented as LDIF, or {@code false} if not.
1613   */
1614  public static boolean needsBase64Encoding(final String v)
1615  {
1616    return needsBase64Encoding(getBytes(v));
1617  }
1618
1619
1620
1621  /**
1622   * Indicates whether the provided value needs to be base64-encoded when
1623   * represented as LDIF.
1624   *
1625   * @param  v  The value for which to make the determination.  It must not be
1626   *            {@code null}.
1627   *
1628   * @return  {@code true} if the provided value needs to be base64-encoded when
1629   *          represented as LDIF, or {@code false} if not.
1630   */
1631  public static boolean needsBase64Encoding(final byte[] v)
1632  {
1633    if (v.length == 0)
1634    {
1635      return false;
1636    }
1637
1638    switch (v[0] & 0xFF)
1639    {
1640      case 0x20: // Space
1641      case 0x3A: // Colon
1642      case 0x3C: // Less-than
1643        return true;
1644    }
1645
1646    if ((v[v.length-1] & 0xFF) == 0x20)
1647    {
1648      return true;
1649    }
1650
1651    for (final byte b : v)
1652    {
1653      switch (b & 0xFF)
1654      {
1655        case 0x00: // NULL
1656        case 0x0A: // LF
1657        case 0x0D: // CR
1658          return true;
1659
1660        default:
1661          if ((b & 0x80) != 0x00)
1662          {
1663            return true;
1664          }
1665          break;
1666      }
1667    }
1668
1669    return false;
1670  }
1671
1672
1673
1674  /**
1675   * Generates a hash code for this LDAP attribute.  It will be the sum of the
1676   * hash codes for the lowercase attribute name and the normalized values.
1677   *
1678   * @return  The generated hash code for this LDAP attribute.
1679   */
1680  @Override()
1681  public int hashCode()
1682  {
1683    if (hashCode == -1)
1684    {
1685      int c = toLowerCase(name).hashCode();
1686
1687      for (final ASN1OctetString value : values)
1688      {
1689        try
1690        {
1691          c += matchingRule.normalize(value).hashCode();
1692        }
1693        catch (final LDAPException le)
1694        {
1695          debugException(le);
1696          c += value.hashCode();
1697        }
1698      }
1699
1700      hashCode = c;
1701    }
1702
1703    return hashCode;
1704  }
1705
1706
1707
1708  /**
1709   * Indicates whether the provided object is equal to this LDAP attribute.  The
1710   * object will be considered equal to this LDAP attribute only if it is an
1711   * LDAP attribute with the same name and set of values.
1712   *
1713   * @param  o  The object for which to make the determination.
1714   *
1715   * @return  {@code true} if the provided object may be considered equal to
1716   *          this LDAP attribute, or {@code false} if not.
1717   */
1718  @Override()
1719  public boolean equals(final Object o)
1720  {
1721    if (o == null)
1722    {
1723      return false;
1724    }
1725
1726    if (o == this)
1727    {
1728      return true;
1729    }
1730
1731    if (! (o instanceof Attribute))
1732    {
1733      return false;
1734    }
1735
1736    final Attribute a = (Attribute) o;
1737    if (! name.equalsIgnoreCase(a.name))
1738    {
1739      return false;
1740    }
1741
1742    if (values.length != a.values.length)
1743    {
1744      return false;
1745    }
1746
1747    // For a small set of values, we can just iterate through the values of one
1748    // and see if they are all present in the other.  However, that can be very
1749    // expensive for a large set of values, so we'll try to go with a more
1750    // efficient approach.
1751    if (values.length > 10)
1752    {
1753      // First, create a hash set containing the un-normalized values of the
1754      // first attribute.
1755      final HashSet<ASN1OctetString> unNormalizedValues =
1756           new HashSet<ASN1OctetString>(values.length);
1757      Collections.addAll(unNormalizedValues, values);
1758
1759      // Next, iterate through the values of the second attribute.  For any
1760      // values that exist in the un-normalized set, remove them from that
1761      // set.  For any values that aren't in the un-normalized set, create a
1762      // new set with the normalized representations of those values.
1763      HashSet<ASN1OctetString> normalizedMissingValues = null;
1764      for (final ASN1OctetString value : a.values)
1765      {
1766        if (! unNormalizedValues.remove(value))
1767        {
1768          if (normalizedMissingValues == null)
1769          {
1770            normalizedMissingValues =
1771                 new HashSet<ASN1OctetString>(values.length);
1772          }
1773
1774          try
1775          {
1776            normalizedMissingValues.add(matchingRule.normalize(value));
1777          }
1778          catch (final Exception e)
1779          {
1780            debugException(e);
1781            return false;
1782          }
1783        }
1784      }
1785
1786      // If the un-normalized set is empty, then that means all the values
1787      // exactly match without the need to compare the normalized
1788      // representations.  For any values that are left, then we will need to
1789      // compare their normalized representations.
1790      if (normalizedMissingValues != null)
1791      {
1792        for (final ASN1OctetString value : unNormalizedValues)
1793        {
1794          try
1795          {
1796            if (! normalizedMissingValues.contains(
1797                       matchingRule.normalize(value)))
1798            {
1799              return false;
1800            }
1801          }
1802          catch (final Exception e)
1803          {
1804            debugException(e);
1805            return false;
1806          }
1807        }
1808      }
1809    }
1810    else
1811    {
1812      for (final ASN1OctetString value : values)
1813      {
1814        if (! a.hasValue(value))
1815        {
1816          return false;
1817        }
1818      }
1819    }
1820
1821
1822    // If we've gotten here, then we can consider them equal.
1823    return true;
1824  }
1825
1826
1827
1828  /**
1829   * Retrieves a string representation of this LDAP attribute.
1830   *
1831   * @return  A string representation of this LDAP attribute.
1832   */
1833  @Override()
1834  public String toString()
1835  {
1836    final StringBuilder buffer = new StringBuilder();
1837    toString(buffer);
1838    return buffer.toString();
1839  }
1840
1841
1842
1843  /**
1844   * Appends a string representation of this LDAP attribute to the provided
1845   * buffer.
1846   *
1847   * @param  buffer  The buffer to which the string representation of this LDAP
1848   *                 attribute should be appended.
1849   */
1850  public void toString(final StringBuilder buffer)
1851  {
1852    buffer.append("Attribute(name=");
1853    buffer.append(name);
1854
1855    if (values.length == 0)
1856    {
1857      buffer.append(", values={");
1858    }
1859    else if (needsBase64Encoding())
1860    {
1861      buffer.append(", base64Values={'");
1862
1863      for (int i=0; i < values.length; i++)
1864      {
1865        if (i > 0)
1866        {
1867          buffer.append("', '");
1868        }
1869
1870        buffer.append(Base64.encode(values[i].getValue()));
1871      }
1872
1873      buffer.append('\'');
1874    }
1875    else
1876    {
1877      buffer.append(", values={'");
1878
1879      for (int i=0; i < values.length; i++)
1880      {
1881        if (i > 0)
1882        {
1883          buffer.append("', '");
1884        }
1885
1886        buffer.append(values[i].stringValue());
1887      }
1888
1889      buffer.append('\'');
1890    }
1891
1892    buffer.append("})");
1893  }
1894}