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