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