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