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