001/* 002 * Copyright 2007-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2019 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.Debug; 051import com.unboundid.util.NotMutable; 052import com.unboundid.util.StaticUtils; 053import com.unboundid.util.ThreadSafety; 054import com.unboundid.util.ThreadSafetyLevel; 055import com.unboundid.util.Validator; 056 057import static com.unboundid.ldap.sdk.LDAPMessages.*; 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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.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 Validator.ensureNotNull(attr1, attr2); 566 567 final String name = attr1.name; 568 Validator.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 Validator.ensureNotNull(attr1, attr2); 654 655 final String name = attr1.name; 656 Validator.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<>(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 = 956 new LinkedHashSet<>(StaticUtils.computeMapCapacity(5)); 957 while (true) 958 { 959 final int nextSemicolonPos = name.indexOf(';', semicolonPos+1); 960 if (nextSemicolonPos > 0) 961 { 962 options.add(name.substring(semicolonPos+1, nextSemicolonPos)); 963 semicolonPos = nextSemicolonPos; 964 } 965 else 966 { 967 options.add(name.substring(semicolonPos+1)); 968 break; 969 } 970 } 971 972 return Collections.unmodifiableSet(options); 973 } 974 else 975 { 976 return Collections.emptySet(); 977 } 978 } 979 980 981 982 /** 983 * Retrieves the matching rule instance used by this attribute. 984 * 985 * @return The matching rule instance used by this attribute. 986 */ 987 public MatchingRule getMatchingRule() 988 { 989 return matchingRule; 990 } 991 992 993 994 /** 995 * Retrieves the value for this attribute as a string. If this attribute has 996 * multiple values, then the first value will be returned. 997 * 998 * @return The value for this attribute, or {@code null} if this attribute 999 * does not have any values. 1000 */ 1001 public String getValue() 1002 { 1003 if (values.length == 0) 1004 { 1005 return null; 1006 } 1007 1008 return values[0].stringValue(); 1009 } 1010 1011 1012 1013 /** 1014 * Retrieves the value for this attribute as a byte array. If this attribute 1015 * has multiple values, then the first value will be returned. The returned 1016 * array must not be altered by the caller. 1017 * 1018 * @return The value for this attribute, or {@code null} if this attribute 1019 * does not have any values. 1020 */ 1021 public byte[] getValueByteArray() 1022 { 1023 if (values.length == 0) 1024 { 1025 return null; 1026 } 1027 1028 return values[0].getValue(); 1029 } 1030 1031 1032 1033 /** 1034 * Retrieves the value for this attribute as a Boolean. If this attribute has 1035 * multiple values, then the first value will be examined. Values of "true", 1036 * "t", "yes", "y", "on", and "1" will be interpreted as {@code TRUE}. Values 1037 * of "false", "f", "no", "n", "off", and "0" will be interpreted as 1038 * {@code FALSE}. 1039 * 1040 * @return The Boolean value for this attribute, or {@code null} if this 1041 * attribute does not have any values or the value cannot be parsed 1042 * as a Boolean. 1043 */ 1044 public Boolean getValueAsBoolean() 1045 { 1046 if (values.length == 0) 1047 { 1048 return null; 1049 } 1050 1051 final String lowerValue = StaticUtils.toLowerCase(values[0].stringValue()); 1052 if (lowerValue.equals("true") || lowerValue.equals("t") || 1053 lowerValue.equals("yes") || lowerValue.equals("y") || 1054 lowerValue.equals("on") || lowerValue.equals("1")) 1055 { 1056 return Boolean.TRUE; 1057 } 1058 else if (lowerValue.equals("false") || lowerValue.equals("f") || 1059 lowerValue.equals("no") || lowerValue.equals("n") || 1060 lowerValue.equals("off") || lowerValue.equals("0")) 1061 { 1062 return Boolean.FALSE; 1063 } 1064 else 1065 { 1066 return null; 1067 } 1068 } 1069 1070 1071 1072 /** 1073 * Retrieves the value for this attribute as a Date, formatted using the 1074 * generalized time syntax. If this attribute has multiple values, then the 1075 * first value will be examined. 1076 * 1077 * @return The Date value for this attribute, or {@code null} if this 1078 * attribute does not have any values or the value cannot be parsed 1079 * as a Date. 1080 */ 1081 public Date getValueAsDate() 1082 { 1083 if (values.length == 0) 1084 { 1085 return null; 1086 } 1087 1088 try 1089 { 1090 return StaticUtils.decodeGeneralizedTime(values[0].stringValue()); 1091 } 1092 catch (final Exception e) 1093 { 1094 Debug.debugException(e); 1095 return null; 1096 } 1097 } 1098 1099 1100 1101 /** 1102 * Retrieves the value for this attribute as a DN. If this attribute has 1103 * multiple values, then the first value will be examined. 1104 * 1105 * @return The DN value for this attribute, or {@code null} if this attribute 1106 * does not have any values or the value cannot be parsed as a DN. 1107 */ 1108 public DN getValueAsDN() 1109 { 1110 if (values.length == 0) 1111 { 1112 return null; 1113 } 1114 1115 try 1116 { 1117 return new DN(values[0].stringValue()); 1118 } 1119 catch (final Exception e) 1120 { 1121 Debug.debugException(e); 1122 return null; 1123 } 1124 } 1125 1126 1127 1128 /** 1129 * Retrieves the value for this attribute as an Integer. If this attribute 1130 * has multiple values, then the first value will be examined. 1131 * 1132 * @return The Integer value for this attribute, or {@code null} if this 1133 * attribute does not have any values or the value cannot be parsed 1134 * as an Integer. 1135 */ 1136 public Integer getValueAsInteger() 1137 { 1138 if (values.length == 0) 1139 { 1140 return null; 1141 } 1142 1143 try 1144 { 1145 return Integer.valueOf(values[0].stringValue()); 1146 } 1147 catch (final NumberFormatException nfe) 1148 { 1149 Debug.debugException(nfe); 1150 return null; 1151 } 1152 } 1153 1154 1155 1156 /** 1157 * Retrieves the value for this attribute as a Long. If this attribute has 1158 * multiple values, then the first value will be examined. 1159 * 1160 * @return The Long value for this attribute, or {@code null} if this 1161 * attribute does not have any values or the value cannot be parsed 1162 * as a Long. 1163 */ 1164 public Long getValueAsLong() 1165 { 1166 if (values.length == 0) 1167 { 1168 return null; 1169 } 1170 1171 try 1172 { 1173 return Long.valueOf(values[0].stringValue()); 1174 } 1175 catch (final NumberFormatException nfe) 1176 { 1177 Debug.debugException(nfe); 1178 return null; 1179 } 1180 } 1181 1182 1183 1184 /** 1185 * Retrieves the set of values for this attribute as strings. The returned 1186 * array must not be altered by the caller. 1187 * 1188 * @return The set of values for this attribute, or an empty array if it does 1189 * not have any values. 1190 */ 1191 public String[] getValues() 1192 { 1193 if (values.length == 0) 1194 { 1195 return StaticUtils.NO_STRINGS; 1196 } 1197 1198 final String[] stringValues = new String[values.length]; 1199 for (int i=0; i < values.length; i++) 1200 { 1201 stringValues[i] = values[i].stringValue(); 1202 } 1203 1204 return stringValues; 1205 } 1206 1207 1208 1209 /** 1210 * Retrieves the set of values for this attribute as byte arrays. The 1211 * returned array must not be altered by the caller. 1212 * 1213 * @return The set of values for this attribute, or an empty array if it does 1214 * not have any values. 1215 */ 1216 public byte[][] getValueByteArrays() 1217 { 1218 if (values.length == 0) 1219 { 1220 return NO_BYTE_VALUES; 1221 } 1222 1223 final byte[][] byteValues = new byte[values.length][]; 1224 for (int i=0; i < values.length; i++) 1225 { 1226 byteValues[i] = values[i].getValue(); 1227 } 1228 1229 return byteValues; 1230 } 1231 1232 1233 1234 /** 1235 * Retrieves the set of values for this attribute as an array of ASN.1 octet 1236 * strings. The returned array must not be altered by the caller. 1237 * 1238 * @return The set of values for this attribute as an array of ASN.1 octet 1239 * strings. 1240 */ 1241 public ASN1OctetString[] getRawValues() 1242 { 1243 return values; 1244 } 1245 1246 1247 1248 /** 1249 * Indicates whether this attribute contains at least one value. 1250 * 1251 * @return {@code true} if this attribute has at least one value, or 1252 * {@code false} if not. 1253 */ 1254 public boolean hasValue() 1255 { 1256 return (values.length > 0); 1257 } 1258 1259 1260 1261 /** 1262 * Indicates whether this attribute contains the specified value. 1263 * 1264 * @param value The value for which to make the determination. It must not 1265 * be {@code null}. 1266 * 1267 * @return {@code true} if this attribute has the specified value, or 1268 * {@code false} if not. 1269 */ 1270 public boolean hasValue(final String value) 1271 { 1272 Validator.ensureNotNull(value); 1273 1274 return hasValue(new ASN1OctetString(value), matchingRule); 1275 } 1276 1277 1278 1279 /** 1280 * Indicates whether this attribute contains the specified value. 1281 * 1282 * @param value The value for which to make the determination. It 1283 * must not be {@code null}. 1284 * @param matchingRule The matching rule to use when making the 1285 * determination. It must not be {@code null}. 1286 * 1287 * @return {@code true} if this attribute has the specified value, or 1288 * {@code false} if not. 1289 */ 1290 public boolean hasValue(final String value, final MatchingRule matchingRule) 1291 { 1292 Validator.ensureNotNull(value); 1293 1294 return hasValue(new ASN1OctetString(value), matchingRule); 1295 } 1296 1297 1298 1299 /** 1300 * Indicates whether this attribute contains the specified value. 1301 * 1302 * @param value The value for which to make the determination. It must not 1303 * be {@code null}. 1304 * 1305 * @return {@code true} if this attribute has the specified value, or 1306 * {@code false} if not. 1307 */ 1308 public boolean hasValue(final byte[] value) 1309 { 1310 Validator.ensureNotNull(value); 1311 1312 return hasValue(new ASN1OctetString(value), matchingRule); 1313 } 1314 1315 1316 1317 /** 1318 * Indicates whether this attribute contains the specified value. 1319 * 1320 * @param value The value for which to make the determination. It 1321 * must not be {@code null}. 1322 * @param matchingRule The matching rule to use when making the 1323 * determination. It must not be {@code null}. 1324 * 1325 * @return {@code true} if this attribute has the specified value, or 1326 * {@code false} if not. 1327 */ 1328 public boolean hasValue(final byte[] value, final MatchingRule matchingRule) 1329 { 1330 Validator.ensureNotNull(value); 1331 1332 return hasValue(new ASN1OctetString(value), matchingRule); 1333 } 1334 1335 1336 1337 /** 1338 * Indicates whether this attribute contains the specified value. 1339 * 1340 * @param value The value for which to make the determination. 1341 * 1342 * @return {@code true} if this attribute has the specified value, or 1343 * {@code false} if not. 1344 */ 1345 boolean hasValue(final ASN1OctetString value) 1346 { 1347 return hasValue(value, matchingRule); 1348 } 1349 1350 1351 1352 /** 1353 * Indicates whether this attribute contains the specified value. 1354 * 1355 * @param value The value for which to make the determination. It 1356 * must not be {@code null}. 1357 * @param matchingRule The matching rule to use when making the 1358 * determination. It must not be {@code null}. 1359 * 1360 * @return {@code true} if this attribute has the specified value, or 1361 * {@code false} if not. 1362 */ 1363 boolean hasValue(final ASN1OctetString value, final MatchingRule matchingRule) 1364 { 1365 try 1366 { 1367 return matchingRule.matchesAnyValue(value, values); 1368 } 1369 catch (final LDAPException le) 1370 { 1371 Debug.debugException(le); 1372 1373 // This probably means that the provided value cannot be normalized. In 1374 // that case, we'll fall back to a byte-for-byte comparison of the values. 1375 for (final ASN1OctetString existingValue : values) 1376 { 1377 if (value.equalsIgnoreType(existingValue)) 1378 { 1379 return true; 1380 } 1381 } 1382 1383 return false; 1384 } 1385 } 1386 1387 1388 1389 /** 1390 * Retrieves the number of values for this attribute. 1391 * 1392 * @return The number of values for this attribute. 1393 */ 1394 public int size() 1395 { 1396 return values.length; 1397 } 1398 1399 1400 1401 /** 1402 * Writes an ASN.1-encoded representation of this attribute to the provided 1403 * ASN.1 buffer. 1404 * 1405 * @param buffer The ASN.1 buffer to which the encoded representation should 1406 * be written. 1407 */ 1408 public void writeTo(final ASN1Buffer buffer) 1409 { 1410 final ASN1BufferSequence attrSequence = buffer.beginSequence(); 1411 buffer.addOctetString(name); 1412 1413 final ASN1BufferSet valueSet = buffer.beginSet(); 1414 for (final ASN1OctetString value : values) 1415 { 1416 buffer.addElement(value); 1417 } 1418 valueSet.end(); 1419 attrSequence.end(); 1420 } 1421 1422 1423 1424 /** 1425 * Encodes this attribute into a form suitable for use in the LDAP protocol. 1426 * It will be encoded as a sequence containing the attribute name (as an octet 1427 * string) and a set of values. 1428 * 1429 * @return An ASN.1 sequence containing the encoded attribute. 1430 */ 1431 public ASN1Sequence encode() 1432 { 1433 final ASN1Element[] elements = 1434 { 1435 new ASN1OctetString(name), 1436 new ASN1Set(values) 1437 }; 1438 1439 return new ASN1Sequence(elements); 1440 } 1441 1442 1443 1444 /** 1445 * Reads and decodes an attribute from the provided ASN.1 stream reader. 1446 * 1447 * @param reader The ASN.1 stream reader from which to read the attribute. 1448 * 1449 * @return The decoded attribute. 1450 * 1451 * @throws LDAPException If a problem occurs while trying to read or decode 1452 * the attribute. 1453 */ 1454 public static Attribute readFrom(final ASN1StreamReader reader) 1455 throws LDAPException 1456 { 1457 return readFrom(reader, null); 1458 } 1459 1460 1461 1462 /** 1463 * Reads and decodes an attribute from the provided ASN.1 stream reader. 1464 * 1465 * @param reader The ASN.1 stream reader from which to read the attribute. 1466 * @param schema The schema to use to select the appropriate matching rule 1467 * for this attribute. It may be {@code null} if the default 1468 * matching rule should be selected. 1469 * 1470 * @return The decoded attribute. 1471 * 1472 * @throws LDAPException If a problem occurs while trying to read or decode 1473 * the attribute. 1474 */ 1475 public static Attribute readFrom(final ASN1StreamReader reader, 1476 final Schema schema) 1477 throws LDAPException 1478 { 1479 try 1480 { 1481 Validator.ensureNotNull(reader.beginSequence()); 1482 final String attrName = reader.readString(); 1483 Validator.ensureNotNull(attrName); 1484 1485 final MatchingRule matchingRule = 1486 MatchingRule.selectEqualityMatchingRule(attrName, schema); 1487 1488 final ArrayList<ASN1OctetString> valueList = new ArrayList<>(10); 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 Debug.debugException(e); 1503 throw new LDAPException(ResultCode.DECODING_ERROR, 1504 ERR_ATTR_CANNOT_DECODE.get(StaticUtils.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 Validator.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 Debug.debugException(ae); 1544 throw new LDAPException(ResultCode.DECODING_ERROR, 1545 ERR_ATTR_DECODE_VALUE_SET.get(StaticUtils.getExceptionMessage(ae)), 1546 ae); 1547 } 1548 1549 final ASN1OctetString[] values = 1550 new ASN1OctetString[valueSet.elements().length]; 1551 for (int i=0; i < values.length; i++) 1552 { 1553 values[i] = ASN1OctetString.decodeAsOctetString(valueSet.elements()[i]); 1554 } 1555 1556 return new Attribute(name, CaseIgnoreStringMatchingRule.getInstance(), 1557 values); 1558 } 1559 1560 1561 1562 /** 1563 * Indicates whether any of the values of this attribute need to be 1564 * base64-encoded when represented as LDIF. 1565 * 1566 * @return {@code true} if any of the values of this attribute need to be 1567 * base64-encoded when represented as LDIF, or {@code false} if not. 1568 */ 1569 public boolean needsBase64Encoding() 1570 { 1571 for (final ASN1OctetString v : values) 1572 { 1573 if (needsBase64Encoding(v.getValue())) 1574 { 1575 return true; 1576 } 1577 } 1578 1579 return false; 1580 } 1581 1582 1583 1584 /** 1585 * Indicates whether the provided value needs to be base64-encoded when 1586 * represented as LDIF. 1587 * 1588 * @param v The value for which to make the determination. It must not be 1589 * {@code null}. 1590 * 1591 * @return {@code true} if the provided value needs to be base64-encoded when 1592 * represented as LDIF, or {@code false} if not. 1593 */ 1594 public static boolean needsBase64Encoding(final String v) 1595 { 1596 return needsBase64Encoding(StaticUtils.getBytes(v)); 1597 } 1598 1599 1600 1601 /** 1602 * Indicates whether the provided value needs to be base64-encoded when 1603 * represented as LDIF. 1604 * 1605 * @param v The value for which to make the determination. It must not be 1606 * {@code null}. 1607 * 1608 * @return {@code true} if the provided value needs to be base64-encoded when 1609 * represented as LDIF, or {@code false} if not. 1610 */ 1611 public static boolean needsBase64Encoding(final byte[] v) 1612 { 1613 if (v.length == 0) 1614 { 1615 return false; 1616 } 1617 1618 switch (v[0] & 0xFF) 1619 { 1620 case 0x20: // Space 1621 case 0x3A: // Colon 1622 case 0x3C: // Less-than 1623 return true; 1624 } 1625 1626 if ((v[v.length-1] & 0xFF) == 0x20) 1627 { 1628 return true; 1629 } 1630 1631 for (final byte b : v) 1632 { 1633 switch (b & 0xFF) 1634 { 1635 case 0x00: // NULL 1636 case 0x0A: // LF 1637 case 0x0D: // CR 1638 return true; 1639 1640 default: 1641 if ((b & 0x80) != 0x00) 1642 { 1643 return true; 1644 } 1645 break; 1646 } 1647 } 1648 1649 return false; 1650 } 1651 1652 1653 1654 /** 1655 * Generates a hash code for this LDAP attribute. It will be the sum of the 1656 * hash codes for the lowercase attribute name and the normalized values. 1657 * 1658 * @return The generated hash code for this LDAP attribute. 1659 */ 1660 @Override() 1661 public int hashCode() 1662 { 1663 if (hashCode == -1) 1664 { 1665 int c = StaticUtils.toLowerCase(name).hashCode(); 1666 1667 for (final ASN1OctetString value : values) 1668 { 1669 try 1670 { 1671 c += matchingRule.normalize(value).hashCode(); 1672 } 1673 catch (final LDAPException le) 1674 { 1675 Debug.debugException(le); 1676 c += value.hashCode(); 1677 } 1678 } 1679 1680 hashCode = c; 1681 } 1682 1683 return hashCode; 1684 } 1685 1686 1687 1688 /** 1689 * Indicates whether the provided object is equal to this LDAP attribute. The 1690 * object will be considered equal to this LDAP attribute only if it is an 1691 * LDAP attribute with the same name and set of values. 1692 * 1693 * @param o The object for which to make the determination. 1694 * 1695 * @return {@code true} if the provided object may be considered equal to 1696 * this LDAP attribute, or {@code false} if not. 1697 */ 1698 @Override() 1699 public boolean equals(final Object o) 1700 { 1701 if (o == null) 1702 { 1703 return false; 1704 } 1705 1706 if (o == this) 1707 { 1708 return true; 1709 } 1710 1711 if (! (o instanceof Attribute)) 1712 { 1713 return false; 1714 } 1715 1716 final Attribute a = (Attribute) o; 1717 if (! name.equalsIgnoreCase(a.name)) 1718 { 1719 return false; 1720 } 1721 1722 if (values.length != a.values.length) 1723 { 1724 return false; 1725 } 1726 1727 // For a small set of values, we can just iterate through the values of one 1728 // and see if they are all present in the other. However, that can be very 1729 // expensive for a large set of values, so we'll try to go with a more 1730 // efficient approach. 1731 if (values.length > 10) 1732 { 1733 // First, create a hash set containing the un-normalized values of the 1734 // first attribute. 1735 final HashSet<ASN1OctetString> unNormalizedValues = 1736 StaticUtils.hashSetOf(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<>(StaticUtils.computeMapCapacity(values.length)); 1751 } 1752 1753 try 1754 { 1755 normalizedMissingValues.add(matchingRule.normalize(value)); 1756 } 1757 catch (final Exception e) 1758 { 1759 Debug.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 Debug.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}