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 private static AttributeType getAttributeTypeOrDefault(String attributeName) 974 { 975 return DirectoryServer.getAttributeTypeOrDefault(toLowerCase(attributeName), attributeName); 976 } 977 978 /** The attribute type for this attribute. */ 979 private AttributeType attributeType; 980 /** The name of this attribute as provided by the end user. */ 981 private String name; 982 /** The normalized set of options if there are more than one. */ 983 private SortedSet<String> normalizedOptions; 984 /** The set of options. */ 985 private final SmallSet<String> options = new SmallSet<>(); 986 /** The set of attribute values, which are lazily normalized. */ 987 private Set<AttributeValue> values = new SmallSet<>(); 988 989 /** 990 * Creates a new attribute builder with an undefined attribute type 991 * and user-provided name. The attribute type, and optionally the 992 * user-provided name, must be defined using 993 * {@link #setAttributeType(AttributeType)} before the attribute 994 * builder can be converted to an {@link Attribute}. Failure to do 995 * so will yield an {@link IllegalStateException}. 996 */ 997 public AttributeBuilder() 998 { 999 // No implementation required. 1000 } 1001 1002 /** 1003 * Creates a new attribute builder from an existing attribute. 1004 * <p> 1005 * Modifications to the attribute builder will not impact the 1006 * provided attribute. 1007 * 1008 * @param attribute 1009 * The attribute to be copied. 1010 */ 1011 public AttributeBuilder(Attribute attribute) 1012 { 1013 this(attribute, false); 1014 } 1015 1016 1017 1018 /** 1019 * Creates a new attribute builder from an existing attribute, 1020 * optionally omitting the values contained in the provided 1021 * attribute. 1022 * <p> 1023 * Modifications to the attribute builder will not impact the 1024 * provided attribute. 1025 * 1026 * @param attribute 1027 * The attribute to be copied. 1028 * @param omitValues 1029 * <CODE>true</CODE> if the values should be omitted. 1030 */ 1031 public AttributeBuilder(Attribute attribute, boolean omitValues) 1032 { 1033 this(attribute.getAttributeType(), attribute.getName()); 1034 1035 for (String option : attribute.getOptions()) 1036 { 1037 setOption(option); 1038 } 1039 1040 if (!omitValues) 1041 { 1042 addAll(attribute); 1043 } 1044 } 1045 1046 1047 1048 /** 1049 * Creates a new attribute builder with the specified type and no 1050 * options and no values. 1051 * 1052 * @param attributeType 1053 * The attribute type for this attribute builder. 1054 */ 1055 public AttributeBuilder(AttributeType attributeType) 1056 { 1057 this(attributeType, attributeType.getNameOrOID()); 1058 } 1059 1060 1061 1062 /** 1063 * Creates a new attribute builder with the specified type and 1064 * user-provided name and no options and no values. 1065 * 1066 * @param attributeType 1067 * The attribute type for this attribute builder. 1068 * @param name 1069 * The user-provided name for this attribute builder. 1070 */ 1071 public AttributeBuilder(AttributeType attributeType, String name) 1072 { 1073 Reject.ifNull(attributeType, name); 1074 1075 this.attributeType = attributeType; 1076 this.name = name; 1077 } 1078 1079 1080 1081 /** 1082 * Creates a new attribute builder with the specified attribute name 1083 * and no options and no values. 1084 * <p> 1085 * If the attribute name cannot be found in the schema, a new 1086 * attribute type is created using the default attribute syntax. 1087 * 1088 * @param attributeName 1089 * The attribute name for this attribute builder. 1090 */ 1091 public AttributeBuilder(String attributeName) 1092 { 1093 this(getAttributeTypeOrDefault(attributeName), attributeName); 1094 } 1095 1096 1097 1098 /** 1099 * Adds the specified attribute value to this attribute builder if 1100 * it is not already present. 1101 * 1102 * @param valueString 1103 * The string representation of the attribute value to be 1104 * added to this attribute builder. 1105 * @return <code>true</code> if this attribute builder did not 1106 * already contain the specified attribute value. 1107 */ 1108 public boolean add(String valueString) 1109 { 1110 return add(ByteString.valueOfUtf8(valueString)); 1111 } 1112 1113 1114 1115 /** 1116 * Adds the specified attribute value to this attribute builder if it is not 1117 * already present. 1118 * 1119 * @param attributeValue 1120 * The {@link ByteString} representation of the attribute value to be 1121 * added to this attribute builder. 1122 * @return <code>true</code> if this attribute builder did not already contain 1123 * the specified attribute value. 1124 */ 1125 public boolean add(ByteString attributeValue) 1126 { 1127 AttributeValue value = createAttributeValue(attributeType, attributeValue); 1128 boolean isNewValue = values.add(value); 1129 if (!isNewValue) 1130 { 1131 // AttributeValue is already present, but the user-provided value may be different 1132 // There is no direct way to check this, so remove and add to ensure 1133 // the last user-provided value is recorded 1134 values.remove(value); 1135 values.add(value); 1136 } 1137 return isNewValue; 1138 } 1139 1140 /** Creates an attribute value with delayed normalization. */ 1141 private static AttributeValue createAttributeValue(AttributeType attributeType, ByteString attributeValue) 1142 { 1143 return new AttributeValue(attributeType, attributeValue); 1144 } 1145 1146 private static ByteString normalize(AttributeType attributeType, ByteString attributeValue) 1147 { 1148 try 1149 { 1150 if (attributeType != null) 1151 { 1152 final MatchingRule eqRule = attributeType.getEqualityMatchingRule(); 1153 return eqRule.normalizeAttributeValue(attributeValue); 1154 } 1155 } 1156 catch (DecodeException e) 1157 { 1158 // nothing to do here 1159 } 1160 return attributeValue; 1161 } 1162 1163 /** 1164 * Adds all the values from the specified attribute to this 1165 * attribute builder if they are not already present. 1166 * 1167 * @param attribute 1168 * The attribute containing the values to be added to this 1169 * attribute builder. 1170 * @return <code>true</code> if this attribute builder was 1171 * modified. 1172 */ 1173 public boolean addAll(Attribute attribute) 1174 { 1175 boolean wasModified = false; 1176 for (ByteString v : attribute) 1177 { 1178 wasModified |= add(v); 1179 } 1180 return wasModified; 1181 } 1182 1183 /** 1184 * Adds the specified attribute values to this attribute builder if 1185 * they are not already present. 1186 * 1187 * @param values 1188 * The attribute values to be added to this attribute builder. 1189 * @return <code>true</code> if this attribute builder was modified. 1190 */ 1191 public boolean addAll(Collection<ByteString> values) 1192 { 1193 boolean wasModified = false; 1194 for (ByteString v : values) 1195 { 1196 wasModified |= add(v); 1197 } 1198 return wasModified; 1199 } 1200 1201 /** 1202 * Adds the specified attribute values to this attribute builder 1203 * if they are not already present. 1204 * 1205 * @param values 1206 * The attribute values to be added to this attribute builder. 1207 * @return <code>true</code> if this attribute builder was modified. 1208 * @throws NullPointerException if any of the values is null 1209 */ 1210 public boolean addAllStrings(Collection<? extends Object> values) 1211 { 1212 boolean wasModified = false; 1213 for (Object v : values) 1214 { 1215 wasModified |= add(v.toString()); 1216 } 1217 return wasModified; 1218 } 1219 1220 /** 1221 * Removes all attribute values from this attribute builder. 1222 */ 1223 public void clear() 1224 { 1225 values.clear(); 1226 } 1227 1228 1229 1230 /** 1231 * Indicates whether this attribute builder contains the specified 1232 * value. 1233 * 1234 * @param value 1235 * The value for which to make the determination. 1236 * @return <CODE>true</CODE> if this attribute builder has the 1237 * specified value, or <CODE>false</CODE> if not. 1238 */ 1239 public boolean contains(ByteString value) 1240 { 1241 return values.contains(createAttributeValue(attributeType, value)); 1242 } 1243 1244 1245 1246 /** 1247 * Indicates whether this attribute builder contains all the values 1248 * in the collection. 1249 * 1250 * @param values 1251 * The set of values for which to make the determination. 1252 * @return <CODE>true</CODE> if this attribute builder contains 1253 * all the values in the provided collection, or 1254 * <CODE>false</CODE> if it does not contain at least one 1255 * of them. 1256 */ 1257 public boolean containsAll(Collection<ByteString> values) 1258 { 1259 for (ByteString v : values) 1260 { 1261 if (!contains(v)) 1262 { 1263 return false; 1264 } 1265 } 1266 return true; 1267 } 1268 1269 1270 1271 /** 1272 * Retrieves the attribute type for this attribute builder. 1273 * 1274 * @return The attribute type for this attribute builder, or 1275 * <code>null</code> if one has not yet been specified. 1276 */ 1277 public AttributeType getAttributeType() 1278 { 1279 return attributeType; 1280 } 1281 1282 1283 1284 /** 1285 * Returns <code>true</code> if this attribute builder contains no 1286 * attribute values. 1287 * 1288 * @return <CODE>true</CODE> if this attribute builder contains no 1289 * attribute values. 1290 */ 1291 public boolean isEmpty() 1292 { 1293 return values.isEmpty(); 1294 } 1295 1296 1297 1298 /** 1299 * Returns an iterator over the attribute values in this attribute 1300 * builder. The attribute values are returned in the order in which 1301 * they were added to this attribute builder. The returned iterator 1302 * supports attribute value removals via its <code>remove</code> 1303 * method. 1304 * 1305 * @return An iterator over the attribute values in this attribute builder. 1306 */ 1307 @Override 1308 public Iterator<ByteString> iterator() 1309 { 1310 return getUnmodifiableIterator(values); 1311 } 1312 1313 1314 1315 /** 1316 * Removes the specified attribute value from this attribute builder 1317 * if it is present. 1318 * 1319 * @param value 1320 * The attribute value to be removed from this attribute 1321 * builder. 1322 * @return <code>true</code> if this attribute builder contained 1323 * the specified attribute value. 1324 */ 1325 public boolean remove(ByteString value) 1326 { 1327 return values.remove(createAttributeValue(attributeType, value)); 1328 } 1329 1330 /** 1331 * Removes the specified attribute value from this attribute builder 1332 * if it is present. 1333 * 1334 * @param valueString 1335 * The string representation of the attribute value to be 1336 * removed from this attribute builder. 1337 * @return <code>true</code> if this attribute builder contained 1338 * the specified attribute value. 1339 */ 1340 public boolean remove(String valueString) 1341 { 1342 return remove(ByteString.valueOfUtf8(valueString)); 1343 } 1344 1345 1346 1347 /** 1348 * Removes all the values from the specified attribute from this 1349 * attribute builder if they are not already present. 1350 * 1351 * @param attribute 1352 * The attribute containing the values to be removed from 1353 * this attribute builder. 1354 * @return <code>true</code> if this attribute builder was 1355 * modified. 1356 */ 1357 public boolean removeAll(Attribute attribute) 1358 { 1359 boolean wasModified = false; 1360 for (ByteString v : attribute) 1361 { 1362 wasModified |= remove(v); 1363 } 1364 return wasModified; 1365 } 1366 1367 1368 1369 /** 1370 * Removes the specified attribute values from this attribute 1371 * builder if they are present. 1372 * 1373 * @param values 1374 * The attribute values to be removed from this attribute 1375 * builder. 1376 * @return <code>true</code> if this attribute builder was 1377 * modified. 1378 */ 1379 public boolean removeAll(Collection<ByteString> values) 1380 { 1381 boolean wasModified = false; 1382 for (ByteString v : values) 1383 { 1384 wasModified |= remove(v); 1385 } 1386 return wasModified; 1387 } 1388 1389 1390 1391 /** 1392 * Replaces all the values in this attribute value with the 1393 * specified attribute value. 1394 * 1395 * @param value 1396 * The attribute value to replace all existing values. 1397 */ 1398 public void replace(ByteString value) 1399 { 1400 clear(); 1401 add(value); 1402 } 1403 1404 1405 1406 /** 1407 * Replaces all the values in this attribute value with the 1408 * specified attribute value. 1409 * 1410 * @param valueString 1411 * The string representation of the attribute value to 1412 * replace all existing values. 1413 */ 1414 public void replace(String valueString) 1415 { 1416 replace(ByteString.valueOfUtf8(valueString)); 1417 } 1418 1419 1420 1421 /** 1422 * Replaces all the values in this attribute value with the 1423 * attributes from the specified attribute. 1424 * 1425 * @param attribute 1426 * The attribute containing the values to replace all 1427 * existing values. 1428 */ 1429 public void replaceAll(Attribute attribute) 1430 { 1431 clear(); 1432 addAll(attribute); 1433 } 1434 1435 1436 1437 /** 1438 * Replaces all the values in this attribute value with the 1439 * specified attribute values. 1440 * 1441 * @param values 1442 * The attribute values to replace all existing values. 1443 */ 1444 public void replaceAll(Collection<ByteString> values) 1445 { 1446 clear(); 1447 addAll(values); 1448 } 1449 1450 1451 1452 /** 1453 * Sets the attribute type associated with this attribute builder. 1454 * 1455 * @param attributeType 1456 * The attribute type for this attribute builder. 1457 */ 1458 public void setAttributeType(AttributeType attributeType) 1459 { 1460 setAttributeType(attributeType, attributeType.getNameOrOID()); 1461 } 1462 1463 1464 1465 /** 1466 * Sets the attribute type and user-provided name associated with 1467 * this attribute builder. 1468 * 1469 * @param attributeType 1470 * The attribute type for this attribute builder. 1471 * @param name 1472 * The user-provided name for this attribute builder. 1473 */ 1474 public void setAttributeType( 1475 AttributeType attributeType, 1476 String name) 1477 { 1478 Reject.ifNull(attributeType, name); 1479 1480 this.attributeType = attributeType; 1481 this.name = name; 1482 } 1483 1484 1485 1486 /** 1487 * Sets the attribute type associated with this attribute builder 1488 * using the provided attribute type name. 1489 * <p> 1490 * If the attribute name cannot be found in the schema, a new 1491 * attribute type is created using the default attribute syntax. 1492 * 1493 * @param attributeName 1494 * The attribute name for this attribute builder. 1495 */ 1496 public void setAttributeType(String attributeName) 1497 { 1498 setAttributeType(getAttributeTypeOrDefault(attributeName), attributeName); 1499 } 1500 1501 /** 1502 * Adds the specified option to this attribute builder if it is not 1503 * already present. 1504 * 1505 * @param option 1506 * The option to be added to this attribute builder. 1507 * @return <code>true</code> if this attribute builder did not 1508 * already contain the specified option. 1509 */ 1510 public boolean setOption(String option) 1511 { 1512 switch (options.size()) 1513 { 1514 case 0: 1515 return options.add(option); 1516 case 1: 1517 // Normalize and add the first option to normalized set. 1518 normalizedOptions = new TreeSet<>(); 1519 normalizedOptions.add(toLowerCase(options.firstElement)); 1520 1521 if (normalizedOptions.add(toLowerCase(option))) 1522 { 1523 options.add(option); 1524 return true; 1525 } 1526 break; 1527 default: 1528 if (normalizedOptions.add(toLowerCase(option))) 1529 { 1530 options.add(option); 1531 return true; 1532 } 1533 break; 1534 } 1535 1536 return false; 1537 } 1538 1539 1540 1541 /** 1542 * Adds the specified options to this attribute builder if they are 1543 * not already present. 1544 * 1545 * @param options 1546 * The options to be added to this attribute builder. 1547 * @return <code>true</code> if this attribute builder was 1548 * modified. 1549 */ 1550 public boolean setOptions(Collection<String> options) 1551 { 1552 boolean isModified = false; 1553 1554 for (String option : options) 1555 { 1556 isModified |= setOption(option); 1557 } 1558 1559 return isModified; 1560 } 1561 1562 1563 1564 /** 1565 * Indicates whether this attribute builder has exactly the 1566 * specified set of options. 1567 * 1568 * This implementation returns 1569 * {@link java.util.AbstractCollection#isEmpty()} 1570 * if the provided set of options is <code>null</code>. 1571 * Otherwise it checks that the size of the provided 1572 * set of options is equal to the size of this attribute 1573 * builder options, returns <code>false</code> if the 1574 * sizes differ. If the sizes are the same then each 1575 * option in the provided set is checked and if all the 1576 * provided options are present <code>true</code> is 1577 * returned. 1578 * 1579 * @param options 1580 * The set of options for which to make the 1581 * determination (may be <code>null</code>). 1582 * @return <code>true</code> if this attribute 1583 * builder has exactly the specified 1584 * set of options. 1585 */ 1586 public boolean optionsEqual(Set<String> options) 1587 { 1588 if (options == null) 1589 { 1590 return this.options.isEmpty(); 1591 } 1592 1593 if (this.options.size() != options.size()) 1594 { 1595 return false; 1596 } 1597 1598 for (String option : options) 1599 { 1600 if (!this.options.contains(option)) 1601 { 1602 return false; 1603 } 1604 } 1605 1606 return true; 1607 } 1608 1609 1610 1611 /** 1612 * Returns the number of attribute values in this attribute builder. 1613 * 1614 * @return The number of attribute values in this attribute builder. 1615 */ 1616 public int size() 1617 { 1618 return values.size(); 1619 } 1620 1621 /** Returns an iterator on values corresponding to the provided attribute values set. */ 1622 private static Iterator<ByteString> getUnmodifiableIterator(Set<AttributeValue> set) 1623 { 1624 final Iterator<AttributeValue> iterator = set.iterator(); 1625 return new Iterator<ByteString>() 1626 { 1627 @Override 1628 public boolean hasNext() 1629 { 1630 return iterator.hasNext(); 1631 } 1632 1633 @Override 1634 public ByteString next() 1635 { 1636 return iterator.next().getValue(); 1637 } 1638 1639 @Override 1640 public void remove() 1641 { 1642 throw new UnsupportedOperationException(); 1643 } 1644 }; 1645 } 1646 1647 /** 1648 * Indicates if the values for this attribute have been normalized. 1649 * <p> 1650 * This method is intended for tests. 1651 */ 1652 boolean isNormalized() 1653 { 1654 for (AttributeValue attrValue : values) 1655 { 1656 if (attrValue.isNormalized()) 1657 { 1658 return true; 1659 } 1660 } 1661 return false; 1662 } 1663 1664 /** 1665 * Returns an attribute representing the content of this attribute builder. 1666 * <p> 1667 * For efficiency purposes this method resets the content of this 1668 * attribute builder so that it no longer contains any options or 1669 * values and its attribute type is <code>null</code>. 1670 * 1671 * @return An attribute representing the content of this attribute builder. 1672 * @throws IllegalStateException 1673 * If this attribute builder has an undefined attribute type or name. 1674 */ 1675 public Attribute toAttribute() throws IllegalStateException 1676 { 1677 if (attributeType == null) 1678 { 1679 throw new IllegalStateException("Undefined attribute type or name"); 1680 } 1681 1682 // Now create the appropriate attribute based on the options. 1683 Attribute attribute = toAttribute0(); 1684 1685 // Reset the state of this builder. 1686 attributeType = null; 1687 name = null; 1688 normalizedOptions = null; 1689 options.clear(); 1690 values = new SmallSet<>(); 1691 1692 return attribute; 1693 } 1694 1695 private Attribute toAttribute0() 1696 { 1697 switch (options.size()) 1698 { 1699 case 0: 1700 return new RealAttributeNoOptions(attributeType, name, values); 1701 case 1: 1702 return new RealAttributeSingleOption(attributeType, name, values, options.firstElement); 1703 default: 1704 return new RealAttributeManyOptions(attributeType, name, values, 1705 Collections.unmodifiableSet(options.elements), Collections.unmodifiableSortedSet(normalizedOptions)); 1706 } 1707 } 1708 1709 /** 1710 * Returns a List with a single attribute representing the content of this attribute builder. 1711 * <p> 1712 * For efficiency purposes this method resets the content of this 1713 * attribute builder so that it no longer contains any options or 1714 * values and its attribute type is <code>null</code>. 1715 * 1716 * @return A List with a single attribute representing the content of this attribute builder. 1717 * @throws IllegalStateException 1718 * If this attribute builder has an undefined attribute type or name. 1719 */ 1720 public List<Attribute> toAttributeList() throws IllegalStateException 1721 { 1722 return CollectionUtils.newArrayList(toAttribute()); 1723 } 1724 1725 @Override 1726 public final String toString() 1727 { 1728 StringBuilder builder = new StringBuilder(); 1729 builder.append("AttributeBuilder("); 1730 builder.append(name); 1731 1732 for (String option : options) 1733 { 1734 builder.append(';'); 1735 builder.append(option); 1736 } 1737 1738 builder.append(", {"); 1739 Utils.joinAsString(builder, ", ", values); 1740 builder.append("})"); 1741 1742 return builder.toString(); 1743 } 1744}