mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Jean-Noël Rouvignac
16.44.2015 e1404672e50c8f6b3c80b522a1988c9827c251e8
Improve AttributeDescription: now implements Comparable, align with SDK

AttributeDescription.java:
Now implements Comparable.
Aligned methods with SDK.
Removed toOptionsString(), used toString() instead.

EntryHistorical.java, HistoricalAttributeValue.java:
Consequence of the changes to AttributeDescription

Attribute.java:
Changed hasAllOptions() parameter from Collection<String> to Set<String>.

AbstractAttribute.java:
Used static methods from AttributeDescription.
Consequence of the changes to Attribute.

VirtualAttribute.java, AttributeBuilder.java, AttributeBuilderTest.java:
Consequence of the changes to Attribute.
6 files modified
458 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/EntryHistorical.java 32 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java 22 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/AbstractAttribute.java 62 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/Attribute.java 72 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/AttributeDescription.java 261 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/test/java/org/opends/server/types/AttributeBuilderTest.java 9 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/EntryHistorical.java
@@ -61,6 +61,8 @@
 */
public class EntryHistorical
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** Name of the attribute used to store historical information. */
  public static final String HISTORICAL_ATTRIBUTE_NAME = "ds-sync-hist";
  /**
@@ -72,8 +74,6 @@
  /** Name of the entryuuid attribute. */
  public static final String ENTRYUUID_ATTRIBUTE_NAME = "entryuuid";
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /**
   * The delay to purge the historical information.
   * <p>
@@ -303,7 +303,7 @@
    // Read from this entryHistorical,
    // Create one empty if none was existing in this entryHistorical.
    AttributeDescription attrDesc = new AttributeDescription(modAttr);
    AttributeDescription attrDesc = AttributeDescription.create(modAttr);
    AttrHistorical attrHist = attributesHistorical.get(attrDesc);
    if (attrHist == null)
    {
@@ -350,8 +350,7 @@
    for (Map.Entry<AttributeDescription, AttrHistorical> mapEntry : attributesHistorical.entrySet())
    {
      AttributeDescription attrDesc = mapEntry.getKey();
      AttributeType type = attrDesc.attributeType;
      String optionsString = attrDesc.toOptionsString();
      String options = attrDesc.toString();
      AttrHistorical attrHist = mapEntry.getValue();
      CSN deleteTime = attrHist.getDeleteTime();
@@ -370,7 +369,7 @@
            // this hist must be purged now, so skip its encoding
            continue;
          }
          String strValue = encode(DEL, type, optionsString, attrValHist.getValueDeleteTime(), value);
          String strValue = encode(DEL, options, attrValHist.getValueDeleteTime(), value);
          builder.add(strValue);
        }
        else if (attrValHist.getValueUpdateTime() != null)
@@ -387,18 +386,18 @@
          // unit tests do not like changing it
          if (attrDel && updateTime == deleteTime && value != null)
          {
            strValue = encode(REPL, type, optionsString, updateTime, value);
            strValue = encode(REPL, options, updateTime, value);
            attrDel = false;
          }
          else if (value != null)
          {
            strValue = encode(ADD, type, optionsString, updateTime, value);
            strValue = encode(ADD, options, updateTime, value);
          }
          else
          {
            // "add" without any value is suspicious. Tests never go there.
            // Is this used to encode "add" with an empty string?
            strValue = encode(ADD, type, optionsString, updateTime);
            strValue = encode(ADD, options, updateTime);
          }
          builder.add(strValue);
@@ -412,8 +411,7 @@
          // this hist must be purged now, so skip its encoding
          continue;
        }
        String strValue = encode(ATTRDEL, type, optionsString, deleteTime);
        builder.add(strValue);
        builder.add(encode(ATTRDEL, options, deleteTime));
      }
    }
@@ -445,16 +443,14 @@
    return needsPurge;
  }
  private String encode(HistAttrModificationKey modKey, AttributeType type,
      String optionsString, CSN changeTime)
  private String encode(HistAttrModificationKey modKey, String options, CSN changeTime)
  {
    return type.getNormalizedPrimaryName() + optionsString + ":" + changeTime + ":" + modKey;
    return options + ":" + changeTime + ":" + modKey;
  }
  private String encode(HistAttrModificationKey modKey, AttributeType type,
      String optionsString, CSN changeTime, ByteString value)
  private String encode(HistAttrModificationKey modKey, String options, CSN changeTime, ByteString value)
  {
    return type.getNormalizedPrimaryName() + optionsString + ":" + changeTime + ":" + modKey + ":" + value;
    return options + ":" + changeTime + ":" + modKey + ":" + value;
  }
  /**
@@ -580,7 +576,7 @@
            AttrHistorical attrInfo = newHistorical.attributesHistorical.get(attrDesc);
            if (attrInfo == null)
            {
              attrInfo = AttrHistorical.createAttributeHistorical(attrDesc.attributeType);
              attrInfo = AttrHistorical.createAttributeHistorical(attrDesc.getAttributeType());
              newHistorical.attributesHistorical.put(attrDesc, attrInfo);
            }
            attrInfo.assign(histVal.getHistKey(), histVal.getAttributeValue(), csn);
opendj-server-legacy/src/main/java/org/opends/server/replication/plugin/HistoricalAttributeValue.java
@@ -135,7 +135,7 @@
        isModDN = true;
      }
    }
    this.attrDesc = attrType != null ? new AttributeDescription(attrType, options) : null;
    this.attrDesc = attrType != null ? AttributeDescription.create(attrType, options) : null;
    csn = new CSN(token[1]);
    histKey = HistAttrModificationKey.decodeKey(token[2]);
@@ -159,6 +159,16 @@
    }
  }
  private AttributeType getAttributeType()
  {
    return attrDesc != null ? attrDesc.getAttributeType() : null;
  }
  private Set<String> getOptions()
  {
    return attrDesc != null ? attrDesc.getOptions() : Collections.<String> emptySet();
  }
  /**
   * Get the String form of the attribute type.
   *
@@ -216,8 +226,8 @@
   */
  public Modification generateMod()
  {
    AttributeBuilder builder = new AttributeBuilder(attrDesc.attributeType, attrString);
    builder.setOptions(attrDesc.options);
    AttributeBuilder builder = new AttributeBuilder(getAttributeType(), attrString);
    builder.setOptions(getOptions());
    if (histKey != ATTRDEL)
    {
@@ -247,7 +257,7 @@
   */
  public boolean isADDOperation()
  {
    return attrDesc.attributeType == null && !isModDN;
    return getAttributeType() == null && !isModDN;
  }
  /**
@@ -258,7 +268,7 @@
   */
  public boolean isMODDNOperation()
  {
    return attrDesc.attributeType == null && isModDN;
    return getAttributeType() == null && isModDN;
  }
  @Override
@@ -266,7 +276,7 @@
  {
    final StringBuilder sb = new StringBuilder();
    sb.append(attrString);
    for (String option : attrDesc.options)
    for (String option : getOptions())
    {
      sb.append(";").append(option);
    }
opendj-server-legacy/src/main/java/org/opends/server/types/AbstractAttribute.java
@@ -33,8 +33,6 @@
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import static org.opends.server.util.StaticUtils.*;
/** An abstract base class for implementing new types of {@link Attribute}. */
@org.opends.server.types.PublicAPI(
    stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
@@ -146,27 +144,16 @@
  /**
   * {@inheritDoc}
   * <p>
   * This implementation returns <code>true</code> if the provided
   * collection of options is <code>null</code> or empty. If the
   * This implementation returns {@code true} if the provided
   * collection of options is {@code null} or empty. If the
   * collection is non-empty and this attribute does not have any
   * options then it returns <code>false</code>. Otherwise, each
   * option in the provided collection is checked using
   * {@link #hasOption(String)} and <code>true</code> is
   * options then it returns {@code false}. Otherwise, {@code true} is
   * returned if all the provided options are present.
   */
  @Override
  public boolean hasAllOptions(Collection<String> options)
  {
    if (options == null || options.isEmpty())
    {
      return true;
    }
    if (hasOptions())
    {
      return hasAllOptions0(options);
    }
    return false;
    return AttributeDescription.containsAllOptions(getOptions(), options);
  }
  @Override
@@ -194,24 +181,12 @@
   * This implementation calls {@link #getOptions()} to
   * retrieve this attribute's set of options and then compares them
   * one at a time against the provided option. All comparisons are
   * case insensitive (this is why we iterate through the set of
   * options, rather than doing a simpler set membership test).
   * case insensitive.
   */
  @Override
  public boolean hasOption(String option)
  {
    String noption = toLowerCase(option);
    // Cannot use Set.contains() because the options are not normalized.
    for (String o : getOptions())
    {
      String no = toLowerCase(o);
      if (no.equals(noption))
      {
        return true;
      }
    }
    return false;
    return AttributeDescription.containsOption(getOptions(), option);
  }
  /**
@@ -259,31 +234,14 @@
  @Override
  public boolean optionsEqual(Set<String> options)
  {
    if (options == null)
    if (options != null)
    {
      return getOptions().size() == options.size()
          && hasAllOptions(options);
    }
      return !hasOptions();
    }
    if (getOptions().size() == options.size())
    {
      return hasAllOptions0(options);
    }
    return false;
  }
  /** Cannot use Set.containsAll() because the set of options are not normalized. */
  private boolean hasAllOptions0(Collection<String> options)
  {
    for (String option : options)
    {
      if (!hasOption(option))
      {
        return false;
      }
    }
    return true;
  }
  @Override
  public final String toString()
  {
opendj-server-legacy/src/main/java/org/opends/server/types/Attribute.java
@@ -61,10 +61,10 @@
   *
   * @param assertionValue
   *          The assertion value for which to make the determination.
   * @return <CODE>UNDEFINED</CODE> if this attribute does not have
   *         an approximate matching rule, <CODE>TRUE</CODE> if at
   * @return {@link ConditionResult#UNDEFINED} if this attribute does not have
   *         an approximate matching rule, {@link ConditionResult#TRUE} if at
   *         least one value is approximately equal to the provided
   *         value, or <CODE>false</CODE> otherwise.
   *         value, or {@link ConditionResult#FALSE} otherwise.
   */
  ConditionResult approximatelyEqualTo(ByteString assertionValue);
@@ -73,8 +73,8 @@
   *
   * @param value
   *          The value for which to make the determination.
   * @return <CODE>true</CODE> if this attribute has the specified
   *         value, or <CODE>false</CODE> if not.
   * @return {@code true} if this attribute has the specified
   *         value, or {@code false} if not.
   */
  boolean contains(ByteString value);
@@ -84,8 +84,8 @@
   *
   * @param values
   *          The set of values for which to make the determination.
   * @return <CODE>true</CODE> if this attribute contains all the
   *         values in the provided collection, or <CODE>false</CODE>
   * @return {@code true} if this attribute contains all the
   *         values in the provided collection, or {@code false}
   *         if it does not contain at least one of them.
   */
  boolean containsAll(Collection<ByteString> values);
@@ -95,8 +95,8 @@
   *
   * @param assertionValue
   *          The assertion value for which to make the determination.
   * @return <CODE>true</CODE> if this attribute matches the specified assertion
   *         value, or <CODE>false</CODE> if not.
   * @return {@code true} if this attribute matches the specified assertion
   *         value, or {@code false} if not.
   */
  ConditionResult matchesEqualityAssertion(ByteString assertionValue);
@@ -107,9 +107,9 @@
   *
   * @param o
   *          The object for which to make the determination.
   * @return <CODE>true</CODE> if the provided object is an
   * @return {@code true} if the provided object is an
   *         attribute that is equal to this attribute, or
   *         <CODE>false</CODE> if not.
   *         {@code false} if not.
   */
  @Override
  boolean equals(Object o);
@@ -152,30 +152,26 @@
   *
   * @param assertionValue
   *          The assertion value for which to make the determination.
   * @return <CODE>UNDEFINED</CODE> if this attribute does not have
   *         an ordering matching rule, <CODE>TRUE</CODE> if at
   * @return {@link ConditionResult#UNDEFINED} if this attribute does not have
   *         an ordering matching rule, {@link ConditionResult#TRUE} if at
   *         least one value is greater than or equal to the provided
   *         assertion value, or <CODE>false</CODE> otherwise.
   *         assertion value, or {@link ConditionResult#FALSE} otherwise.
   */
  ConditionResult greaterThanOrEqualTo(ByteString assertionValue);
  /**
   * Indicates whether this attribute has all of the options in the
   * provided collection.
   * Indicates whether this attribute has all of the options in the provided collection.
   *
   * @param options
   *          The collection of options for which to make the
   *          determination (may be <code>null</code>).
   * @return <CODE>true</CODE> if this attribute has all of the
   *         specified options, or <CODE>false</CODE> if it does not
   *         have at least one of them.
   *          The collection of options for which to make the determination (may be {@code null}).
   * @return {@code true} if this attribute has all of the specified options,
   *         or {@code false} if it does not have at least one of them.
   */
  boolean hasAllOptions(Collection<String> options);
  /**
   * Retrieves the hash code for this attribute. It will be calculated
   * as the sum of the hash code for the attribute type and all
   * values.
   * as the sum of the hash code for the attribute type and all values.
   *
   * @return The hash code for this attribute.
   */
@@ -187,24 +183,24 @@
   *
   * @param option
   *          The option for which to make the determination.
   * @return <CODE>true</CODE> if this attribute has the specified
   *         option, or <CODE>false</CODE> if not.
   * @return {@code true} if this attribute has the specified option,
   *         or {@code false} if not.
   */
  boolean hasOption(String option);
  /**
   * Indicates whether this attribute has any options at all.
   *
   * @return <CODE>true</CODE> if this attribute has at least one
   *         option, or <CODE>false</CODE> if not.
   * @return {@code true} if this attribute has at least one
   *         option, or {@code false} if not.
   */
  boolean hasOptions();
  /**
   * Returns <code>true</code> if this attribute contains no
   * Returns {@code true} if this attribute contains no
   * attribute values.
   *
   * @return <CODE>true</CODE> if this attribute contains no
   * @return {@code true} if this attribute contains no
   *         attribute values.
   */
  boolean isEmpty();
@@ -229,7 +225,7 @@
   * Returns an iterator over the attribute values in this attribute.
   * The attribute values are returned in the order in which they were
   * added this attribute. The returned iterator does not support
   * attribute value removals via its <code>remove</code> method.
   * attribute value removals via {@link Iterator#remove()}.
   *
   * @return An iterator over the attribute values in this attribute.
   */
@@ -242,10 +238,10 @@
   *
   * @param assertionValue
   *          The assertion value for which to make the determination.
   * @return <CODE>UNDEFINED</CODE> if this attribute does not have
   *         an ordering matching rule, <CODE>TRUE</CODE> if at
   * @return {@link ConditionResult#UNDEFINED} if this attribute does not have
   *         an ordering matching rule, {@link ConditionResult#TRUE} if at
   *         least one value is less than or equal to the provided
   *         assertion value, or <CODE>false</CODE> otherwise.
   *         assertion value, or {@link ConditionResult#FALSE} otherwise.
   */
  ConditionResult lessThanOrEqualTo(ByteString assertionValue);
@@ -259,10 +255,10 @@
   *          The subAny components to use in the determination.
   * @param subFinal
   *          The subFinal component to use in the determination.
   * @return <CODE>UNDEFINED</CODE> if this attribute does not have
   *         a substring matching rule, <CODE>TRUE</CODE> if at
   * @return {@link ConditionResult#UNDEFINED} if this attribute does not have
   *         a substring matching rule, {@link ConditionResult#TRUE} if at
   *         least one value matches the provided substring, or
   *         <CODE>FALSE</CODE> otherwise.
   *         {@link ConditionResult#FALSE} otherwise.
   */
  ConditionResult matchesSubstring(ByteString subInitial,
      List<ByteString> subAny, ByteString subFinal);
@@ -273,8 +269,8 @@
   *
   * @param options
   *          The set of options for which to make the determination
   *          (may be <code>null</code>).
   * @return <CODE>true</CODE> if this attribute has exactly the
   *          (may be {@code null}).
   * @return {@code true} if this attribute has exactly the
   *         specified set of options.
   */
  boolean optionsEqual(Set<String> options);
opendj-server-legacy/src/main/java/org/opends/server/types/AttributeDescription.java
@@ -24,37 +24,251 @@
 */
package org.opends.server.types;
import static org.opends.server.util.StaticUtils.*;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.forgerock.util.Reject;
/** Temporary class until we move to {@link org.forgerock.opendj.ldap.AttributeDescription}. */
public final class AttributeDescription
public final class AttributeDescription implements Comparable<AttributeDescription>
{
  final AttributeType attributeType;
  final Set<String> options;
  private final AttributeType attributeType;
  private final Set<String> options;
  AttributeDescription(Attribute attr)
  private AttributeDescription(AttributeType attributeType, Set<String> options)
  {
    this(attr.getAttributeType(), attr.getOptions());
  }
  AttributeDescription(AttributeType attributeType, Set<String> options)
  {
    Reject.ifNull(attributeType);
    Reject.ifNull(options);
    this.attributeType = attributeType;
    this.options = options;
  }
  String toOptionsString()
  /**
   * Creates an attribute description with the attribute type and options of the provided
   * {@link Attribute}.
   *
   * @param attr
   *          The attribute.
   * @return The attribute description.
   * @throws NullPointerException
   *           If {@code attributeType} or {@code options} was {@code null}.
   */
  public static AttributeDescription create(Attribute attr)
  {
    if (options != null)
    {
      StringBuilder optionsBuilder = new StringBuilder();
      for (String s : options)
      {
        optionsBuilder.append(';').append(s);
    return create(attr.getAttributeType(), attr.getOptions());
      }
      return optionsBuilder.toString();
  /**
   * Creates an attribute description having the provided attribute type and options.
   *
   * @param attributeType
   *          The attribute type.
   * @param options
   *          The attribute options.
   * @return The attribute description.
   * @throws NullPointerException
   *           If {@code attributeType} or {@code options} was {@code null}.
   */
  public static AttributeDescription create(AttributeType attributeType, Set<String> options)
  {
    return new AttributeDescription(attributeType, options);
    }
    return "";
  /**
   * Returns the attribute type associated with this attribute description.
   *
   * @return The attribute type associated with this attribute description.
   */
  public AttributeType getAttributeType()
  {
    return attributeType;
  }
  /**
   * Returns the set of not normalized options contained in this attribute description.
   *
   * @return A set containing the not normalized options.
   */
  public Set<String> getOptions()
  {
    return options;
  }
  /**
   * Indicates whether the provided set of not normalized options contains the provided option.
   *
   * @param options
   *          The set of not normalized options where to do the search.
   * @param optionToFind
   *          The option for which to make the determination.
   * @return {@code true} if the provided set of options has the provided option, or {@code false}
   *         if not.
   */
  public static boolean containsOption(Set<String> options, String optionToFind)
  {
    String normToFind = toLowerCase(optionToFind);
    // Cannot use Set.contains() because the options are not normalized.
    for (String o : options)
    {
      String norm = toLowerCase(o);
      if (norm.equals(normToFind))
      {
        return true;
      }
    }
    return false;
  }
  /**
   * Indicates whether the provided first set of not normalized options contains all values from the
   * second set of not normalized options.
   *
   * @param options1
   *          The first set of not normalized options where to do the search.
   * @param options2
   *          The second set of not normalized options that must all be found.
   * @return {@code true} if the first provided set of options has all the options from the second
   *         provided set of options.
   */
  public static boolean containsAllOptions(Collection<String> options1, Collection<String> options2)
  {
    if (options1 == options2)
    {
      return true;
    }
    else if (isEmpty(options2))
    {
      return true;
    }
    else if (isEmpty(options1))
    {
      return false;
    }
    // normalize all options before calling containsAll()
    Set<String> set1 = toLowercaseSet(options1);
    Set<String> set2 = toLowercaseSet(options2);
    return set1.size() >= set2.size() && set1.containsAll(set2);
  }
  /**
   * Indicates whether the provided first set of not normalized options equals the second set of not
   * normalized options.
   *
   * @param options1
   *          The first set of not normalized options.
   * @param options2
   *          The second set of not normalized options.
   * @return {@code true} if the first provided set of options equals the second provided set of
   *         options.
   */
  public static boolean optionsEqual(Set<String> options1, Set<String> options2)
  {
    if (options1 == options2)
    {
      return true;
    }
    else if (isEmpty(options2))
    {
      return isEmpty(options1);
    }
    else if (isEmpty(options1))
    {
      return false;
    }
    // normalize all options before calling containsAll()
    Set<String> set1 = toLowercaseSet(options1);
    Set<String> set2 = toLowercaseSet(options2);
    return set1.equals(set2);
  }
  private static boolean isEmpty(Collection<String> col)
  {
    return col == null || col.isEmpty();
  }
  private static SortedSet<String> toLowercaseSet(Collection<String> strings)
  {
    final SortedSet<String> results = new TreeSet<>();
    for (String s : strings)
    {
      results.add(toLowerCase(s));
    }
    return results;
  }
  @Override
  public int compareTo(AttributeDescription other)
  {
    if (this == other)
    {
      return 0;
    }
    return compare(attributeType, options, other.attributeType, other.options);
  }
  /**
   * Compares the first attribute type and options to the second attribute type and options, as if
   * they were both instances of {@link AttributeDescription}.
   * <p>
   * The attribute types are compared first and then, if equal, the options are normalized, sorted,
   * and compared.
   *
   * @param attrType1
   *          The first attribute type to be compared.
   * @param options1
   *          The first options to be compared.
   * @param attrType2
   *          The second attribute type to be compared.
   * @param options2
   *          The second options to be compared.
   * @return A negative integer, zero, or a positive integer as this attribute description is less
   *         than, equal to, or greater than the specified attribute description.
   * @throws NullPointerException
   *           If {@code name} was {@code null}.
   * @see AttributeDescription#compareTo(AttributeDescription)
   */
  public static int compare(AttributeType attrType1, Set<String> options1,
      AttributeType attrType2, Set<String> options2)
  {
    int cmp = attrType1.compareTo(attrType2);
    if (cmp != 0)
    {
      return cmp;
    }
    if (options1 == options2)
    {
      return 0;
    }
    return compare(toLowercaseSet(options1), toLowercaseSet(options2));
  }
  private static int compare(SortedSet<String> options1, SortedSet<String> options2)
  {
    Iterator<String> it1 = options1.iterator();
    Iterator<String> it2 = options2.iterator();
    while (it1.hasNext() && it2.hasNext())
    {
      int cmp = it1.next().compareTo(it2.next());
      if (cmp != 0)
      {
        return cmp;
      }
    }
    if (it1.hasNext())
    {
      return 1;
    }
    else if (it2.hasNext())
    {
      return -1;
    }
    return 0;
  }
  @Override
@@ -69,7 +283,7 @@
      return false;
    }
    final AttributeDescription other = (AttributeDescription) obj;
    return attributeType.equals(other.attributeType) && options.equals(other.options);
    return attributeType.equals(other.attributeType) && optionsEqual(options, other.options);
  }
  @Override
@@ -85,6 +299,13 @@
  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "(" + "attributeType=" + attributeType + ", options=" + options + ")";
    final StringBuilder buffer = new StringBuilder();
    buffer.append(attributeType.getNormalizedPrimaryName());
    for (String option : options)
    {
      buffer.append(';');
      buffer.append(option);
    }
    return buffer.toString();
  }
}
opendj-server-legacy/src/test/java/org/opends/server/types/AttributeBuilderTest.java
@@ -1396,7 +1396,7 @@
  /**
   * Tests {@link Attribute#hasAllOptions(java.util.Collection)}.
   * Tests {@link Attribute#hasAllOptions(Collection)}.
   *
   * @param testCase
   *          Test case index (useful for debugging).
@@ -1416,20 +1416,21 @@
      AttributeType type, String name, String[] options, String[] values)
      throws Exception
  {
    // Check hasAllOptions().
    Assert.assertTrue(a.hasAllOptions(null));
    Assert.assertTrue(a.hasAllOptions(Collections.<String> emptySet()));
    Assert.assertTrue(a.hasAllOptions(Arrays.asList(options)));
    if (options.length > 1)
    {
      Assert.assertTrue(a.hasAllOptions(Arrays.asList(options).subList(1,
          options.length)));
      Assert.assertTrue(a.hasAllOptions(Arrays.asList(options).subList(1, options.length)));
    }
    List<String> tmp = newArrayList(options);
    tmp.add("xxxx");
    Assert.assertFalse(a.hasAllOptions(tmp));
    Assert.assertFalse(a.hasAllOptions(newHashSet("xxxx")));
    tmp.clear();
    for (String option : options)
    {