/*
 * 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 legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * 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 legal-notices/CDDLv1_0.txt.
 * 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-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2015 ForgeRock AS
 */
package org.opends.server.types;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.schema.AttributeUsage;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.opends.server.core.DirectoryServer;

import static org.forgerock.util.Reject.*;
import static org.opends.server.util.ServerConstants.*;

/**
 * This class defines a data structure for storing and interacting
 * with an attribute type, which contains information about the format
 * of an attribute and the syntax and matching rules that should be
 * used when interacting with it.
 * <p>
 * Any methods which accesses the set of names associated with this
 * attribute type, will retrieve the primary name as the first name,
 * regardless of whether or not it was contained in the original set
 * of <code>names</code> passed to the constructor.
 * <p>
 * Where ordered sets of names, or extra properties are provided, the
 * ordering will be preserved when the associated fields are accessed
 * via their getters or via the {@link #toString()} methods.
 */
@org.opends.server.types.PublicAPI(
     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
     mayInstantiate=false,
     mayExtend=false,
     mayInvoke=true)
public final class AttributeType
       extends CommonSchemaElements
       implements Comparable<AttributeType>
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /** The approximate matching rule for this attribute type. */
  private final MatchingRule approximateMatchingRule;

  /** The syntax for this attribute type. */
  private final Syntax syntax;

  /** The superior attribute type from which this attribute type inherits. */
  private final AttributeType superiorType;

  /** The attribute usage for this attribute type. */
  private final AttributeUsage attributeUsage;

  /** Indicates whether this attribute type is declared "collective". */
  private final boolean isCollective;

  /**
   * Indicates whether this attribute type is declared
   * "no-user-modification".
   */
  private final boolean isNoUserModification;

  /** Indicates whether this attribute type is the objectclass type. */
  private final boolean isObjectClassType;

  /** Indicates whether this attribute type is operational. */
  private final boolean isOperational;

  /** Indicates whether this attribute type is declared "single-value". */
  private final boolean isSingleValue;

  /**
   * Indicates whether there is a possibility that this attribute type
   * may have one or more subtypes that list this type or one of its
   * subtypes as a superior.  Note that this variable is intentional
   * not declared "final", but if it ever gets set to "true", then it
   * should never be unset back to "false".
   */
  private boolean mayHaveSubordinateTypes;
  /** The definition string used to create this attribute type. */
  private final String definition;

  /** The equality matching rule for this attribute type. */
  private final MatchingRule equalityMatchingRule;
  /** The ordering matching rule for this attribute type. */
  private final MatchingRule orderingMatchingRule;
  /** The substring matching rule for this attribute type. */
  private final MatchingRule substringMatchingRule;

  /** True once this attribute type has been removed from the schema. */
  private volatile boolean isDirty;



  /**
   * Creates a new attribute type with the provided information.
   * <p>
   * If no <code>primaryName</code> is specified, but a set of
   * <code>names</code> is specified, then the first name retrieved
   * from the set of <code>names</code> will be used as the primary
   * name.
   *
   * @param definition
   *          The definition string used to create this attribute
   *          type.  It must not be {@code null}.
   * @param primaryName
   *          The primary name for this attribute type, or
   *          <code>null</code> if there is no primary name.
   * @param typeNames
   *          The full set of names for this attribute type, or
   *          <code>null</code> if there are no names.
   * @param oid
   *          The OID for this attribute type.  It must not be
   *          {@code null}.
   * @param description
   *          The description for the attribute type, or
   *          <code>null</code> if there is no description.
   * @param superiorType
   *          The reference to the superior type for this attribute
   *          type, or <code>null</code> if there is no superior
   *          type.
   * @param syntax
   *          The syntax for this attribute type, or <code>null</code>
   *          if there is no syntax.
   * @param attributeUsage
   *          The attribute usage for this attribute type, or
   *          <code>null</code> to default to user applications.
   * @param isCollective
   *          Indicates whether this attribute type is declared
   *          "collective".
   * @param isNoUserModification
   *          Indicates whether this attribute type is declared
   *          "no-user-modification".
   * @param isObsolete
   *          Indicates whether this attribute type is declared
   *          "obsolete".
   * @param isSingleValue
   *          Indicates whether this attribute type is declared
   *          "single-value".
   */
  public AttributeType(String definition, String primaryName,
                       Collection<String> typeNames,
                       String oid, String description,
                       AttributeType superiorType,
                       Syntax syntax,
                       AttributeUsage attributeUsage,
                       boolean isCollective,
                       boolean isNoUserModification,
                       boolean isObsolete, boolean isSingleValue)
  {
    this(definition, primaryName, typeNames, oid, description,
        superiorType, syntax, null, null, null,
        null, attributeUsage, isCollective,
        isNoUserModification, isObsolete, isSingleValue, null);
  }



  /**
   * Creates a new attribute type with the provided information.
   * <p>
   * If no <code>primaryName</code> is specified, but a set of
   * <code>names</code> is specified, then the first name retrieved
   * from the set of <code>names</code> will be used as the primary
   * name.
   *
   * @param definition
   *          The definition string used to create this attribute
   *          type.  It must not be {@code null}.
   * @param primaryName
   *          The primary name for this attribute type, or
   *          <code>null</code> if there is no primary name.
   * @param typeNames
   *          The full set of names for this attribute type, or
   *          <code>null</code> if there are no names.
   * @param oid
   *          The OID for this attribute type.  It must not be
   *          {@code null}.
   * @param description
   *          The description for the attribute type, or
   *          <code>null</code> if there is no description.
   * @param superiorType
   *          The reference to the superior type for this attribute
   *          type, or <code>null</code> if there is no superior
   *          type.
   * @param syntax
   *          The syntax for this attribute type, or <code>null</code>
   *          if there is no syntax.
   * @param approximateMatchingRule
   *          The approximate matching rule for this attribute type,
   *          or <code>null</code> if there is no rule.
   * @param equalityMatchingRule
   *          The equality matching rule for this attribute type, or
   *          <code>null</code> if there is no rule.
   * @param orderingMatchingRule
   *          The ordering matching rule for this attribute type, or
   *          <code>null</code> if there is no rule.
   * @param substringMatchingRule
   *          The substring matching rule for this attribute type, or
   *          <code>null</code> if there is no rule.
   * @param attributeUsage
   *          The attribute usage for this attribute type, or
   *          <code>null</code> to default to user applications.
   * @param isCollective
   *          Indicates whether this attribute type is declared
   *          "collective".
   * @param isNoUserModification
   *          Indicates whether this attribute type is declared
   *          "no-user-modification".
   * @param isObsolete
   *          Indicates whether this attribute type is declared
   *          "obsolete".
   * @param isSingleValue
   *          Indicates whether this attribute type is declared
   *          "single-value".
   * @param extraProperties
   *          A set of extra properties for this attribute type, or
   *          <code>null</code> if there are no extra properties.
   */
  public AttributeType(String definition, String primaryName,
                       Collection<String> typeNames,
                       String oid, String description,
                       AttributeType superiorType,
                       Syntax syntax,
                       MatchingRule approximateMatchingRule,
                       MatchingRule equalityMatchingRule,
                       MatchingRule orderingMatchingRule,
                       MatchingRule substringMatchingRule,
                       AttributeUsage attributeUsage,
                       boolean isCollective,
                       boolean isNoUserModification,
                       boolean isObsolete, boolean isSingleValue,
                       Map<String,List<String>> extraProperties)
  {
    super(primaryName, typeNames, oid, description, isObsolete,
        extraProperties);


    ifNull(definition, oid);

    this.superiorType = superiorType;
    this.isCollective = isCollective;
    this.isNoUserModification = isNoUserModification;
    this.isSingleValue = isSingleValue;

    mayHaveSubordinateTypes = false;

    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);

        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        logger.traceException(e);

        defStr = definition;
      }

      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }

    if (syntax == null)
    {
      if (superiorType != null)
      {
        this.syntax = superiorType.getSyntax();
      }
      else
      {
        this.syntax = DirectoryServer.getDefaultAttributeSyntax();
      }
    }
    else
    {
      this.syntax = syntax;
    }

    if (approximateMatchingRule == null)
    {
      this.approximateMatchingRule =
             this.syntax.getApproximateMatchingRule();
    }
    else
    {
      this.approximateMatchingRule = approximateMatchingRule;
    }


    if (equalityMatchingRule == null)
    {
      this.equalityMatchingRule =
        this.syntax.getEqualityMatchingRule();
    }
    else
    {
      this.equalityMatchingRule = equalityMatchingRule;
    }


    if (orderingMatchingRule == null)
    {
      this.orderingMatchingRule =
        this.syntax.getOrderingMatchingRule();
    }
    else
    {
      this.orderingMatchingRule = orderingMatchingRule;
    }


    if (substringMatchingRule == null)
    {
      this.substringMatchingRule =
        this.syntax.getSubstringMatchingRule();
    }
    else
    {
      this.substringMatchingRule = substringMatchingRule;
    }

    if (attributeUsage != null)
    {
      this.attributeUsage = attributeUsage;
    }
    else
    {
      this.attributeUsage = AttributeUsage.USER_APPLICATIONS;
    }

    if (oid.equals(OBJECTCLASS_ATTRIBUTE_TYPE_OID))
    {
      isObjectClassType = true;
    }
    else
    {
      isObjectClassType = hasName(OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
    }

    isOperational = this.attributeUsage.isOperational();
  }



  /**
   * Retrieves the superior type for this attribute type.
   *
   * @return  The superior type for this attribute type, or
   *          <CODE>null</CODE> if it does not have one.
   */
  public AttributeType getSuperiorType()
  {
    return superiorType;
  }



  /**
   * Indicates whether there is a possibility that this attribute type
   * may have one or more subordinate attribute types defined in the
   * server schema.  This is only intended for use by the
   * {@code org.opends.server.types.Entry} class for the purpose of
   * determining whether to check for subtypes when retrieving
   * attributes.  Note that it is possible for this method to report
   * false positives (if an attribute type that previously had one or
   * more subordinate types no longer has any), but not false
   * negatives.
   *
   * @return  {@code true} if the {@code hasSubordinateTypes} flag has
   *          been set for this attribute type at any time since
   *          startup, or {@code false} if not.
   */
  boolean mayHaveSubordinateTypes()
  {
    return mayHaveSubordinateTypes;
  }



  /**
   * Sets a flag indicating that this attribute type may have one or
   * more subordinate attribute types defined in the server schema.
   * This is only intended for use by the
   * {@code org.opends.server.types.Schema} class.
   */
  void setMayHaveSubordinateTypes()
  {
    mayHaveSubordinateTypes = true;
  }



  /**
   * Retrieves the syntax for this attribute type.
   *
   * @return  The syntax for this attribute type.
   */
  public Syntax getSyntax()
  {
    return syntax;
  }

  /**
   * Retrieves the matching rule that should be used for approximate
   * matching with this attribute type.
   *
   * @return  The matching rule that should be used for approximate
   *          matching with this attribute type.
   */
  public MatchingRule getApproximateMatchingRule()
  {
    return approximateMatchingRule;
  }



  /**
   * Retrieves the matching rule that should be used for equality
   * matching with this attribute type.
   *
   * @return  The matching rule that should be used for equality
   *          matching with this attribute type.
   */
  public MatchingRule getEqualityMatchingRule()
  {
    return equalityMatchingRule;
  }



  /**
   * Retrieves the matching rule that should be used for ordering with
   * this attribute type.
   *
   * @return  The matching rule that should be used for ordering with
   *          this attribute type.
   */
  public MatchingRule getOrderingMatchingRule()
  {
    return orderingMatchingRule;
  }



  /**
   * Retrieves the matching rule that should be used for substring
   * matching with this attribute type.
   *
   * @return  The matching rule that should be used for substring
   *          matching with this attribute type.
   */
  public MatchingRule getSubstringMatchingRule()
  {
    return substringMatchingRule;
  }



  /**
   * Retrieves the usage indicator for this attribute type.
   *
   * @return  The usage indicator for this attribute type.
   */
  public AttributeUsage getUsage()
  {
    return attributeUsage;
  }



  /**
   * Indicates whether this is an operational attribute.  An
   * operational attribute is one with a usage of
   * "directoryOperation", "distributedOperation", or "dSAOperation"
   * (i.e., only userApplications is not operational).
   *
   * @return  <CODE>true</CODE> if this is an operational attribute,
   *          or <CODE>false</CODE> if not.
   */
  public boolean isOperational()
  {
    return isOperational;
  }



  /**
   * Indicates whether this attribute type is declared "collective".
   *
   * @return  <CODE>true</CODE> if this attribute type is declared
   * "collective", or <CODE>false</CODE> if not.
   */
  public boolean isCollective()
  {
    return isCollective;
  }



  /**
   * Indicates whether this attribute type is declared
   * "no-user-modification".
   *
   * @return  <CODE>true</CODE> if this attribute type is declared
   *          "no-user-modification", or <CODE>false</CODE> if not.
   */
  public boolean isNoUserModification()
  {
    return isNoUserModification;
  }



  /**
   * Indicates whether this attribute type is declared "single-value".
   *
   * @return  <CODE>true</CODE> if this attribute type is declared
   *          "single-value", or <CODE>false</CODE> if not.
   */
  public boolean isSingleValue()
  {
    return isSingleValue;
  }



  /**
   * Indicates whether this attribute type represents the
   * "objectclass" attribute.  The determination will be made based on
   * the name and/or OID.
   *
   * @return  <CODE>true</CODE> if this attribute type is the
   *          objectclass type, or <CODE>false</CODE> if not.
   */
  public boolean isObjectClass()
  {
    return isObjectClassType;
  }

  /** {@inheritDoc} */
  @Override
  public String toString()
  {
    return definition;
  }

  /** {@inheritDoc} */
  @Override
  public int compareTo(AttributeType o) {
    return getNormalizedPrimaryNameOrOID().compareTo(
      o.getNormalizedPrimaryNameOrOID());
  }



  /**
   * Marks this attribute type as dirty, indicating that it has been removed or
   * replaced in the schema.
   *
   * @return A reference to this attribute type.
   */
  public AttributeType setDirty()
  {
    isDirty = true;
    return this;
  }



  /**
   * Returns {@code true} if this attribute type has been removed or replaced in
   * the schema.
   *
   * @return {@code true} if this attribute type has been removed or replaced in
   *         the schema.
   */
  public boolean isDirty()
  {
    return isDirty;
  }
}

