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