/**
 * 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-2002 (C) Intalio Inc. All Rights Reserved.
 *
 * $Id$
 */

package org.exolab.castor.xml.schema;

import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Hashtable;
import java.util.Map;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Messages;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.schema.simpletypes.AtomicType;
import org.exolab.castor.xml.schema.simpletypes.ListType;
import org.exolab.castor.xml.schema.simpletypes.RealType;
import org.exolab.castor.xml.schema.simpletypes.UrType;
import org.exolab.castor.xml.schema.simpletypes.factory.Type;
import org.exolab.castor.xml.schema.simpletypes.factory.TypeList;
import org.exolab.castor.xml.schema.simpletypes.factory.TypeProperty;
import org.xml.sax.InputSource;

/**
 * SimpleTypesFactory provides code constants for every built in type defined in
 * www.w3.org/TR/xmlschma-2-20000407 USER_TYPE is used for user derived types.
 *
 * This factory can also create instances of classes derived from SimpleType that represent the
 * simple types defined by xmlschema and those derived from them.
 *
 * @author <a href="mailto:berry@intalio.com">Arnaud Berry</a>
 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 **/
public class SimpleTypesFactory {
  /** The Logger instance to use. */
  private static final Log LOG = LogFactory.getLog(SimpleTypesFactory.class);

  // Type Codes:

  /** This code is for errors or uninitialized types. */
  public static final int INVALID_TYPE = -1;

  /** Simple type defined by the user. */
  public static final int USER_TYPE = 0;

  // Primitive types
  public static final int STRING_TYPE = 1;
  public static final int DURATION_TYPE = 2;
  public static final int DATETIME_TYPE = 3;
  public static final int TIME_TYPE = 4;
  public static final int DATE_TYPE = 5;
  public static final int GYEARMONTH_TYPE = 6;
  public static final int GYEAR_TYPE = 7;
  public static final int GMONTHDAY_TYPE = 8;
  public static final int GDAY_TYPE = 9;
  public static final int GMONTH_TYPE = 10;
  public static final int BOOLEAN_TYPE = 11;
  public static final int BASE64BINARY_TYPE = 12;
  public static final int HEXBINARY_TYPE = 13;
  public static final int FLOAT_TYPE = 14;
  public static final int DOUBLE_TYPE = 15;
  public static final int DECIMAL_TYPE = 16;
  public static final int ANYURI_TYPE = 17;
  public static final int QNAME_TYPE = 18;
  public static final int NOTATION_TYPE = 19;
  // Derived datatypes
  public static final int NORMALIZEDSTRING_TYPE = 20;
  public static final int TOKEN_TYPE = 21;
  public static final int LANGUAGE_TYPE = 22;
  public static final int NAME_TYPE = 23;
  public static final int NCNAME_TYPE = 24;
  public static final int ID_TYPE = 25;
  public static final int IDREF_TYPE = 26;
  public static final int IDREFS_TYPE = 27;
  public static final int ENTITY_TYPE = 28;
  public static final int ENTITIES_TYPE = 29;
  public static final int NMTOKEN_TYPE = 30;
  public static final int NMTOKENS_TYPE = 31;
  public static final int INTEGER_TYPE = 32;
  public static final int NON_POSITIVE_INTEGER_TYPE = 33;
  public static final int NEGATIVE_INTEGER_TYPE = 34;
  public static final int LONG_TYPE = 35;
  public static final int INT_TYPE = 36;
  public static final int SHORT_TYPE = 37;
  public static final int BYTE_TYPE = 38;
  public static final int NON_NEGATIVE_INTEGER_TYPE = 39;
  public static final int UNSIGNED_LONG_TYPE = 40;
  public static final int UNSIGNED_INT_TYPE = 41;
  public static final int UNSIGNED_SHORT_TYPE = 42;
  public static final int UNSIGNED_BYTE_TYPE = 43;
  public static final int POSITIVE_INTEGER_TYPE = 44;

  public static final int ANYSIMPLETYPE_TYPE = 100;

  /** The resource location for the built-in types property files. */
  static final String RESOURCE_LOCATION = "/org/exolab/castor/util/resources/";

  /** The resource for the mapping properties. */
  static final String TYPE_MAPPINGS = RESOURCE_LOCATION + "SimpleTypesMapping.properties";

  /** The resource for the Simple types. */
  static final String TYPE_DEFINITIONS = RESOURCE_LOCATION + "SimpleTypes.properties";

  /**
   * Holds simpletypesfactory.Type instances that record information about XML schema built in
   * types.
   */
  private static Map<String, Type> _typesByName = new Hashtable<String, Type>();

  /**
   * Cross index for _typesByName to quickly get type information from its code.
   */
  private static Map<Integer, Type> _typesByCode = new Hashtable<Integer, Type>();

  /** The built-in schema, hopefully only temporary. */
  private static final Schema BUILD_IN_SCHEMA = new Schema();

  /**
   * Creates an instance of {@link SimpleTypesFactory}, loading type definition information from the
   * relevant files.
   */
  public SimpleTypesFactory() {
    loadTypesDefinitions();
  }

  /**
   * Indicates if a type code corresponds to an xml schema built in type.
   * 
   * @param codeType The type code to check.
   * @return True if the given type code represents an XML schema built-in type.
   */
  public static boolean isBuiltInType(final int codeType) {
    return USER_TYPE < codeType;
  }

  /**
   * Tells if a type code corresponds to an xml schema (built-in) primitive type.
   * 
   * @param codeType The type code to check.
   * @return True if the given type code represents an XML schema built-in primitive type.
   */
  public static boolean isPrimitiveType(final int codeType) {
    return (STRING_TYPE <= codeType) && (codeType <= NOTATION_TYPE);
  }

  /**
   * Tells if a type code corresponds to an xml schema (built-in) numeric type.
   * 
   * @param codeType The type code to check.
   * @return True if the given type code represents an XML schema built-in numeric type.
   */
  public static boolean isNumericType(final int codeType) {
    return ((FLOAT_TYPE <= codeType) && (codeType <= DECIMAL_TYPE))
        || ((INTEGER_TYPE <= codeType) && (codeType <= POSITIVE_INTEGER_TYPE));
  }

  /**
   * Tells if a type code corresponds to an xml schema (built-in) date/time type.
   * 
   * @param codeType The type code to check.
   * @return True if the given type code represents an XML schema built-in date/time type.
   */
  public static boolean isDateTimeType(final int codeType) {
    return (DURATION_TYPE <= codeType) && (codeType <= GMONTH_TYPE);
  }


  /**
   * Gets an instance of a class derived from {@link SimpleType} representing the built in type
   * which name is given as a parameter.
   * 
   * @param typeName Name of the simple type.
   * @return The {@link SimpleType} instance for the type name.
   */
  public SimpleType getBuiltInType(final String typeName) {
    Type type = getType(typeName);
    if (type == null) {
      return null;
    }
    return type.getSimpleType();
  }

  /**
   * Gets a built in type's name given its code.
   */
  public String getBuiltInTypeName(final int builtInTypeCode) {
    Type type = getType(builtInTypeCode);
    if (type == null) {
      return null;
    }
    return type.getName();
  }


  /**
   * Creates an instance of a class derived from SimpleType, representing the user type defined by
   * the given name, baseName and derivation method.
   *
   * Package private (used by Schema and DeferredSimpleType).
   *
   * The given schema is used as the owning Schema document, yet a call to schema.addSimpleType must
   * till be made to add the SimpleType to the Schema.
   *
   * If the base type is not found in the schema, a DeferredSimpleType will be returned if
   * createDeferredSimpleType is true, null otherwise.
   *
   * @param schema the owning schema
   * @param name the name of the SimpleType
   * @param baseName the name of the SimpleType's base type
   * @param derivation the name of the derivation method (null/""/"list"/"restriction")
   * @param createDeferredSimpleType should the type be deferred if it can't be created.
   * @return the new SimpleType, or null if its parent could not be found.
   **/
  SimpleType createUserSimpleType(final Schema schema, final String name, final String baseName,
      final String derivation, final boolean createDeferredSimpleType) {
    if ((baseName == null) || (baseName.length() == 0)) {
      // We need a base type name...
      LOG.warn(Messages.format("schema.noBaseType", name));
      return null;
    }

    // Find the base type
    SimpleType baseType = schema.getSimpleType(baseName);
    if (baseType == null) {
      // couldn't find the base type, must be forward declared
      if (createDeferredSimpleType) { // => create a DeferredSimpleType
        DeferredSimpleType result = new DeferredSimpleType();
        result.setSchema(schema);
        result.setName(name);
        result.setBaseTypeName(baseName);
        result.setDerivationMethod(derivation);
        result.setTypeCode(USER_TYPE);
        return result;
      }
      return null;
    }

    return createUserSimpleType(schema, name, baseType, derivation);
  }

  /**
   * Creates an instance of a class derived from SimpleType, representing the user type defined by
   * the given name, baseName and derivation method.
   *
   * Package private (used by Schema and DeferredSimpleType).
   *
   * The given schema is used as the owning Schema document, yet a call to schema#addSimpleType must
   * still be made to add the SimpleType to the Schema if the SimpleType is not anonymous.
   *
   * If the base type is not found in the schema, a DeferredSimpleType will be returned if
   * createDeferredSimpleType is true, null otherwise.
   *
   * @param schema the owning schema
   * @param name the name of the SimpleType
   * @param baseType the base type
   * @param derivation the name of the derivation method (null/""/"list"/"restriction")
   * @return the new SimpleType, or null if its parent could not be found.
   **/
  SimpleType createUserSimpleType(final Schema schema, final String name, final SimpleType baseType,
      final String derivation) {
    String internalName = name;
    if (name == null) {
      internalName = "anonymous-simple-type";
    }

    if (baseType == null) {
      // We need a base type
      LOG.warn(Messages.format("schema.noBaseType", internalName));
      return null;
    }

    SimpleType result = null;

    if ((derivation != null) && (derivation.equals(SchemaNames.LIST))) {
      // derive as list
      /*
       * This doesn't seem valid based on the XML Schema Recommendation, so I am commenting it out
       * (kvisco) if ( !(baseType instanceof AtomicType) ) { //only lists of atomic values are
       * allowed by the specification sendToLog( Messages.format("schema.deriveByListError",
       * internalName, baseType.getName()) ); return null; }
       */
      try {
        result = new ListType(schema);
      } catch (SchemaException sx) {
        LOG.warn(Messages.format("schema.deriveByListError", internalName, baseType.getName()));
        return null;
      }
      ((ListType) result).setItemType(baseType);
    } else {
      // derive as restriction (only derivation allowed apart from list for simple types)

      if (baseType instanceof Union) {
        // Stoil Valchkov (stoill@mail.bg) - this is my fix for union extension
        // if we have union - handle it here. it wont be found as buildInBaseType
        try {
          result = new Union(schema);
        } catch (SchemaException sx) {
          LOG.warn(Messages.format("schema.deriveByListError", internalName, baseType.getName()));
          return null;
        }
      } else {
        // Find the built in ancestor type
        SimpleType builtInBase = baseType.getBuiltInBaseType();
        if (builtInBase == null) {
          LOG.warn(Messages.format("schema.noBuiltInParent", internalName));
          return null;
        }
        // creates the instance of a class derived from SimpleType representing the new type.
        result = createInstance(schema, builtInBase.getName());
        if (result == null) {
          throw new SimpleTypesFactoryException(Messages.message("schema.cantLoadBuiltInTypes"));
        }
      }
    }

    result.setSchema(schema);
    result.setName(name);
    result.setBaseType(baseType);
    result.setDerivationMethod(derivation);
    result.setTypeCode(USER_TYPE);
    return result;
  }

  /**
   * Gets the informations about the built in type which name is provided as input parameter. Loads
   * the types definitions if they were not yet loaded
   */
  private Type getType(final String typeName) {
    return _typesByName.get(typeName);
  }

  /**
   * Gets the informations about the built in type which code is provided as input parameter. Loads
   * the types definitions if they were not yet loaded
   */
  private Type getType(final int typeCode) {
    return _typesByCode.get(Integer.valueOf(typeCode));
  }

  /**
   * Loads the built in type definitions from their xml file and its mapping file into the static
   * fields typesByName and typeByCode. Loading is done only once.
   */
  private synchronized void loadTypesDefinitions() {
    InputStream is = null;

    try { // Load the mapping file
      Mapping mapping = new Mapping(getClass().getClassLoader());

      is = this.getClass().getResourceAsStream(TYPE_MAPPINGS);
      mapping.loadMapping(new InputSource(is));

      // unmarshall the list of built in simple types
      Unmarshaller unmarshaller = new Unmarshaller(TypeList.class);
      unmarshaller.setMapping(mapping);
      // -- turn off validation
      unmarshaller.setValidation(false);

      is = this.getClass().getResourceAsStream(TYPE_DEFINITIONS);
      TypeList typeList = (TypeList) unmarshaller.unmarshal(new org.xml.sax.InputSource(is));

      if (LOG.isTraceEnabled()) {
        LOG.trace(typeList.toString());
      }

      // Store the types by name in the typesByName and typesByCode hashtables
      // and create for each its associated SimpleType instance.
      for (Type type : typeList.getTypes()) {
        _typesByName.put(type.getName(), type);
        type.setSimpleType(createSimpleType(BUILD_IN_SCHEMA, type));
        _typesByCode.put(Integer.valueOf(type.getSimpleType().getTypeCode()), type);
      }
    } catch (Exception except) {
      // Of course, this should not happen if the config files are there.
      String err = Messages.message("schema.cantLoadBuiltInTypes") + "; " + except;
      throw new SimpleTypesFactoryException(except, err);
    }
  }


  /**
   * Creates a SimpleType, valid only for built in types.
   */
  private SimpleType createSimpleType(final Schema schema, final Type type) {
    // Creates the instance of a class derived from SimpleType representing the type.
    SimpleType result = createInstance(schema, type.getName());

    if (result == null) {
      String err = Messages.message("schema.cantLoadBuiltInTypes");
      throw new SimpleTypesFactoryException(err);
    }

    result.setName(type.getName());

    // Load the result's typeCode
    int intCode;
    try {
      intCode = getClass().getDeclaredField(type.getCode()).getInt(null);
    } catch (Exception ex) {

      String error = Messages.message("schema.cantLoadBuiltInTypes") + ex;
      throw new SimpleTypesFactoryException(ex, error);
    }

    result.setTypeCode(intCode);

    // Find and set the result's SimpleType basetype (if any).
    if (type.getBase() != null) {
      result.setBaseType(getType(type.getBase()).getSimpleType());
    }

    // Adds the facets to the result
    Vector<TypeProperty> facets = type.getFacet();
    FacetFactory facetFactory = FacetFactory.getInstance();
    for (int index = 0; index < facets.size(); index++) {
      TypeProperty prop = facets.elementAt(index);
      if (!prop.getPseudo()) {
        // adds a "real" facet (defined in the xml specs)
        Facet facet = facetFactory.createFacet(prop.getName(), prop.getValue());
        facet.setOwningType(result);
        result.addFacet(facet);
      } else {
        // sets the information linked with the pseudo facet
        if (RealType.class.isInstance(result)) {
          RealType realResult = (RealType) result;
          if (prop.getName().equals("minM")) {
            realResult.setMinMantissa(Long.parseLong(prop.getValue()));
          } else if (prop.getName().equals("maxM")) {
            realResult.setMaxMantissa(Long.parseLong(prop.getValue()));
          } else if (prop.getName().equals("minE")) {
            realResult.setMinExponent(Long.parseLong(prop.getValue()));
          } else if (prop.getName().equals("maxE")) {
            realResult.setMaxExponent(Long.parseLong(prop.getValue()));
          }
        }
      }
    }
    return result;
  }

  /**
   * Creates the correct instance for the given type name. Valid only for built in type names.
   */
  private SimpleType createInstance(final Schema schema, final String builtInTypeName) {
    Type type = getType(builtInTypeName);

    // If the type is derived by list, return a new ListType.
    String derivation = type.getDerivedBy();
    ListType resultList = null;
    if ((derivation != null) && (derivation.equals(SchemaNames.LIST))) {
      try {
        resultList = new ListType(schema);
      } catch (SchemaException sx) {
        // -- This should not happen...but who knows!
        throw new SimpleTypesFactoryException(sx);
      }
    }

    // Finds the primitive ancestor (defines the class that represents it)
    Class implClass = null;
    while (type != null) {
      if (type.getImplClass() != null) {
        implClass = type.getImplClass();
        break;
      }
      type = getType(type.getBase());
    }

    if (implClass == null) {
      return null;
    }

    SimpleType result;
    if (implClass.isAssignableFrom(UrType.class)) {
      try {
        result = (UrType) implClass.newInstance();
        result.setSchema(schema);
      } catch (Exception e) {
        throw new SimpleTypesFactoryException(e);
      }
    } else {
      try {
        result = (AtomicType) implClass.newInstance();
        result.setSchema(schema);
      } catch (Exception except) {
        except.printStackTrace();
        result = null;
      }
      if (resultList != null) {
        resultList.setItemType(result);
        return resultList;
      }
    }
    return result;
  }
}


/**
 * A RuntimeException which allows nested exceptions.
 *
 * @author <a href="arkin@intalio.com">Assaf Arkin</a>
 * @author <a href="kvisco@intalio.com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 */
class SimpleTypesFactoryException extends RuntimeException {
  /** SerialVersionUID. */
  private static final long serialVersionUID = -7343397006284999081L;

  /** The exception which caused this Exception. */
  private Throwable _exception;


  /**
   * Creates a new SimpleTypesFactoryException.
   *
   * @param message the error message
   */
  public SimpleTypesFactoryException(final String message) {
    super(message);
  }


  /**
   * Creates a new SimpleTypesFactoryException.
   *
   * @param exception the Exception which caused this Exception.
   */
  public SimpleTypesFactoryException(final Throwable exception) {
    super(exception.toString());
    _exception = exception;
  }

  /**
   * Creates a new SimpleTypesFactoryException.
   *
   * @param exception the Exception which caused this Exception.
   * @param message the error message
   */
  public SimpleTypesFactoryException(final Throwable exception, final String message) {
    super(message);
    _exception = exception;
  }


  /**
   * Returns the Exception which caused this Exception, or null if no nested exception exists.
   *
   * @return the nested Exception.
   **/
  public Throwable getException() {
    return _exception;
  }


  public void printStackTrace() {
    if (_exception == null) {
      super.printStackTrace();
    } else {
      _exception.printStackTrace();
    }
  }


  public void printStackTrace(final PrintStream print) {
    if (_exception == null) {
      super.printStackTrace(print);
    } else {
      _exception.printStackTrace(print);
    }
  }


  public void printStackTrace(final PrintWriter print) {
    if (_exception == null) {
      super.printStackTrace(print);
    } else {
      _exception.printStackTrace(print);
    }
  }
}

