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