/*
 * Copyright 2007 Werner Guttmann
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.exolab.castor.xml;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.mapping.BindingType;
import org.castor.mapping.MappingUnmarshaller;
import org.castor.xml.AbstractInternalContext;
import org.castor.xml.InternalContext;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.MappingLoader;
import org.exolab.castor.tools.MappingTool;
import org.exolab.castor.util.ChangeLog2XML;
import org.exolab.castor.xml.util.ResolverStrategy;
import org.exolab.castor.xml.util.resolvers.CastorXMLStrategy;

/**
 * Bootstrap class for Castor XML that allows you to load information about the domain objects used
 * with Castor XML (marshallers and unmarshallers) by various means.
 * 
 * @author <a href="mailto:werner DOT guttmann At gmx DOT net">Werner Guttmann</a>
 * @since 1.1.2
 */
public class XMLContext {
  /** Logger to be used. */
  private static final Log LOG = LogFactory.getFactory().getInstance(XMLContext.class);

  /**
   * The internal XML context is the class which holds a couple of Castor states as it provides some
   * central methods needed in various places of Castor.
   */
  private InternalContext _internalContext;

  /**
   * Creates an instance of {@link XMLContext} with an internal XML context.
   */
  public XMLContext() {
    AbstractInternalContext internalContext = new AbstractInternalContext() {};

    internalContext.setClassLoader(getClass().getClassLoader());

    XMLClassDescriptorResolver cdr = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory
        .createClassDescriptorResolver(BindingType.XML);
    internalContext.setXMLClassDescriptorResolver(cdr);
    cdr.setInternalContext(internalContext);

    Introspector introspector = new Introspector();
    introspector.setInternalContext(internalContext);
    internalContext.setIntrospector(introspector);
    cdr.setIntrospector(introspector);

    ResolverStrategy resolverStrategy = new CastorXMLStrategy();
    internalContext.setResolverStrategy(resolverStrategy);
    cdr.setResolverStrategy(resolverStrategy);

    _internalContext = internalContext;
  }

  /**
   * Instructs Castor to load class descriptors from the mapping given.
   * 
   * @param mapping Castor XML mapping (file), from which the required class descriptors will be
   *        derived.
   * @throws MappingException If the {@link Mapping} cannot be loaded and analyzed successfully.
   */
  public void addMapping(final Mapping mapping) throws MappingException {
    MappingUnmarshaller mappingUnmarshaller = new MappingUnmarshaller();
    MappingLoader mappingLoader = mappingUnmarshaller.getMappingLoader(mapping, BindingType.XML);
    _internalContext.getXMLClassDescriptorResolver().setMappingLoader(mappingLoader);
  }

  /**
   * Loads the class descriptor for the class instance specified. The use of this method is useful
   * when no mapping is used, as happens when the domain classes has been generated using the XML
   * code generator (in which case instead of a mapping file class descriptor files will be
   * generated).
   * 
   * @param clazz the class for which the associated descriptor should be loaded.
   * @throws ResolverException in case that resolving the Class fails fatally
   */
  public void addClass(final Class clazz) throws ResolverException {
    _internalContext.getXMLClassDescriptorResolver().addClass(clazz);
  }

  /**
   * Loads the class descriptor for the class instance specified. The use of this method is useful
   * when no mapping is used, as happens when the domain classes hase been generated using the XML
   * code generator (in which case instead of a mapping file class descriptor files will be
   * generated).
   * 
   * @param clazzes the classes for which the associated descriptor should be loaded.
   * @throws ResolverException in case that resolving the Class fails fatally
   */
  public void addClasses(final Class[] clazzes) throws ResolverException {
    _internalContext.getXMLClassDescriptorResolver().addClasses(clazzes);
  }

  /**
   * Loads class descriptors from the package specified. The use of this method is useful when no
   * mapping is used, as happens when the domain classes hase been generated using the XML code
   * generator (in which case instead of a mapping file class descriptor files will be generated).
   * <p>
   * Please note that this functionality will work only if you provide the <tt>.castor.cdr</tt> file
   * with your generated classes (as generated by the XML code generator).
   * <p>
   * 
   * @param packageName The package name for the (descriptor) classes
   * @throws ResolverException If there's a problem loading class descriptors for the given package.
   */
  public void addPackage(final String packageName) throws ResolverException {
    _internalContext.getXMLClassDescriptorResolver().addPackage(packageName);
  }

  /**
   * Loads class descriptors from the packages specified. The use of this method is useful when no
   * mapping is used, as happens when the domain classes hase been generated using the XML code
   * generator (in which case instead of a mapping file class descriptor files will be generated).
   * <p>
   * Please note that this functionality will work only if you provide the <tt>.castor.cdr</tt>
   * files with your generated classes (as generated by the XML code generator).
   * <p>
   * 
   * @param packageNames The package names for the (descriptor) classes
   * @throws ResolverException If there's a problem loading class descriptors for the given package.
   */
  public void addPackages(final String[] packageNames) throws ResolverException {
    _internalContext.getXMLClassDescriptorResolver().addPackages(packageNames);
  }

  /**
   * Creates an instance of a Castor XML specific {@link Mapping} instance.
   * 
   * @return a Castor XML specific {@link Mapping} instance.
   */
  public Mapping createMapping() {
    Mapping mapping = new Mapping();
    // mapping.setBindingType(BindingType.XML);
    return mapping;
  }

  /**
   * Creates a new {@link Marshaller} instance to be used for marshalling.
   * 
   * @return A new {@link Marshaller} instance.
   */
  public Marshaller createMarshaller() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Creating new Marshaller instance.");
    }
    Marshaller marshaller = new Marshaller(_internalContext);
    return marshaller;
  }

  /**
   * Creates a new {@link Unmarshaller} instance to be used for unmarshalling.
   * 
   * @return A new {@link Unmarshaller} instance, preconfigured with a
   *         {@link XMLClassDescriptorResolver} instance with the class descriptors cached as loaded
   *         above.
   */
  public Unmarshaller createUnmarshaller() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Creating new Unmarshaller instance.");
    }
    Unmarshaller unmarshaller = new Unmarshaller(_internalContext);
    return unmarshaller;
  }

  // /**
  // * To create a schema reader instance for reading XSD files.
  // * @param inputSource the InputSource to read from
  // * @return the SchemaReader instance created and initialized
  // */
  // public SchemaReader createSchemaReader(final InputSource inputSource) {
  // if (LOG.isDebugEnabled()) {
  // LOG.debug("Creating new SchemaReader instance.");
  // }
  // SchemaReader sr = new SchemaReader();
  // sr.setInternalContext(_internalContext);
  // sr.setInputSource(inputSource);
  // return sr;
  // }
  //
  // /**
  // * To create a schema writer instance for writing XSD files.
  // * @param writer the Writer to write the text representation of the schema to
  // * @return the SchemaWriter instance created and initialized
  // * @throws IOException in case that initialization of SchemaWriter fails
  // */
  // public SchemaWriter createSchemaWriter(final Writer writer) throws IOException {
  // if (LOG.isDebugEnabled()) {
  // LOG.debug("Creating new SchemaWriter instance.");
  // }
  // SchemaWriter sw = new SchemaWriter();
  // sw.setInternalContext(_internalContext);
  // sw.setDocumentHandler(writer);
  // return sw;
  // }

  /**
   * To create a MappingTool instance.
   * 
   * @return the MappingTool instance ready to use
   */
  public MappingTool createMappingTool() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Creating new MappingTool instance.");
    }
    MappingTool mt = new MappingTool();
    mt.setInternalContext(_internalContext);
    return mt;
  }

  /**
   * To create a new {@link ChangeLog2XML} instance.
   * 
   * @return the {@link ChangeLog2XML} instance ready to use
   */
  public ChangeLog2XML createChangeLog2XML() {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Creating new ChangeLog2XML instance.");
    }
    ChangeLog2XML changeLog2XML = new ChangeLog2XML();
    changeLog2XML.setInternalContext(_internalContext);
    return changeLog2XML;
  }

  /**
   * To set properties for marshalling and unmarshalling behavior.
   * 
   * @param propertyName name of the property to set
   * @param value the value to set to
   */
  public void setProperty(final String propertyName, final Object value) {
    _internalContext.setProperty(propertyName, value);
  }

  /**
   * To set properties for marshalling and unmarshalling behavior.
   * 
   * @param propertyName name of the property to set
   * @param value the value to set to
   */
  public void setProperty(final String propertyName, final boolean value) {
    _internalContext.setProperty(propertyName, value);
  }

  /**
   * To get the value of a specific property.
   * 
   * @param propertyName name of the Property
   * @return the value (Object) of the property
   */
  public Object getProperty(final String propertyName) {
    return _internalContext.getProperty(propertyName);
  }

  /**
   * To get the {@link InternalContext} as used when instantiating other classes. Mind that this
   * method is only used in tests and should NOT be used in production code!
   * 
   * @return the {@link InternalContext} used
   * @deprecated
   */
  public InternalContext getInternalContext() {
    return _internalContext;
  }

  /**
   * Sets a custom {@link ClassLoader} to be used for loading classes.
   * 
   * @param classLoader A custom {@link ClassLoader}.
   */
  public void setClassLoader(ClassLoader classLoader) {
    this._internalContext.setClassLoader(classLoader);
  }
}
