package org.exolab.castor.mapping.loader;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.xml.types.FieldMappingCollectionType;

public class FieldDescriptorImpl implements FieldDescriptor {

  /** A reference to the containing class descriptor (parent). */
  private ClassDescriptor _parent;

  /** The name of the field. */
  private String _fieldName;

  /** The type of the field. */
  private Class<?> _fieldType;

  /** The type class descriptor, if this field is of a type known by a descriptor. */
  private ClassDescriptor _classDescriptor;

  /** The field handler for get/set field value. */
  private FieldHandler _handler;

  /** True if the field is transient and should not be saved/stored. */
  private boolean _transient;

  /** True if the described field type is immutable. */
  private boolean _immutable;

  /** True if the field type is required. */
  private boolean _required;

  /** True if access to field is direct. */
  private boolean _direct;

  /** True if access to field is lazy. */
  private boolean _lazy;

  /** True if the object described by this descriptor is multi-valued. */
  private boolean _multivalued;

  /** True if the field is part of the identity of the class. */
  private boolean _identity;

  /** The name of a class that implements the {@link Comparator} interface. */
  private String _comparator;

  /** The name of the 'getter' method used to obtain the value of a property. */
  private String _getMethod;

  /** The name of the 'setter' method used to set the value of a property. */
  private String _setMethod;

  /** The name of the 'create' method used to create an instance of a property. */
  private String _createMethod;

  /** The type of the collection, if the field is multi-valued. */
  private FieldMappingCollectionType _collection;

  /**
   * Map holding the properties set and read by natures.
   */
  private Map<String, Object> _properties = new HashMap<String, Object>();

  /**
   * Map holding the available natures.
   */
  private Set<String> _natures = new HashSet<String>();

  /**
   * Creates a default instance of a field descriptor.
   */
  protected FieldDescriptorImpl() {}

  /**
   * Constructs a new field descriptor.
   *
   * @param fieldName The field name
   * @param typeInfo The field type information
   * @param handler The field handler (may be null)
   * @param isTransitive True if the field is transient
   */
  public FieldDescriptorImpl(final String fieldName, final TypeInfo typeInfo,
      final FieldHandler handler, final boolean isTransitive) {

    if (fieldName == null) {
      throw new IllegalArgumentException("Internal error. Field name not specified.");
    }

    if (handler == null) {
      throw new IllegalArgumentException("Internal error. No FieldHandler provided.");
    }

    setFieldName(fieldName);
    setFieldType(typeInfo.getFieldType());
    setHandler(handler);
    setTransient(isTransitive);
    setImmutable(typeInfo.isImmutable());
    setRequired(typeInfo.isRequired());
    setMultivalued(typeInfo.getCollectionHandler() != null);
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#setContainingClassDescriptor(
   *      org.exolab.castor.mapping.ClassDescriptor) {@inheritDoc}
   */
  public final void setContainingClassDescriptor(final ClassDescriptor parent) {
    _parent = parent;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#getContainingClassDescriptor() {@inheritDoc}
   */
  public final ClassDescriptor getContainingClassDescriptor() {
    return _parent;
  }

  /**
   * Set the name of the field.
   * 
   * @param fieldName Field name.
   */
  public final void setFieldName(final String fieldName) {
    _fieldName = fieldName;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#getFieldName() {@inheritDoc}
   */
  public final String getFieldName() {
    return _fieldName;
  }

  /**
   * Set the type of the field.
   * 
   * @param fieldType Field type.
   */
  public final void setFieldType(final Class<?> fieldType) {
    _fieldType = fieldType;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#getFieldType() {@inheritDoc}
   */
  public final Class<?> getFieldType() {
    return _fieldType;
  }

  /**
   * Set the ClassDescriptor for the described field.
   *
   * @param classDescriptor The ClassDescriptor for the described field.
   */
  public final void setClassDescriptor(final ClassDescriptor classDescriptor) {
    _classDescriptor = classDescriptor;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#getClassDescriptor() {@inheritDoc}
   */
  public final ClassDescriptor getClassDescriptor() {
    return _classDescriptor;
  }

  /**
   * Set the FieldHandler for the field being described by this FieldDescriptor.
   *
   * @param handler The FieldHandler for the field being described.
   */
  public final void setHandler(final FieldHandler handler) {
    _handler = handler;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#getHandler() {@inheritDoc}
   */
  public final FieldHandler getHandler() {
    return _handler;
  }

  /**
   * Sets whether or not the describled field is 'transient'.
   * 
   * @param isTransient The flag indicating if the described field is 'transient'.
   */
  public final void setTransient(final boolean isTransient) {
    _transient = isTransient;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#isTransient() {@inheritDoc}
   */
  public final boolean isTransient() {
    return _transient;
  }

  /**
   * Set the immutable flag which indicates that changes to this Field result in a new Object to be
   * created, such as java.lang.String. It serves to identify fields which should not be constructed
   * until all the data is available.
   * 
   * @param immutable Flag which if true indicates that the field is immutable.
   */
  public final void setImmutable(final boolean immutable) {
    _immutable = immutable;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#isImmutable() {@inheritDoc}
   */
  public final boolean isImmutable() {
    return _immutable;
  }

  /**
   * Set whether or not the described field is required.
   * 
   * @param required The flag indicating whether or not the described field is required.
   */
  public final void setRequired(final boolean required) {
    _required = required;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#isRequired() {@inheritDoc}
   */
  public final boolean isRequired() {
    return _required;
  }

  /**
   * Set wheter the object described by this descriptor is multivalued or not.
   * 
   * @param multivalued True if the object described by this descriptor is multivalued.
   */
  public final void setMultivalued(final boolean multivalued) {
    _multivalued = multivalued;
  }

  /**
   * @see org.exolab.castor.mapping.FieldDescriptor#isMultivalued() {@inheritDoc}
   */
  public final boolean isMultivalued() {
    return _multivalued;
  }

  /**
   * Set wether the described field is part of the identity of the class it belongs to.
   * 
   * @param identity <code>true</code> if field is part of the classes identity.
   */
  public final void setIdentity(final boolean identity) {
    _identity = identity;
  }

  /**
   * Is the described field is part of the identity of the class it belongs to?
   * 
   * @return <code>true</code> if field is part of the classes identity.
   */
  public final boolean isIdentity() {
    return _identity;
  }

  /**
   * @see org.exolab.castor.builder.info.nature.PropertyHolder# getProperty(java.lang.String)
   * @param name of the property
   * @return value of the property
   */
  public Object getProperty(final String name) {
    return _properties.get(name);
  }

  /**
   * @see org.exolab.castor.builder.info.nature.PropertyHolder# setProperty(java.lang.String,
   *      java.lang.Object)
   * @param name of the property
   * @param value of the property
   */
  public void setProperty(final String name, final Object value) {
    _properties.put(name, value);
  }

  /**
   * @see org.exolab.castor.builder.info.nature.NatureExtendable# addNature(java.lang.String)
   * @param nature ID of the Nature
   */
  public void addNature(final String nature) {
    _natures.add(nature);
  }

  /**
   * @see org.exolab.castor.builder.info.nature.NatureExtendable# hasNature(java.lang.String)
   * @param nature ID of the Nature
   * @return true if the Nature ID was added.
   */
  public boolean hasNature(final String nature) {
    return _natures.contains(nature);
  }

  public String getComparator() {
    return _comparator;
  }

  public void setComparator(String comparator) {
    _comparator = comparator;
  }

  public FieldMappingCollectionType getCollection() {
    return _collection;
  }

  public void setCollection(FieldMappingCollectionType collection) {
    _collection = collection;
  }

  public boolean isDirect() {
    return _direct;
  }

  public void setDirect(boolean direct) {
    _direct = direct;
  }

  public String getGetMethod() {
    return _getMethod;
  }

  public void setGetMethod(String getMethod) {
    _getMethod = getMethod;
  }

  public String getSetMethod() {
    return _setMethod;
  }

  public void setSetMethod(String setMethod) {
    _setMethod = setMethod;
  }

  public String getCreateMethod() {
    return _createMethod;
  }

  public void setCreateMethod(String createMethod) {
    _createMethod = createMethod;
  }

  public boolean isLazy() {
    return _lazy;
  }

  public void setLazy(boolean lazy) {
    _lazy = lazy;
  }

  public String toString() {
    return new StringBuilder().append(getFieldName()).append('(').append(getFieldType().getName())
        .append(')').toString();
    // TODO[WG]: find a way to emit additional nature-specific information.
    // try {
    // Class natureClass =
    // Class.forName("org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature");
    // if (hasNature(natureClass.getName())) {
    // FieldDescriptorJDONature jdoNature = new FieldDescriptorJDONature(this);
    // buffer.append(jdoNature.getSQLName() == null ? "" : " AS "
    // + jdoNature.getSQLName()[0]);
    // }
    // } catch (ClassNotFoundException e) {
    // // ignore
    // }
  }

}
