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 &lt; 10; i++)
074 * {
075 *   builder.setAttributeType(&quot;myAttribute&quot; + i);
076 *   builder.setOption(&quot;an-option&quot;);
077 *   builder.add(&quot;a value&quot;);
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}