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 &lt; 10; i++)
081 * {
082 *   builder.setAttributeType(&quot;myAttribute&quot; + i);
083 *   builder.setOption(&quot;an-option&quot;);
084 *   builder.add(&quot;a value&quot;);
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}