/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package javax.xml.bind;

import java.io.InputStream;
import java.util.Properties;
import java.util.StringTokenizer;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

// $Id$

/**
 * The JAXBContext class provides the client's entry point to the JAXB API.
 * It provides an abstraction for managing the XML/Java binding information necessary to implement the JAXB binding
 * framework operations: unmarshal, marshal and validate. A client application obtains new instances of this class via
 * the newInstance(contextPath) method.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 18-Oct-2004
 */
public abstract class JAXBContext
{
   /** The name of the property that contains the name of the class capable of creating new JAXBContext objects. */
   public static final String JAXB_CONTEXT_FACTORY = "javax.xml.bind.context.factory";

   /** Create a Marshaller object that can be used to convert a java content tree into XML data.
    */
   public abstract Marshaller createMarshaller() throws JAXBException;

   /** Create an Unmarshaller object that can be used to convert XML data into a java content tree.
    */
   public abstract Unmarshaller createUnmarshaller() throws JAXBException;

   /** Create a Validator object that can be used to validate a java content tree against its source schema.
    */
   public abstract Validator createValidator() throws JAXBException;

   /** Obtain a new instance of a JAXBContext class.
    *
    * This is a convenience method for the newInstance method. It uses the context class loader of the current thread.
    * To specify the use of a different class loader, either set it via the Thread.setContextClassLoader() api
    * or use the newInstance method.
    */
   public static JAXBContext newInstance(String contextPath) throws JAXBException
   {
      return newInstance(contextPath, Thread.currentThread().getContextClassLoader());
   }

   /** Obtain a new instance of a JAXBContext class.
    *
    * The client application must supply a context path which is a list of colon (':', :) separated java package names
    * that contain schema derived classes. The JAXB provider will ensure that each package on the context path has
    * a jaxb.properties file which contains a value for the javax.xml.bind.context.factory property and that all
    * values resolve to the same provider.
    *
    * If there are any global XML element name collisions across the various packages listed on the contextPath,
    * a JAXBException will be thrown. Mixing generated classes from multiple JAXB Providers in the same context
    * path will also result in a JAXBException being thrown.
    */
   public static JAXBContext newInstance(String contextPath, ClassLoader classLoader) throws JAXBException
   {
      JAXBContext jaxbContext = null;
      String jaxbContextFactory = null;

      if (contextPath == null)
         throw new JAXBException("Invalid contextPath: " + contextPath);

      try
      {
         StringTokenizer st = new StringTokenizer(contextPath, ":");
         while (st.hasMoreTokens())
         {
            String path = st.nextToken().replace('.', '/');
            String jaxbPropFile = path + "/jaxb.properties";
            InputStream is = classLoader.getResourceAsStream(jaxbPropFile);
            if (is == null)
               throw new JAXBException("Cannot load resource: " + jaxbPropFile);

            Properties jaxbProps = new Properties();
            jaxbProps.load(is);
            is.close();

            String nextContextName = jaxbProps.getProperty("javax.xml.bind.context.factory");
            if (nextContextName == null)
               throw new JAXBException("Cannot read property 'javax.xml.bind.context.factory' from: " + jaxbPropFile);

            if (jaxbContextFactory != null && jaxbContextFactory.equals(nextContextName) == false)
               throw new JAXBException("JAXB provider collision: " + jaxbContextFactory + "," + nextContextName);
            else
               jaxbContextFactory = nextContextName;
         }
         Class contextFactoryClass = classLoader.loadClass(jaxbContextFactory);
         Method createMethod = contextFactoryClass.getMethod("createContext", new Class[]{String.class, ClassLoader.class});
         jaxbContext = (JAXBContext)createMethod.invoke(contextFactoryClass, new Object[]{contextPath, classLoader});
      }
      catch (InvocationTargetException ite)
      {
         Throwable targetException = ite.getTargetException();
         throw new JAXBException("Failed to create JAXBContext: " + jaxbContextFactory, targetException);
      }
      catch (Throwable t)
      {
         throw new JAXBException("Failed to create JAXBContext: " + jaxbContextFactory, t);
      }
      return jaxbContext;
   }
}
