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