001/*
002 * Copyright 2007-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2018 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 attr2Value : attr2.values)
586    {
587      if (! attr1.hasValue(attr2Value, mr))
588      {
589        mergedValues[pos++] = attr2Value;
590      }
591    }
592
593    if (pos != mergedValues.length)
594    {
595      // This indicates that there were duplicate values.
596      final ASN1OctetString[] newMergedValues = new ASN1OctetString[pos];
597      System.arraycopy(mergedValues, 0, newMergedValues, 0, pos);
598      mergedValues = newMergedValues;
599    }
600
601    return new Attribute(name, mr, mergedValues);
602  }
603
604
605
606  /**
607   * Creates a new attribute containing all of the values of the first attribute
608   * that are not contained in the second attribute.  Any values contained in
609   * the second attribute that are not contained in the first will be ignored.
610   * The names of the provided attributes must be the same.
611   *
612   * @param  attr1  The attribute from which to remove the values.  It must not
613   *                be {@code null}.
614   * @param  attr2  The attribute containing the values to remove.  It must not
615   *                be {@code null}.
616   *
617   * @return  A new attribute containing all of the values of the first
618   *          attribute not contained in the second.  It may contain zero values
619   *          if all the values of the first attribute were also contained in
620   *          the second.
621   */
622  public static Attribute removeValues(final Attribute attr1,
623                                       final Attribute attr2)
624  {
625    return removeValues(attr1, attr2, attr1.matchingRule);
626  }
627
628
629
630  /**
631   * Creates a new attribute containing all of the values of the first attribute
632   * that are not contained in the second attribute.  Any values contained in
633   * the second attribute that are not contained in the first will be ignored.
634   * The names of the provided attributes must be the same.
635   *
636   * @param  attr1         The attribute from which to remove the values.  It
637   *                       must not be {@code null}.
638   * @param  attr2         The attribute containing the values to remove.  It
639   *                       must not be {@code null}.
640   * @param  matchingRule  The matching rule to use to locate matching values.
641   *                       It may be {@code null} if the matching rule
642   *                       associated with the first attribute should be used.
643   *
644   * @return  A new attribute containing all of the values of the first
645   *          attribute not contained in the second.  It may contain zero values
646   *          if all the values of the first attribute were also contained in
647   *          the second.
648   */
649  public static Attribute removeValues(final Attribute attr1,
650                                       final Attribute attr2,
651                                       final MatchingRule matchingRule)
652  {
653    ensureNotNull(attr1, attr2);
654
655    final String name = attr1.name;
656    ensureTrue(name.equalsIgnoreCase(attr2.name));
657
658    final MatchingRule mr;
659    if (matchingRule == null)
660    {
661      mr = attr1.matchingRule;
662    }
663    else
664    {
665      mr = matchingRule;
666    }
667
668    final ArrayList<ASN1OctetString> newValues =
669         new ArrayList<ASN1OctetString>(Arrays.asList(attr1.values));
670
671    final Iterator<ASN1OctetString> iterator = newValues.iterator();
672    while (iterator.hasNext())
673    {
674      if (attr2.hasValue(iterator.next(), mr))
675      {
676        iterator.remove();
677      }
678    }
679
680    final ASN1OctetString[] newValueArray =
681         new ASN1OctetString[newValues.size()];
682    newValues.toArray(newValueArray);
683
684    return new Attribute(name, mr, newValueArray);
685  }
686
687
688
689  /**
690   * Retrieves the name for this attribute (i.e., the attribute description),
691   * which may include zero or more attribute options.
692   *
693   * @return  The name for this attribute.
694   */
695  public String getName()
696  {
697    return name;
698  }
699
700
701
702  /**
703   * Retrieves the base name for this attribute, which is the name or OID of the
704   * attribute type, without any attribute options.  For an attribute without
705   * any options, the value returned by this method will be identical the value
706   * returned by the {@link #getName} method.
707   *
708   * @return  The base name for this attribute.
709   */
710  public String getBaseName()
711  {
712    return getBaseName(name);
713  }
714
715
716
717  /**
718   * Retrieves the base name for an attribute with the given name, which will be
719   * the provided name without any attribute options.  If the given name does
720   * not include any attribute options, then it will be returned unaltered.  If
721   * it does contain one or more attribute options, then the name will be
722   * returned without those options.
723   *
724   * @param  name  The name to be processed.
725   *
726   * @return  The base name determined from the provided attribute name.
727   */
728  public static String getBaseName(final String name)
729  {
730    final int semicolonPos = name.indexOf(';');
731    if (semicolonPos > 0)
732    {
733      return name.substring(0, semicolonPos);
734    }
735    else
736    {
737      return name;
738    }
739  }
740
741
742
743  /**
744   * Indicates whether the name of this attribute is valid as per RFC 4512.  The
745   * name will be considered valid only if it starts with an ASCII alphabetic
746   * character ('a' through 'z', or 'A' through 'Z'), and contains only ASCII
747   * alphabetic characters, ASCII numeric digits ('0' through '9'), and the
748   * ASCII hyphen character ('-').  It will also be allowed to include zero or
749   * more attribute options, in which the option must be separate from the base
750   * name by a semicolon and has the same naming constraints as the base name.
751   *
752   * @return  {@code true} if this attribute has a valid name, or {@code false}
753   *          if not.
754   */
755  public boolean nameIsValid()
756  {
757    return nameIsValid(name, true);
758  }
759
760
761
762  /**
763   * Indicates whether the provided string represents a valid attribute name as
764   * per RFC 4512.  It will be considered valid only if it starts with an ASCII
765   * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains
766   * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'),
767   * and the ASCII hyphen character ('-').  It will also be allowed to include
768   * zero or more attribute options, in which the option must be separate from
769   * the base name by a semicolon and has the same naming constraints as the
770   * base name.
771   *
772   * @param  s  The name for which to make the determination.
773   *
774   * @return  {@code true} if this attribute has a valid name, or {@code false}
775   *          if not.
776   */
777  public static boolean nameIsValid(final String s)
778  {
779    return nameIsValid(s, true);
780  }
781
782
783
784  /**
785   * Indicates whether the provided string represents a valid attribute name as
786   * per RFC 4512.  It will be considered valid only if it starts with an ASCII
787   * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains
788   * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'),
789   * and the ASCII hyphen character ('-').  It may optionally be allowed to
790   * include zero or more attribute options, in which the option must be
791   * separate from the base name by a semicolon and has the same naming
792   * constraints as the base name.
793   *
794   * @param  s             The name for which to make the determination.
795   * @param  allowOptions  Indicates whether the provided name will be allowed
796   *                       to contain attribute options.
797   *
798   * @return  {@code true} if this attribute has a valid name, or {@code false}
799   *          if not.
800   */
801  public static boolean nameIsValid(final String s, final boolean allowOptions)
802  {
803    final int length;
804    if ((s == null) || ((length = s.length()) == 0))
805    {
806      return false;
807    }
808
809    final char firstChar = s.charAt(0);
810    if (! (((firstChar >= 'a') && (firstChar <= 'z')) ||
811          ((firstChar >= 'A') && (firstChar <= 'Z'))))
812    {
813      return false;
814    }
815
816    boolean lastWasSemiColon = false;
817    for (int i=1; i < length; i++)
818    {
819      final char c = s.charAt(i);
820      if (((c >= 'a') && (c <= 'z')) ||
821          ((c >= 'A') && (c <= 'Z')))
822      {
823        // This will always be acceptable.
824        lastWasSemiColon = false;
825      }
826      else if (((c >= '0') && (c <= '9')) ||
827               (c == '-'))
828      {
829        // These will only be acceptable if the last character was not a
830        // semicolon.
831        if (lastWasSemiColon)
832        {
833          return false;
834        }
835
836        lastWasSemiColon = false;
837      }
838      else if (c == ';')
839      {
840        // This will only be acceptable if attribute options are allowed and the
841        // last character was not a semicolon.
842        if (lastWasSemiColon || (! allowOptions))
843        {
844          return false;
845        }
846
847        lastWasSemiColon = true;
848      }
849      else
850      {
851        return false;
852      }
853    }
854
855    return (! lastWasSemiColon);
856  }
857
858
859
860  /**
861   * Indicates whether this attribute has any attribute options.
862   *
863   * @return  {@code true} if this attribute has at least one attribute option,
864   *          or {@code false} if not.
865   */
866  public boolean hasOptions()
867  {
868    return hasOptions(name);
869  }
870
871
872
873  /**
874   * Indicates whether the provided attribute name contains any options.
875   *
876   * @param  name  The name for which to make the determination.
877   *
878   * @return  {@code true} if the provided attribute name has at least one
879   *          attribute option, or {@code false} if not.
880   */
881  public static boolean hasOptions(final String name)
882  {
883    return (name.indexOf(';') > 0);
884  }
885
886
887
888  /**
889   * Indicates whether this attribute has the specified attribute option.
890   *
891   * @param  option  The attribute option for which to make the determination.
892   *
893   * @return  {@code true} if this attribute has the specified attribute option,
894   *          or {@code false} if not.
895   */
896  public boolean hasOption(final String option)
897  {
898    return hasOption(name, option);
899  }
900
901
902
903  /**
904   * Indicates whether the provided attribute name has the specified attribute
905   * option.
906   *
907   * @param  name    The name to be examined.
908   * @param  option  The attribute option for which to make the determination.
909   *
910   * @return  {@code true} if the provided attribute name has the specified
911   *          attribute option, or {@code false} if not.
912   */
913  public static boolean hasOption(final String name, final String option)
914  {
915    final Set<String> options = getOptions(name);
916    for (final String s : options)
917    {
918      if (s.equalsIgnoreCase(option))
919      {
920        return true;
921      }
922    }
923
924    return false;
925  }
926
927
928
929  /**
930   * Retrieves the set of options for this attribute.
931   *
932   * @return  The set of options for this attribute, or an empty set if there
933   *          are none.
934   */
935  public Set<String> getOptions()
936  {
937    return getOptions(name);
938  }
939
940
941
942  /**
943   * Retrieves the set of options for the provided attribute name.
944   *
945   * @param  name  The name to be examined.
946   *
947   * @return  The set of options for the provided attribute name, or an empty
948   *          set if there are none.
949   */
950  public static Set<String> getOptions(final String name)
951  {
952    int semicolonPos = name.indexOf(';');
953    if (semicolonPos > 0)
954    {
955      final LinkedHashSet<String> options = new LinkedHashSet<String>();
956      while (true)
957      {
958        final int nextSemicolonPos = name.indexOf(';', semicolonPos+1);
959        if (nextSemicolonPos > 0)
960        {
961          options.add(name.substring(semicolonPos+1, nextSemicolonPos));
962          semicolonPos = nextSemicolonPos;
963        }
964        else
965        {
966          options.add(name.substring(semicolonPos+1));
967          break;
968        }
969      }
970
971      return Collections.unmodifiableSet(options);
972    }
973    else
974    {
975      return Collections.emptySet();
976    }
977  }
978
979
980
981  /**
982   * Retrieves the matching rule instance used by this attribute.
983   *
984   * @return  The matching rule instance used by this attribute.
985   */
986  public MatchingRule getMatchingRule()
987  {
988    return matchingRule;
989  }
990
991
992
993  /**
994   * Retrieves the value for this attribute as a string.  If this attribute has
995   * multiple values, then the first value will be returned.
996   *
997   * @return  The value for this attribute, or {@code null} if this attribute
998   *          does not have any values.
999   */
1000  public String getValue()
1001  {
1002    if (values.length == 0)
1003    {
1004      return null;
1005    }
1006
1007    return values[0].stringValue();
1008  }
1009
1010
1011
1012  /**
1013   * Retrieves the value for this attribute as a byte array.  If this attribute
1014   * has multiple values, then the first value will be returned.  The returned
1015   * array must not be altered by the caller.
1016   *
1017   * @return  The value for this attribute, or {@code null} if this attribute
1018   *          does not have any values.
1019   */
1020  public byte[] getValueByteArray()
1021  {
1022    if (values.length == 0)
1023    {
1024      return null;
1025    }
1026
1027    return values[0].getValue();
1028  }
1029
1030
1031
1032  /**
1033   * Retrieves the value for this attribute as a Boolean.  If this attribute has
1034   * multiple values, then the first value will be examined.  Values of "true",
1035   * "t", "yes", "y", "on", and "1" will be interpreted as {@code TRUE}.  Values
1036   * of "false", "f", "no", "n", "off", and "0" will be interpreted as
1037   * {@code FALSE}.
1038   *
1039   * @return  The Boolean value for this attribute, or {@code null} if this
1040   *          attribute does not have any values or the value cannot be parsed
1041   *          as a Boolean.
1042   */
1043  public Boolean getValueAsBoolean()
1044  {
1045    if (values.length == 0)
1046    {
1047      return null;
1048    }
1049
1050    final String lowerValue = toLowerCase(values[0].stringValue());
1051    if (lowerValue.equals("true") || lowerValue.equals("t") ||
1052        lowerValue.equals("yes") || lowerValue.equals("y") ||
1053        lowerValue.equals("on") || lowerValue.equals("1"))
1054    {
1055      return Boolean.TRUE;
1056    }
1057    else if (lowerValue.equals("false") || lowerValue.equals("f") ||
1058             lowerValue.equals("no") || lowerValue.equals("n") ||
1059             lowerValue.equals("off") || lowerValue.equals("0"))
1060    {
1061      return Boolean.FALSE;
1062    }
1063    else
1064    {
1065      return null;
1066    }
1067  }
1068
1069
1070
1071  /**
1072   * Retrieves the value for this attribute as a Date, formatted using the
1073   * generalized time syntax.  If this attribute has multiple values, then the
1074   * first value will be examined.
1075   *
1076   * @return  The Date value for this attribute, or {@code null} if this
1077   *          attribute does not have any values or the value cannot be parsed
1078   *          as a Date.
1079   */
1080  public Date getValueAsDate()
1081  {
1082    if (values.length == 0)
1083    {
1084      return null;
1085    }
1086
1087    try
1088    {
1089      return decodeGeneralizedTime(values[0].stringValue());
1090    }
1091    catch (final Exception e)
1092    {
1093      debugException(e);
1094      return null;
1095    }
1096  }
1097
1098
1099
1100  /**
1101   * Retrieves the value for this attribute as a DN.  If this attribute has
1102   * multiple values, then the first value will be examined.
1103   *
1104   * @return  The DN value for this attribute, or {@code null} if this attribute
1105   *          does not have any values or the value cannot be parsed as a DN.
1106   */
1107  public DN getValueAsDN()
1108  {
1109    if (values.length == 0)
1110    {
1111      return null;
1112    }
1113
1114    try
1115    {
1116      return new DN(values[0].stringValue());
1117    }
1118    catch (final Exception e)
1119    {
1120      debugException(e);
1121      return null;
1122    }
1123  }
1124
1125
1126
1127  /**
1128   * Retrieves the value for this attribute as an Integer.  If this attribute
1129   * has multiple values, then the first value will be examined.
1130   *
1131   * @return  The Integer value for this attribute, or {@code null} if this
1132   *          attribute does not have any values or the value cannot be parsed
1133   *          as an Integer.
1134   */
1135  public Integer getValueAsInteger()
1136  {
1137    if (values.length == 0)
1138    {
1139      return null;
1140    }
1141
1142    try
1143    {
1144      return Integer.valueOf(values[0].stringValue());
1145    }
1146    catch (final NumberFormatException nfe)
1147    {
1148      debugException(nfe);
1149      return null;
1150    }
1151  }
1152
1153
1154
1155  /**
1156   * Retrieves the value for this attribute as a Long.  If this attribute has
1157   * multiple values, then the first value will be examined.
1158   *
1159   * @return  The Long value for this attribute, or {@code null} if this
1160   *          attribute does not have any values or the value cannot be parsed
1161   *          as a Long.
1162   */
1163  public Long getValueAsLong()
1164  {
1165    if (values.length == 0)
1166    {
1167      return null;
1168    }
1169
1170    try
1171    {
1172      return Long.valueOf(values[0].stringValue());
1173    }
1174    catch (final NumberFormatException nfe)
1175    {
1176      debugException(nfe);
1177      return null;
1178    }
1179  }
1180
1181
1182
1183  /**
1184   * Retrieves the set of values for this attribute as strings.  The returned
1185   * array must not be altered by the caller.
1186   *
1187   * @return  The set of values for this attribute, or an empty array if it does
1188   *          not have any values.
1189   */
1190  public String[] getValues()
1191  {
1192    if (values.length == 0)
1193    {
1194      return NO_STRINGS;
1195    }
1196
1197    final String[] stringValues = new String[values.length];
1198    for (int i=0; i < values.length; i++)
1199    {
1200      stringValues[i] = values[i].stringValue();
1201    }
1202
1203    return stringValues;
1204  }
1205
1206
1207
1208  /**
1209   * Retrieves the set of values for this attribute as byte arrays.  The
1210   * returned array must not be altered by the caller.
1211   *
1212   * @return  The set of values for this attribute, or an empty array if it does
1213   *          not have any values.
1214   */
1215  public byte[][] getValueByteArrays()
1216  {
1217    if (values.length == 0)
1218    {
1219      return NO_BYTE_VALUES;
1220    }
1221
1222    final byte[][] byteValues = new byte[values.length][];
1223    for (int i=0; i < values.length; i++)
1224    {
1225      byteValues[i] = values[i].getValue();
1226    }
1227
1228    return byteValues;
1229  }
1230
1231
1232
1233  /**
1234   * Retrieves the set of values for this attribute as an array of ASN.1 octet
1235   * strings.  The returned array must not be altered by the caller.
1236   *
1237   * @return  The set of values for this attribute as an array of ASN.1 octet
1238   *          strings.
1239   */
1240  public ASN1OctetString[] getRawValues()
1241  {
1242    return values;
1243  }
1244
1245
1246
1247  /**
1248   * Indicates whether this attribute contains at least one value.
1249   *
1250   * @return  {@code true} if this attribute has at least one value, or
1251   *          {@code false} if not.
1252   */
1253  public boolean hasValue()
1254  {
1255    return (values.length > 0);
1256  }
1257
1258
1259
1260  /**
1261   * Indicates whether this attribute contains the specified value.
1262   *
1263   * @param  value  The value for which to make the determination.  It must not
1264   *                be {@code null}.
1265   *
1266   * @return  {@code true} if this attribute has the specified value, or
1267   *          {@code false} if not.
1268   */
1269  public boolean hasValue(final String value)
1270  {
1271    ensureNotNull(value);
1272
1273    return hasValue(new ASN1OctetString(value), matchingRule);
1274  }
1275
1276
1277
1278  /**
1279   * Indicates whether this attribute contains the specified value.
1280   *
1281   * @param  value         The value for which to make the determination.  It
1282   *                       must not be {@code null}.
1283   * @param  matchingRule  The matching rule to use when making the
1284   *                       determination.  It must not be {@code null}.
1285   *
1286   * @return  {@code true} if this attribute has the specified value, or
1287   *          {@code false} if not.
1288   */
1289  public boolean hasValue(final String value, final MatchingRule matchingRule)
1290  {
1291    ensureNotNull(value);
1292
1293    return hasValue(new ASN1OctetString(value), matchingRule);
1294  }
1295
1296
1297
1298  /**
1299   * Indicates whether this attribute contains the specified value.
1300   *
1301   * @param  value  The value for which to make the determination.  It must not
1302   *                be {@code null}.
1303   *
1304   * @return  {@code true} if this attribute has the specified value, or
1305   *          {@code false} if not.
1306   */
1307  public boolean hasValue(final byte[] value)
1308  {
1309    ensureNotNull(value);
1310
1311    return hasValue(new ASN1OctetString(value), matchingRule);
1312  }
1313
1314
1315
1316  /**
1317   * Indicates whether this attribute contains the specified value.
1318   *
1319   * @param  value         The value for which to make the determination.  It
1320   *                       must not be {@code null}.
1321   * @param  matchingRule  The matching rule to use when making the
1322   *                       determination.  It must not be {@code null}.
1323   *
1324   * @return  {@code true} if this attribute has the specified value, or
1325   *          {@code false} if not.
1326   */
1327  public boolean hasValue(final byte[] value, final MatchingRule matchingRule)
1328  {
1329    ensureNotNull(value);
1330
1331    return hasValue(new ASN1OctetString(value), matchingRule);
1332  }
1333
1334
1335
1336  /**
1337   * Indicates whether this attribute contains the specified value.
1338   *
1339   * @param  value  The value for which to make the determination.
1340   *
1341   * @return  {@code true} if this attribute has the specified value, or
1342   *          {@code false} if not.
1343   */
1344  boolean hasValue(final ASN1OctetString value)
1345  {
1346    return hasValue(value, matchingRule);
1347  }
1348
1349
1350
1351  /**
1352   * Indicates whether this attribute contains the specified value.
1353   *
1354   * @param  value         The value for which to make the determination.  It
1355   *                       must not be {@code null}.
1356   * @param  matchingRule  The matching rule to use when making the
1357   *                       determination.  It must not be {@code null}.
1358   *
1359   * @return  {@code true} if this attribute has the specified value, or
1360   *          {@code false} if not.
1361   */
1362  boolean hasValue(final ASN1OctetString value, final MatchingRule matchingRule)
1363  {
1364    try
1365    {
1366      return matchingRule.matchesAnyValue(value, values);
1367    }
1368    catch (final LDAPException le)
1369    {
1370      debugException(le);
1371
1372      // This probably means that the provided value cannot be normalized.  In
1373      // that case, we'll fall back to a byte-for-byte comparison of the values.
1374      for (final ASN1OctetString existingValue : values)
1375      {
1376        if (value.equalsIgnoreType(existingValue))
1377        {
1378          return true;
1379        }
1380      }
1381
1382      return false;
1383    }
1384  }
1385
1386
1387
1388  /**
1389   * Retrieves the number of values for this attribute.
1390   *
1391   * @return  The number of values for this attribute.
1392   */
1393  public int size()
1394  {
1395    return values.length;
1396  }
1397
1398
1399
1400  /**
1401   * Writes an ASN.1-encoded representation of this attribute to the provided
1402   * ASN.1 buffer.
1403   *
1404   * @param  buffer  The ASN.1 buffer to which the encoded representation should
1405   *                 be written.
1406   */
1407  public void writeTo(final ASN1Buffer buffer)
1408  {
1409    final ASN1BufferSequence attrSequence = buffer.beginSequence();
1410    buffer.addOctetString(name);
1411
1412    final ASN1BufferSet valueSet = buffer.beginSet();
1413    for (final ASN1OctetString value : values)
1414    {
1415      buffer.addElement(value);
1416    }
1417    valueSet.end();
1418    attrSequence.end();
1419  }
1420
1421
1422
1423  /**
1424   * Encodes this attribute into a form suitable for use in the LDAP protocol.
1425   * It will be encoded as a sequence containing the attribute name (as an octet
1426   * string) and a set of values.
1427   *
1428   * @return  An ASN.1 sequence containing the encoded attribute.
1429   */
1430  public ASN1Sequence encode()
1431  {
1432    final ASN1Element[] elements =
1433    {
1434      new ASN1OctetString(name),
1435      new ASN1Set(values)
1436    };
1437
1438    return new ASN1Sequence(elements);
1439  }
1440
1441
1442
1443  /**
1444   * Reads and decodes an attribute from the provided ASN.1 stream reader.
1445   *
1446   * @param  reader  The ASN.1 stream reader from which to read the attribute.
1447   *
1448   * @return  The decoded attribute.
1449   *
1450   * @throws  LDAPException  If a problem occurs while trying to read or decode
1451   *                         the attribute.
1452   */
1453  public static Attribute readFrom(final ASN1StreamReader reader)
1454         throws LDAPException
1455  {
1456    return readFrom(reader, null);
1457  }
1458
1459
1460
1461  /**
1462   * Reads and decodes an attribute from the provided ASN.1 stream reader.
1463   *
1464   * @param  reader  The ASN.1 stream reader from which to read the attribute.
1465   * @param  schema  The schema to use to select the appropriate matching rule
1466   *                 for this attribute.  It may be {@code null} if the default
1467   *                 matching rule should be selected.
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                                   final Schema schema)
1476         throws LDAPException
1477  {
1478    try
1479    {
1480      ensureNotNull(reader.beginSequence());
1481      final String attrName = reader.readString();
1482      ensureNotNull(attrName);
1483
1484      final MatchingRule matchingRule =
1485           MatchingRule.selectEqualityMatchingRule(attrName, schema);
1486
1487      final ArrayList<ASN1OctetString> valueList =
1488           new ArrayList<ASN1OctetString>();
1489      final ASN1StreamReaderSet valueSet = reader.beginSet();
1490      while (valueSet.hasMoreElements())
1491      {
1492        valueList.add(new ASN1OctetString(reader.readBytes()));
1493      }
1494
1495      final ASN1OctetString[] values = new ASN1OctetString[valueList.size()];
1496      valueList.toArray(values);
1497
1498      return new Attribute(attrName, matchingRule, values);
1499    }
1500    catch (final Exception e)
1501    {
1502      debugException(e);
1503      throw new LDAPException(ResultCode.DECODING_ERROR,
1504           ERR_ATTR_CANNOT_DECODE.get(getExceptionMessage(e)), e);
1505    }
1506  }
1507
1508
1509
1510  /**
1511   * Decodes the provided ASN.1 sequence as an LDAP attribute.
1512   *
1513   * @param  encodedAttribute  The ASN.1 sequence to be decoded as an LDAP
1514   *                           attribute.  It must not be {@code null}.
1515   *
1516   * @return  The decoded LDAP attribute.
1517   *
1518   * @throws  LDAPException  If a problem occurs while attempting to decode the
1519   *                         provided ASN.1 sequence as an LDAP attribute.
1520   */
1521  public static Attribute decode(final ASN1Sequence encodedAttribute)
1522         throws LDAPException
1523  {
1524    ensureNotNull(encodedAttribute);
1525
1526    final ASN1Element[] elements = encodedAttribute.elements();
1527    if (elements.length != 2)
1528    {
1529      throw new LDAPException(ResultCode.DECODING_ERROR,
1530                     ERR_ATTR_DECODE_INVALID_COUNT.get(elements.length));
1531    }
1532
1533    final String name =
1534         ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
1535
1536    final ASN1Set valueSet;
1537    try
1538    {
1539      valueSet = ASN1Set.decodeAsSet(elements[1]);
1540    }
1541    catch (final ASN1Exception ae)
1542    {
1543      debugException(ae);
1544      throw new LDAPException(ResultCode.DECODING_ERROR,
1545           ERR_ATTR_DECODE_VALUE_SET.get(getExceptionMessage(ae)), ae);
1546    }
1547
1548    final ASN1OctetString[] values =
1549         new ASN1OctetString[valueSet.elements().length];
1550    for (int i=0; i < values.length; i++)
1551    {
1552      values[i] = ASN1OctetString.decodeAsOctetString(valueSet.elements()[i]);
1553    }
1554
1555    return new Attribute(name, CaseIgnoreStringMatchingRule.getInstance(),
1556                         values);
1557  }
1558
1559
1560
1561  /**
1562   * Indicates whether any of the values of this attribute need to be
1563   * base64-encoded when represented as LDIF.
1564   *
1565   * @return  {@code true} if any of the values of this attribute need to be
1566   *          base64-encoded when represented as LDIF, or {@code false} if not.
1567   */
1568  public boolean needsBase64Encoding()
1569  {
1570    for (final ASN1OctetString v : values)
1571    {
1572      if (needsBase64Encoding(v.getValue()))
1573      {
1574        return true;
1575      }
1576    }
1577
1578    return false;
1579  }
1580
1581
1582
1583  /**
1584   * Indicates whether the provided value needs to be base64-encoded when
1585   * represented as LDIF.
1586   *
1587   * @param  v  The value for which to make the determination.  It must not be
1588   *            {@code null}.
1589   *
1590   * @return  {@code true} if the provided value needs to be base64-encoded when
1591   *          represented as LDIF, or {@code false} if not.
1592   */
1593  public static boolean needsBase64Encoding(final String v)
1594  {
1595    return needsBase64Encoding(getBytes(v));
1596  }
1597
1598
1599
1600  /**
1601   * Indicates whether the provided value needs to be base64-encoded when
1602   * represented as LDIF.
1603   *
1604   * @param  v  The value for which to make the determination.  It must not be
1605   *            {@code null}.
1606   *
1607   * @return  {@code true} if the provided value needs to be base64-encoded when
1608   *          represented as LDIF, or {@code false} if not.
1609   */
1610  public static boolean needsBase64Encoding(final byte[] v)
1611  {
1612    if (v.length == 0)
1613    {
1614      return false;
1615    }
1616
1617    switch (v[0] & 0xFF)
1618    {
1619      case 0x20: // Space
1620      case 0x3A: // Colon
1621      case 0x3C: // Less-than
1622        return true;
1623    }
1624
1625    if ((v[v.length-1] & 0xFF) == 0x20)
1626    {
1627      return true;
1628    }
1629
1630    for (final byte b : v)
1631    {
1632      switch (b & 0xFF)
1633      {
1634        case 0x00: // NULL
1635        case 0x0A: // LF
1636        case 0x0D: // CR
1637          return true;
1638
1639        default:
1640          if ((b & 0x80) != 0x00)
1641          {
1642            return true;
1643          }
1644          break;
1645      }
1646    }
1647
1648    return false;
1649  }
1650
1651
1652
1653  /**
1654   * Generates a hash code for this LDAP attribute.  It will be the sum of the
1655   * hash codes for the lowercase attribute name and the normalized values.
1656   *
1657   * @return  The generated hash code for this LDAP attribute.
1658   */
1659  @Override()
1660  public int hashCode()
1661  {
1662    if (hashCode == -1)
1663    {
1664      int c = toLowerCase(name).hashCode();
1665
1666      for (final ASN1OctetString value : values)
1667      {
1668        try
1669        {
1670          c += matchingRule.normalize(value).hashCode();
1671        }
1672        catch (final LDAPException le)
1673        {
1674          debugException(le);
1675          c += value.hashCode();
1676        }
1677      }
1678
1679      hashCode = c;
1680    }
1681
1682    return hashCode;
1683  }
1684
1685
1686
1687  /**
1688   * Indicates whether the provided object is equal to this LDAP attribute.  The
1689   * object will be considered equal to this LDAP attribute only if it is an
1690   * LDAP attribute with the same name and set of values.
1691   *
1692   * @param  o  The object for which to make the determination.
1693   *
1694   * @return  {@code true} if the provided object may be considered equal to
1695   *          this LDAP attribute, or {@code false} if not.
1696   */
1697  @Override()
1698  public boolean equals(final Object o)
1699  {
1700    if (o == null)
1701    {
1702      return false;
1703    }
1704
1705    if (o == this)
1706    {
1707      return true;
1708    }
1709
1710    if (! (o instanceof Attribute))
1711    {
1712      return false;
1713    }
1714
1715    final Attribute a = (Attribute) o;
1716    if (! name.equalsIgnoreCase(a.name))
1717    {
1718      return false;
1719    }
1720
1721    if (values.length != a.values.length)
1722    {
1723      return false;
1724    }
1725
1726    // For a small set of values, we can just iterate through the values of one
1727    // and see if they are all present in the other.  However, that can be very
1728    // expensive for a large set of values, so we'll try to go with a more
1729    // efficient approach.
1730    if (values.length > 10)
1731    {
1732      // First, create a hash set containing the un-normalized values of the
1733      // first attribute.
1734      final HashSet<ASN1OctetString> unNormalizedValues =
1735           new HashSet<ASN1OctetString>(values.length);
1736      Collections.addAll(unNormalizedValues, values);
1737
1738      // Next, iterate through the values of the second attribute.  For any
1739      // values that exist in the un-normalized set, remove them from that
1740      // set.  For any values that aren't in the un-normalized set, create a
1741      // new set with the normalized representations of those values.
1742      HashSet<ASN1OctetString> normalizedMissingValues = null;
1743      for (final ASN1OctetString value : a.values)
1744      {
1745        if (! unNormalizedValues.remove(value))
1746        {
1747          if (normalizedMissingValues == null)
1748          {
1749            normalizedMissingValues =
1750                 new HashSet<ASN1OctetString>(values.length);
1751          }
1752
1753          try
1754          {
1755            normalizedMissingValues.add(matchingRule.normalize(value));
1756          }
1757          catch (final Exception e)
1758          {
1759            debugException(e);
1760            return false;
1761          }
1762        }
1763      }
1764
1765      // If the un-normalized set is empty, then that means all the values
1766      // exactly match without the need to compare the normalized
1767      // representations.  For any values that are left, then we will need to
1768      // compare their normalized representations.
1769      if (normalizedMissingValues != null)
1770      {
1771        for (final ASN1OctetString value : unNormalizedValues)
1772        {
1773          try
1774          {
1775            if (! normalizedMissingValues.contains(
1776                       matchingRule.normalize(value)))
1777            {
1778              return false;
1779            }
1780          }
1781          catch (final Exception e)
1782          {
1783            debugException(e);
1784            return false;
1785          }
1786        }
1787      }
1788    }
1789    else
1790    {
1791      for (final ASN1OctetString value : values)
1792      {
1793        if (! a.hasValue(value))
1794        {
1795          return false;
1796        }
1797      }
1798    }
1799
1800
1801    // If we've gotten here, then we can consider them equal.
1802    return true;
1803  }
1804
1805
1806
1807  /**
1808   * Retrieves a string representation of this LDAP attribute.
1809   *
1810   * @return  A string representation of this LDAP attribute.
1811   */
1812  @Override()
1813  public String toString()
1814  {
1815    final StringBuilder buffer = new StringBuilder();
1816    toString(buffer);
1817    return buffer.toString();
1818  }
1819
1820
1821
1822  /**
1823   * Appends a string representation of this LDAP attribute to the provided
1824   * buffer.
1825   *
1826   * @param  buffer  The buffer to which the string representation of this LDAP
1827   *                 attribute should be appended.
1828   */
1829  public void toString(final StringBuilder buffer)
1830  {
1831    buffer.append("Attribute(name=");
1832    buffer.append(name);
1833
1834    if (values.length == 0)
1835    {
1836      buffer.append(", values={");
1837    }
1838    else if (needsBase64Encoding())
1839    {
1840      buffer.append(", base64Values={'");
1841
1842      for (int i=0; i < values.length; i++)
1843      {
1844        if (i > 0)
1845        {
1846          buffer.append("', '");
1847        }
1848
1849        buffer.append(Base64.encode(values[i].getValue()));
1850      }
1851
1852      buffer.append('\'');
1853    }
1854    else
1855    {
1856      buffer.append(", values={'");
1857
1858      for (int i=0; i < values.length; i++)
1859      {
1860        if (i > 0)
1861        {
1862          buffer.append("', '");
1863        }
1864
1865        buffer.append(values[i].stringValue());
1866      }
1867
1868      buffer.append('\'');
1869    }
1870
1871    buffer.append("})");
1872  }
1873}