/*
 * 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 2008-2009 Sun Microsystems, Inc.
 */
package org.opends.server.types;



import static org.opends.server.util.StaticUtils.*;

import java.util.Collection;
import java.util.Collections;

import org.opends.server.core.DirectoryServer;



/**
 * This class contains various methods for manipulating
 * {@link Attribute}s as well as static factory methods for
 * facilitating common {@link Attribute} construction use-cases.
 * <p>
 * Of particular interest are the following three factory methods:
 *
 * <pre>
 * empty(String);
 *
 * create(String, String);
 *
 * create(String, String, String...);
 * </pre>
 *
 * These are provided in order to facilitate construction of empty,
 * single-valued, and multi-valued attributes respectively, for
 * example, in unit tests. The last factory method is not designed for
 * performance critical functionality and, instead, an
 * {@link AttributeBuilder} should be used in order to incrementally
 * construct multi-valued attributes.
 */
public final class Attributes
{

  /**
   * Creates a new single-valued attribute with the specified
   * attribute type and value.
   *
   * @param attributeType
   *          The attribute type to use.
   * @param value
   *          The attribute value.
   * @return A new attribute with the attribute type and value.
   */
  public static Attribute create(AttributeType attributeType,
      AttributeValue value)
  {
    return create(attributeType, attributeType.getNameOrOID(), value);
  }



  /**
   * Creates a new single-valued attribute with the specified name and
   * value.
   *
   * @param attributeType
   *          The attribute type to use.
   * @param valueString
   *          The String representation of the attribute value.
   * @return A new attribute with the specified name and value.
   */
  public static Attribute create(AttributeType attributeType,
      String valueString)
  {
    return create(attributeType, attributeType.getNameOrOID(),
        valueString);
  }



  /**
   * Creates a new single-valued attribute with the specified
   * attribute type and value.
   *
   * @param attributeType
   *          The attribute type to use.
   * @param name
   *          The user-provided name for this attribute.
   * @param value
   *          The attribute value.
   * @return A new attribute with the attribute type and value.
   */
  public static Attribute create(AttributeType attributeType,
      String name, AttributeValue value)
  {
    return AttributeBuilder.create(attributeType, name, Collections
        .singleton(value));
  }



  /**
   * Creates a new single-valued attribute with the attribute type and
   * value.
   *
   * @param attributeType
   *          The attribute type to use.
   * @param name
   *          The user-provided name for this attribute.
   * @param valueString
   *          The String representation of the attribute value.
   * @return A new attribute with the attribute type and value.
   */
  public static Attribute create(AttributeType attributeType,
      String name, String valueString)
  {
    AttributeValue value = AttributeValues.create(attributeType,
        valueString);
    return create(attributeType, name, value);
  }



  /**
   * Creates a new single-valued attribute with the specified
   * attribute name and attribute value.
   * <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 name or OID of the attribute type for this attribute
   *          (can be mixed case).
   * @param valueString
   *          The String representation of the attribute value.
   * @return A new attribute with the specified name and value.
   */
  public static Attribute create(String attributeName,
      String valueString)
  {
    return create(getAttributeType(attributeName), attributeName,
        valueString);
  }



  /**
   * Creates a new multi-valued attribute with the specified attribute
   * name and attribute values.
   * <p>
   * If the attribute name cannot be found in the schema, a new
   * attribute type is created using the default attribute syntax.
   * <p>
   * <b>NOTE:</b> this method is provided as a convenience and should
   * typically be reserved for use in unit tests and places where
   * performance is not an issue. In particular, this method will
   * construct a temporary array containing the attribute's values.
   * For peformance critical purposes, incrementally construct an
   * attribute using an {@link AttributeBuilder}.
   *
   * @param attributeName
   *          The name or OID of the attribute type for this attribute
   *          (can be mixed case).
   * @param firstValueString
   *          The string representation of the first attribute value.
   * @param otherValueStrings
   *          The string representation of the remaining attribute
   *          values.
   * @return A new attribute with the specified name and values.
   */
  public static Attribute create(String attributeName,
      String firstValueString, String... otherValueStrings)
  {
    AttributeBuilder builder = new AttributeBuilder(attributeName);

    builder.add(firstValueString);

    for (String value : otherValueStrings)
    {
      builder.add(value);
    }

    return builder.toAttribute();
  }



  /**
   * Creates a new attribute which has the same attribute type and
   * attribute options as the provided attribute but no attribute
   * values.
   *
   * @param attribute
   *          The attribute to be copied.
   * @return A new attribute which has the same attribute type and
   *         attribute options as the provided attribute but no
   *         attribute values.
   */
  public static Attribute empty(Attribute attribute)
  {
    return new AttributeBuilder(attribute, true).toAttribute();
  }



  /**
   * Creates a new attribute with the provided attribute type and no
   * values.
   *
   * @param attributeType
   *          The attribute type to use.
   * @return A new attribute with the provided attribute type and no
   *         values.
   */
  public static Attribute empty(AttributeType attributeType)
  {
    return empty(attributeType, attributeType.getNameOrOID());
  }



  /**
   * Creates a new attribute with the provided attribute type and no
   * values.
   *
   * @param attributeType
   *          The attribute type to use.
   * @param name
   *          The user-provided name for this attribute.
   * @return A new attribute with the provided attribute type and no
   *         values.
   */
  public static Attribute empty(AttributeType attributeType,
      String name)
  {
    return AttributeBuilder.create(attributeType, name, Collections
        .<AttributeValue> emptySet());
  }



  /**
   * Creates a new attribute with the specified attribute name and no
   * attribute 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 name or OID of the attribute type for this attribute
   *          (can be mixed case).
   * @return A new attribute with the specified name and no values.
   */
  public static Attribute empty(String attributeName)
  {
    return empty(getAttributeType(attributeName), attributeName);
  }



  /**
   * Creates a new attribute containing all the values from the two
   * provided attributes. The returned attribute will use the name and
   * options taken from the first attribute.
   * <p>
   * This method is logically equivalent to:
   *
   * <pre>
   * merge(a1, a2, null);
   * </pre>
   *
   * @param a1
   *          The first attribute.
   * @param a2
   *          The second attribute.
   * @return A new attribute containing all the values from the two
   *         provided attributes.
   */
  public static Attribute merge(Attribute a1, Attribute a2)
  {
    return merge(a1, a2, null);
  }



  /**
   * Creates a new attribute containing all the values from the two
   * provided attributes and put any duplicate values into the
   * provided collection. The returned attribute will use the name
   * and options taken from the first attribute.
   *
   * @param a1
   *          The first attribute.
   * @param a2
   *          The second attribute.
   * @param duplicateValues
   *          A collection which will be used to store any duplicate
   *          values, or <code>null</code> if duplicate values should
   *          not be stored.
   * @return A new attribute containing all the values from the two
   *         provided attributes.
   */
  public static Attribute merge(Attribute a1, Attribute a2,
      Collection<AttributeValue> duplicateValues)
  {
    AttributeBuilder builder = new AttributeBuilder(a1);
    for (AttributeValue av : a2)
    {
      if (!builder.add(av) && duplicateValues != null)
      {
        duplicateValues.add(av);
      }
    }
    return builder.toAttribute();
  }



  /**
   * Creates a new attribute containing the values from the first
   * attribute which are not in the second attribute. The returned
   * attribute will use the name and options taken from the first
   * attribute.
   * <p>
   * This method is logically equivalent to:
   *
   * <pre>
   * subtract(a1, a2, null);
   * </pre>
   *
   * @param a1
   *          The first attribute.
   * @param a2
   *          The second attribute.
   * @return A new attribute containing the values from the first
   *         attribute which are not in the second attribute.
   */
  public static Attribute subtract(Attribute a1, Attribute a2)
  {
    return subtract(a1, a2, null);
  }



  /**
   * Creates a new attribute containing the values from the first
   * attribute which are not in the second attribute. Any values which
   * were present in the second attribute but which were not present
   * in the first attribute will be put into the provided collection.
   * The returned attribute will use the name and options taken from
   * the first attribute.
   *
   * @param a1
   *          The first attribute.
   * @param a2
   *          The second attribute.
   * @param missingValues
   *          A collection which will be used to store any missing
   *          values, or <code>null</code> if missing values should
   *          not be stored.
   * @return A new attribute containing the values from the first
   *         attribute which are not in the second attribute.
   */
  public static Attribute subtract(Attribute a1, Attribute a2,
      Collection<AttributeValue> missingValues)
  {
    AttributeBuilder builder = new AttributeBuilder(a1);
    for (AttributeValue av : a2)
    {
      if (!builder.remove(av) && missingValues != null)
      {
        missingValues.add(av);
      }
    }
    return builder.toAttribute();
  }



  /**
   * 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;
  }

}
