/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2012-2013 ForgeRock AS
*/
package org.opends.server.types;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.StaticUtils.*;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.server.api.ApproximateMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.api.SubstringMatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.util.Validator;
/**
* This class provides an interface for creating new non-virtual
* {@link Attribute}s, or "real" attributes.
* <p>
* An attribute can be created incrementally using either
* {@link #AttributeBuilder(AttributeType)} or
* {@link #AttributeBuilder(AttributeType, String)}. The caller is
* then free to add new options using {@link #setOption(String)} and
* new values using {@link #add(AttributeValue)} or
* {@link #addAll(Collection)}. Once all the options and values have
* been added, the attribute can be retrieved using the
* {@link #toAttribute()} method.
* <p>
* A real attribute can also be created based on the values taken from
* another attribute using the {@link #AttributeBuilder(Attribute)}
* constructor. The caller is then free to modify the values within
* the attribute before retrieving the updated attribute using the
* {@link #toAttribute()} method.
* <p>
* The {@link org.opends.server.types.Attributes} class contains
* convenience factory methods,
* e.g. {@link org.opends.server.types.Attributes#empty(String)} for
* creating empty attributes, and
* {@link org.opends.server.types.Attributes#create(String, String)}
* for creating single-valued attributes.
* <p>
* <code>AttributeBuilder</code>s can be re-used. Once an
* <code>AttributeBuilder</code> has been converted to an
* {@link Attribute} using {@link #toAttribute()}, its state is reset
* so that its attribute type, user-provided name, options, and values
* are all undefined:
*
* <pre>
* AttributeBuilder builder = new AttributeBuilder();
* for (int i = 0; i < 10; i++)
* {
* builder.setAttributeType("myAttribute" + i);
* builder.setOption("an-option");
* builder.add("a value");
* Attribute attribute = builder.toAttribute();
* // Do something with attribute...
* }
* </pre>
* <p>
* <b>Implementation Note:</b> this class is optimized for the common
* case where there is only a single value. By doing so, we avoid
* using unnecessary storage space and also performing any unnecessary
* normalization. In addition, this class is optimized for the common
* cases where there are zero or one attribute type options.
*/
@org.opends.server.types.PublicAPI(
stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
mayInstantiate = true,
mayExtend = false,
mayInvoke = true)
public final class AttributeBuilder
implements Iterable<AttributeValue>
{
/**
* A real attribute - options handled by sub-classes.
*/
private static abstract class RealAttribute
extends AbstractAttribute
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
// The attribute type for this attribute.
private final AttributeType attributeType;
// The name of this attribute as provided by the end user.
private final String name;
// The unmodifiable set of values for this attribute.
private final Set<AttributeValue> values;
/**
* Creates a new real attribute.
*
* @param attributeType
* The attribute type.
* @param name
* The user-provided attribute name.
* @param values
* The attribute values.
*/
private RealAttribute(
AttributeType attributeType,
String name,
Set<AttributeValue> values)
{
this.attributeType = attributeType;
this.name = name;
this.values = values;
}
/**
* {@inheritDoc}
*/
@Override
public final ConditionResult approximatelyEqualTo(
AttributeValue value)
{
ApproximateMatchingRule matchingRule = attributeType
.getApproximateMatchingRule();
if (matchingRule == null)
{
return ConditionResult.UNDEFINED;
}
ByteString normalizedValue;
try
{
normalizedValue =
matchingRule.normalizeValue(value.getValue());
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// We couldn't normalize the provided value. We should return
// "undefined".
return ConditionResult.UNDEFINED;
}
ConditionResult result = ConditionResult.FALSE;
for (AttributeValue v : values)
{
try
{
ByteString nv = matchingRule.normalizeValue(v.getValue());
if (matchingRule.approximatelyMatch(nv, normalizedValue))
{
return ConditionResult.TRUE;
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// We couldn't normalize one of the attribute values. If we
// can't find a definite match, then we should return
// "undefined".
result = ConditionResult.UNDEFINED;
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final boolean contains(AttributeValue value)
{
return values.contains(value);
}
/**
* {@inheritDoc}
*/
@Override
public final boolean containsAll(
Collection<AttributeValue> values)
{
return this.values.containsAll(values);
}
/**
* {@inheritDoc}
*/
@Override
public final AttributeType getAttributeType()
{
return attributeType;
}
/**
* {@inheritDoc}
*/
@Override
public final String getName()
{
return name;
}
/**
* {@inheritDoc}
*/
@Override
public final ConditionResult greaterThanOrEqualTo(
AttributeValue value)
{
OrderingMatchingRule matchingRule = attributeType
.getOrderingMatchingRule();
if (matchingRule == null)
{
return ConditionResult.UNDEFINED;
}
ByteString normalizedValue;
try
{
normalizedValue =
matchingRule.normalizeValue(value.getValue());
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// We couldn't normalize the provided value. We should return
// "undefined".
return ConditionResult.UNDEFINED;
}
ConditionResult result = ConditionResult.FALSE;
for (AttributeValue v : values)
{
try
{
ByteString nv =
matchingRule.normalizeValue(v.getValue());
int comparisonResult = matchingRule
.compareValues(nv, normalizedValue);
if (comparisonResult >= 0)
{
return ConditionResult.TRUE;
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// We couldn't normalize one of the attribute values. If we
// can't find a definite match, then we should return
// "undefined".
result = ConditionResult.UNDEFINED;
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isVirtual()
{
return false;
}
/**
* {@inheritDoc}
*/
@Override
public final Iterator<AttributeValue> iterator()
{
return values.iterator();
}
/**
* {@inheritDoc}
*/
@Override
public final ConditionResult lessThanOrEqualTo(
AttributeValue value)
{
OrderingMatchingRule matchingRule = attributeType
.getOrderingMatchingRule();
if (matchingRule == null)
{
return ConditionResult.UNDEFINED;
}
ByteString normalizedValue;
try
{
normalizedValue =
matchingRule.normalizeValue(value.getValue());
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// We couldn't normalize the provided value. We should return
// "undefined".
return ConditionResult.UNDEFINED;
}
ConditionResult result = ConditionResult.FALSE;
for (AttributeValue v : values)
{
try
{
ByteString nv = matchingRule.normalizeValue(v.getValue());
int comparisonResult = matchingRule
.compareValues(nv, normalizedValue);
if (comparisonResult <= 0)
{
return ConditionResult.TRUE;
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// We couldn't normalize one of the attribute values. If we
// can't find a definite match, then we should return
// "undefined".
result = ConditionResult.UNDEFINED;
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final ConditionResult matchesSubstring(
ByteString subInitial,
List<ByteString> subAny, ByteString subFinal)
{
SubstringMatchingRule matchingRule = attributeType
.getSubstringMatchingRule();
if (matchingRule == null)
{
return ConditionResult.UNDEFINED;
}
ByteString normalizedSubInitial;
if (subInitial == null)
{
normalizedSubInitial = null;
}
else
{
try
{
normalizedSubInitial =
matchingRule.normalizeSubstring(subInitial);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// The substring couldn't be normalized. We have to return
// "undefined".
return ConditionResult.UNDEFINED;
}
}
ArrayList<ByteSequence> normalizedSubAny;
if (subAny == null)
{
normalizedSubAny = null;
}
else
{
normalizedSubAny = new ArrayList<ByteSequence>(subAny.size());
for (ByteString subAnyElement : subAny)
{
try
{
normalizedSubAny
.add(matchingRule.normalizeSubstring(subAnyElement));
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// The substring couldn't be normalized. We have to return
// "undefined".
return ConditionResult.UNDEFINED;
}
}
}
ByteString normalizedSubFinal;
if (subFinal == null)
{
normalizedSubFinal = null;
}
else
{
try
{
normalizedSubFinal =
matchingRule.normalizeSubstring(subFinal);
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// The substring couldn't be normalized. We have to return
// "undefined".
return ConditionResult.UNDEFINED;
}
}
ConditionResult result = ConditionResult.FALSE;
for (AttributeValue value : values)
{
try
{
if (matchingRule.valueMatchesSubstring(
attributeType.getSubstringMatchingRule().
normalizeValue(value.getValue()),
normalizedSubInitial,
normalizedSubAny,
normalizedSubFinal))
{
return ConditionResult.TRUE;
}
}
catch (Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
// The value couldn't be normalized. If we can't find a
// definite match, then we should return "undefined".
result = ConditionResult.UNDEFINED;
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public final int size()
{
return values.size();
}
/**
* {@inheritDoc}
*/
@Override
public final void toString(StringBuilder buffer)
{
buffer.append("Attribute(");
buffer.append(getNameWithOptions());
buffer.append(", {");
boolean firstValue = true;
for (AttributeValue value : values)
{
if (!firstValue)
{
buffer.append(", ");
}
value.toString(buffer);
firstValue = false;
}
buffer.append("})");
}
}
/**
* A real attribute with a many options.
*/
private static final class RealAttributeManyOptions
extends RealAttribute
{
// The normalized options.
private final SortedSet<String> normalizedOptions;
// The options.
private final Set<String> options;
/**
* Creates a new real attribute that has multiple options.
*
* @param attributeType
* The attribute type.
* @param name
* The user-provided attribute name.
* @param values
* The attribute values.
* @param options
* The attribute options.
* @param normalizedOptions
* The normalized attribute options.
*/
private RealAttributeManyOptions(
AttributeType attributeType,
String name,
Set<AttributeValue> values,
Set<String> options,
SortedSet<String> normalizedOptions)
{
super(attributeType, name, values);
this.options = options;
this.normalizedOptions = normalizedOptions;
}
/**
* {@inheritDoc}
*/
@Override
public Set<String> getOptions()
{
return options;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasOption(String option)
{
String s = toLowerCase(option);
return normalizedOptions.contains(s);
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasOptions()
{
return true;
}
}
/**
* A real attribute with no options.
*/
private static final class RealAttributeNoOptions
extends RealAttribute
{
/**
* Creates a new real attribute that has no options.
*
* @param attributeType
* The attribute type.
* @param name
* The user-provided attribute name.
* @param values
* The attribute values.
*/
private RealAttributeNoOptions(
AttributeType attributeType,
String name,
Set<AttributeValue> values)
{
super(attributeType, name, values);
}
/**
* {@inheritDoc}
*/
@Override
public String getNameWithOptions()
{
return getName();
}
/**
* {@inheritDoc}
*/
@Override
public Set<String> getOptions()
{
return Collections.emptySet();
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasAllOptions(Collection<String> options)
{
return (options == null || options.isEmpty());
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasOption(String option)
{
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasOptions()
{
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean optionsEqual(Set<String> options)
{
return (options == null || options.isEmpty());
}
}
/**
* A real attribute with a single option.
*/
private static final class RealAttributeSingleOption
extends RealAttribute
{
// The normalized single option.
private final String normalizedOption;
// A singleton set containing the single option.
private final Set<String> option;
/**
* Creates a new real attribute that has a single option.
*
* @param attributeType
* The attribute type.
* @param name
* The user-provided attribute name.
* @param values
* The attribute values.
* @param option
* The attribute option.
*/
private RealAttributeSingleOption(
AttributeType attributeType,
String name,
Set<AttributeValue> values,
String option)
{
super(attributeType, name, values);
this.option = Collections.singleton(option);
this.normalizedOption = toLowerCase(option);
}
/**
* {@inheritDoc}
*/
@Override
public Set<String> getOptions()
{
return option;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasOption(String option)
{
String s = toLowerCase(option);
return normalizedOption.equals(s);
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasOptions()
{
return true;
}
}
/**
* A small set of values. This set implementation is optimized to
* use as little memory as possible in the case where there zero or
* one elements. In addition, any normalization of elements is
* delayed until the second element is added (normalization may be
* triggered by invoking {@link Object#hashCode()} or
* {@link Object#equals(Object)}.
*
* @param <T>
* The type of elements to be contained in this small set.
*/
private static final class SmallSet<T>
extends AbstractSet<T>
implements Set<T>
{
// The set of elements if there are more than one.
private LinkedHashSet<T> elements = null;
// The first element.
private T firstElement = null;
/**
* Creates a new small set which is initially empty.
*/
public SmallSet()
{
// No implementation required.
}
/**
* {@inheritDoc}
*/
@Override
public boolean add(T e)
{
// Special handling for the first value. This avoids potentially
// expensive normalization.
if (firstElement == null && elements == null)
{
firstElement = e;
return true;
}
// Create the value set if necessary.
if (elements == null)
{
if (firstElement.equals(e))
{
return false;
}
elements = new LinkedHashSet<T>(2);
// Move the first value into the set.
elements.add(firstElement);
firstElement = null;
}
return elements.add(e);
}
/**
* {@inheritDoc}
*/
@Override
public boolean addAll(Collection<? extends T> c)
{
if (elements != null)
{
return elements.addAll(c);
}
if (firstElement != null)
{
elements = new LinkedHashSet<T>(1 + c.size());
elements.add(firstElement);
firstElement = null;
return elements.addAll(c);
}
// Initially empty.
switch (c.size())
{
case 0:
// Do nothing.
return false;
case 1:
firstElement = c.iterator().next();
return true;
default:
elements = new LinkedHashSet<T>(c);
return true;
}
}
/**
* {@inheritDoc}
*/
@Override
public void clear()
{
firstElement = null;
elements = null;
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<T> iterator()
{
if (elements != null)
{
return elements.iterator();
}
else if (firstElement != null)
{
return new Iterator<T>()
{
private boolean hasNext = true;
@Override
public boolean hasNext()
{
return hasNext;
}
@Override
public T next()
{
if (!hasNext)
{
throw new NoSuchElementException();
}
hasNext = false;
return firstElement;
}
@Override
public void remove()
{
if (hasNext || firstElement == null)
{
throw new IllegalStateException();
}
firstElement = null;
}
};
}
else
{
return Collections.<T> emptySet().iterator();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(Object o)
{
if (elements != null)
{
// Note: if there is one or zero values left we could stop
// using the set. However, lets assume that if the set
// was multi-valued before then it may become multi-valued
// again.
return elements.remove(o);
}
if (firstElement != null && firstElement.equals(o))
{
firstElement = null;
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean contains(Object o)
{
if (elements != null)
{
return elements.contains(o);
}
return (firstElement != null && firstElement.equals(o));
}
/**
* Sets the initial capacity of this small set. If this small set
* already contains elements or if its capacity has already been
* defined then an {@link IllegalStateException} is thrown.
*
* @param initialCapacity
* The initial capacity of this small set.
* @throws IllegalStateException
* If this small set already contains elements or if its
* capacity has already been defined.
*/
public void setInitialCapacity(int initialCapacity)
throws IllegalStateException
{
Validator.ensureTrue(initialCapacity >= 0);
if (elements != null)
{
throw new IllegalStateException();
}
if (initialCapacity > 1)
{
elements = new LinkedHashSet<T>(initialCapacity);
}
}
/**
* {@inheritDoc}
*/
@Override
public int size()
{
if (elements != null)
{
return elements.size();
}
else if (firstElement != null)
{
return 1;
}
else
{
return 0;
}
}
}
/**
* Creates an attribute that has no options.
* <p>
* This method is only intended for use by the {@link Attributes}
* class.
*
* @param attributeType
* The attribute type.
* @param name
* The user-provided attribute name.
* @param values
* The attribute values.
* @return The new attribute.
*/
static Attribute create(AttributeType attributeType, String name,
Set<AttributeValue> values)
{
return new RealAttributeNoOptions(attributeType, name, values);
}
/**
* Gets the named attribute type, creating a default attribute if
* necessary.
*
* @param attributeName
* The name of the attribute type.
* @return The attribute type associated with the provided attribute
* name.
*/
private static AttributeType getAttributeType(String attributeName)
{
String lc = toLowerCase(attributeName);
AttributeType type = DirectoryServer.getAttributeType(lc);
if (type == null)
{
type = DirectoryServer.getDefaultAttributeType(attributeName);
}
return type;
}
// The attribute type for this attribute.
private AttributeType attributeType = null;
// The name of this attribute as provided by the end user.
private String name = null;
// The normalized set of options if there are more than one.
private SortedSet<String> normalizedOptions = null;
// The set of options.
private final SmallSet<String> options = new SmallSet<String>();
// The set of values for this attribute.
private final SmallSet<AttributeValue> values =
new SmallSet<AttributeValue>();
/**
* Creates a new attribute builder with an undefined attribute type
* and user-provided name. The attribute type, and optionally the
* user-provided name, must be defined using
* {@link #setAttributeType(AttributeType)} before the attribute
* builder can be converted to an {@link Attribute}. Failure to do
* so will yield an {@link IllegalStateException}.
*/
public AttributeBuilder()
{
// No implementation required.
}
/**
* Creates a new attribute builder from an existing attribute.
* <p>
* Modifications to the attribute builder will not impact the
* provided attribute.
*
* @param attribute
* The attribute to be copied.
*/
public AttributeBuilder(Attribute attribute)
{
this(attribute, false);
}
/**
* Creates a new attribute builder from an existing attribute,
* optionally omitting the values contained in the provided
* attribute.
* <p>
* Modifications to the attribute builder will not impact the
* provided attribute.
*
* @param attribute
* The attribute to be copied.
* @param omitValues
* <CODE>true</CODE> if the values should be omitted.
*/
public AttributeBuilder(Attribute attribute, boolean omitValues)
{
this(attribute.getAttributeType(), attribute.getName());
for (String option : attribute.getOptions())
{
setOption(option);
}
if (!omitValues)
{
addAll(attribute);
}
}
/**
* Creates a new attribute builder with the specified type and no
* options and no values.
*
* @param attributeType
* The attribute type for this attribute builder.
*/
public AttributeBuilder(AttributeType attributeType)
{
this(attributeType, attributeType.getNameOrOID());
}
/**
* Creates a new attribute builder with the specified type and
* user-provided name and no options and no values.
*
* @param attributeType
* The attribute type for this attribute builder.
* @param name
* The user-provided name for this attribute builder.
*/
public AttributeBuilder(AttributeType attributeType, String name)
{
Validator.ensureNotNull(attributeType, name);
this.attributeType = attributeType;
this.name = name;
}
/**
* Creates a new attribute builder with the specified attribute name
* and no options and no values.
* <p>
* If the attribute name cannot be found in the schema, a new
* attribute type is created using the default attribute syntax.
*
* @param attributeName
* The attribute name for this attribute builder.
*/
public AttributeBuilder(String attributeName)
{
this(getAttributeType(attributeName), attributeName);
}
/**
* Adds the specified attribute value to this attribute builder if
* it is not already present.
*
* @param value
* The attribute value to be added to this attribute
* builder.
* @return <code>true</code> if this attribute builder did not
* already contain the specified attribute value.
*/
public boolean add(AttributeValue value)
{
return values.add(value);
}
/**
* Adds the specified attribute value to this attribute builder if
* it is not already present.
*
* @param valueString
* The string representation of the attribute value to be
* added to this attribute builder.
* @return <code>true</code> if this attribute builder did not
* already contain the specified attribute value.
*/
public boolean add(String valueString)
{
return add(AttributeValues.create(attributeType, valueString));
}
/**
* Adds the specified attribute value to this attribute builder if it is not
* already present.
*
* @param value
* The {@link ByteString} representation of the attribute value to be
* added to this attribute builder.
* @return <code>true</code> if this attribute builder did not already contain
* the specified attribute value.
*/
public boolean add(ByteString value)
{
return add(AttributeValues.create(attributeType, value));
}
/**
* Adds all the values from the specified attribute to this
* attribute builder if they are not already present.
*
* @param attribute
* The attribute containing the values to be added to this
* attribute builder.
* @return <code>true</code> if this attribute builder was
* modified.
*/
public boolean addAll(Attribute attribute)
{
boolean wasModified = false;
for (AttributeValue v : attribute)
{
wasModified |= add(v);
}
return wasModified;
}
/**
* Adds the specified attribute values to this attribute builder if
* they are not already present.
*
* @param values
* The attribute values to be added to this attribute
* builder.
* @return <code>true</code> if this attribute builder was
* modified.
*/
public boolean addAll(Collection<AttributeValue> values)
{
return this.values.addAll(values);
}
/**
* Removes all attribute values from this attribute builder.
*/
public void clear()
{
values.clear();
}
/**
* Indicates whether this attribute builder contains the specified
* value.
*
* @param value
* The value for which to make the determination.
* @return <CODE>true</CODE> if this attribute builder has the
* specified value, or <CODE>false</CODE> if not.
*/
public boolean contains(AttributeValue value)
{
return values.contains(value);
}
/**
* Indicates whether this attribute builder contains all the values
* in the collection.
*
* @param values
* The set of values for which to make the determination.
* @return <CODE>true</CODE> if this attribute builder contains
* all the values in the provided collection, or
* <CODE>false</CODE> if it does not contain at least one
* of them.
*/
public boolean containsAll(Collection<AttributeValue> values)
{
return this.values.containsAll(values);
}
/**
* Retrieves the attribute type for this attribute builder.
*
* @return The attribute type for this attribute builder, or
* <code>null</code> if one has not yet been specified.
*/
public AttributeType getAttributeType()
{
return attributeType;
}
/**
* Returns <code>true</code> if this attribute builder contains no
* attribute values.
*
* @return <CODE>true</CODE> if this attribute builder contains no
* attribute values.
*/
public boolean isEmpty()
{
return values.isEmpty();
}
/**
* Returns an iterator over the attribute values in this attribute
* builder. The attribute values are returned in the order in which
* they were added to this attribute builder. The returned iterator
* supports attribute value removals via its <code>remove</code>
* method.
*
* @return An iterator over the attribute values in this attribute
* builder.
*/
@Override
public Iterator<AttributeValue> iterator()
{
return values.iterator();
}
/**
* Removes the specified attribute value from this attribute builder
* if it is present.
*
* @param value
* The attribute value to be removed from this attribute
* builder.
* @return <code>true</code> if this attribute builder contained
* the specified attribute value.
*/
public boolean remove(AttributeValue value)
{
return values.remove(value);
}
/**
* Removes the specified attribute value from this attribute builder
* if it is present.
*
* @param valueString
* The string representation of the attribute value to be
* removed from this attribute builder.
* @return <code>true</code> if this attribute builder contained
* the specified attribute value.
*/
public boolean remove(String valueString)
{
AttributeValue value =
AttributeValues.create(attributeType, valueString);
return remove(value);
}
/**
* Removes all the values from the specified attribute from this
* attribute builder if they are not already present.
*
* @param attribute
* The attribute containing the values to be removed from
* this attribute builder.
* @return <code>true</code> if this attribute builder was
* modified.
*/
public boolean removeAll(Attribute attribute)
{
boolean wasModified = false;
for (AttributeValue v : attribute)
{
wasModified |= remove(v);
}
return wasModified;
}
/**
* Removes the specified attribute values from this attribute
* builder if they are present.
*
* @param values
* The attribute values to be removed from this attribute
* builder.
* @return <code>true</code> if this attribute builder was
* modified.
*/
public boolean removeAll(Collection<AttributeValue> values)
{
return this.values.removeAll(values);
}
/**
* Replaces all the values in this attribute value with the
* specified attribute value.
*
* @param value
* The attribute value to replace all existing values.
*/
public void replace(AttributeValue value)
{
clear();
add(value);
}
/**
* Replaces all the values in this attribute value with the
* specified attribute value.
*
* @param valueString
* The string representation of the attribute value to
* replace all existing values.
*/
public void replace(String valueString)
{
AttributeValue value =
AttributeValues.create(attributeType, valueString);
replace(value);
}
/**
* Replaces all the values in this attribute value with the
* attributes from the specified attribute.
*
* @param attribute
* The attribute containing the values to replace all
* existing values.
*/
public void replaceAll(Attribute attribute)
{
clear();
addAll(attribute);
}
/**
* Replaces all the values in this attribute value with the
* specified attribute values.
*
* @param values
* The attribute values to replace all existing values.
*/
public void replaceAll(Collection<AttributeValue> values)
{
clear();
addAll(values);
}
/**
* Sets the attribute type associated with this attribute builder.
*
* @param attributeType
* The attribute type for this attribute builder.
*/
public void setAttributeType(AttributeType attributeType)
{
setAttributeType(attributeType, attributeType.getNameOrOID());
}
/**
* Sets the attribute type and user-provided name associated with
* this attribute builder.
*
* @param attributeType
* The attribute type for this attribute builder.
* @param name
* The user-provided name for this attribute builder.
*/
public void setAttributeType(
AttributeType attributeType,
String name)
{
Validator.ensureNotNull(attributeType, name);
this.attributeType = attributeType;
this.name = name;
}
/**
* Sets the attribute type associated with this attribute builder
* using the provided attribute type name.
* <p>
* If the attribute name cannot be found in the schema, a new
* attribute type is created using the default attribute syntax.
*
* @param attributeName
* The attribute name for this attribute builder.
*/
public void setAttributeType(String attributeName)
{
setAttributeType(getAttributeType(attributeName), attributeName);
}
/**
* Sets the initial capacity of this attribute builders internal set
* of attribute values.
* <p>
* The initial capacity of an attribute builder defaults to one.
* Applications should override this default if the attribute being
* built is expected to contain many values.
* <p>
* This method should only be called before any attribute values
* have been added to this attribute builder. If it is called
* afterwards an {@link IllegalStateException} will be thrown.
*
* @param initialCapacity
* The initial capacity of this attribute builder.
* @return This attribute builder.
* @throws IllegalStateException
* If this attribute builder already contains attribute
* values.
*/
public AttributeBuilder setInitialCapacity(int initialCapacity)
throws IllegalStateException
{
values.setInitialCapacity(initialCapacity);
return this;
}
/**
* Adds the specified option to this attribute builder if it is not
* already present.
*
* @param option
* The option to be added to this attribute builder.
* @return <code>true</code> if this attribute builder did not
* already contain the specified option.
*/
public boolean setOption(String option)
{
switch (options.size())
{
case 0:
return options.add(option);
case 1:
// Normalize and add the first option to normalized set.
normalizedOptions = new TreeSet<String>();
normalizedOptions.add(toLowerCase(options.firstElement));
if (normalizedOptions.add(toLowerCase(option)))
{
options.add(option);
return true;
}
break;
default:
if (normalizedOptions.add(toLowerCase(option)))
{
options.add(option);
return true;
}
break;
}
return false;
}
/**
* Adds the specified options to this attribute builder if they are
* not already present.
*
* @param options
* The options to be added to this attribute builder.
* @return <code>true</code> if this attribute builder was
* modified.
*/
public boolean setOptions(Collection<String> options)
{
boolean isModified = false;
for (String option : options)
{
isModified |= setOption(option);
}
return isModified;
}
/**
* Indicates whether this attribute builder has exactly the
* specified set of options.
*
* This implementation returns
* {@link java.util.AbstractCollection#isEmpty()}
* if the provided set of options is <code>null</code>.
* Otherwise it checks that the size of the provided
* set of options is equal to the size of this attribute
* builder options, returns <code>false</code> if the
* sizes differ. If the sizes are the same then each
* option in the provided set is checked and if all the
* provided options are present <code>true</code> is
* returned.
*
* @param options
* The set of options for which to make the
* determination (may be <code>null</code>).
* @return <code>true</code> if this attribute
* builder has exactly the specified
* set of options.
*/
public boolean optionsEqual(Set<String> options)
{
if (options == null)
{
return this.options.isEmpty();
}
if (this.options.size() != options.size())
{
return false;
}
for (String option : options)
{
if (!this.options.contains(option))
{
return false;
}
}
return true;
}
/**
* Returns the number of attribute values in this attribute builder.
*
* @return The number of attribute values in this attribute builder.
*/
public int size()
{
return values.size();
}
/**
* Returns an attribute representing the content of this attribute
* builder.
* <p>
* For efficiency purposes this method resets the content of this
* attribute builder so that it no longer contains any options or
* values and its attribute type is <code>null</code>.
*
* @return An attribute representing the content of this attribute
* builder.
* @throws IllegalStateException
* If this attribute builder has an undefined attribute
* type or name.
*/
public Attribute toAttribute() throws IllegalStateException
{
if (attributeType == null)
{
throw new IllegalStateException(
"Undefined attribute type or name");
}
// First determine the minimum representation required for the set
// of values.
Set<AttributeValue> newValues;
if (values.elements != null)
{
newValues = Collections.unmodifiableSet(values.elements);
}
else if (values.firstElement != null)
{
newValues = Collections.singleton(values.firstElement);
}
else
{
newValues = Collections.emptySet();
}
// Now create the appropriate attribute based on the options.
Attribute attribute;
switch (options.size())
{
case 0:
attribute =
new RealAttributeNoOptions(attributeType, name, newValues);
break;
case 1:
attribute =
new RealAttributeSingleOption(attributeType, name, newValues,
options.firstElement);
break;
default:
attribute =
new RealAttributeManyOptions(attributeType, name, newValues,
Collections.unmodifiableSet(options.elements), Collections
.unmodifiableSortedSet(normalizedOptions));
break;
}
// Reset the state of this builder.
attributeType = null;
name = null;
normalizedOptions = null;
options.clear();
values.clear();
return attribute;
}
/**
* {@inheritDoc}
*/
@Override
public final String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("AttributeBuilder(");
builder.append(String.valueOf(name));
for (String option : options)
{
builder.append(';');
builder.append(option);
}
builder.append(", {");
boolean firstValue = true;
for (AttributeValue value : values)
{
if (!firstValue)
{
builder.append(", ");
}
value.toString(builder);
firstValue = false;
}
builder.append("})");
return builder.toString();
}
}