001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2012-2015 ForgeRock AS 026 */ 027package org.opends.server.types; 028 029import java.util.AbstractSet; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.LinkedHashSet; 034import java.util.List; 035import java.util.NoSuchElementException; 036import java.util.Set; 037import java.util.SortedSet; 038import java.util.TreeSet; 039 040import org.forgerock.i18n.slf4j.LocalizedLogger; 041import org.forgerock.opendj.ldap.Assertion; 042import org.forgerock.opendj.ldap.ByteString; 043import org.forgerock.opendj.ldap.ConditionResult; 044import org.forgerock.opendj.ldap.DecodeException; 045import org.forgerock.opendj.ldap.schema.MatchingRule; 046import org.forgerock.util.Reject; 047import org.forgerock.util.Utils; 048import org.opends.server.core.DirectoryServer; 049import org.opends.server.util.CollectionUtils; 050 051import static org.opends.server.util.StaticUtils.*; 052 053/** 054 * This class provides an interface for creating new non-virtual 055 * {@link Attribute}s, or "real" attributes. 056 * <p> 057 * An attribute can be created incrementally using either 058 * {@link #AttributeBuilder(AttributeType)} or 059 * {@link #AttributeBuilder(AttributeType, String)}. The caller is 060 * then free to add new options using {@link #setOption(String)} and 061 * new values using {@link #add(ByteString)} or 062 * {@link #addAll(Collection)}. Once all the options and values have 063 * been added, the attribute can be retrieved using the 064 * {@link #toAttribute()} method. 065 * <p> 066 * A real attribute can also be created based on the values taken from 067 * another attribute using the {@link #AttributeBuilder(Attribute)} 068 * constructor. The caller is then free to modify the values within 069 * the attribute before retrieving the updated attribute using the 070 * {@link #toAttribute()} method. 071 * <p> 072 * The {@link org.opends.server.types.Attributes} class contains 073 * convenience factory methods, 074 * e.g. {@link org.opends.server.types.Attributes#empty(String)} for 075 * creating empty attributes, and 076 * {@link org.opends.server.types.Attributes#create(String, String)} 077 * for creating single-valued attributes. 078 * <p> 079 * <code>AttributeBuilder</code>s can be re-used. Once an 080 * <code>AttributeBuilder</code> has been converted to an 081 * {@link Attribute} using {@link #toAttribute()}, its state is reset 082 * so that its attribute type, user-provided name, options, and values 083 * are all undefined: 084 * 085 * <pre> 086 * AttributeBuilder builder = new AttributeBuilder(); 087 * for (int i = 0; i < 10; i++) 088 * { 089 * builder.setAttributeType("myAttribute" + i); 090 * builder.setOption("an-option"); 091 * builder.add("a value"); 092 * Attribute attribute = builder.toAttribute(); 093 * // Do something with attribute... 094 * } 095 * </pre> 096 * <p> 097 * <b>Implementation Note:</b> this class is optimized for the common 098 * case where there is only a single value. By doing so, we avoid 099 * using unnecessary storage space and also performing any unnecessary 100 * normalization. In addition, this class is optimized for the common 101 * cases where there are zero or one attribute type options. 102 */ 103@org.opends.server.types.PublicAPI( 104 stability = org.opends.server.types.StabilityLevel.UNCOMMITTED, 105 mayInstantiate = true, 106 mayExtend = false, 107 mayInvoke = true) 108public final class AttributeBuilder implements Iterable<ByteString> 109{ 110 111 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 112 113 /** 114 * A real attribute - options handled by sub-classes. 115 */ 116 private static abstract class RealAttribute 117 extends AbstractAttribute 118 { 119 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 120 121 /** The attribute type for this attribute. */ 122 private final AttributeType attributeType; 123 124 /** The name of this attribute as provided by the end user. */ 125 private final String name; 126 127 /** 128 * The unmodifiable set of attribute values, which are lazily normalized. 129 * <p> 130 * When required, the attribute values are normalized according to the equality 131 * matching rule. 132 */ 133 private final Set<AttributeValue> values; 134 135 /** 136 * Creates a new real attribute. 137 * 138 * @param attributeType 139 * The attribute type. 140 * @param name 141 * The user-provided attribute name. 142 * @param values 143 * The attribute values. 144 */ 145 private RealAttribute(AttributeType attributeType, String name, Set<AttributeValue> values) 146 { 147 this.attributeType = attributeType; 148 this.name = name; 149 this.values = values; 150 } 151 152 153 @Override 154 public final ConditionResult approximatelyEqualTo(ByteString assertionValue) 155 { 156 MatchingRule matchingRule = attributeType.getApproximateMatchingRule(); 157 if (matchingRule == null) 158 { 159 return ConditionResult.UNDEFINED; 160 } 161 162 Assertion assertion = null; 163 try 164 { 165 assertion = matchingRule.getAssertion(assertionValue); 166 } 167 catch (Exception e) 168 { 169 logger.traceException(e); 170 return ConditionResult.UNDEFINED; 171 } 172 173 ConditionResult result = ConditionResult.FALSE; 174 for (AttributeValue v : values) 175 { 176 try 177 { 178 result = assertion.matches(matchingRule.normalizeAttributeValue(v.getValue())); 179 } 180 catch (Exception e) 181 { 182 logger.traceException(e); 183 // We couldn't normalize one of the attribute values. If we 184 // can't find a definite match, then we should return 185 // "undefined". 186 result = ConditionResult.UNDEFINED; 187 } 188 } 189 190 return result; 191 } 192 193 194 195 @Override 196 public final boolean contains(ByteString value) 197 { 198 return values.contains(createAttributeValue(attributeType, value)); 199 } 200 201 @Override 202 public ConditionResult matchesEqualityAssertion(ByteString assertionValue) 203 { 204 try 205 { 206 MatchingRule eqRule = getAttributeType().getEqualityMatchingRule(); 207 final Assertion assertion = eqRule.getAssertion(assertionValue); 208 for (AttributeValue value : values) 209 { 210 if (assertion.matches(value.getNormalizedValue()).toBoolean()) 211 { 212 return ConditionResult.TRUE; 213 } 214 } 215 return ConditionResult.FALSE; 216 } 217 catch (DecodeException e) 218 { 219 return ConditionResult.UNDEFINED; 220 } 221 } 222 223 @Override 224 public final AttributeType getAttributeType() 225 { 226 return attributeType; 227 } 228 229 230 231 @Override 232 public final String getName() 233 { 234 return name; 235 } 236 237 238 239 @Override 240 public final ConditionResult greaterThanOrEqualTo(ByteString assertionValue) 241 { 242 MatchingRule matchingRule = attributeType.getOrderingMatchingRule(); 243 if (matchingRule == null) 244 { 245 return ConditionResult.UNDEFINED; 246 } 247 248 Assertion assertion; 249 try 250 { 251 assertion = matchingRule.getGreaterOrEqualAssertion(assertionValue); 252 } 253 catch (DecodeException e) 254 { 255 logger.traceException(e); 256 return ConditionResult.UNDEFINED; 257 } 258 259 ConditionResult result = ConditionResult.FALSE; 260 for (AttributeValue v : values) 261 { 262 try 263 { 264 if (assertion.matches(matchingRule.normalizeAttributeValue(v.getValue())).toBoolean()) 265 { 266 return ConditionResult.TRUE; 267 } 268 } 269 catch (Exception e) 270 { 271 logger.traceException(e); 272 // We couldn't normalize one of the attribute values. If we 273 // can't find a definite match, then we should return "undefined". 274 result = ConditionResult.UNDEFINED; 275 } 276 } 277 278 return result; 279 } 280 281 282 283 @Override 284 public final boolean isVirtual() 285 { 286 return false; 287 } 288 289 290 @Override 291 public final Iterator<ByteString> iterator() 292 { 293 return getUnmodifiableIterator(values); 294 } 295 296 @Override 297 public final ConditionResult lessThanOrEqualTo(ByteString assertionValue) 298 { 299 MatchingRule matchingRule = attributeType.getOrderingMatchingRule(); 300 if (matchingRule == null) 301 { 302 return ConditionResult.UNDEFINED; 303 } 304 305 Assertion assertion; 306 try 307 { 308 assertion = matchingRule.getLessOrEqualAssertion(assertionValue); 309 } 310 catch (DecodeException e) 311 { 312 logger.traceException(e); 313 return ConditionResult.UNDEFINED; 314 } 315 316 ConditionResult result = ConditionResult.FALSE; 317 for (AttributeValue v : values) 318 { 319 try 320 { 321 if (assertion.matches(matchingRule.normalizeAttributeValue(v.getValue())).toBoolean()) 322 { 323 return ConditionResult.TRUE; 324 } 325 } 326 catch (Exception e) 327 { 328 logger.traceException(e); 329 330 // We couldn't normalize one of the attribute values. If we 331 // can't find a definite match, then we should return "undefined". 332 result = ConditionResult.UNDEFINED; 333 } 334 } 335 336 return result; 337 } 338 339 340 341 @Override 342 public final ConditionResult matchesSubstring(ByteString subInitial, List<ByteString> subAny, ByteString subFinal) 343 { 344 MatchingRule matchingRule = attributeType.getSubstringMatchingRule(); 345 if (matchingRule == null) 346 { 347 return ConditionResult.UNDEFINED; 348 } 349 350 351 Assertion assertion; 352 try 353 { 354 assertion = matchingRule.getSubstringAssertion(subInitial, subAny, subFinal); 355 } 356 catch (DecodeException e) 357 { 358 logger.traceException(e); 359 return ConditionResult.UNDEFINED; 360 } 361 362 ConditionResult result = ConditionResult.FALSE; 363 for (AttributeValue value : values) 364 { 365 try 366 { 367 if (assertion.matches(matchingRule.normalizeAttributeValue(value.getValue())).toBoolean()) 368 { 369 return ConditionResult.TRUE; 370 } 371 } 372 catch (Exception e) 373 { 374 logger.traceException(e); 375 376 // The value couldn't be normalized. If we can't find a 377 // definite match, then we should return "undefined". 378 result = ConditionResult.UNDEFINED; 379 } 380 } 381 382 return result; 383 } 384 385 386 387 @Override 388 public final int size() 389 { 390 return values.size(); 391 } 392 393 @Override 394 public int hashCode() 395 { 396 int hashCode = getAttributeType().hashCode(); 397 for (AttributeValue value : values) 398 { 399 hashCode += value.hashCode(); 400 } 401 return hashCode; 402 } 403 404 @Override 405 public final void toString(StringBuilder buffer) 406 { 407 buffer.append("Attribute("); 408 buffer.append(getNameWithOptions()); 409 buffer.append(", {"); 410 Utils.joinAsString(buffer, ", ", values); 411 buffer.append("})"); 412 } 413 } 414 415 416 417 /** 418 * A real attribute with a many options. 419 */ 420 private static final class RealAttributeManyOptions 421 extends RealAttribute 422 { 423 424 /** The normalized options. */ 425 private final SortedSet<String> normalizedOptions; 426 427 /** The options. */ 428 private final Set<String> options; 429 430 431 432 /** 433 * Creates a new real attribute that has multiple options. 434 * 435 * @param attributeType 436 * The attribute type. 437 * @param name 438 * The user-provided attribute name. 439 * @param values 440 * The attribute values. 441 * @param options 442 * The attribute options. 443 * @param normalizedOptions 444 * The normalized attribute options. 445 */ 446 private RealAttributeManyOptions( 447 AttributeType attributeType, String name, Set<AttributeValue> values, Set<String> options, 448 SortedSet<String> normalizedOptions) 449 { 450 super(attributeType, name, values); 451 this.options = options; 452 this.normalizedOptions = normalizedOptions; 453 } 454 455 @Override 456 public Set<String> getOptions() 457 { 458 return options; 459 } 460 461 @Override 462 public boolean hasOption(String option) 463 { 464 String s = toLowerCase(option); 465 return normalizedOptions.contains(s); 466 } 467 468 @Override 469 public boolean hasOptions() 470 { 471 return true; 472 } 473 } 474 475 476 477 /** 478 * A real attribute with no options. 479 */ 480 private static final class RealAttributeNoOptions extends RealAttribute 481 { 482 /** 483 * Creates a new real attribute that has no options. 484 * 485 * @param attributeType 486 * The attribute type. 487 * @param name 488 * The user-provided attribute name. 489 * @param values 490 * The attribute values. 491 */ 492 private RealAttributeNoOptions(AttributeType attributeType, String name, Set<AttributeValue> values) 493 { 494 super(attributeType, name, values); 495 } 496 497 @Override 498 public String getNameWithOptions() 499 { 500 return getName(); 501 } 502 503 @Override 504 public Set<String> getOptions() 505 { 506 return Collections.emptySet(); 507 } 508 509 @Override 510 public boolean hasAllOptions(Collection<String> options) 511 { 512 return options == null || options.isEmpty(); 513 } 514 515 @Override 516 public boolean hasOption(String option) 517 { 518 return false; 519 } 520 521 @Override 522 public boolean hasOptions() 523 { 524 return false; 525 } 526 527 @Override 528 public boolean optionsEqual(Set<String> options) 529 { 530 return options == null || options.isEmpty(); 531 } 532 } 533 534 535 536 /** 537 * A real attribute with a single option. 538 */ 539 private static final class RealAttributeSingleOption 540 extends RealAttribute 541 { 542 /** The normalized single option. */ 543 private final String normalizedOption; 544 545 /** A singleton set containing the single option. */ 546 private final Set<String> option; 547 548 549 550 /** 551 * Creates a new real attribute that has a single option. 552 * 553 * @param attributeType 554 * The attribute type. 555 * @param name 556 * The user-provided attribute name. 557 * @param values 558 * The attribute values. 559 * @param option 560 * The attribute option. 561 */ 562 private RealAttributeSingleOption( 563 AttributeType attributeType, 564 String name, 565 Set<AttributeValue> values, 566 String option) 567 { 568 super(attributeType, name, values); 569 this.option = Collections.singleton(option); 570 this.normalizedOption = toLowerCase(option); 571 } 572 573 @Override 574 public Set<String> getOptions() 575 { 576 return option; 577 } 578 579 @Override 580 public boolean hasOption(String option) 581 { 582 String s = toLowerCase(option); 583 return normalizedOption.equals(s); 584 } 585 586 @Override 587 public boolean hasOptions() 588 { 589 return true; 590 } 591 } 592 593 594 595 /** 596 * A small set of values. This set implementation is optimized to 597 * use as little memory as possible in the case where there zero or 598 * one elements. In addition, any normalization of elements is 599 * delayed until the second element is added (normalization may be 600 * triggered by invoking {@link Object#hashCode()} or 601 * {@link Object#equals(Object)}. 602 * 603 * @param <T> 604 * The type of elements to be contained in this small set. 605 */ 606 private static final class SmallSet<T> extends AbstractSet<T> 607 { 608 609 /** The set of elements if there are more than one. */ 610 private LinkedHashSet<T> elements; 611 612 /** The first element. */ 613 private T firstElement; 614 615 /** 616 * Creates a new small set which is initially empty. 617 */ 618 public SmallSet() 619 { 620 // No implementation required. 621 } 622 623 /** 624 * Creates a new small set with an initial capacity. 625 * 626 * @param initialCapacity 627 * The capacity of the set 628 */ 629 public SmallSet(int initialCapacity) 630 { 631 Reject.ifFalse(initialCapacity >= 0); 632 633 if (initialCapacity > 1) 634 { 635 elements = new LinkedHashSet<>(initialCapacity); 636 } 637 } 638 639 @Override 640 public boolean add(T e) 641 { 642 // Special handling for the first value. This avoids potentially 643 // expensive normalization. 644 if (firstElement == null && elements == null) 645 { 646 firstElement = e; 647 return true; 648 } 649 650 // Create the value set if necessary. 651 if (elements == null) 652 { 653 if (firstElement.equals(e)) 654 { 655 return false; 656 } 657 658 elements = new LinkedHashSet<>(2); 659 660 // Move the first value into the set. 661 elements.add(firstElement); 662 firstElement = null; 663 } 664 665 return elements.add(e); 666 } 667 668 @Override 669 public boolean addAll(Collection<? extends T> c) 670 { 671 if (elements != null) 672 { 673 return elements.addAll(c); 674 } 675 676 if (firstElement != null) 677 { 678 elements = new LinkedHashSet<>(1 + c.size()); 679 elements.add(firstElement); 680 firstElement = null; 681 return elements.addAll(c); 682 } 683 684 // Initially empty. 685 switch (c.size()) 686 { 687 case 0: 688 // Do nothing. 689 return false; 690 case 1: 691 firstElement = c.iterator().next(); 692 return true; 693 default: 694 elements = new LinkedHashSet<>(c); 695 return true; 696 } 697 } 698 699 @Override 700 public void clear() 701 { 702 firstElement = null; 703 elements = null; 704 } 705 706 @Override 707 public Iterator<T> iterator() 708 { 709 if (elements != null) 710 { 711 return elements.iterator(); 712 } 713 else if (firstElement != null) 714 { 715 return new Iterator<T>() 716 { 717 private boolean hasNext = true; 718 719 @Override 720 public boolean hasNext() 721 { 722 return hasNext; 723 } 724 725 @Override 726 public T next() 727 { 728 if (!hasNext) 729 { 730 throw new NoSuchElementException(); 731 } 732 733 hasNext = false; 734 return firstElement; 735 } 736 737 @Override 738 public void remove() 739 { 740 throw new UnsupportedOperationException(); 741 } 742 743 }; 744 } 745 else 746 { 747 return Collections.<T> emptySet().iterator(); 748 } 749 } 750 751 @Override 752 public boolean remove(Object o) 753 { 754 if (elements != null) 755 { 756 // Note: if there is one or zero values left we could stop 757 // using the set. However, lets assume that if the set 758 // was multi-valued before then it may become multi-valued 759 // again. 760 return elements.remove(o); 761 } 762 763 if (firstElement != null && firstElement.equals(o)) 764 { 765 firstElement = null; 766 return true; 767 } 768 769 return false; 770 } 771 772 @Override 773 public boolean contains(Object o) 774 { 775 if (elements != null) 776 { 777 return elements.contains(o); 778 } 779 780 return firstElement != null && firstElement.equals(o); 781 } 782 783 /** 784 * Sets the initial capacity of this small set. If this small set 785 * already contains elements or if its capacity has already been 786 * defined then an {@link IllegalStateException} is thrown. 787 * 788 * @param initialCapacity 789 * The initial capacity of this small set. 790 * @throws IllegalStateException 791 * If this small set already contains elements or if its 792 * capacity has already been defined. 793 */ 794 public void setInitialCapacity(int initialCapacity) 795 throws IllegalStateException 796 { 797 Reject.ifFalse(initialCapacity >= 0); 798 799 if (elements != null) 800 { 801 throw new IllegalStateException(); 802 } 803 804 if (initialCapacity > 1) 805 { 806 elements = new LinkedHashSet<>(initialCapacity); 807 } 808 } 809 810 @Override 811 public int size() 812 { 813 if (elements != null) 814 { 815 return elements.size(); 816 } 817 else if (firstElement != null) 818 { 819 return 1; 820 } 821 else 822 { 823 return 0; 824 } 825 } 826 } 827 828 /** 829 * An attribute value which is lazily normalized. 830 * <p> 831 * Stores the value in user-provided form and a reference to the associated 832 * attribute type. The normalized form of the value will be initialized upon 833 * first request. The normalized form of the value should only be used in 834 * cases where equality matching between two values can be performed with 835 * byte-for-byte comparisons of the normalized values. 836 */ 837 private static final class AttributeValue 838 { 839 private final AttributeType attributeType; 840 841 /** User-provided value. */ 842 private final ByteString value; 843 844 /** Normalized value, which is {@code null} until computation is required. */ 845 private ByteString normalizedValue; 846 847 /** 848 * Construct a new attribute value. 849 * 850 * @param attributeType 851 * The attribute type. 852 * @param value 853 * The value of the attribute. 854 */ 855 private AttributeValue(AttributeType attributeType, ByteString value) 856 { 857 this.attributeType = attributeType; 858 this.value = value; 859 } 860 861 /** 862 * Retrieves the normalized form of this attribute value. 863 * 864 * @return The normalized form of this attribute value. 865 */ 866 public ByteString getNormalizedValue() 867 { 868 if (normalizedValue == null) 869 { 870 normalizedValue = normalize(attributeType, value); 871 } 872 return normalizedValue; 873 } 874 875 boolean isNormalized() 876 { 877 return normalizedValue != null; 878 } 879 880 /** 881 * Retrieves the user-defined form of this attribute value. 882 * 883 * @return The user-defined form of this attribute value. 884 */ 885 public ByteString getValue() 886 { 887 return value; 888 } 889 890 /** 891 * Indicates whether the provided object is an attribute value that is equal 892 * to this attribute value. It will be considered equal if the normalized 893 * representations of both attribute values are equal. 894 * 895 * @param o 896 * The object for which to make the determination. 897 * @return <CODE>true</CODE> if the provided object is an attribute value 898 * that is equal to this attribute value, or <CODE>false</CODE> if 899 * not. 900 */ 901 @Override 902 public boolean equals(Object o) 903 { 904 if (this == o) 905 { 906 return true; 907 } 908 else if (o instanceof AttributeValue) 909 { 910 AttributeValue attrValue = (AttributeValue) o; 911 try 912 { 913 return getNormalizedValue().equals(attrValue.getNormalizedValue()); 914 } 915 catch (Exception e) 916 { 917 logger.traceException(e); 918 return value.equals(attrValue.getValue()); 919 } 920 } 921 return false; 922 } 923 924 /** 925 * Retrieves the hash code for this attribute value. It will be calculated 926 * using the normalized representation of the value. 927 * 928 * @return The hash code for this attribute value. 929 */ 930 @Override 931 public int hashCode() 932 { 933 try 934 { 935 return getNormalizedValue().hashCode(); 936 } 937 catch (Exception e) 938 { 939 logger.traceException(e); 940 return value.hashCode(); 941 } 942 } 943 944 @Override 945 public String toString() 946 { 947 return value != null ? value.toString() : "null"; 948 } 949 } 950 951 /** 952 * Creates an attribute that has no options. 953 * <p> 954 * This method is only intended for use by the {@link Attributes} 955 * class. 956 * 957 * @param attributeType 958 * The attribute type. 959 * @param name 960 * The user-provided attribute name. 961 * @param values 962 * The attribute values. 963 * @return The new attribute. 964 */ 965 static Attribute create(AttributeType attributeType, String name, 966 Set<ByteString> values) 967 { 968 final AttributeBuilder builder = new AttributeBuilder(attributeType, name); 969 builder.addAll(values); 970 return builder.toAttribute(); 971 } 972 973 974 975 /** 976 * Gets the named attribute type, creating a default attribute if 977 * necessary. 978 * 979 * @param attributeName 980 * The name of the attribute type. 981 * @return The attribute type associated with the provided attribute 982 * name. 983 */ 984 private static AttributeType getAttributeType(String attributeName) 985 { 986 return DirectoryServer.getAttributeTypeOrDefault(toLowerCase(attributeName), attributeName); 987 } 988 989 /** The attribute type for this attribute. */ 990 private AttributeType attributeType; 991 /** The name of this attribute as provided by the end user. */ 992 private String name; 993 /** The normalized set of options if there are more than one. */ 994 private SortedSet<String> normalizedOptions; 995 /** The set of options. */ 996 private final SmallSet<String> options = new SmallSet<>(); 997 /** The set of attribute values, which are lazily normalized. */ 998 private Set<AttributeValue> values = new SmallSet<>(); 999 1000 /** 1001 * Creates a new attribute builder with an undefined attribute type 1002 * and user-provided name. The attribute type, and optionally the 1003 * user-provided name, must be defined using 1004 * {@link #setAttributeType(AttributeType)} before the attribute 1005 * builder can be converted to an {@link Attribute}. Failure to do 1006 * so will yield an {@link IllegalStateException}. 1007 */ 1008 public AttributeBuilder() 1009 { 1010 // No implementation required. 1011 } 1012 1013 /** 1014 * Creates a new attribute builder from an existing attribute. 1015 * <p> 1016 * Modifications to the attribute builder will not impact the 1017 * provided attribute. 1018 * 1019 * @param attribute 1020 * The attribute to be copied. 1021 */ 1022 public AttributeBuilder(Attribute attribute) 1023 { 1024 this(attribute, false); 1025 } 1026 1027 1028 1029 /** 1030 * Creates a new attribute builder from an existing attribute, 1031 * optionally omitting the values contained in the provided 1032 * attribute. 1033 * <p> 1034 * Modifications to the attribute builder will not impact the 1035 * provided attribute. 1036 * 1037 * @param attribute 1038 * The attribute to be copied. 1039 * @param omitValues 1040 * <CODE>true</CODE> if the values should be omitted. 1041 */ 1042 public AttributeBuilder(Attribute attribute, boolean omitValues) 1043 { 1044 this(attribute.getAttributeType(), attribute.getName()); 1045 1046 for (String option : attribute.getOptions()) 1047 { 1048 setOption(option); 1049 } 1050 1051 if (!omitValues) 1052 { 1053 addAll(attribute); 1054 } 1055 } 1056 1057 1058 1059 /** 1060 * Creates a new attribute builder with the specified type and no 1061 * options and no values. 1062 * 1063 * @param attributeType 1064 * The attribute type for this attribute builder. 1065 */ 1066 public AttributeBuilder(AttributeType attributeType) 1067 { 1068 this(attributeType, attributeType.getNameOrOID()); 1069 } 1070 1071 1072 1073 /** 1074 * Creates a new attribute builder with the specified type and 1075 * user-provided name and no options and no values. 1076 * 1077 * @param attributeType 1078 * The attribute type for this attribute builder. 1079 * @param name 1080 * The user-provided name for this attribute builder. 1081 */ 1082 public AttributeBuilder(AttributeType attributeType, String name) 1083 { 1084 Reject.ifNull(attributeType, name); 1085 1086 this.attributeType = attributeType; 1087 this.name = name; 1088 } 1089 1090 1091 1092 /** 1093 * Creates a new attribute builder with the specified attribute name 1094 * and no options and no values. 1095 * <p> 1096 * If the attribute name cannot be found in the schema, a new 1097 * attribute type is created using the default attribute syntax. 1098 * 1099 * @param attributeName 1100 * The attribute name for this attribute builder. 1101 */ 1102 public AttributeBuilder(String attributeName) 1103 { 1104 this(getAttributeType(attributeName), attributeName); 1105 } 1106 1107 1108 1109 /** 1110 * Adds the specified attribute value to this attribute builder if 1111 * it is not already present. 1112 * 1113 * @param valueString 1114 * The string representation of the attribute value to be 1115 * added to this attribute builder. 1116 * @return <code>true</code> if this attribute builder did not 1117 * already contain the specified attribute value. 1118 */ 1119 public boolean add(String valueString) 1120 { 1121 return add(ByteString.valueOf(valueString)); 1122 } 1123 1124 1125 1126 /** 1127 * Adds the specified attribute value to this attribute builder if it is not 1128 * already present. 1129 * 1130 * @param attributeValue 1131 * The {@link ByteString} representation of the attribute value to be 1132 * added to this attribute builder. 1133 * @return <code>true</code> if this attribute builder did not already contain 1134 * the specified attribute value. 1135 */ 1136 public boolean add(ByteString attributeValue) 1137 { 1138 AttributeValue value = createAttributeValue(attributeType, attributeValue); 1139 boolean isNewValue = values.add(value); 1140 if (!isNewValue) 1141 { 1142 // AttributeValue is already present, but the user-provided value may be different 1143 // There is no direct way to check this, so remove and add to ensure 1144 // the last user-provided value is recorded 1145 values.remove(value); 1146 values.add(value); 1147 } 1148 return isNewValue; 1149 } 1150 1151 /** Creates an attribute value with delayed normalization. */ 1152 private static AttributeValue createAttributeValue(AttributeType attributeType, ByteString attributeValue) 1153 { 1154 return new AttributeValue(attributeType, attributeValue); 1155 } 1156 1157 private static ByteString normalize(AttributeType attributeType, ByteString attributeValue) 1158 { 1159 try 1160 { 1161 if (attributeType != null) 1162 { 1163 final MatchingRule eqRule = attributeType.getEqualityMatchingRule(); 1164 return eqRule.normalizeAttributeValue(attributeValue); 1165 } 1166 } 1167 catch (DecodeException e) 1168 { 1169 // nothing to do here 1170 } 1171 return attributeValue; 1172 } 1173 1174 /** 1175 * Adds all the values from the specified attribute to this 1176 * attribute builder if they are not already present. 1177 * 1178 * @param attribute 1179 * The attribute containing the values to be added to this 1180 * attribute builder. 1181 * @return <code>true</code> if this attribute builder was 1182 * modified. 1183 */ 1184 public boolean addAll(Attribute attribute) 1185 { 1186 boolean wasModified = false; 1187 for (ByteString v : attribute) 1188 { 1189 wasModified |= add(v); 1190 } 1191 return wasModified; 1192 } 1193 1194 /** 1195 * Adds the specified attribute values to this attribute builder if 1196 * they are not already present. 1197 * 1198 * @param values 1199 * The attribute values to be added to this attribute builder. 1200 * @return <code>true</code> if this attribute builder was modified. 1201 */ 1202 public boolean addAll(Collection<ByteString> values) 1203 { 1204 boolean wasModified = false; 1205 for (ByteString v : values) 1206 { 1207 wasModified |= add(v); 1208 } 1209 return wasModified; 1210 } 1211 1212 /** 1213 * Adds the specified attribute values to this attribute builder 1214 * if they are not already present. 1215 * 1216 * @param values 1217 * The attribute values to be added to this attribute builder. 1218 * @return <code>true</code> if this attribute builder was modified. 1219 * @throws NullPointerException if any of the values is null 1220 */ 1221 public boolean addAllStrings(Collection<? extends Object> values) 1222 { 1223 boolean wasModified = false; 1224 for (Object v : values) 1225 { 1226 wasModified |= add(v.toString()); 1227 } 1228 return wasModified; 1229 } 1230 1231 /** 1232 * Removes all attribute values from this attribute builder. 1233 */ 1234 public void clear() 1235 { 1236 values.clear(); 1237 } 1238 1239 1240 1241 /** 1242 * Indicates whether this attribute builder contains the specified 1243 * value. 1244 * 1245 * @param value 1246 * The value for which to make the determination. 1247 * @return <CODE>true</CODE> if this attribute builder has the 1248 * specified value, or <CODE>false</CODE> if not. 1249 */ 1250 public boolean contains(ByteString value) 1251 { 1252 return values.contains(createAttributeValue(attributeType, value)); 1253 } 1254 1255 1256 1257 /** 1258 * Indicates whether this attribute builder contains all the values 1259 * in the collection. 1260 * 1261 * @param values 1262 * The set of values for which to make the determination. 1263 * @return <CODE>true</CODE> if this attribute builder contains 1264 * all the values in the provided collection, or 1265 * <CODE>false</CODE> if it does not contain at least one 1266 * of them. 1267 */ 1268 public boolean containsAll(Collection<ByteString> values) 1269 { 1270 for (ByteString v : values) 1271 { 1272 if (!contains(v)) 1273 { 1274 return false; 1275 } 1276 } 1277 return true; 1278 } 1279 1280 1281 1282 /** 1283 * Retrieves the attribute type for this attribute builder. 1284 * 1285 * @return The attribute type for this attribute builder, or 1286 * <code>null</code> if one has not yet been specified. 1287 */ 1288 public AttributeType getAttributeType() 1289 { 1290 return attributeType; 1291 } 1292 1293 1294 1295 /** 1296 * Returns <code>true</code> if this attribute builder contains no 1297 * attribute values. 1298 * 1299 * @return <CODE>true</CODE> if this attribute builder contains no 1300 * attribute values. 1301 */ 1302 public boolean isEmpty() 1303 { 1304 return values.isEmpty(); 1305 } 1306 1307 1308 1309 /** 1310 * Returns an iterator over the attribute values in this attribute 1311 * builder. The attribute values are returned in the order in which 1312 * they were added to this attribute builder. The returned iterator 1313 * supports attribute value removals via its <code>remove</code> 1314 * method. 1315 * 1316 * @return An iterator over the attribute values in this attribute builder. 1317 */ 1318 @Override 1319 public Iterator<ByteString> iterator() 1320 { 1321 return getUnmodifiableIterator(values); 1322 } 1323 1324 1325 1326 /** 1327 * Removes the specified attribute value from this attribute builder 1328 * if it is present. 1329 * 1330 * @param value 1331 * The attribute value to be removed from this attribute 1332 * builder. 1333 * @return <code>true</code> if this attribute builder contained 1334 * the specified attribute value. 1335 */ 1336 public boolean remove(ByteString value) 1337 { 1338 return values.remove(createAttributeValue(attributeType, value)); 1339 } 1340 1341 /** 1342 * Removes the specified attribute value from this attribute builder 1343 * if it is present. 1344 * 1345 * @param valueString 1346 * The string representation of the attribute value to be 1347 * removed from this attribute builder. 1348 * @return <code>true</code> if this attribute builder contained 1349 * the specified attribute value. 1350 */ 1351 public boolean remove(String valueString) 1352 { 1353 return remove(ByteString.valueOf(valueString)); 1354 } 1355 1356 1357 1358 /** 1359 * Removes all the values from the specified attribute from this 1360 * attribute builder if they are not already present. 1361 * 1362 * @param attribute 1363 * The attribute containing the values to be removed from 1364 * this attribute builder. 1365 * @return <code>true</code> if this attribute builder was 1366 * modified. 1367 */ 1368 public boolean removeAll(Attribute attribute) 1369 { 1370 boolean wasModified = false; 1371 for (ByteString v : attribute) 1372 { 1373 wasModified |= remove(v); 1374 } 1375 return wasModified; 1376 } 1377 1378 1379 1380 /** 1381 * Removes the specified attribute values from this attribute 1382 * builder if they are present. 1383 * 1384 * @param values 1385 * The attribute values to be removed from this attribute 1386 * builder. 1387 * @return <code>true</code> if this attribute builder was 1388 * modified. 1389 */ 1390 public boolean removeAll(Collection<ByteString> values) 1391 { 1392 boolean wasModified = false; 1393 for (ByteString v : values) 1394 { 1395 wasModified |= remove(v); 1396 } 1397 return wasModified; 1398 } 1399 1400 1401 1402 /** 1403 * Replaces all the values in this attribute value with the 1404 * specified attribute value. 1405 * 1406 * @param value 1407 * The attribute value to replace all existing values. 1408 */ 1409 public void replace(ByteString value) 1410 { 1411 clear(); 1412 add(value); 1413 } 1414 1415 1416 1417 /** 1418 * Replaces all the values in this attribute value with the 1419 * specified attribute value. 1420 * 1421 * @param valueString 1422 * The string representation of the attribute value to 1423 * replace all existing values. 1424 */ 1425 public void replace(String valueString) 1426 { 1427 replace(ByteString.valueOf(valueString)); 1428 } 1429 1430 1431 1432 /** 1433 * Replaces all the values in this attribute value with the 1434 * attributes from the specified attribute. 1435 * 1436 * @param attribute 1437 * The attribute containing the values to replace all 1438 * existing values. 1439 */ 1440 public void replaceAll(Attribute attribute) 1441 { 1442 clear(); 1443 addAll(attribute); 1444 } 1445 1446 1447 1448 /** 1449 * Replaces all the values in this attribute value with the 1450 * specified attribute values. 1451 * 1452 * @param values 1453 * The attribute values to replace all existing values. 1454 */ 1455 public void replaceAll(Collection<ByteString> values) 1456 { 1457 clear(); 1458 addAll(values); 1459 } 1460 1461 1462 1463 /** 1464 * Sets the attribute type associated with this attribute builder. 1465 * 1466 * @param attributeType 1467 * The attribute type for this attribute builder. 1468 */ 1469 public void setAttributeType(AttributeType attributeType) 1470 { 1471 setAttributeType(attributeType, attributeType.getNameOrOID()); 1472 } 1473 1474 1475 1476 /** 1477 * Sets the attribute type and user-provided name associated with 1478 * this attribute builder. 1479 * 1480 * @param attributeType 1481 * The attribute type for this attribute builder. 1482 * @param name 1483 * The user-provided name for this attribute builder. 1484 */ 1485 public void setAttributeType( 1486 AttributeType attributeType, 1487 String name) 1488 { 1489 Reject.ifNull(attributeType, name); 1490 1491 this.attributeType = attributeType; 1492 this.name = name; 1493 } 1494 1495 1496 1497 /** 1498 * Sets the attribute type associated with this attribute builder 1499 * using the provided attribute type name. 1500 * <p> 1501 * If the attribute name cannot be found in the schema, a new 1502 * attribute type is created using the default attribute syntax. 1503 * 1504 * @param attributeName 1505 * The attribute name for this attribute builder. 1506 */ 1507 public void setAttributeType(String attributeName) 1508 { 1509 setAttributeType(getAttributeType(attributeName), attributeName); 1510 } 1511 1512 /** 1513 * Adds the specified option to this attribute builder if it is not 1514 * already present. 1515 * 1516 * @param option 1517 * The option to be added to this attribute builder. 1518 * @return <code>true</code> if this attribute builder did not 1519 * already contain the specified option. 1520 */ 1521 public boolean setOption(String option) 1522 { 1523 switch (options.size()) 1524 { 1525 case 0: 1526 return options.add(option); 1527 case 1: 1528 // Normalize and add the first option to normalized set. 1529 normalizedOptions = new TreeSet<>(); 1530 normalizedOptions.add(toLowerCase(options.firstElement)); 1531 1532 if (normalizedOptions.add(toLowerCase(option))) 1533 { 1534 options.add(option); 1535 return true; 1536 } 1537 break; 1538 default: 1539 if (normalizedOptions.add(toLowerCase(option))) 1540 { 1541 options.add(option); 1542 return true; 1543 } 1544 break; 1545 } 1546 1547 return false; 1548 } 1549 1550 1551 1552 /** 1553 * Adds the specified options to this attribute builder if they are 1554 * not already present. 1555 * 1556 * @param options 1557 * The options to be added to this attribute builder. 1558 * @return <code>true</code> if this attribute builder was 1559 * modified. 1560 */ 1561 public boolean setOptions(Collection<String> options) 1562 { 1563 boolean isModified = false; 1564 1565 for (String option : options) 1566 { 1567 isModified |= setOption(option); 1568 } 1569 1570 return isModified; 1571 } 1572 1573 1574 1575 /** 1576 * Indicates whether this attribute builder has exactly the 1577 * specified set of options. 1578 * 1579 * This implementation returns 1580 * {@link java.util.AbstractCollection#isEmpty()} 1581 * if the provided set of options is <code>null</code>. 1582 * Otherwise it checks that the size of the provided 1583 * set of options is equal to the size of this attribute 1584 * builder options, returns <code>false</code> if the 1585 * sizes differ. If the sizes are the same then each 1586 * option in the provided set is checked and if all the 1587 * provided options are present <code>true</code> is 1588 * returned. 1589 * 1590 * @param options 1591 * The set of options for which to make the 1592 * determination (may be <code>null</code>). 1593 * @return <code>true</code> if this attribute 1594 * builder has exactly the specified 1595 * set of options. 1596 */ 1597 public boolean optionsEqual(Set<String> options) 1598 { 1599 if (options == null) 1600 { 1601 return this.options.isEmpty(); 1602 } 1603 1604 if (this.options.size() != options.size()) 1605 { 1606 return false; 1607 } 1608 1609 for (String option : options) 1610 { 1611 if (!this.options.contains(option)) 1612 { 1613 return false; 1614 } 1615 } 1616 1617 return true; 1618 } 1619 1620 1621 1622 /** 1623 * Returns the number of attribute values in this attribute builder. 1624 * 1625 * @return The number of attribute values in this attribute builder. 1626 */ 1627 public int size() 1628 { 1629 return values.size(); 1630 } 1631 1632 /** Returns an iterator on values corresponding to the provided attribute values set. */ 1633 private static Iterator<ByteString> getUnmodifiableIterator(Set<AttributeValue> set) 1634 { 1635 final Iterator<AttributeValue> iterator = set.iterator(); 1636 return new Iterator<ByteString>() 1637 { 1638 @Override 1639 public boolean hasNext() 1640 { 1641 return iterator.hasNext(); 1642 } 1643 1644 @Override 1645 public ByteString next() 1646 { 1647 return iterator.next().getValue(); 1648 } 1649 1650 @Override 1651 public void remove() 1652 { 1653 throw new UnsupportedOperationException(); 1654 } 1655 }; 1656 } 1657 1658 /** 1659 * Indicates if the values for this attribute have been normalized. 1660 * <p> 1661 * This method is intended for tests. 1662 */ 1663 boolean isNormalized() 1664 { 1665 for (AttributeValue attrValue : values) 1666 { 1667 if (attrValue.isNormalized()) 1668 { 1669 return true; 1670 } 1671 } 1672 return false; 1673 } 1674 1675 /** 1676 * Returns an attribute representing the content of this attribute builder. 1677 * <p> 1678 * For efficiency purposes this method resets the content of this 1679 * attribute builder so that it no longer contains any options or 1680 * values and its attribute type is <code>null</code>. 1681 * 1682 * @return An attribute representing the content of this attribute builder. 1683 * @throws IllegalStateException 1684 * If this attribute builder has an undefined attribute type or name. 1685 */ 1686 public Attribute toAttribute() throws IllegalStateException 1687 { 1688 if (attributeType == null) 1689 { 1690 throw new IllegalStateException("Undefined attribute type or name"); 1691 } 1692 1693 // Now create the appropriate attribute based on the options. 1694 Attribute attribute = toAttribute0(); 1695 1696 // Reset the state of this builder. 1697 attributeType = null; 1698 name = null; 1699 normalizedOptions = null; 1700 options.clear(); 1701 values = new SmallSet<>(); 1702 1703 return attribute; 1704 } 1705 1706 private Attribute toAttribute0() 1707 { 1708 switch (options.size()) 1709 { 1710 case 0: 1711 return new RealAttributeNoOptions(attributeType, name, values); 1712 case 1: 1713 return new RealAttributeSingleOption(attributeType, name, values, options.firstElement); 1714 default: 1715 return new RealAttributeManyOptions(attributeType, name, values, 1716 Collections.unmodifiableSet(options.elements), Collections.unmodifiableSortedSet(normalizedOptions)); 1717 } 1718 } 1719 1720 /** 1721 * Returns a List with a single attribute representing the content of this attribute builder. 1722 * <p> 1723 * For efficiency purposes this method resets the content of this 1724 * attribute builder so that it no longer contains any options or 1725 * values and its attribute type is <code>null</code>. 1726 * 1727 * @return A List with a single attribute representing the content of this attribute builder. 1728 * @throws IllegalStateException 1729 * If this attribute builder has an undefined attribute type or name. 1730 */ 1731 public List<Attribute> toAttributeList() throws IllegalStateException 1732 { 1733 return CollectionUtils.newArrayList(toAttribute()); 1734 } 1735 1736 @Override 1737 public final String toString() 1738 { 1739 StringBuilder builder = new StringBuilder(); 1740 builder.append("AttributeBuilder("); 1741 builder.append(name); 1742 1743 for (String option : options) 1744 { 1745 builder.append(';'); 1746 builder.append(option); 1747 } 1748 1749 builder.append(", {"); 1750 Utils.joinAsString(builder, ", ", values); 1751 builder.append("})"); 1752 1753 return builder.toString(); 1754 } 1755}