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 java.util.Collection; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Set; 023 024import org.forgerock.i18n.slf4j.LocalizedLogger; 025import org.forgerock.opendj.ldap.Assertion; 026import org.forgerock.opendj.ldap.AttributeDescription; 027import org.forgerock.opendj.ldap.ByteString; 028import org.forgerock.opendj.ldap.ConditionResult; 029import org.forgerock.opendj.ldap.DecodeException; 030import org.forgerock.opendj.ldap.schema.AttributeType; 031import org.forgerock.opendj.ldap.schema.MatchingRule; 032import org.forgerock.util.Reject; 033import org.forgerock.util.Utils; 034import org.opends.server.types.Attribute.RemoveOnceSwitchingAttributes; 035import org.opends.server.util.CollectionUtils; 036 037import com.forgerock.opendj.util.SmallSet; 038 039/** 040 * This class provides an interface for creating new non-virtual 041 * {@link Attribute}s, or "real" attributes. 042 * <p> 043 * An attribute can be created incrementally using either 044 * {@link #AttributeBuilder(AttributeType)} or 045 * {@link #AttributeBuilder(AttributeType, String)}. The caller is 046 * then free to add new options using {@link #setOption(String)} and 047 * new values using {@link #add(ByteString)} or 048 * {@link #addAll(Collection)}. Once all the options and values have 049 * been added, the attribute can be retrieved using the 050 * {@link #toAttribute()} method. 051 * <p> 052 * A real attribute can also be created based on the values taken from 053 * another attribute using the {@link #AttributeBuilder(Attribute)} 054 * constructor. The caller is then free to modify the values within 055 * the attribute before retrieving the updated attribute using the 056 * {@link #toAttribute()} method. 057 * <p> 058 * The {@link org.opends.server.types.Attributes} class contains 059 * convenience factory methods, 060 * e.g. {@link org.opends.server.types.Attributes#empty(String)} for 061 * creating empty attributes, and 062 * {@link org.opends.server.types.Attributes#create(String, String)} 063 * for creating single-valued attributes. 064 * <p> 065 * <code>AttributeBuilder</code>s can be re-used. Once an 066 * <code>AttributeBuilder</code> has been converted to an 067 * {@link Attribute} using {@link #toAttribute()}, its state is reset 068 * so that its attribute type, user-provided name, options, and values 069 * are all undefined: 070 * 071 * <pre> 072 * AttributeBuilder builder = new AttributeBuilder(); 073 * for (int i = 0; i < 10; i++) 074 * { 075 * builder.setAttributeType("myAttribute" + i); 076 * builder.setOption("an-option"); 077 * builder.add("a value"); 078 * Attribute attribute = builder.toAttribute(); 079 * // Do something with attribute... 080 * } 081 * </pre> 082 * <p> 083 * <b>Implementation Note:</b> this class is optimized for the common 084 * case where there is only a single value. By doing so, we avoid 085 * using unnecessary storage space and also performing any unnecessary 086 * normalization. In addition, this class is optimized for the common 087 * cases where there are zero or one attribute type options. 088 */ 089@org.opends.server.types.PublicAPI( 090 stability = org.opends.server.types.StabilityLevel.UNCOMMITTED, 091 mayInstantiate = true, 092 mayExtend = false, 093 mayInvoke = true) 094@RemoveOnceSwitchingAttributes 095public final class AttributeBuilder implements Iterable<ByteString> 096{ 097 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 098 099 /** A real attribute */ 100 private static class RealAttribute extends AbstractAttribute 101 { 102 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 103 104 /** The attribute description for this attribute. */ 105 private final AttributeDescription attributeDescription; 106 /** 107 * The unmodifiable set of attribute values, which are lazily normalized. 108 * <p> 109 * When required, the attribute values are normalized according to the equality matching rule. 110 */ 111 private final Set<AttributeValue> values; 112 113 private RealAttribute(AttributeDescription attributeDescription, Set<AttributeValue> values) 114 { 115 this.attributeDescription = attributeDescription; 116 this.values = values; 117 } 118 119 private AttributeType getAttributeType() 120 { 121 return getAttributeDescription().getAttributeType(); 122 } 123 124 @Override 125 public final ConditionResult approximatelyEqualTo(ByteString assertionValue) 126 { 127 MatchingRule matchingRule = getAttributeType().getApproximateMatchingRule(); 128 if (matchingRule == null) 129 { 130 return ConditionResult.UNDEFINED; 131 } 132 133 Assertion assertion = null; 134 try 135 { 136 assertion = matchingRule.getAssertion(assertionValue); 137 } 138 catch (Exception e) 139 { 140 logger.traceException(e); 141 return ConditionResult.UNDEFINED; 142 } 143 144 ConditionResult result = ConditionResult.FALSE; 145 for (AttributeValue v : values) 146 { 147 try 148 { 149 result = assertion.matches(matchingRule.normalizeAttributeValue(v.getValue())); 150 } 151 catch (Exception e) 152 { 153 logger.traceException(e); 154 // We could not normalize one of the attribute values. 155 // If we cannot find a definite match, then we should return "undefined". 156 result = ConditionResult.UNDEFINED; 157 } 158 } 159 160 return result; 161 } 162 163 164 165 @Override 166 public final boolean contains(ByteString value) 167 { 168 return values.contains(createAttributeValue(attributeDescription, value)); 169 } 170 171 @Override 172 public ConditionResult matchesEqualityAssertion(ByteString assertionValue) 173 { 174 try 175 { 176 MatchingRule eqRule = getAttributeType().getEqualityMatchingRule(); 177 final Assertion assertion = eqRule.getAssertion(assertionValue); 178 for (AttributeValue value : values) 179 { 180 if (assertion.matches(value.getNormalizedValue()).toBoolean()) 181 { 182 return ConditionResult.TRUE; 183 } 184 } 185 return ConditionResult.FALSE; 186 } 187 catch (DecodeException e) 188 { 189 return ConditionResult.UNDEFINED; 190 } 191 } 192 193 @Override 194 public AttributeDescription getAttributeDescription() 195 { 196 return attributeDescription; 197 } 198 199 @Override 200 public final ConditionResult greaterThanOrEqualTo(ByteString assertionValue) 201 { 202 MatchingRule matchingRule = getAttributeType().getOrderingMatchingRule(); 203 if (matchingRule == null) 204 { 205 return ConditionResult.UNDEFINED; 206 } 207 208 Assertion assertion; 209 try 210 { 211 assertion = matchingRule.getGreaterOrEqualAssertion(assertionValue); 212 } 213 catch (DecodeException e) 214 { 215 logger.traceException(e); 216 return ConditionResult.UNDEFINED; 217 } 218 219 ConditionResult result = ConditionResult.FALSE; 220 for (AttributeValue v : values) 221 { 222 try 223 { 224 if (assertion.matches(matchingRule.normalizeAttributeValue(v.getValue())).toBoolean()) 225 { 226 return ConditionResult.TRUE; 227 } 228 } 229 catch (Exception e) 230 { 231 logger.traceException(e); 232 // We could not normalize one of the attribute values. 233 // If we cannot find a definite match, then we should return "undefined". 234 result = ConditionResult.UNDEFINED; 235 } 236 } 237 238 return result; 239 } 240 241 @Override 242 public final boolean isVirtual() 243 { 244 return false; 245 } 246 247 248 @Override 249 public final Iterator<ByteString> iterator() 250 { 251 return getUnmodifiableIterator(values); 252 } 253 254 @Override 255 public final ConditionResult lessThanOrEqualTo(ByteString assertionValue) 256 { 257 MatchingRule matchingRule = getAttributeType().getOrderingMatchingRule(); 258 if (matchingRule == null) 259 { 260 return ConditionResult.UNDEFINED; 261 } 262 263 Assertion assertion; 264 try 265 { 266 assertion = matchingRule.getLessOrEqualAssertion(assertionValue); 267 } 268 catch (DecodeException e) 269 { 270 logger.traceException(e); 271 return ConditionResult.UNDEFINED; 272 } 273 274 ConditionResult result = ConditionResult.FALSE; 275 for (AttributeValue v : values) 276 { 277 try 278 { 279 if (assertion.matches(matchingRule.normalizeAttributeValue(v.getValue())).toBoolean()) 280 { 281 return ConditionResult.TRUE; 282 } 283 } 284 catch (Exception e) 285 { 286 logger.traceException(e); 287 288 // We could not normalize one of the attribute values. 289 // If we cannot find a definite match, then we should return "undefined". 290 result = ConditionResult.UNDEFINED; 291 } 292 } 293 294 return result; 295 } 296 297 298 299 @Override 300 public final ConditionResult matchesSubstring(ByteString subInitial, List<ByteString> subAny, ByteString subFinal) 301 { 302 MatchingRule matchingRule = getAttributeType().getSubstringMatchingRule(); 303 if (matchingRule == null) 304 { 305 return ConditionResult.UNDEFINED; 306 } 307 308 309 Assertion assertion; 310 try 311 { 312 assertion = matchingRule.getSubstringAssertion(subInitial, subAny, subFinal); 313 } 314 catch (DecodeException e) 315 { 316 logger.traceException(e); 317 return ConditionResult.UNDEFINED; 318 } 319 320 ConditionResult result = ConditionResult.FALSE; 321 for (AttributeValue value : values) 322 { 323 try 324 { 325 if (assertion.matches(matchingRule.normalizeAttributeValue(value.getValue())).toBoolean()) 326 { 327 return ConditionResult.TRUE; 328 } 329 } 330 catch (Exception e) 331 { 332 logger.traceException(e); 333 334 // The value could not be normalized. 335 // If we cannot find a definite match, then we should return "undefined". 336 result = ConditionResult.UNDEFINED; 337 } 338 } 339 340 return result; 341 } 342 343 @Override 344 public final int size() 345 { 346 return values.size(); 347 } 348 349 @Override 350 public int hashCode() 351 { 352 int hashCode = getAttributeType().hashCode(); 353 for (AttributeValue value : values) 354 { 355 hashCode += value.hashCode(); 356 } 357 return hashCode; 358 } 359 360 @Override 361 public final void toString(StringBuilder buffer) 362 { 363 buffer.append("Attribute("); 364 buffer.append(attributeDescription); 365 buffer.append(", {"); 366 Utils.joinAsString(buffer, ", ", values); 367 buffer.append("})"); 368 } 369 } 370 371 /** 372 * An attribute value which is lazily normalized. 373 * <p> 374 * Stores the value in user-provided form and a reference to the associated 375 * attribute type. The normalized form of the value will be initialized upon 376 * first request. The normalized form of the value should only be used in 377 * cases where equality matching between two values can be performed with 378 * byte-for-byte comparisons of the normalized values. 379 */ 380 private static final class AttributeValue 381 { 382 private final AttributeDescription attributeDescription; 383 384 /** User-provided value. */ 385 private final ByteString value; 386 387 /** Normalized value, which is {@code null} until computation is required. */ 388 private ByteString normalizedValue; 389 390 /** 391 * Construct a new attribute value. 392 * 393 * @param attributeDescription 394 * The attribute description. 395 * @param value 396 * The value of the attribute. 397 */ 398 private AttributeValue(AttributeDescription attributeDescription, ByteString value) 399 { 400 this.attributeDescription = attributeDescription; 401 this.value = value; 402 } 403 404 /** 405 * Retrieves the normalized form of this attribute value. 406 * 407 * @return The normalized form of this attribute value. 408 */ 409 public ByteString getNormalizedValue() 410 { 411 if (normalizedValue == null) 412 { 413 normalizedValue = normalize(attributeDescription, value); 414 } 415 return normalizedValue; 416 } 417 418 boolean isNormalized() 419 { 420 return normalizedValue != null; 421 } 422 423 /** 424 * Retrieves the user-defined form of this attribute value. 425 * 426 * @return The user-defined form of this attribute value. 427 */ 428 public ByteString getValue() 429 { 430 return value; 431 } 432 433 /** 434 * Indicates whether the provided object is an attribute value that is equal 435 * to this attribute value. It will be considered equal if the normalized 436 * representations of both attribute values are equal. 437 * 438 * @param o 439 * The object for which to make the determination. 440 * @return <CODE>true</CODE> if the provided object is an attribute value 441 * that is equal to this attribute value, or <CODE>false</CODE> if 442 * not. 443 */ 444 @Override 445 public boolean equals(Object o) 446 { 447 if (this == o) 448 { 449 return true; 450 } 451 else if (o instanceof AttributeValue) 452 { 453 AttributeValue attrValue = (AttributeValue) o; 454 try 455 { 456 return getNormalizedValue().equals(attrValue.getNormalizedValue()); 457 } 458 catch (Exception e) 459 { 460 logger.traceException(e); 461 return value.equals(attrValue.getValue()); 462 } 463 } 464 return false; 465 } 466 467 /** 468 * Retrieves the hash code for this attribute value. It will be calculated 469 * using the normalized representation of the value. 470 * 471 * @return The hash code for this attribute value. 472 */ 473 @Override 474 public int hashCode() 475 { 476 try 477 { 478 return getNormalizedValue().hashCode(); 479 } 480 catch (Exception e) 481 { 482 logger.traceException(e); 483 return value.hashCode(); 484 } 485 } 486 487 @Override 488 public String toString() 489 { 490 return value != null ? value.toString() : "null"; 491 } 492 } 493 494 /** 495 * Creates an attribute that has no options. 496 * <p> 497 * This method is only intended for use by the {@link Attributes} 498 * class. 499 * 500 * @param attributeType 501 * The attribute type. 502 * @param name 503 * The user-provided attribute name. 504 * @param values 505 * The attribute values. 506 * @return The new attribute. 507 */ 508 static Attribute create(AttributeType attributeType, String name, 509 Set<ByteString> values) 510 { 511 final AttributeBuilder builder = new AttributeBuilder(attributeType, name); 512 builder.addAll(values); 513 return builder.toAttribute(); 514 } 515 516 /** The attribute description for this attribute. */ 517 private AttributeDescription attributeDescription; 518 /** The set of attribute values, which are lazily normalized. */ 519 private SmallSet<AttributeValue> values = new SmallSet<>(); 520 521 /** 522 * Creates a new attribute builder with an undefined attribute type 523 * and user-provided name. The attribute type, and optionally the 524 * user-provided name, must be defined using 525 * {@link #setAttributeDescription(AttributeDescription)} before the attribute 526 * builder can be converted to an {@link Attribute}. Failure to do 527 * so will yield an {@link IllegalStateException}. 528 */ 529 public AttributeBuilder() 530 { 531 // No implementation required. 532 } 533 534 /** 535 * Creates a new attribute builder from an existing attribute. 536 * <p> 537 * Modifications to the attribute builder will not impact the 538 * provided attribute. 539 * 540 * @param attribute 541 * The attribute to be copied. 542 */ 543 public AttributeBuilder(Attribute attribute) 544 { 545 this(attribute.getAttributeDescription()); 546 addAll(attribute); 547 } 548 549 /** 550 * Creates a new attribute builder with the specified description. 551 * 552 * @param attributeDescription 553 * The attribute description for this attribute builder. 554 */ 555 public AttributeBuilder(AttributeDescription attributeDescription) 556 { 557 this.attributeDescription = attributeDescription; 558 } 559 560 /** 561 * Creates a new attribute builder with the specified type and no 562 * options and no values. 563 * 564 * @param attributeType 565 * The attribute type for this attribute builder. 566 */ 567 public AttributeBuilder(AttributeType attributeType) 568 { 569 this(AttributeDescription.create(attributeType)); 570 } 571 572 573 574 /** 575 * Creates a new attribute builder with the specified type and 576 * user-provided name and no options and no values. 577 * 578 * @param attributeType 579 * The attribute type for this attribute builder. 580 * @param name 581 * The user-provided name for this attribute builder. 582 */ 583 public AttributeBuilder(AttributeType attributeType, String name) 584 { 585 Reject.ifNull(attributeType, name); 586 587 this.attributeDescription = AttributeDescription.create(name, attributeType); 588 } 589 590 591 592 /** 593 * Creates a new attribute builder with the specified attribute description and no values. 594 * <p> 595 * If the attribute name cannot be found in the schema, a new attribute type is created using the 596 * default attribute syntax. 597 * 598 * @param attributeDescription 599 * The attribute description for this attribute builder. 600 */ 601 public AttributeBuilder(String attributeDescription) 602 { 603 this(AttributeDescription.valueOf(attributeDescription)); 604 } 605 606 607 608 /** 609 * Adds the specified attribute value to this attribute builder if 610 * it is not already present. 611 * 612 * @param valueString 613 * The string representation of the attribute value to be 614 * added to this attribute builder. 615 * @return <code>true</code> if this attribute builder did not 616 * already contain the specified attribute value. 617 */ 618 public boolean add(String valueString) 619 { 620 return add(ByteString.valueOfUtf8(valueString)); 621 } 622 623 624 625 /** 626 * Adds the specified attribute value to this attribute builder if it is not 627 * already present. 628 * 629 * @param attributeValue 630 * The {@link ByteString} representation of the attribute value to be 631 * added to this attribute builder. 632 * @return <code>true</code> if this attribute builder did not already contain 633 * the specified attribute value. 634 */ 635 public boolean add(ByteString attributeValue) 636 { 637 AttributeValue value = createAttributeValue(attributeDescription, attributeValue); 638 boolean isNewValue = values.add(value); 639 if (!isNewValue) 640 { 641 // AttributeValue is already present, but the user-provided value may be different 642 // There is no direct way to check this, so remove and add to ensure 643 // the last user-provided value is recorded 644 values.addOrReplace(value); 645 } 646 return isNewValue; 647 } 648 649 /** Creates an attribute value with delayed normalization. */ 650 private static AttributeValue createAttributeValue(AttributeDescription attributeDescription, 651 ByteString attributeValue) 652 { 653 return new AttributeValue(attributeDescription, attributeValue); 654 } 655 656 private static ByteString normalize(AttributeDescription attributeDescription, ByteString attributeValue) 657 { 658 try 659 { 660 if (attributeDescription != null) 661 { 662 final MatchingRule eqRule = attributeDescription.getAttributeType().getEqualityMatchingRule(); 663 return eqRule.normalizeAttributeValue(attributeValue); 664 } 665 } 666 catch (DecodeException e) 667 { 668 // nothing to do here 669 } 670 return attributeValue; 671 } 672 673 /** 674 * Adds all the values from the specified attribute to this 675 * attribute builder if they are not already present. 676 * 677 * @param attribute 678 * The attribute containing the values to be added to this 679 * attribute builder. 680 * @return <code>true</code> if this attribute builder was 681 * modified. 682 */ 683 public boolean addAll(Attribute attribute) 684 { 685 boolean wasModified = false; 686 for (ByteString v : attribute) 687 { 688 wasModified |= add(v); 689 } 690 return wasModified; 691 } 692 693 /** 694 * Adds the specified attribute values to this attribute builder if 695 * they are not already present. 696 * 697 * @param values 698 * The attribute values to be added to this attribute builder. 699 * @return <code>true</code> if this attribute builder was modified. 700 */ 701 public boolean addAll(Collection<ByteString> values) 702 { 703 boolean wasModified = false; 704 for (ByteString v : values) 705 { 706 wasModified |= add(v); 707 } 708 return wasModified; 709 } 710 711 /** 712 * Adds the specified attribute values to this attribute builder 713 * if they are not already present. 714 * 715 * @param values 716 * The attribute values to be added to this attribute builder. 717 * @return <code>true</code> if this attribute builder was modified. 718 * @throws NullPointerException if any of the values is null 719 */ 720 public boolean addAllStrings(Collection<? extends Object> values) 721 { 722 boolean wasModified = false; 723 for (Object v : values) 724 { 725 wasModified |= add(v.toString()); 726 } 727 return wasModified; 728 } 729 730 /** 731 * Removes all attribute values from this attribute builder. 732 */ 733 public void clear() 734 { 735 values.clear(); 736 } 737 738 739 740 /** 741 * Indicates whether this attribute builder contains the specified 742 * value. 743 * 744 * @param value 745 * The value for which to make the determination. 746 * @return <CODE>true</CODE> if this attribute builder has the 747 * specified value, or <CODE>false</CODE> if not. 748 */ 749 public boolean contains(ByteString value) 750 { 751 return values.contains(createAttributeValue(attributeDescription, value)); 752 } 753 754 /** 755 * Indicates whether this attribute builder contains all the values 756 * in the collection. 757 * 758 * @param values 759 * The set of values for which to make the determination. 760 * @return <CODE>true</CODE> if this attribute builder contains 761 * all the values in the provided collection, or 762 * <CODE>false</CODE> if it does not contain at least one 763 * of them. 764 */ 765 public boolean containsAll(Collection<?> values) 766 { 767 for (Object v : values) 768 { 769 if (!contains(ByteString.valueOfObject(v))) 770 { 771 return false; 772 } 773 } 774 return true; 775 } 776 777 /** 778 * Returns <code>true</code> if this attribute builder contains no 779 * attribute values. 780 * 781 * @return <CODE>true</CODE> if this attribute builder contains no 782 * attribute values. 783 */ 784 public boolean isEmpty() 785 { 786 return values.isEmpty(); 787 } 788 789 790 791 /** 792 * Returns an iterator over the attribute values in this attribute 793 * builder. The attribute values are returned in the order in which 794 * they were added to this attribute builder. The returned iterator 795 * supports attribute value removals via its <code>remove</code> 796 * method. 797 * 798 * @return An iterator over the attribute values in this attribute builder. 799 */ 800 @Override 801 public Iterator<ByteString> iterator() 802 { 803 return getUnmodifiableIterator(values); 804 } 805 806 807 808 /** 809 * Removes the specified attribute value from this attribute builder 810 * if it is present. 811 * 812 * @param value 813 * The attribute value to be removed from this attribute 814 * builder. 815 * @return <code>true</code> if this attribute builder contained 816 * the specified attribute value. 817 */ 818 public boolean remove(ByteString value) 819 { 820 return values.remove(createAttributeValue(attributeDescription, value)); 821 } 822 823 /** 824 * Removes the specified attribute value from this attribute builder 825 * if it is present. 826 * 827 * @param valueString 828 * The string representation of the attribute value to be 829 * removed from this attribute builder. 830 * @return <code>true</code> if this attribute builder contained 831 * the specified attribute value. 832 */ 833 public boolean remove(String valueString) 834 { 835 return remove(ByteString.valueOfUtf8(valueString)); 836 } 837 838 839 840 /** 841 * Removes all the values from the specified attribute from this 842 * attribute builder if they are not already present. 843 * 844 * @param attribute 845 * The attribute containing the values to be removed from 846 * this attribute builder. 847 * @return <code>true</code> if this attribute builder was 848 * modified. 849 */ 850 public boolean removeAll(Attribute attribute) 851 { 852 boolean wasModified = false; 853 for (ByteString v : attribute) 854 { 855 wasModified |= remove(v); 856 } 857 return wasModified; 858 } 859 860 861 862 /** 863 * Removes the specified attribute values from this attribute 864 * builder if they are present. 865 * 866 * @param values 867 * The attribute values to be removed from this attribute 868 * builder. 869 * @return <code>true</code> if this attribute builder was 870 * modified. 871 */ 872 public boolean removeAll(Collection<ByteString> values) 873 { 874 boolean wasModified = false; 875 for (ByteString v : values) 876 { 877 wasModified |= remove(v); 878 } 879 return wasModified; 880 } 881 882 883 884 /** 885 * Replaces all the values in this attribute value with the 886 * specified attribute value. 887 * 888 * @param value 889 * The attribute value to replace all existing values. 890 */ 891 public void replace(ByteString value) 892 { 893 clear(); 894 add(value); 895 } 896 897 898 899 /** 900 * Replaces all the values in this attribute value with the 901 * specified attribute value. 902 * 903 * @param valueString 904 * The string representation of the attribute value to 905 * replace all existing values. 906 */ 907 public void replace(String valueString) 908 { 909 replace(ByteString.valueOfUtf8(valueString)); 910 } 911 912 913 914 /** 915 * Replaces all the values in this attribute value with the 916 * attributes from the specified attribute. 917 * 918 * @param attribute 919 * The attribute containing the values to replace all 920 * existing values. 921 */ 922 public void replaceAll(Attribute attribute) 923 { 924 clear(); 925 addAll(attribute); 926 } 927 928 929 930 /** 931 * Replaces all the values in this attribute value with the 932 * specified attribute values. 933 * 934 * @param values 935 * The attribute values to replace all existing values. 936 */ 937 public void replaceAll(Collection<ByteString> values) 938 { 939 clear(); 940 addAll(values); 941 } 942 943 /** 944 * Sets the attribute description associated with this attribute builder. 945 * 946 * @param attrDesc 947 * The attribute description for this attribute builder. 948 */ 949 void setAttributeDescription(AttributeDescription attrDesc) 950 { 951 attributeDescription = attrDesc; 952 } 953 954 /** 955 * Adds the specified option to this attribute builder if it is not 956 * already present. 957 * 958 * @param option 959 * The option to be added to this attribute builder. 960 * @return <code>true</code> if this attribute builder did not 961 * already contain the specified option. 962 */ 963 public boolean setOption(String option) 964 { 965 AttributeDescription newAD = attributeDescription.withOption(option); 966 if (attributeDescription != newAD) 967 { 968 attributeDescription = newAD; 969 return true; 970 } 971 return false; 972 } 973 974 975 976 /** 977 * Adds the specified options to this attribute builder if they are 978 * not already present. 979 * 980 * @param options 981 * The options to be added to this attribute builder. 982 * @return <code>true</code> if this attribute builder was 983 * modified. 984 */ 985 public boolean setOptions(Iterable<String> options) 986 { 987 boolean isModified = false; 988 989 for (String option : options) 990 { 991 isModified |= setOption(option); 992 } 993 994 return isModified; 995 } 996 997 /** 998 * Indicates whether this attribute builder has exactly the specified set of options. 999 * 1000 * @param attributeDescription 1001 * The attribute description containing the set of options for which to make the 1002 * determination 1003 * @return <code>true</code> if this attribute builder has exactly the specified set of options. 1004 */ 1005 public boolean optionsEqual(AttributeDescription attributeDescription) 1006 { 1007 return toAttribute0().getAttributeDescription().equals(attributeDescription); 1008 } 1009 1010 /** 1011 * Returns the number of attribute values in this attribute builder. 1012 * 1013 * @return The number of attribute values in this attribute builder. 1014 */ 1015 public int size() 1016 { 1017 return values.size(); 1018 } 1019 1020 /** Returns an iterator on values corresponding to the provided attribute values set. */ 1021 private static Iterator<ByteString> getUnmodifiableIterator(Set<AttributeValue> set) 1022 { 1023 final Iterator<AttributeValue> iterator = set.iterator(); 1024 return new Iterator<ByteString>() 1025 { 1026 @Override 1027 public boolean hasNext() 1028 { 1029 return iterator.hasNext(); 1030 } 1031 1032 @Override 1033 public ByteString next() 1034 { 1035 return iterator.next().getValue(); 1036 } 1037 1038 @Override 1039 public void remove() 1040 { 1041 throw new UnsupportedOperationException(); 1042 } 1043 }; 1044 } 1045 1046 /** 1047 * Indicates if the values for this attribute have been normalized. 1048 * <p> 1049 * This method is intended for tests. 1050 */ 1051 boolean isNormalized() 1052 { 1053 for (AttributeValue attrValue : values) 1054 { 1055 if (attrValue.isNormalized()) 1056 { 1057 return true; 1058 } 1059 } 1060 return false; 1061 } 1062 1063 /** 1064 * Returns an attribute representing the content of this attribute builder. 1065 * <p> 1066 * For efficiency purposes this method resets the content of this 1067 * attribute builder so that it no longer contains any options or 1068 * values and its attribute type is <code>null</code>. 1069 * 1070 * @return An attribute representing the content of this attribute builder. 1071 * @throws IllegalStateException 1072 * If this attribute builder has an undefined attribute type or name. 1073 */ 1074 public Attribute toAttribute() throws IllegalStateException 1075 { 1076 if (attributeDescription == null) 1077 { 1078 throw new IllegalStateException("Undefined attribute type or name"); 1079 } 1080 1081 // Now create the appropriate attribute based on the options. 1082 Attribute attribute = toAttribute0(); 1083 1084 // Reset the state of this builder. 1085 attributeDescription = null; 1086 values = new SmallSet<>(); 1087 1088 return attribute; 1089 } 1090 1091 private Attribute toAttribute0() 1092 { 1093 return new RealAttribute(attributeDescription, values); 1094 } 1095 1096 /** 1097 * Returns a List with a single attribute representing the content of this attribute builder. 1098 * <p> 1099 * For efficiency purposes this method resets the content of this 1100 * attribute builder so that it no longer contains any options or 1101 * values and its attribute type is <code>null</code>. 1102 * 1103 * @return A List with a single attribute representing the content of this attribute builder. 1104 * @throws IllegalStateException 1105 * If this attribute builder has an undefined attribute type or name. 1106 */ 1107 public List<Attribute> toAttributeList() throws IllegalStateException 1108 { 1109 return CollectionUtils.newArrayList(toAttribute()); 1110 } 1111 1112 @Override 1113 public final String toString() 1114 { 1115 StringBuilder builder = new StringBuilder(); 1116 builder.append("AttributeBuilder("); 1117 builder.append(attributeDescription); 1118 builder.append(", {"); 1119 Utils.joinAsString(builder, ", ", values); 1120 builder.append("})"); 1121 return builder.toString(); 1122 } 1123}