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