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