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
974
975  /**
976   * Gets the named attribute type, creating a default attribute if
977   * necessary.
978   *
979   * @param attributeName
980   *          The name of the attribute type.
981   * @return The attribute type associated with the provided attribute
982   *         name.
983   */
984  private static AttributeType getAttributeType(String attributeName)
985  {
986    return DirectoryServer.getAttributeTypeOrDefault(toLowerCase(attributeName), attributeName);
987  }
988
989  /** The attribute type for this attribute. */
990  private AttributeType attributeType;
991  /** The name of this attribute as provided by the end user. */
992  private String name;
993  /** The normalized set of options if there are more than one. */
994  private SortedSet<String> normalizedOptions;
995  /** The set of options. */
996  private final SmallSet<String> options = new SmallSet<>();
997  /** The set of attribute values, which are lazily normalized. */
998  private Set<AttributeValue> values = new SmallSet<>();
999
1000  /**
1001   * Creates a new attribute builder with an undefined attribute type
1002   * and user-provided name. The attribute type, and optionally the
1003   * user-provided name, must be defined using
1004   * {@link #setAttributeType(AttributeType)} before the attribute
1005   * builder can be converted to an {@link Attribute}. Failure to do
1006   * so will yield an {@link IllegalStateException}.
1007   */
1008  public AttributeBuilder()
1009  {
1010    // No implementation required.
1011  }
1012
1013  /**
1014   * Creates a new attribute builder from an existing attribute.
1015   * <p>
1016   * Modifications to the attribute builder will not impact the
1017   * provided attribute.
1018   *
1019   * @param attribute
1020   *          The attribute to be copied.
1021   */
1022  public AttributeBuilder(Attribute attribute)
1023  {
1024    this(attribute, false);
1025  }
1026
1027
1028
1029  /**
1030   * Creates a new attribute builder from an existing attribute,
1031   * optionally omitting the values contained in the provided
1032   * attribute.
1033   * <p>
1034   * Modifications to the attribute builder will not impact the
1035   * provided attribute.
1036   *
1037   * @param attribute
1038   *          The attribute to be copied.
1039   * @param omitValues
1040   *          <CODE>true</CODE> if the values should be omitted.
1041   */
1042  public AttributeBuilder(Attribute attribute, boolean omitValues)
1043  {
1044    this(attribute.getAttributeType(), attribute.getName());
1045
1046    for (String option : attribute.getOptions())
1047    {
1048      setOption(option);
1049    }
1050
1051    if (!omitValues)
1052    {
1053      addAll(attribute);
1054    }
1055  }
1056
1057
1058
1059  /**
1060   * Creates a new attribute builder with the specified type and no
1061   * options and no values.
1062   *
1063   * @param attributeType
1064   *          The attribute type for this attribute builder.
1065   */
1066  public AttributeBuilder(AttributeType attributeType)
1067  {
1068    this(attributeType, attributeType.getNameOrOID());
1069  }
1070
1071
1072
1073  /**
1074   * Creates a new attribute builder with the specified type and
1075   * user-provided name and no options and no values.
1076   *
1077   * @param attributeType
1078   *          The attribute type for this attribute builder.
1079   * @param name
1080   *          The user-provided name for this attribute builder.
1081   */
1082  public AttributeBuilder(AttributeType attributeType, String name)
1083  {
1084    Reject.ifNull(attributeType, name);
1085
1086    this.attributeType = attributeType;
1087    this.name = name;
1088  }
1089
1090
1091
1092  /**
1093   * Creates a new attribute builder with the specified attribute name
1094   * and no options and no values.
1095   * <p>
1096   * If the attribute name cannot be found in the schema, a new
1097   * attribute type is created using the default attribute syntax.
1098   *
1099   * @param attributeName
1100   *          The attribute name for this attribute builder.
1101   */
1102  public AttributeBuilder(String attributeName)
1103  {
1104    this(getAttributeType(attributeName), attributeName);
1105  }
1106
1107
1108
1109  /**
1110   * Adds the specified attribute value to this attribute builder if
1111   * it is not already present.
1112   *
1113   * @param valueString
1114   *          The string representation of the attribute value to be
1115   *          added to this attribute builder.
1116   * @return <code>true</code> if this attribute builder did not
1117   *         already contain the specified attribute value.
1118   */
1119  public boolean add(String valueString)
1120  {
1121    return add(ByteString.valueOf(valueString));
1122  }
1123
1124
1125
1126  /**
1127   * Adds the specified attribute value to this attribute builder if it is not
1128   * already present.
1129   *
1130   * @param attributeValue
1131   *          The {@link ByteString} representation of the attribute value to be
1132   *          added to this attribute builder.
1133   * @return <code>true</code> if this attribute builder did not already contain
1134   *         the specified attribute value.
1135   */
1136  public boolean add(ByteString attributeValue)
1137  {
1138    AttributeValue value = createAttributeValue(attributeType, attributeValue);
1139    boolean isNewValue = values.add(value);
1140    if (!isNewValue)
1141    {
1142      // AttributeValue is already present, but the user-provided value may be different
1143      // There is no direct way to check this, so remove and add to ensure
1144      // the last user-provided value is recorded
1145      values.remove(value);
1146      values.add(value);
1147    }
1148    return isNewValue;
1149  }
1150
1151  /** Creates an attribute value with delayed normalization. */
1152  private static AttributeValue createAttributeValue(AttributeType attributeType, ByteString attributeValue)
1153  {
1154    return new AttributeValue(attributeType, attributeValue);
1155  }
1156
1157  private static ByteString normalize(AttributeType attributeType, ByteString attributeValue)
1158  {
1159    try
1160    {
1161      if (attributeType != null)
1162      {
1163        final MatchingRule eqRule = attributeType.getEqualityMatchingRule();
1164        return eqRule.normalizeAttributeValue(attributeValue);
1165      }
1166    }
1167    catch (DecodeException e)
1168    {
1169      // nothing to do here
1170    }
1171    return attributeValue;
1172  }
1173
1174  /**
1175   * Adds all the values from the specified attribute to this
1176   * attribute builder if they are not already present.
1177   *
1178   * @param attribute
1179   *          The attribute containing the values to be added to this
1180   *          attribute builder.
1181   * @return <code>true</code> if this attribute builder was
1182   *         modified.
1183   */
1184  public boolean addAll(Attribute attribute)
1185  {
1186    boolean wasModified = false;
1187    for (ByteString v : attribute)
1188    {
1189      wasModified |= add(v);
1190    }
1191    return wasModified;
1192  }
1193
1194  /**
1195   * Adds the specified attribute values to this attribute builder if
1196   * they are not already present.
1197   *
1198   * @param values
1199   *          The attribute values to be added to this attribute builder.
1200   * @return <code>true</code> if this attribute builder was modified.
1201   */
1202  public boolean addAll(Collection<ByteString> values)
1203  {
1204    boolean wasModified = false;
1205    for (ByteString v : values)
1206    {
1207      wasModified |= add(v);
1208    }
1209    return wasModified;
1210  }
1211
1212  /**
1213   * Adds the specified attribute values to this attribute builder
1214   * if they are not already present.
1215   *
1216   * @param values
1217   *          The attribute values to be added to this attribute builder.
1218   * @return <code>true</code> if this attribute builder was modified.
1219   * @throws NullPointerException if any of the values is null
1220   */
1221  public boolean addAllStrings(Collection<? extends Object> values)
1222  {
1223    boolean wasModified = false;
1224    for (Object v : values)
1225    {
1226      wasModified |= add(v.toString());
1227    }
1228    return wasModified;
1229  }
1230
1231  /**
1232   * Removes all attribute values from this attribute builder.
1233   */
1234  public void clear()
1235  {
1236    values.clear();
1237  }
1238
1239
1240
1241  /**
1242   * Indicates whether this attribute builder contains the specified
1243   * value.
1244   *
1245   * @param value
1246   *          The value for which to make the determination.
1247   * @return <CODE>true</CODE> if this attribute builder has the
1248   *         specified value, or <CODE>false</CODE> if not.
1249   */
1250  public boolean contains(ByteString value)
1251  {
1252    return values.contains(createAttributeValue(attributeType, value));
1253  }
1254
1255
1256
1257  /**
1258   * Indicates whether this attribute builder contains all the values
1259   * in the collection.
1260   *
1261   * @param values
1262   *          The set of values for which to make the determination.
1263   * @return <CODE>true</CODE> if this attribute builder contains
1264   *         all the values in the provided collection, or
1265   *         <CODE>false</CODE> if it does not contain at least one
1266   *         of them.
1267   */
1268  public boolean containsAll(Collection<ByteString> values)
1269  {
1270    for (ByteString v : values)
1271    {
1272      if (!contains(v))
1273      {
1274        return false;
1275      }
1276    }
1277    return true;
1278  }
1279
1280
1281
1282  /**
1283   * Retrieves the attribute type for this attribute builder.
1284   *
1285   * @return The attribute type for this attribute builder, or
1286   *         <code>null</code> if one has not yet been specified.
1287   */
1288  public AttributeType getAttributeType()
1289  {
1290    return attributeType;
1291  }
1292
1293
1294
1295  /**
1296   * Returns <code>true</code> if this attribute builder contains no
1297   * attribute values.
1298   *
1299   * @return <CODE>true</CODE> if this attribute builder contains no
1300   *         attribute values.
1301   */
1302  public boolean isEmpty()
1303  {
1304    return values.isEmpty();
1305  }
1306
1307
1308
1309  /**
1310   * Returns an iterator over the attribute values in this attribute
1311   * builder. The attribute values are returned in the order in which
1312   * they were added to this attribute builder. The returned iterator
1313   * supports attribute value removals via its <code>remove</code>
1314   * method.
1315   *
1316   * @return An iterator over the attribute values in this attribute builder.
1317   */
1318  @Override
1319  public Iterator<ByteString> iterator()
1320  {
1321    return getUnmodifiableIterator(values);
1322  }
1323
1324
1325
1326  /**
1327   * Removes the specified attribute value from this attribute builder
1328   * if it is present.
1329   *
1330   * @param value
1331   *          The attribute value to be removed from this attribute
1332   *          builder.
1333   * @return <code>true</code> if this attribute builder contained
1334   *         the specified attribute value.
1335   */
1336  public boolean remove(ByteString value)
1337  {
1338    return values.remove(createAttributeValue(attributeType, value));
1339  }
1340
1341  /**
1342   * Removes the specified attribute value from this attribute builder
1343   * if it is present.
1344   *
1345   * @param valueString
1346   *          The string representation of the attribute value to be
1347   *          removed from this attribute builder.
1348   * @return <code>true</code> if this attribute builder contained
1349   *         the specified attribute value.
1350   */
1351  public boolean remove(String valueString)
1352  {
1353    return remove(ByteString.valueOf(valueString));
1354  }
1355
1356
1357
1358  /**
1359   * Removes all the values from the specified attribute from this
1360   * attribute builder if they are not already present.
1361   *
1362   * @param attribute
1363   *          The attribute containing the values to be removed from
1364   *          this attribute builder.
1365   * @return <code>true</code> if this attribute builder was
1366   *         modified.
1367   */
1368  public boolean removeAll(Attribute attribute)
1369  {
1370    boolean wasModified = false;
1371    for (ByteString v : attribute)
1372    {
1373      wasModified |= remove(v);
1374    }
1375    return wasModified;
1376  }
1377
1378
1379
1380  /**
1381   * Removes the specified attribute values from this attribute
1382   * builder if they are present.
1383   *
1384   * @param values
1385   *          The attribute values to be removed from this attribute
1386   *          builder.
1387   * @return <code>true</code> if this attribute builder was
1388   *         modified.
1389   */
1390  public boolean removeAll(Collection<ByteString> values)
1391  {
1392    boolean wasModified = false;
1393    for (ByteString v : values)
1394    {
1395      wasModified |= remove(v);
1396    }
1397    return wasModified;
1398  }
1399
1400
1401
1402  /**
1403   * Replaces all the values in this attribute value with the
1404   * specified attribute value.
1405   *
1406   * @param value
1407   *          The attribute value to replace all existing values.
1408   */
1409  public void replace(ByteString value)
1410  {
1411    clear();
1412    add(value);
1413  }
1414
1415
1416
1417  /**
1418   * Replaces all the values in this attribute value with the
1419   * specified attribute value.
1420   *
1421   * @param valueString
1422   *          The string representation of the attribute value to
1423   *          replace all existing values.
1424   */
1425  public void replace(String valueString)
1426  {
1427    replace(ByteString.valueOf(valueString));
1428  }
1429
1430
1431
1432  /**
1433   * Replaces all the values in this attribute value with the
1434   * attributes from the specified attribute.
1435   *
1436   * @param attribute
1437   *          The attribute containing the values to replace all
1438   *          existing values.
1439   */
1440  public void replaceAll(Attribute attribute)
1441  {
1442    clear();
1443    addAll(attribute);
1444  }
1445
1446
1447
1448  /**
1449   * Replaces all the values in this attribute value with the
1450   * specified attribute values.
1451   *
1452   * @param values
1453   *          The attribute values to replace all existing values.
1454   */
1455  public void replaceAll(Collection<ByteString> values)
1456  {
1457    clear();
1458    addAll(values);
1459  }
1460
1461
1462
1463  /**
1464   * Sets the attribute type associated with this attribute builder.
1465   *
1466   * @param attributeType
1467   *          The attribute type for this attribute builder.
1468   */
1469  public void setAttributeType(AttributeType attributeType)
1470  {
1471    setAttributeType(attributeType, attributeType.getNameOrOID());
1472  }
1473
1474
1475
1476  /**
1477   * Sets the attribute type and user-provided name associated with
1478   * this attribute builder.
1479   *
1480   * @param attributeType
1481   *          The attribute type for this attribute builder.
1482   * @param name
1483   *          The user-provided name for this attribute builder.
1484   */
1485  public void setAttributeType(
1486      AttributeType attributeType,
1487      String name)
1488  {
1489    Reject.ifNull(attributeType, name);
1490
1491    this.attributeType = attributeType;
1492    this.name = name;
1493  }
1494
1495
1496
1497  /**
1498   * Sets the attribute type associated with this attribute builder
1499   * using the provided attribute type name.
1500   * <p>
1501   * If the attribute name cannot be found in the schema, a new
1502   * attribute type is created using the default attribute syntax.
1503   *
1504   * @param attributeName
1505   *          The attribute name for this attribute builder.
1506   */
1507  public void setAttributeType(String attributeName)
1508  {
1509    setAttributeType(getAttributeType(attributeName), attributeName);
1510  }
1511
1512  /**
1513   * Adds the specified option to this attribute builder if it is not
1514   * already present.
1515   *
1516   * @param option
1517   *          The option to be added to this attribute builder.
1518   * @return <code>true</code> if this attribute builder did not
1519   *         already contain the specified option.
1520   */
1521  public boolean setOption(String option)
1522  {
1523    switch (options.size())
1524    {
1525    case 0:
1526      return options.add(option);
1527    case 1:
1528      // Normalize and add the first option to normalized set.
1529      normalizedOptions = new TreeSet<>();
1530      normalizedOptions.add(toLowerCase(options.firstElement));
1531
1532      if (normalizedOptions.add(toLowerCase(option)))
1533      {
1534        options.add(option);
1535        return true;
1536      }
1537      break;
1538    default:
1539      if (normalizedOptions.add(toLowerCase(option)))
1540      {
1541        options.add(option);
1542        return true;
1543      }
1544      break;
1545    }
1546
1547    return false;
1548  }
1549
1550
1551
1552  /**
1553   * Adds the specified options to this attribute builder if they are
1554   * not already present.
1555   *
1556   * @param options
1557   *          The options to be added to this attribute builder.
1558   * @return <code>true</code> if this attribute builder was
1559   *         modified.
1560   */
1561  public boolean setOptions(Collection<String> options)
1562  {
1563    boolean isModified = false;
1564
1565    for (String option : options)
1566    {
1567      isModified |= setOption(option);
1568    }
1569
1570    return isModified;
1571  }
1572
1573
1574
1575  /**
1576   * Indicates whether this attribute builder has exactly the
1577   * specified set of options.
1578   *
1579   * This implementation returns
1580   * {@link java.util.AbstractCollection#isEmpty()}
1581   * if the provided set of options is <code>null</code>.
1582   * Otherwise it checks that the size of the provided
1583   * set of options is equal to the size of this attribute
1584   * builder options, returns <code>false</code> if the
1585   * sizes differ. If the sizes are the same then each
1586   * option in the provided set is checked and if all the
1587   * provided options are present <code>true</code> is
1588   * returned.
1589   *
1590   * @param  options
1591   *         The set of options for which to make the
1592   *         determination (may be <code>null</code>).
1593   * @return <code>true</code> if this attribute
1594   *         builder has exactly the specified
1595   *         set of options.
1596   */
1597  public boolean optionsEqual(Set<String> options)
1598  {
1599    if (options == null)
1600    {
1601      return this.options.isEmpty();
1602    }
1603
1604    if (this.options.size() != options.size())
1605    {
1606      return false;
1607    }
1608
1609    for (String option : options)
1610    {
1611      if (!this.options.contains(option))
1612      {
1613        return false;
1614      }
1615    }
1616
1617    return true;
1618  }
1619
1620
1621
1622  /**
1623   * Returns the number of attribute values in this attribute builder.
1624   *
1625   * @return The number of attribute values in this attribute builder.
1626   */
1627  public int size()
1628  {
1629    return values.size();
1630  }
1631
1632  /** Returns an iterator on values corresponding to the provided attribute values set. */
1633  private static Iterator<ByteString> getUnmodifiableIterator(Set<AttributeValue> set)
1634  {
1635    final Iterator<AttributeValue> iterator = set.iterator();
1636    return new Iterator<ByteString>()
1637    {
1638      @Override
1639      public boolean hasNext()
1640      {
1641        return iterator.hasNext();
1642      }
1643
1644      @Override
1645      public ByteString next()
1646      {
1647         return iterator.next().getValue();
1648      }
1649
1650      @Override
1651      public void remove()
1652      {
1653        throw new UnsupportedOperationException();
1654      }
1655    };
1656  }
1657
1658  /**
1659   * Indicates if the values for this attribute have been normalized.
1660   * <p>
1661   * This method is intended for tests.
1662   */
1663  boolean isNormalized()
1664  {
1665    for (AttributeValue attrValue : values)
1666    {
1667      if (attrValue.isNormalized())
1668      {
1669        return true;
1670      }
1671    }
1672    return false;
1673  }
1674
1675  /**
1676   * Returns an attribute representing the content of this attribute builder.
1677   * <p>
1678   * For efficiency purposes this method resets the content of this
1679   * attribute builder so that it no longer contains any options or
1680   * values and its attribute type is <code>null</code>.
1681   *
1682   * @return An attribute representing the content of this attribute builder.
1683   * @throws IllegalStateException
1684   *           If this attribute builder has an undefined attribute type or name.
1685   */
1686  public Attribute toAttribute() throws IllegalStateException
1687  {
1688    if (attributeType == null)
1689    {
1690      throw new IllegalStateException("Undefined attribute type or name");
1691    }
1692
1693    // Now create the appropriate attribute based on the options.
1694    Attribute attribute = toAttribute0();
1695
1696    // Reset the state of this builder.
1697    attributeType = null;
1698    name = null;
1699    normalizedOptions = null;
1700    options.clear();
1701    values = new SmallSet<>();
1702
1703    return attribute;
1704  }
1705
1706  private Attribute toAttribute0()
1707  {
1708    switch (options.size())
1709    {
1710    case 0:
1711      return new RealAttributeNoOptions(attributeType, name, values);
1712    case 1:
1713      return new RealAttributeSingleOption(attributeType, name, values, options.firstElement);
1714    default:
1715      return new RealAttributeManyOptions(attributeType, name, values,
1716          Collections.unmodifiableSet(options.elements), Collections.unmodifiableSortedSet(normalizedOptions));
1717    }
1718  }
1719
1720  /**
1721   * Returns a List with a single attribute representing the content of this attribute builder.
1722   * <p>
1723   * For efficiency purposes this method resets the content of this
1724   * attribute builder so that it no longer contains any options or
1725   * values and its attribute type is <code>null</code>.
1726   *
1727   * @return A List with a single attribute representing the content of this attribute builder.
1728   * @throws IllegalStateException
1729   *           If this attribute builder has an undefined attribute type or name.
1730   */
1731  public List<Attribute> toAttributeList() throws IllegalStateException
1732  {
1733    return CollectionUtils.newArrayList(toAttribute());
1734  }
1735
1736  @Override
1737  public final String toString()
1738  {
1739    StringBuilder builder = new StringBuilder();
1740    builder.append("AttributeBuilder(");
1741    builder.append(name);
1742
1743    for (String option : options)
1744    {
1745      builder.append(';');
1746      builder.append(option);
1747    }
1748
1749    builder.append(", {");
1750    Utils.joinAsString(builder, ", ", values);
1751    builder.append("})");
1752
1753    return builder.toString();
1754  }
1755}