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