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