001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collection; 029import java.util.Collections; 030import java.util.Date; 031import java.util.HashSet; 032import java.util.Iterator; 033import java.util.LinkedHashSet; 034import java.util.Set; 035 036import com.unboundid.asn1.ASN1Buffer; 037import com.unboundid.asn1.ASN1BufferSequence; 038import com.unboundid.asn1.ASN1BufferSet; 039import com.unboundid.asn1.ASN1Element; 040import com.unboundid.asn1.ASN1Exception; 041import com.unboundid.asn1.ASN1OctetString; 042import com.unboundid.asn1.ASN1Sequence; 043import com.unboundid.asn1.ASN1Set; 044import com.unboundid.asn1.ASN1StreamReader; 045import com.unboundid.asn1.ASN1StreamReaderSet; 046import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule; 047import com.unboundid.ldap.matchingrules.MatchingRule; 048import com.unboundid.ldap.sdk.schema.Schema; 049import com.unboundid.util.Base64; 050import com.unboundid.util.NotMutable; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053 054import static com.unboundid.ldap.sdk.LDAPMessages.*; 055import static com.unboundid.util.Debug.*; 056import static com.unboundid.util.StaticUtils.*; 057import 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) 104public 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}