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