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