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