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