001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2018 Ping Identity Corporation 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 return mergeAttributes(attr1, attr2, attr1.matchingRule); 540 } 541 542 543 544 /** 545 * Creates a new attribute containing the merged values of the provided 546 * attributes. Any duplicate values will only be present once in the 547 * resulting attribute. The names of the provided attributes must be the 548 * same. 549 * 550 * @param attr1 The first attribute containing the values to merge. 551 * It must not be {@code null}. 552 * @param attr2 The second attribute containing the values to merge. 553 * It must not be {@code null}. 554 * @param matchingRule The matching rule to use to locate matching values. 555 * It may be {@code null} if the matching rule 556 * associated with the first attribute should be used. 557 * 558 * @return The new attribute containing the values of both of the 559 * provided attributes. 560 */ 561 public static Attribute mergeAttributes(final Attribute attr1, 562 final Attribute attr2, 563 final MatchingRule matchingRule) 564 { 565 ensureNotNull(attr1, attr2); 566 567 final String name = attr1.name; 568 ensureTrue(name.equalsIgnoreCase(attr2.name)); 569 570 final MatchingRule mr; 571 if (matchingRule == null) 572 { 573 mr = attr1.matchingRule; 574 } 575 else 576 { 577 mr = matchingRule; 578 } 579 580 ASN1OctetString[] mergedValues = 581 new ASN1OctetString[attr1.values.length + attr2.values.length]; 582 System.arraycopy(attr1.values, 0, mergedValues, 0, attr1.values.length); 583 584 int pos = attr1.values.length; 585 for (final ASN1OctetString attr2Value : attr2.values) 586 { 587 if (! attr1.hasValue(attr2Value, mr)) 588 { 589 mergedValues[pos++] = attr2Value; 590 } 591 } 592 593 if (pos != mergedValues.length) 594 { 595 // This indicates that there were duplicate values. 596 final ASN1OctetString[] newMergedValues = new ASN1OctetString[pos]; 597 System.arraycopy(mergedValues, 0, newMergedValues, 0, pos); 598 mergedValues = newMergedValues; 599 } 600 601 return new Attribute(name, mr, mergedValues); 602 } 603 604 605 606 /** 607 * Creates a new attribute containing all of the values of the first attribute 608 * that are not contained in the second attribute. Any values contained in 609 * the second attribute that are not contained in the first will be ignored. 610 * The names of the provided attributes must be the same. 611 * 612 * @param attr1 The attribute from which to remove the values. It must not 613 * be {@code null}. 614 * @param attr2 The attribute containing the values to remove. It must not 615 * be {@code null}. 616 * 617 * @return A new attribute containing all of the values of the first 618 * attribute not contained in the second. It may contain zero values 619 * if all the values of the first attribute were also contained in 620 * the second. 621 */ 622 public static Attribute removeValues(final Attribute attr1, 623 final Attribute attr2) 624 { 625 return removeValues(attr1, attr2, attr1.matchingRule); 626 } 627 628 629 630 /** 631 * Creates a new attribute containing all of the values of the first attribute 632 * that are not contained in the second attribute. Any values contained in 633 * the second attribute that are not contained in the first will be ignored. 634 * The names of the provided attributes must be the same. 635 * 636 * @param attr1 The attribute from which to remove the values. It 637 * must not be {@code null}. 638 * @param attr2 The attribute containing the values to remove. It 639 * must not be {@code null}. 640 * @param matchingRule The matching rule to use to locate matching values. 641 * It may be {@code null} if the matching rule 642 * associated with the first attribute should be used. 643 * 644 * @return A new attribute containing all of the values of the first 645 * attribute not contained in the second. It may contain zero values 646 * if all the values of the first attribute were also contained in 647 * the second. 648 */ 649 public static Attribute removeValues(final Attribute attr1, 650 final Attribute attr2, 651 final MatchingRule matchingRule) 652 { 653 ensureNotNull(attr1, attr2); 654 655 final String name = attr1.name; 656 ensureTrue(name.equalsIgnoreCase(attr2.name)); 657 658 final MatchingRule mr; 659 if (matchingRule == null) 660 { 661 mr = attr1.matchingRule; 662 } 663 else 664 { 665 mr = matchingRule; 666 } 667 668 final ArrayList<ASN1OctetString> newValues = 669 new ArrayList<ASN1OctetString>(Arrays.asList(attr1.values)); 670 671 final Iterator<ASN1OctetString> iterator = newValues.iterator(); 672 while (iterator.hasNext()) 673 { 674 if (attr2.hasValue(iterator.next(), mr)) 675 { 676 iterator.remove(); 677 } 678 } 679 680 final ASN1OctetString[] newValueArray = 681 new ASN1OctetString[newValues.size()]; 682 newValues.toArray(newValueArray); 683 684 return new Attribute(name, mr, newValueArray); 685 } 686 687 688 689 /** 690 * Retrieves the name for this attribute (i.e., the attribute description), 691 * which may include zero or more attribute options. 692 * 693 * @return The name for this attribute. 694 */ 695 public String getName() 696 { 697 return name; 698 } 699 700 701 702 /** 703 * Retrieves the base name for this attribute, which is the name or OID of the 704 * attribute type, without any attribute options. For an attribute without 705 * any options, the value returned by this method will be identical the value 706 * returned by the {@link #getName} method. 707 * 708 * @return The base name for this attribute. 709 */ 710 public String getBaseName() 711 { 712 return getBaseName(name); 713 } 714 715 716 717 /** 718 * Retrieves the base name for an attribute with the given name, which will be 719 * the provided name without any attribute options. If the given name does 720 * not include any attribute options, then it will be returned unaltered. If 721 * it does contain one or more attribute options, then the name will be 722 * returned without those options. 723 * 724 * @param name The name to be processed. 725 * 726 * @return The base name determined from the provided attribute name. 727 */ 728 public static String getBaseName(final String name) 729 { 730 final int semicolonPos = name.indexOf(';'); 731 if (semicolonPos > 0) 732 { 733 return name.substring(0, semicolonPos); 734 } 735 else 736 { 737 return name; 738 } 739 } 740 741 742 743 /** 744 * Indicates whether the name of this attribute is valid as per RFC 4512. The 745 * name will be considered valid only if it starts with an ASCII alphabetic 746 * character ('a' through 'z', or 'A' through 'Z'), and contains only ASCII 747 * alphabetic characters, ASCII numeric digits ('0' through '9'), and the 748 * ASCII hyphen character ('-'). It will also be allowed to include zero or 749 * more attribute options, in which the option must be separate from the base 750 * name by a semicolon and has the same naming constraints as the base name. 751 * 752 * @return {@code true} if this attribute has a valid name, or {@code false} 753 * if not. 754 */ 755 public boolean nameIsValid() 756 { 757 return nameIsValid(name, true); 758 } 759 760 761 762 /** 763 * Indicates whether the provided string represents a valid attribute name as 764 * per RFC 4512. It will be considered valid only if it starts with an ASCII 765 * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains 766 * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'), 767 * and the ASCII hyphen character ('-'). It will also be allowed to include 768 * zero or more attribute options, in which the option must be separate from 769 * the base name by a semicolon and has the same naming constraints as the 770 * base name. 771 * 772 * @param s The name for which to make the determination. 773 * 774 * @return {@code true} if this attribute has a valid name, or {@code false} 775 * if not. 776 */ 777 public static boolean nameIsValid(final String s) 778 { 779 return nameIsValid(s, true); 780 } 781 782 783 784 /** 785 * Indicates whether the provided string represents a valid attribute name as 786 * per RFC 4512. It will be considered valid only if it starts with an ASCII 787 * alphabetic character ('a' through 'z', or 'A' through 'Z'), and contains 788 * only ASCII alphabetic characters, ASCII numeric digits ('0' through '9'), 789 * and the ASCII hyphen character ('-'). It may optionally be allowed to 790 * include zero or more attribute options, in which the option must be 791 * separate from the base name by a semicolon and has the same naming 792 * constraints as the base name. 793 * 794 * @param s The name for which to make the determination. 795 * @param allowOptions Indicates whether the provided name will be allowed 796 * to contain attribute options. 797 * 798 * @return {@code true} if this attribute has a valid name, or {@code false} 799 * if not. 800 */ 801 public static boolean nameIsValid(final String s, final boolean allowOptions) 802 { 803 final int length; 804 if ((s == null) || ((length = s.length()) == 0)) 805 { 806 return false; 807 } 808 809 final char firstChar = s.charAt(0); 810 if (! (((firstChar >= 'a') && (firstChar <= 'z')) || 811 ((firstChar >= 'A') && (firstChar <= 'Z')))) 812 { 813 return false; 814 } 815 816 boolean lastWasSemiColon = false; 817 for (int i=1; i < length; i++) 818 { 819 final char c = s.charAt(i); 820 if (((c >= 'a') && (c <= 'z')) || 821 ((c >= 'A') && (c <= 'Z'))) 822 { 823 // This will always be acceptable. 824 lastWasSemiColon = false; 825 } 826 else if (((c >= '0') && (c <= '9')) || 827 (c == '-')) 828 { 829 // These will only be acceptable if the last character was not a 830 // semicolon. 831 if (lastWasSemiColon) 832 { 833 return false; 834 } 835 836 lastWasSemiColon = false; 837 } 838 else if (c == ';') 839 { 840 // This will only be acceptable if attribute options are allowed and the 841 // last character was not a semicolon. 842 if (lastWasSemiColon || (! allowOptions)) 843 { 844 return false; 845 } 846 847 lastWasSemiColon = true; 848 } 849 else 850 { 851 return false; 852 } 853 } 854 855 return (! lastWasSemiColon); 856 } 857 858 859 860 /** 861 * Indicates whether this attribute has any attribute options. 862 * 863 * @return {@code true} if this attribute has at least one attribute option, 864 * or {@code false} if not. 865 */ 866 public boolean hasOptions() 867 { 868 return hasOptions(name); 869 } 870 871 872 873 /** 874 * Indicates whether the provided attribute name contains any options. 875 * 876 * @param name The name for which to make the determination. 877 * 878 * @return {@code true} if the provided attribute name has at least one 879 * attribute option, or {@code false} if not. 880 */ 881 public static boolean hasOptions(final String name) 882 { 883 return (name.indexOf(';') > 0); 884 } 885 886 887 888 /** 889 * Indicates whether this attribute has the specified attribute option. 890 * 891 * @param option The attribute option for which to make the determination. 892 * 893 * @return {@code true} if this attribute has the specified attribute option, 894 * or {@code false} if not. 895 */ 896 public boolean hasOption(final String option) 897 { 898 return hasOption(name, option); 899 } 900 901 902 903 /** 904 * Indicates whether the provided attribute name has the specified attribute 905 * option. 906 * 907 * @param name The name to be examined. 908 * @param option The attribute option for which to make the determination. 909 * 910 * @return {@code true} if the provided attribute name has the specified 911 * attribute option, or {@code false} if not. 912 */ 913 public static boolean hasOption(final String name, final String option) 914 { 915 final Set<String> options = getOptions(name); 916 for (final String s : options) 917 { 918 if (s.equalsIgnoreCase(option)) 919 { 920 return true; 921 } 922 } 923 924 return false; 925 } 926 927 928 929 /** 930 * Retrieves the set of options for this attribute. 931 * 932 * @return The set of options for this attribute, or an empty set if there 933 * are none. 934 */ 935 public Set<String> getOptions() 936 { 937 return getOptions(name); 938 } 939 940 941 942 /** 943 * Retrieves the set of options for the provided attribute name. 944 * 945 * @param name The name to be examined. 946 * 947 * @return The set of options for the provided attribute name, or an empty 948 * set if there are none. 949 */ 950 public static Set<String> getOptions(final String name) 951 { 952 int semicolonPos = name.indexOf(';'); 953 if (semicolonPos > 0) 954 { 955 final LinkedHashSet<String> options = new LinkedHashSet<String>(); 956 while (true) 957 { 958 final int nextSemicolonPos = name.indexOf(';', semicolonPos+1); 959 if (nextSemicolonPos > 0) 960 { 961 options.add(name.substring(semicolonPos+1, nextSemicolonPos)); 962 semicolonPos = nextSemicolonPos; 963 } 964 else 965 { 966 options.add(name.substring(semicolonPos+1)); 967 break; 968 } 969 } 970 971 return Collections.unmodifiableSet(options); 972 } 973 else 974 { 975 return Collections.emptySet(); 976 } 977 } 978 979 980 981 /** 982 * Retrieves the matching rule instance used by this attribute. 983 * 984 * @return The matching rule instance used by this attribute. 985 */ 986 public MatchingRule getMatchingRule() 987 { 988 return matchingRule; 989 } 990 991 992 993 /** 994 * Retrieves the value for this attribute as a string. If this attribute has 995 * multiple values, then the first value will be returned. 996 * 997 * @return The value for this attribute, or {@code null} if this attribute 998 * does not have any values. 999 */ 1000 public String getValue() 1001 { 1002 if (values.length == 0) 1003 { 1004 return null; 1005 } 1006 1007 return values[0].stringValue(); 1008 } 1009 1010 1011 1012 /** 1013 * Retrieves the value for this attribute as a byte array. If this attribute 1014 * has multiple values, then the first value will be returned. The returned 1015 * array must not be altered by the caller. 1016 * 1017 * @return The value for this attribute, or {@code null} if this attribute 1018 * does not have any values. 1019 */ 1020 public byte[] getValueByteArray() 1021 { 1022 if (values.length == 0) 1023 { 1024 return null; 1025 } 1026 1027 return values[0].getValue(); 1028 } 1029 1030 1031 1032 /** 1033 * Retrieves the value for this attribute as a Boolean. If this attribute has 1034 * multiple values, then the first value will be examined. Values of "true", 1035 * "t", "yes", "y", "on", and "1" will be interpreted as {@code TRUE}. Values 1036 * of "false", "f", "no", "n", "off", and "0" will be interpreted as 1037 * {@code FALSE}. 1038 * 1039 * @return The Boolean value for this attribute, or {@code null} if this 1040 * attribute does not have any values or the value cannot be parsed 1041 * as a Boolean. 1042 */ 1043 public Boolean getValueAsBoolean() 1044 { 1045 if (values.length == 0) 1046 { 1047 return null; 1048 } 1049 1050 final String lowerValue = toLowerCase(values[0].stringValue()); 1051 if (lowerValue.equals("true") || lowerValue.equals("t") || 1052 lowerValue.equals("yes") || lowerValue.equals("y") || 1053 lowerValue.equals("on") || lowerValue.equals("1")) 1054 { 1055 return Boolean.TRUE; 1056 } 1057 else if (lowerValue.equals("false") || lowerValue.equals("f") || 1058 lowerValue.equals("no") || lowerValue.equals("n") || 1059 lowerValue.equals("off") || lowerValue.equals("0")) 1060 { 1061 return Boolean.FALSE; 1062 } 1063 else 1064 { 1065 return null; 1066 } 1067 } 1068 1069 1070 1071 /** 1072 * Retrieves the value for this attribute as a Date, formatted using the 1073 * generalized time syntax. If this attribute has multiple values, then the 1074 * first value will be examined. 1075 * 1076 * @return The Date value for this attribute, or {@code null} if this 1077 * attribute does not have any values or the value cannot be parsed 1078 * as a Date. 1079 */ 1080 public Date getValueAsDate() 1081 { 1082 if (values.length == 0) 1083 { 1084 return null; 1085 } 1086 1087 try 1088 { 1089 return decodeGeneralizedTime(values[0].stringValue()); 1090 } 1091 catch (final Exception e) 1092 { 1093 debugException(e); 1094 return null; 1095 } 1096 } 1097 1098 1099 1100 /** 1101 * Retrieves the value for this attribute as a DN. If this attribute has 1102 * multiple values, then the first value will be examined. 1103 * 1104 * @return The DN value for this attribute, or {@code null} if this attribute 1105 * does not have any values or the value cannot be parsed as a DN. 1106 */ 1107 public DN getValueAsDN() 1108 { 1109 if (values.length == 0) 1110 { 1111 return null; 1112 } 1113 1114 try 1115 { 1116 return new DN(values[0].stringValue()); 1117 } 1118 catch (final Exception e) 1119 { 1120 debugException(e); 1121 return null; 1122 } 1123 } 1124 1125 1126 1127 /** 1128 * Retrieves the value for this attribute as an Integer. If this attribute 1129 * has multiple values, then the first value will be examined. 1130 * 1131 * @return The Integer value for this attribute, or {@code null} if this 1132 * attribute does not have any values or the value cannot be parsed 1133 * as an Integer. 1134 */ 1135 public Integer getValueAsInteger() 1136 { 1137 if (values.length == 0) 1138 { 1139 return null; 1140 } 1141 1142 try 1143 { 1144 return Integer.valueOf(values[0].stringValue()); 1145 } 1146 catch (final NumberFormatException nfe) 1147 { 1148 debugException(nfe); 1149 return null; 1150 } 1151 } 1152 1153 1154 1155 /** 1156 * Retrieves the value for this attribute as a Long. If this attribute has 1157 * multiple values, then the first value will be examined. 1158 * 1159 * @return The Long value for this attribute, or {@code null} if this 1160 * attribute does not have any values or the value cannot be parsed 1161 * as a Long. 1162 */ 1163 public Long getValueAsLong() 1164 { 1165 if (values.length == 0) 1166 { 1167 return null; 1168 } 1169 1170 try 1171 { 1172 return Long.valueOf(values[0].stringValue()); 1173 } 1174 catch (final NumberFormatException nfe) 1175 { 1176 debugException(nfe); 1177 return null; 1178 } 1179 } 1180 1181 1182 1183 /** 1184 * Retrieves the set of values for this attribute as strings. The returned 1185 * array must not be altered by the caller. 1186 * 1187 * @return The set of values for this attribute, or an empty array if it does 1188 * not have any values. 1189 */ 1190 public String[] getValues() 1191 { 1192 if (values.length == 0) 1193 { 1194 return NO_STRINGS; 1195 } 1196 1197 final String[] stringValues = new String[values.length]; 1198 for (int i=0; i < values.length; i++) 1199 { 1200 stringValues[i] = values[i].stringValue(); 1201 } 1202 1203 return stringValues; 1204 } 1205 1206 1207 1208 /** 1209 * Retrieves the set of values for this attribute as byte arrays. The 1210 * returned array must not be altered by the caller. 1211 * 1212 * @return The set of values for this attribute, or an empty array if it does 1213 * not have any values. 1214 */ 1215 public byte[][] getValueByteArrays() 1216 { 1217 if (values.length == 0) 1218 { 1219 return NO_BYTE_VALUES; 1220 } 1221 1222 final byte[][] byteValues = new byte[values.length][]; 1223 for (int i=0; i < values.length; i++) 1224 { 1225 byteValues[i] = values[i].getValue(); 1226 } 1227 1228 return byteValues; 1229 } 1230 1231 1232 1233 /** 1234 * Retrieves the set of values for this attribute as an array of ASN.1 octet 1235 * strings. The returned array must not be altered by the caller. 1236 * 1237 * @return The set of values for this attribute as an array of ASN.1 octet 1238 * strings. 1239 */ 1240 public ASN1OctetString[] getRawValues() 1241 { 1242 return values; 1243 } 1244 1245 1246 1247 /** 1248 * Indicates whether this attribute contains at least one value. 1249 * 1250 * @return {@code true} if this attribute has at least one value, or 1251 * {@code false} if not. 1252 */ 1253 public boolean hasValue() 1254 { 1255 return (values.length > 0); 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 must not 1264 * be {@code null}. 1265 * 1266 * @return {@code true} if this attribute has the specified value, or 1267 * {@code false} if not. 1268 */ 1269 public boolean hasValue(final String value) 1270 { 1271 ensureNotNull(value); 1272 1273 return hasValue(new ASN1OctetString(value), matchingRule); 1274 } 1275 1276 1277 1278 /** 1279 * Indicates whether this attribute contains the specified value. 1280 * 1281 * @param value The value for which to make the determination. It 1282 * must not be {@code null}. 1283 * @param matchingRule The matching rule to use when making the 1284 * determination. It must not 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 String value, final MatchingRule matchingRule) 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 must not 1302 * be {@code null}. 1303 * 1304 * @return {@code true} if this attribute has the specified value, or 1305 * {@code false} if not. 1306 */ 1307 public boolean hasValue(final byte[] value) 1308 { 1309 ensureNotNull(value); 1310 1311 return hasValue(new ASN1OctetString(value), matchingRule); 1312 } 1313 1314 1315 1316 /** 1317 * Indicates whether this attribute contains the specified value. 1318 * 1319 * @param value The value for which to make the determination. It 1320 * must not be {@code null}. 1321 * @param matchingRule The matching rule to use when making the 1322 * determination. It must not be {@code null}. 1323 * 1324 * @return {@code true} if this attribute has the specified value, or 1325 * {@code false} if not. 1326 */ 1327 public boolean hasValue(final byte[] value, final MatchingRule matchingRule) 1328 { 1329 ensureNotNull(value); 1330 1331 return hasValue(new ASN1OctetString(value), matchingRule); 1332 } 1333 1334 1335 1336 /** 1337 * Indicates whether this attribute contains the specified value. 1338 * 1339 * @param value The value for which to make the determination. 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) 1345 { 1346 return hasValue(value, matchingRule); 1347 } 1348 1349 1350 1351 /** 1352 * Indicates whether this attribute contains the specified value. 1353 * 1354 * @param value The value for which to make the determination. It 1355 * must not be {@code null}. 1356 * @param matchingRule The matching rule to use when making the 1357 * determination. It must not be {@code null}. 1358 * 1359 * @return {@code true} if this attribute has the specified value, or 1360 * {@code false} if not. 1361 */ 1362 boolean hasValue(final ASN1OctetString value, final MatchingRule matchingRule) 1363 { 1364 try 1365 { 1366 return matchingRule.matchesAnyValue(value, values); 1367 } 1368 catch (final LDAPException le) 1369 { 1370 debugException(le); 1371 1372 // This probably means that the provided value cannot be normalized. In 1373 // that case, we'll fall back to a byte-for-byte comparison of the values. 1374 for (final ASN1OctetString existingValue : values) 1375 { 1376 if (value.equalsIgnoreType(existingValue)) 1377 { 1378 return true; 1379 } 1380 } 1381 1382 return false; 1383 } 1384 } 1385 1386 1387 1388 /** 1389 * Retrieves the number of values for this attribute. 1390 * 1391 * @return The number of values for this attribute. 1392 */ 1393 public int size() 1394 { 1395 return values.length; 1396 } 1397 1398 1399 1400 /** 1401 * Writes an ASN.1-encoded representation of this attribute to the provided 1402 * ASN.1 buffer. 1403 * 1404 * @param buffer The ASN.1 buffer to which the encoded representation should 1405 * be written. 1406 */ 1407 public void writeTo(final ASN1Buffer buffer) 1408 { 1409 final ASN1BufferSequence attrSequence = buffer.beginSequence(); 1410 buffer.addOctetString(name); 1411 1412 final ASN1BufferSet valueSet = buffer.beginSet(); 1413 for (final ASN1OctetString value : values) 1414 { 1415 buffer.addElement(value); 1416 } 1417 valueSet.end(); 1418 attrSequence.end(); 1419 } 1420 1421 1422 1423 /** 1424 * Encodes this attribute into a form suitable for use in the LDAP protocol. 1425 * It will be encoded as a sequence containing the attribute name (as an octet 1426 * string) and a set of values. 1427 * 1428 * @return An ASN.1 sequence containing the encoded attribute. 1429 */ 1430 public ASN1Sequence encode() 1431 { 1432 final ASN1Element[] elements = 1433 { 1434 new ASN1OctetString(name), 1435 new ASN1Set(values) 1436 }; 1437 1438 return new ASN1Sequence(elements); 1439 } 1440 1441 1442 1443 /** 1444 * Reads and decodes an attribute from the provided ASN.1 stream reader. 1445 * 1446 * @param reader The ASN.1 stream reader from which to read the attribute. 1447 * 1448 * @return The decoded attribute. 1449 * 1450 * @throws LDAPException If a problem occurs while trying to read or decode 1451 * the attribute. 1452 */ 1453 public static Attribute readFrom(final ASN1StreamReader reader) 1454 throws LDAPException 1455 { 1456 return readFrom(reader, null); 1457 } 1458 1459 1460 1461 /** 1462 * Reads and decodes an attribute from the provided ASN.1 stream reader. 1463 * 1464 * @param reader The ASN.1 stream reader from which to read the attribute. 1465 * @param schema The schema to use to select the appropriate matching rule 1466 * for this attribute. It may be {@code null} if the default 1467 * matching rule should be selected. 1468 * 1469 * @return The decoded attribute. 1470 * 1471 * @throws LDAPException If a problem occurs while trying to read or decode 1472 * the attribute. 1473 */ 1474 public static Attribute readFrom(final ASN1StreamReader reader, 1475 final Schema schema) 1476 throws LDAPException 1477 { 1478 try 1479 { 1480 ensureNotNull(reader.beginSequence()); 1481 final String attrName = reader.readString(); 1482 ensureNotNull(attrName); 1483 1484 final MatchingRule matchingRule = 1485 MatchingRule.selectEqualityMatchingRule(attrName, schema); 1486 1487 final ArrayList<ASN1OctetString> valueList = 1488 new ArrayList<ASN1OctetString>(); 1489 final ASN1StreamReaderSet valueSet = reader.beginSet(); 1490 while (valueSet.hasMoreElements()) 1491 { 1492 valueList.add(new ASN1OctetString(reader.readBytes())); 1493 } 1494 1495 final ASN1OctetString[] values = new ASN1OctetString[valueList.size()]; 1496 valueList.toArray(values); 1497 1498 return new Attribute(attrName, matchingRule, values); 1499 } 1500 catch (final Exception e) 1501 { 1502 debugException(e); 1503 throw new LDAPException(ResultCode.DECODING_ERROR, 1504 ERR_ATTR_CANNOT_DECODE.get(getExceptionMessage(e)), e); 1505 } 1506 } 1507 1508 1509 1510 /** 1511 * Decodes the provided ASN.1 sequence as an LDAP attribute. 1512 * 1513 * @param encodedAttribute The ASN.1 sequence to be decoded as an LDAP 1514 * attribute. It must not be {@code null}. 1515 * 1516 * @return The decoded LDAP attribute. 1517 * 1518 * @throws LDAPException If a problem occurs while attempting to decode the 1519 * provided ASN.1 sequence as an LDAP attribute. 1520 */ 1521 public static Attribute decode(final ASN1Sequence encodedAttribute) 1522 throws LDAPException 1523 { 1524 ensureNotNull(encodedAttribute); 1525 1526 final ASN1Element[] elements = encodedAttribute.elements(); 1527 if (elements.length != 2) 1528 { 1529 throw new LDAPException(ResultCode.DECODING_ERROR, 1530 ERR_ATTR_DECODE_INVALID_COUNT.get(elements.length)); 1531 } 1532 1533 final String name = 1534 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 1535 1536 final ASN1Set valueSet; 1537 try 1538 { 1539 valueSet = ASN1Set.decodeAsSet(elements[1]); 1540 } 1541 catch (final ASN1Exception ae) 1542 { 1543 debugException(ae); 1544 throw new LDAPException(ResultCode.DECODING_ERROR, 1545 ERR_ATTR_DECODE_VALUE_SET.get(getExceptionMessage(ae)), ae); 1546 } 1547 1548 final ASN1OctetString[] values = 1549 new ASN1OctetString[valueSet.elements().length]; 1550 for (int i=0; i < values.length; i++) 1551 { 1552 values[i] = ASN1OctetString.decodeAsOctetString(valueSet.elements()[i]); 1553 } 1554 1555 return new Attribute(name, CaseIgnoreStringMatchingRule.getInstance(), 1556 values); 1557 } 1558 1559 1560 1561 /** 1562 * Indicates whether any of the values of this attribute need to be 1563 * base64-encoded when represented as LDIF. 1564 * 1565 * @return {@code true} if any of the values of this attribute need to be 1566 * base64-encoded when represented as LDIF, or {@code false} if not. 1567 */ 1568 public boolean needsBase64Encoding() 1569 { 1570 for (final ASN1OctetString v : values) 1571 { 1572 if (needsBase64Encoding(v.getValue())) 1573 { 1574 return true; 1575 } 1576 } 1577 1578 return false; 1579 } 1580 1581 1582 1583 /** 1584 * Indicates whether the provided value needs to be base64-encoded when 1585 * represented as LDIF. 1586 * 1587 * @param v The value for which to make the determination. It must not be 1588 * {@code null}. 1589 * 1590 * @return {@code true} if the provided value needs to be base64-encoded when 1591 * represented as LDIF, or {@code false} if not. 1592 */ 1593 public static boolean needsBase64Encoding(final String v) 1594 { 1595 return needsBase64Encoding(getBytes(v)); 1596 } 1597 1598 1599 1600 /** 1601 * Indicates whether the provided value needs to be base64-encoded when 1602 * represented as LDIF. 1603 * 1604 * @param v The value for which to make the determination. It must not be 1605 * {@code null}. 1606 * 1607 * @return {@code true} if the provided value needs to be base64-encoded when 1608 * represented as LDIF, or {@code false} if not. 1609 */ 1610 public static boolean needsBase64Encoding(final byte[] v) 1611 { 1612 if (v.length == 0) 1613 { 1614 return false; 1615 } 1616 1617 switch (v[0] & 0xFF) 1618 { 1619 case 0x20: // Space 1620 case 0x3A: // Colon 1621 case 0x3C: // Less-than 1622 return true; 1623 } 1624 1625 if ((v[v.length-1] & 0xFF) == 0x20) 1626 { 1627 return true; 1628 } 1629 1630 for (final byte b : v) 1631 { 1632 switch (b & 0xFF) 1633 { 1634 case 0x00: // NULL 1635 case 0x0A: // LF 1636 case 0x0D: // CR 1637 return true; 1638 1639 default: 1640 if ((b & 0x80) != 0x00) 1641 { 1642 return true; 1643 } 1644 break; 1645 } 1646 } 1647 1648 return false; 1649 } 1650 1651 1652 1653 /** 1654 * Generates a hash code for this LDAP attribute. It will be the sum of the 1655 * hash codes for the lowercase attribute name and the normalized values. 1656 * 1657 * @return The generated hash code for this LDAP attribute. 1658 */ 1659 @Override() 1660 public int hashCode() 1661 { 1662 if (hashCode == -1) 1663 { 1664 int c = toLowerCase(name).hashCode(); 1665 1666 for (final ASN1OctetString value : values) 1667 { 1668 try 1669 { 1670 c += matchingRule.normalize(value).hashCode(); 1671 } 1672 catch (final LDAPException le) 1673 { 1674 debugException(le); 1675 c += value.hashCode(); 1676 } 1677 } 1678 1679 hashCode = c; 1680 } 1681 1682 return hashCode; 1683 } 1684 1685 1686 1687 /** 1688 * Indicates whether the provided object is equal to this LDAP attribute. The 1689 * object will be considered equal to this LDAP attribute only if it is an 1690 * LDAP attribute with the same name and set of values. 1691 * 1692 * @param o The object for which to make the determination. 1693 * 1694 * @return {@code true} if the provided object may be considered equal to 1695 * this LDAP attribute, or {@code false} if not. 1696 */ 1697 @Override() 1698 public boolean equals(final Object o) 1699 { 1700 if (o == null) 1701 { 1702 return false; 1703 } 1704 1705 if (o == this) 1706 { 1707 return true; 1708 } 1709 1710 if (! (o instanceof Attribute)) 1711 { 1712 return false; 1713 } 1714 1715 final Attribute a = (Attribute) o; 1716 if (! name.equalsIgnoreCase(a.name)) 1717 { 1718 return false; 1719 } 1720 1721 if (values.length != a.values.length) 1722 { 1723 return false; 1724 } 1725 1726 // For a small set of values, we can just iterate through the values of one 1727 // and see if they are all present in the other. However, that can be very 1728 // expensive for a large set of values, so we'll try to go with a more 1729 // efficient approach. 1730 if (values.length > 10) 1731 { 1732 // First, create a hash set containing the un-normalized values of the 1733 // first attribute. 1734 final HashSet<ASN1OctetString> unNormalizedValues = 1735 new HashSet<ASN1OctetString>(values.length); 1736 Collections.addAll(unNormalizedValues, values); 1737 1738 // Next, iterate through the values of the second attribute. For any 1739 // values that exist in the un-normalized set, remove them from that 1740 // set. For any values that aren't in the un-normalized set, create a 1741 // new set with the normalized representations of those values. 1742 HashSet<ASN1OctetString> normalizedMissingValues = null; 1743 for (final ASN1OctetString value : a.values) 1744 { 1745 if (! unNormalizedValues.remove(value)) 1746 { 1747 if (normalizedMissingValues == null) 1748 { 1749 normalizedMissingValues = 1750 new HashSet<ASN1OctetString>(values.length); 1751 } 1752 1753 try 1754 { 1755 normalizedMissingValues.add(matchingRule.normalize(value)); 1756 } 1757 catch (final Exception e) 1758 { 1759 debugException(e); 1760 return false; 1761 } 1762 } 1763 } 1764 1765 // If the un-normalized set is empty, then that means all the values 1766 // exactly match without the need to compare the normalized 1767 // representations. For any values that are left, then we will need to 1768 // compare their normalized representations. 1769 if (normalizedMissingValues != null) 1770 { 1771 for (final ASN1OctetString value : unNormalizedValues) 1772 { 1773 try 1774 { 1775 if (! normalizedMissingValues.contains( 1776 matchingRule.normalize(value))) 1777 { 1778 return false; 1779 } 1780 } 1781 catch (final Exception e) 1782 { 1783 debugException(e); 1784 return false; 1785 } 1786 } 1787 } 1788 } 1789 else 1790 { 1791 for (final ASN1OctetString value : values) 1792 { 1793 if (! a.hasValue(value)) 1794 { 1795 return false; 1796 } 1797 } 1798 } 1799 1800 1801 // If we've gotten here, then we can consider them equal. 1802 return true; 1803 } 1804 1805 1806 1807 /** 1808 * Retrieves a string representation of this LDAP attribute. 1809 * 1810 * @return A string representation of this LDAP attribute. 1811 */ 1812 @Override() 1813 public String toString() 1814 { 1815 final StringBuilder buffer = new StringBuilder(); 1816 toString(buffer); 1817 return buffer.toString(); 1818 } 1819 1820 1821 1822 /** 1823 * Appends a string representation of this LDAP attribute to the provided 1824 * buffer. 1825 * 1826 * @param buffer The buffer to which the string representation of this LDAP 1827 * attribute should be appended. 1828 */ 1829 public void toString(final StringBuilder buffer) 1830 { 1831 buffer.append("Attribute(name="); 1832 buffer.append(name); 1833 1834 if (values.length == 0) 1835 { 1836 buffer.append(", values={"); 1837 } 1838 else if (needsBase64Encoding()) 1839 { 1840 buffer.append(", base64Values={'"); 1841 1842 for (int i=0; i < values.length; i++) 1843 { 1844 if (i > 0) 1845 { 1846 buffer.append("', '"); 1847 } 1848 1849 buffer.append(Base64.encode(values[i].getValue())); 1850 } 1851 1852 buffer.append('\''); 1853 } 1854 else 1855 { 1856 buffer.append(", values={'"); 1857 1858 for (int i=0; i < values.length; i++) 1859 { 1860 if (i > 0) 1861 { 1862 buffer.append("', '"); 1863 } 1864 1865 buffer.append(values[i].stringValue()); 1866 } 1867 1868 buffer.append('\''); 1869 } 1870 1871 buffer.append("})"); 1872 } 1873}