/*
 * ConverterFactory.java January 2010
 *
 * Copyright (C) 2010, Niall Gallagher <niallg@users.sf.net>
 *
 * 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.simpleframework.xml.convert;

import java.lang.reflect.Constructor;

/**
 * The <code>ConverterFactory</code> is used to instantiate objects
 * based on a provided type or annotation. This provides a single
 * point of creation for all converters within the framework. For
 * performance all the instantiated converters are cached against
 * the class for that converter. This ensures the converters can
 * be acquired without the overhead of instantiation.
 * 
 * @author Niall Gallagher
 * 
 * @see org.simpleframework.xml.convert.ConverterCache
 */
class ConverterFactory {
   
   /**
    * This is the cache that is used to cache converter instances.
    */
   private final ConverterCache cache; 
   
   /**
    * Constructor for the <code>ConverterFactory</code> object. 
    * This will create an internal cache which is used to cache all
    * instantiations made by the factory. Caching the converters
    * ensures there is no overhead with instantiations.
    */
   public ConverterFactory() {
      this.cache = new ConverterCache();
   }
   
   /**
    * This is used to instantiate the converter based on the type
    * provided. If the type provided can not be instantiated for
    * some reason then an exception is thrown from this method.
    * 
    * @param type this is the converter type to be instantiated
    * 
    * @return this returns an instance of the provided type
    */
   public Converter getInstance(Class type) throws Exception {
      Converter converter = cache.fetch(type);
      
      if(converter == null) {
         return getConverter(type);
      }
      return converter;
   }
   
   /**
    * This is used to instantiate the converter based on the type
    * of the <code>Convert</code> annotation provided. If the type 
    * can not be instantiated for some reason then an exception is 
    * thrown from this method.
    * 
    * @param convert this is the annotation containing the type
    * 
    * @return this returns an instance of the provided type
    */
   public Converter getInstance(Convert convert) throws Exception {
      Class type = convert.value();
      
      if(type.isInterface()) {
         throw new ConvertException("Can not instantiate %s", type);
      }
      return getInstance(type);
   }
   
   /**
    * This is used to instantiate the converter based on the type
    * provided. If the type provided can not be instantiated for
    * some reason then an exception is thrown from this method.
    * 
    * @param type this is the converter type to be instantiated
    * 
    * @return this returns an instance of the provided type
    */
   private Converter getConverter(Class type) throws Exception {
      Constructor factory = getConstructor(type);
      
      if(factory == null){
         throw new ConvertException("No default constructor for %s", type);
      }
      return getConverter(type, factory);
   }
   
   /**
    * This is used to instantiate the converter based on the type
    * provided. If the type provided can not be instantiated for
    * some reason then an exception is thrown from this method.
    * 
    * @param type this is the converter type to be instantiated
    * @param factory this is the constructor used to instantiate
    * 
    * @return this returns an instance of the provided type
    */
   private Converter getConverter(Class type, Constructor factory) throws Exception {
      Converter converter = (Converter)factory.newInstance();
      
      if(converter != null){
         cache.cache(type, converter);
      }
      return converter;
   }
   
   /**
    * This is used to acquire the default no argument constructor
    * for the the provided type. If the constructor is not accessible
    * then it will be made accessible so that it can be instantiated.
    * 
    * @param type this is the type to acquire the constructor for
    * 
    * @return this returns the constructor for the type provided
    */
   private Constructor getConstructor(Class type) throws Exception {
      Constructor factory = type.getDeclaredConstructor();
      
      if(!factory.isAccessible()) {
         factory.setAccessible(true);
      }
      return factory;
   }
}
