/*
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
 *
 * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
 * Portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
 * Visco. All Rights Reserverd.
 *
 * $Id$
 */
package org.exolab.castor.builder.info;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.castor.core.nature.PropertyHolder;
import org.exolab.castor.builder.factory.FieldMemberAndAccessorFactory;
import org.exolab.castor.builder.info.nature.XMLInfoNature;
import org.exolab.castor.builder.types.XSType;
import org.exolab.javasource.JType;

/**
 * A class for representing field members of a class. FieldInfo objects hold all the information
 * required about a member in order to be able to produce XML data binding (marshal/unmarshal) and
 * validation code.
 *
 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 */
public class FieldInfo implements XMLInfo, PropertyHolder {
  /**
   * Map holding the properties set and read by Natures.
   */
  private Map<String, Object> _properties = new HashMap<String, Object>();

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

  /** The Read / Getter method flag. */
  public static final int READ_METHOD = 1;
  /** The Write / Setter method flag. */
  public static final int WRITE_METHOD = 2;
  /** The Read and Write methods flags. */
  public static final int READ_WRITE_METHODS = 3;

  /** Method prefixes for "Add" methods. */
  protected static final String METHOD_PREFIX_ADD = "add";
  /** Method prefixes for "Delete" methods. */
  protected static final String METHOD_PREFIX_DELETE = "delete";
  /** Method prefixes for "Get" methods. */
  protected static final String METHOD_PREFIX_GET = "get";
  /** Method prefixes for "Has" methods. */
  protected static final String METHOD_PREFIX_HAS = "has";
  /** Method prefixes for "Set" methods. */
  protected static final String METHOD_PREFIX_SET = "set";
  /** Method prefixes for "Is" methods. */
  protected static final String METHOD_PREFIX_IS = "is";

  /** The Java name for Members described by this FieldInfo. */
  private String _name = null;

  /**
   * {@link ClassInfo} instance which 'own' (declares) this {@link FieldInfo}.
   */
  private ClassInfo _declaringClassInfo = null;

  /** JavaDoc comment. */
  private String _comment = null;

  /** The default value for this FieldInfo. */
  private String _default = null;
  /** The fixed production for this FieldInfo. */
  private String _fixed = null;
  /** A flag to indicate a final member. */
  private boolean _final = false;
  /** The methods flags, indicates which methods to create. */
  private int _methods = READ_WRITE_METHODS;
  /** A reference to the FieldInfo instance within the same class. */
  private FieldInfo _fieldInfoReference = null;
  /** A flag to indicate a static member. */
  private boolean _static = false;
  /** Flags whether or not the a MarshalDescriptor should be created for this FieldInfo. */
  private boolean _transient = false;
  /** A flag to indicate a bound property. */
  private boolean _bound = false;
  /** A flag to indicate a container field. */
  private boolean _isContainer = false;
  /**
   * The fully qualified name of the XMLFieldHandler (if any) to use in the generated descriptor.
   */
  private String _fieldHandler;
  /** A boolean to indicate that this field represents a "nillable" field. */
  private boolean _nillable = false;
  /** The fully qualified name of the Validator (if any) to use in the generated descriptor. */
  private String _validator;
  /** Visibility of this FieldInfo. */
  private String _visibility = "private";

  /**
   * Factory responsible for creating a {@link JField} out of a {@link FieldInfo}.
   */
  private FieldMemberAndAccessorFactory _memberAndAccessorFactory;

  /**
   * Holds the possible substitution groups for this class.
   */
  private List<String> _substitutionGroupMembers = new LinkedList<String>();

  /**
   * Creates a new FieldInfo with the given XML Schema type and the given member name. Adds the
   * {@link XMLInfoNature} for legacy compliance.
   *
   * @param type the XML Schema type of this member
   * @param name the name of the member
   * @param memberAndAccessorFactory the FieldMemberAndAccessorFactory to be used
   */
  public FieldInfo(final XSType type, final String name,
      final FieldMemberAndAccessorFactory memberAndAccessorFactory) {
    this._name = name;
    this._memberAndAccessorFactory = memberAndAccessorFactory;
    this.addNature(XMLInfoNature.class.getName());
    XMLInfoNature xmlNature = new XMLInfoNature(this);
    xmlNature.setSchemaType(type);
  } // -- FieldInfo


  /**
   * Returns the FieldMemberAndAccessorFactory instance to use to create a JField out of this
   * FieldInfo.
   * 
   * @return the suitable FieldMemberAndAccessorFactory
   */
  public FieldMemberAndAccessorFactory getMemberAndAccessorFactory() {
    return _memberAndAccessorFactory;
  }

  /**
   * Returns the default value for this FieldInfo.
   *
   * @return the default value for this FieldInfo, or null if no default value was set;
   */
  public final String getDefaultValue() {
    return _default;
  }

  /**
   * Returns the fixed production for this FieldInfo, or null if no fixed value has been specified.
   * <p>
   * NOTE: Fixed values are NOT the same as default values
   *
   * @return the fixed value for this FieldInfo
   */
  public final String getFixedValue() {
    return _fixed;
  }

  /**
   * Returns the name of the delete method for this FieldInfo.
   * 
   * @return the name of the delete method for this FieldInfo.
   */
  public final String getDeleteMethodName() {
    return METHOD_PREFIX_DELETE + getMethodSuffix();
  } // -- getDeleteMethodName

  /**
   * Returns the name of the has method for this FieldInfo.
   * 
   * @return the name of the has method for this FieldInfo.
   */
  public final String getHasMethodName() {
    return METHOD_PREFIX_HAS + getMethodSuffix();
  } // -- getHasMethodName

  /**
   * Returns the name of the read method for this FieldInfo.
   * 
   * @return the name of the read method for this FieldInfo.
   */
  public final String getReadMethodName() {
    return METHOD_PREFIX_GET + getMethodSuffix();
  } // -- getReadMethodName

  /**
   * Returns the fully qualified name of the Validator to use.
   *
   * @return the fully qualified name of the Validator to use.
   */
  public final String getValidator() {
    return _validator;
  }

  /**
   * Returns the name of the write method for this FieldInfo.
   * 
   * @return the name of the write method for this FieldInfo.
   */
  public final String getWriteMethodName() {
    if (new XMLInfoNature(this).isMultivalued()) {
      return METHOD_PREFIX_ADD + getMethodSuffix();
    }
    return METHOD_PREFIX_SET + getMethodSuffix();
  } // -- getWriteMethodName

  /**
   * Get the 'is' method for this FieldInfo.
   * 
   * @return the name of the 'is' method for this FieldInfo
   */
  public final String getIsMethodName() {
    return METHOD_PREFIX_IS + getMethodSuffix();
  }

  /**
   * Returns the fully qualified name of the XMLFieldHandler to use.
   *
   * @return the fully qualified name of the XMLFieldHandler to use.
   */
  public final String getXMLFieldHandler() {
    return _fieldHandler;
  }

  /**
   * Returns the comment associated with this Member.
   *
   * @return the comment associated with this Member, or null. if one has not been set.
   */
  public final String getComment() {
    return _comment;
  } // -- getComment

  /**
   * Returns the methods flag that indicates which.
   *
   * methods will be created.
   *
   * @return the methods flag
   */
  public final int getMethods() {
    return _methods;
  } // -- getMethods

  /**
   * Returns the name of this FieldInfo.
   *
   * @return the name of this FieldInfo.
   */
  public final String getName() {
    return this._name;
  } // -- getName

  /**
   * Returns true if this FieldInfo represents a bound property.
   *
   * @return true if this FieldInfo represents a bound property.
   */
  public final boolean isBound() {
    return _bound;
  } // -- isBound

  /**
   * Returns true if this FieldInfo describes a container class. A container class is a class which
   * should not be marshalled as XML, but whose members should be.
   *
   * @return true if this ClassInfo describes a container class.
   */
  public final boolean isContainer() {
    return _isContainer;
  } // -- isContainer

  /**
   * Returns true if the "has" and "delete" methods are needed for the field associated with this
   * FieldInfo.
   *
   * @return true if the has and delete methods are needed.
   */
  public final boolean requiresHasAndDeleteMethods() {
    XSType xsType = new XMLInfoNature(this).getSchemaType();
    JType jType = xsType.getJType();
    return ((!xsType.isEnumerated()) && jType.isPrimitive());
  } // -- requiresHasAndDeleteMethods

  /**
   * Returns true if this field represents a nillable field. A nillable field is a field that can
   * have null content (see XML Schema 1.0 definition of nillable).
   *
   * @return true if nillable, otherwise false.
   * @see #setNillable(boolean)
   */
  public final boolean isNillable() {
    return _nillable;
  } // -- isNillable

  /**
   * Returns true if this FieldInfo is a transient member. Transient members are members which
   * should be ignored by the Marshalling framework.
   *
   * @return true if this FieldInfo is transient.
   */
  public final boolean isTransient() {
    return (_transient || _final || _static);
  } // -- isTransient

  /**
   * Sets the comment for this Member.
   * 
   * @param comment the comment or description for this Member
   */
  public final void setComment(final String comment) {
    _comment = comment;
  } // -- setComment

  /**
   * Returns the ClassInfo to which this Member was declared, for inheritance reasons.
   * 
   * @return the ClassInfo to which this Member was declared.
   */
  public final ClassInfo getDeclaringClassInfo() {
    return this._declaringClassInfo;
  } // -- getDeclaringClassInfo

  /**
   * Sets whether or not this FieldInfo represents a bound property.
   *
   * @param bound the flag when true indicates that this FieldInfo represents a bound property.
   */
  public final void setBound(final boolean bound) {
    _bound = bound;
  } // -- setBound

  /**
   * Sets whether or not this FieldInfo describes a container field. A container field is a field
   * which should not be marshalled directly as XML, but whose members should be. By default this is
   * false.
   *
   * @param isContainer the boolean value when true indicates this class should be a container
   *        class.
   */
  public final void setContainer(final boolean isContainer) {
    _isContainer = isContainer;
  } // -- setContainer

  /**
   * Sets the {@link ClassInfo} of the class that declares this field.
   * 
   * @param declaringClassInfo The {@link ClassInfo} of the declaring class.
   */
  public final void setDeclaringClassInfo(final ClassInfo declaringClassInfo) {
    this._declaringClassInfo = declaringClassInfo;
  }

  /**
   * Sets the default value for this FieldInfo.
   * 
   * @param defaultValue the default value
   */
  public final void setDefaultValue(final String defaultValue) {
    this._default = defaultValue;
  }

  /**
   * Sets the "final" status of this FieldInfo. Final members are also transient.
   *
   * @param isFinal the boolean indicating the final status, if true this FieldInfo will be treated
   *        as final.
   */
  public final void setFinal(final boolean isFinal) {
    this._final = isFinal;
  } // -- isFinal

  /**
   * Sets the fixed value in which instances of this field type must lexically match. NOTE: This is
   * not the same as default value!
   *
   * @param fixedValue the fixed production for this FieldInfo
   */
  public final void setFixedValue(final String fixedValue) {
    this._fixed = fixedValue;
  } // -- setFixedValue

  /**
   * Sets which methods to create: READ_METHOD, WRITE_METHOD, READ_WRITE_METHODS.
   *
   * @param methods a flag describing which methods to create.
   */
  public final void setMethods(final int methods) {
    _methods = methods;
  } // -- setMethods

  /**
   * Sets whether or not this field can be nillable.
   *
   * @param nillable a boolean that when true means the field may be nil.
   * @see #isNillable()
   */
  public final void setNillable(final boolean nillable) {
    _nillable = nillable;
  } // -- setNillable

  /**
   * Sets the name of the field within the same class that is a reference to this field.
   *
   * @param fieldInfo {@link FieldInfo} for the referencing field (within the same class).
   */
  public final void setFieldInfoReference(final FieldInfo fieldInfo) {
    _fieldInfoReference = fieldInfo;
  }

  /**
   * Sets the "static" status of this FieldInfo. Static members are also transient.
   *
   * @param isStatic the boolean indicating the static status, if true this FieldInfo will be
   *        treated as static
   */
  public final void setStatic(final boolean isStatic) {
    this._static = isStatic;
  } // -- setStatic

  /**
   * Sets the transient status of this FieldInfo.
   *
   * @param isTransient the boolean indicating the transient status, if true this FieldInfo will be
   *        treated as transient
   */
  public final void setTransient(final boolean isTransient) {
    this._transient = isTransient;
  } // -- setTransient

  /**
   * Sets the name of the Validator to use.
   *
   * @param validator the fully qualified name of the validator to use.
   */
  public final void setValidator(final String validator) {
    _validator = validator;
  }

  /**
   * Sets the name of the XMLfieldHandler to use.
   *
   * @param handler the fully qualified name of the handler to use.
   */
  public final void setXMLFieldHandler(final String handler) {
    _fieldHandler = handler;
  }

  /**
   * Returns the method suffix for creating method names.
   *
   * @return the method suffix used when creating method names.
   */
  public String getMethodSuffix() {
    if (_name.startsWith("_")) {
      return _memberAndAccessorFactory.getJavaNaming().toJavaClassName(_name.substring(1));
    }
    return _memberAndAccessorFactory.getJavaNaming().toJavaClassName(_name);
  }

  /**
   * Sets the visibility of this FieldInfo.
   *
   * @param visibility the visibility of this FieldInfo.
   */
  public final void setVisibility(final String visibility) {
    _visibility = visibility;
  }

  /**
   * Sets the possible substitution groups for this class.
   * 
   * @param substitutionGroupMembers Possible substitution groups for this class.
   */
  public void setSubstitutionGroupMembers(final List<String> substitutionGroupMembers) {
    this._substitutionGroupMembers = substitutionGroupMembers;
  }

  /**
   * Returns the possible substitution groups for this class.
   * 
   * @return the possible substitution groups for this class.
   */
  public List<String> getSubstitutionGroupMembers() {
    return this._substitutionGroupMembers;
  }

  /**
   * Indicates whether this field is static.
   * 
   * @return True if this field is static.
   */
  public boolean isStatic() {
    return _static;
  }

  /**
   * Indicates whether this field is final.
   * 
   * @return True if this field is final.
   */
  public boolean isFinal() {
    return _final;
  }

  /**
   * Returns this field's 'visibility'.
   * 
   * @return This field's visibility.
   */
  public Object getVisibility() {
    return _visibility;
  }

  /**
   * Returns the reference to the {@link FieldInfo} instance within the same class.
   * 
   * @return the reference to the {@link FieldInfo} instance
   */
  public FieldInfo getFieldInfoReference() {
    return _fieldInfoReference;
  }

  /**
   * @see org.exolab.castor.builder.info.nature.PropertyHolder# getProperty(java.lang.String)
   * @param name of the property
   * @return value of the property
   */
  public final 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 final 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 final 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 final boolean hasNature(final String nature) {
    return _natures.contains(nature);
  }

}
