001    /*
002     * Copyright 2007-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2016 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     */
021    package com.unboundid.ldap.sdk;
022    
023    
024    
025    import java.io.Serializable;
026    import java.util.ArrayList;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.Collections;
030    import java.util.Date;
031    import java.util.HashSet;
032    import java.util.Iterator;
033    import java.util.LinkedHashSet;
034    import java.util.Set;
035    
036    import com.unboundid.asn1.ASN1Buffer;
037    import com.unboundid.asn1.ASN1BufferSequence;
038    import com.unboundid.asn1.ASN1BufferSet;
039    import com.unboundid.asn1.ASN1Element;
040    import com.unboundid.asn1.ASN1Exception;
041    import com.unboundid.asn1.ASN1OctetString;
042    import com.unboundid.asn1.ASN1Sequence;
043    import com.unboundid.asn1.ASN1Set;
044    import com.unboundid.asn1.ASN1StreamReader;
045    import com.unboundid.asn1.ASN1StreamReaderSet;
046    import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule;
047    import com.unboundid.ldap.matchingrules.MatchingRule;
048    import com.unboundid.ldap.sdk.schema.Schema;
049    import com.unboundid.util.Base64;
050    import com.unboundid.util.NotMutable;
051    import com.unboundid.util.ThreadSafety;
052    import com.unboundid.util.ThreadSafetyLevel;
053    
054    import static com.unboundid.ldap.sdk.LDAPMessages.*;
055    import static com.unboundid.util.Debug.*;
056    import static com.unboundid.util.StaticUtils.*;
057    import 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)
104    public 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    }