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