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