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