/*
 * 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);
    }
}
