/*
 *  Copyright 2010, 2011 Chris Pheby
 *
 *  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.jadira.bindings.core.binder;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import javax.enterprise.inject.Typed;

import org.jadira.bindings.core.annotation.BindingScope;
import org.jadira.bindings.core.annotation.DefaultBinding;
import org.jadira.bindings.core.api.Binding;
import org.jadira.bindings.core.api.Converter;
import org.jadira.bindings.core.api.FromUnmarshaller;
import org.jadira.bindings.core.api.ToMarshaller;
import org.jadira.bindings.core.general.binding.CompositeBinding;
import org.jadira.bindings.core.general.binding.InverseCompositeBinding;
import org.jadira.bindings.core.general.converter.FromUnmarshallerConverter;
import org.jadira.bindings.core.general.converter.ToMarshallerConverter;
import org.jadira.bindings.core.general.marshaller.ConverterToMarshaller;
import org.jadira.bindings.core.general.marshaller.MethodToMarshaller;
import org.jadira.bindings.core.general.unmarshaller.ConstructorFromUnmarshaller;
import org.jadira.bindings.core.general.unmarshaller.ConverterFromUnmarshaller;
import org.jadira.bindings.core.general.unmarshaller.MethodFromUnmarshaller;
import org.jadira.bindings.core.jdk.AtomicBooleanStringBinding;
import org.jadira.bindings.core.jdk.AtomicIntegerStringBinding;
import org.jadira.bindings.core.jdk.AtomicLongStringBinding;
import org.jadira.bindings.core.jdk.BigDecimalStringBinding;
import org.jadira.bindings.core.jdk.BigIntegerStringBinding;
import org.jadira.bindings.core.jdk.BooleanStringBinding;
import org.jadira.bindings.core.jdk.ByteStringBinding;
import org.jadira.bindings.core.jdk.CalendarStringBinding;
import org.jadira.bindings.core.jdk.CharSequenceStringBinding;
import org.jadira.bindings.core.jdk.CharacterStringBinding;
import org.jadira.bindings.core.jdk.ClassStringBinding;
import org.jadira.bindings.core.jdk.CurrencyStringBinding;
import org.jadira.bindings.core.jdk.DateStringBinding;
import org.jadira.bindings.core.jdk.DoubleStringBinding;
import org.jadira.bindings.core.jdk.FileStringBinding;
import org.jadira.bindings.core.jdk.FloatStringBinding;
import org.jadira.bindings.core.jdk.InetAddressStringBinding;
import org.jadira.bindings.core.jdk.IntegerStringBinding;
import org.jadira.bindings.core.jdk.LocaleStringBinding;
import org.jadira.bindings.core.jdk.LongStringBinding;
import org.jadira.bindings.core.jdk.PackageStringBinding;
import org.jadira.bindings.core.jdk.ShortStringBinding;
import org.jadira.bindings.core.jdk.StringBufferStringBinding;
import org.jadira.bindings.core.jdk.StringBuilderStringBinding;
import org.jadira.bindings.core.jdk.StringStringBinding;
import org.jadira.bindings.core.jdk.TimeZoneStringBinding;
import org.jadira.bindings.core.jdk.URIStringBinding;
import org.jadira.bindings.core.jdk.URLStringBinding;
import org.jadira.bindings.core.jdk.UUIDStringBinding;
import org.jadira.bindings.core.loader.BindingConfiguration;
import org.jadira.bindings.core.loader.BindingConfigurationEntry;
import org.jadira.bindings.core.loader.BindingXmlLoader;
import org.jadira.bindings.core.loader.Extension;
import org.jadira.bindings.core.loader.Provider;
import org.jadira.bindings.core.spi.ConverterProvider;
import org.jadira.bindings.core.utils.lang.IterableEnumeration;
import org.jadira.bindings.core.utils.reflection.ClassLoaderUtils;

/**
 * Core binding capability.
 * Typically you would obtain an instance of this class either
 * by direct construction, or in JDK6 and above by using 
 * <code>java.util.ServiceLoader</code>.
 * 
 * Default construction of this class registers bindings for core
 * JDK classes, as well as any bindings specified using bindings.xml
 * 
 * You can also register additional bindings programmatically.
 * @see Binding Binding is used to define a binding
 */
@Typed({})
public class BasicBinder implements Binder, RegisterableBinder {

	/**
	 * ConverterProviders are used to resolve converters using annotations on the target class.
	 * Because additional converter providers can be registered they provide a mechanism for extending the framework.
	 */
	private final List<ConverterProvider> converterProviders = new ArrayList<ConverterProvider>();
	
	/**
	 * A map of registered converters
	 */
	private ConcurrentHashMap<ConverterKey<?,?>, Converter<?,?>> registeredConverters = new ConcurrentHashMap<ConverterKey<?,?>, Converter<?,?>>();

	/**
	 * A map of registered converters
	 */
	private ConcurrentHashMap<Class<?>, Object> extendedBinders = new ConcurrentHashMap<Class<?>, Object>();
	
	/**
	 * A reference of classes which have already been introspected for conversion annotations.
	 */
    private final Set<Class<?>> inspectedClasses = new HashSet<Class<?>>();
	
    /**
     * Creates a new instance, initialised with standard, and registered bindings
     */
    public BasicBinder() {
        this(true);
    }

    /**
     * Creates a new instance
     * @param includeBuiltInBindings If true, initialise with standard, and registered bindings
     */
    public BasicBinder(boolean includeBuiltInBindings) {

    	initExtendedBinders();
    	
        if (includeBuiltInBindings) {

            initJdkBindings();

            // Before we load other configuration, load the binding
            // configuration from
            // jadira-bindings' jar to ensure no-one overrides our built-in
            // mappings.
            initBuiltInBindings();

            // Init bindings registered from other jars
            initRegisteredBindings();
        }
    }
	
    /******************
     *                *
     * Bootstrapping  * 
     *                *
     ******************/

	/**
	 *  Initialise the instance with built in extended binders
	 */
	private void initExtendedBinders() {
		extendedBinders.put(StringBinder.class, this);
	}
    
    /**
     * Initialises standard bindings for Java built in types
     */
    private void initJdkBindings() {
        
        registerBinding(AtomicBoolean.class, String.class, new AtomicBooleanStringBinding());
        registerBinding(AtomicInteger.class, String.class, new AtomicIntegerStringBinding());
        registerBinding(AtomicLong.class, String.class, new AtomicLongStringBinding());
        registerBinding(BigDecimal.class, String.class, new BigDecimalStringBinding());
        registerBinding(BigInteger.class, String.class, new BigIntegerStringBinding());
        registerBinding(Boolean.class, String.class, new BooleanStringBinding());
        registerBinding(Byte.class, String.class, new ByteStringBinding());
        registerBinding(Calendar.class, String.class, new CalendarStringBinding());
        registerBinding(Character.class, String.class, new CharacterStringBinding());
        registerBinding(CharSequence.class, String.class, new CharSequenceStringBinding());
        registerBinding(Class.class, String.class, new ClassStringBinding());
        registerBinding(Currency.class, String.class, new CurrencyStringBinding());
        registerBinding(Date.class, String.class, new DateStringBinding());
        registerBinding(Double.class, String.class, new DoubleStringBinding());
        registerBinding(File.class, String.class, new FileStringBinding());
        registerBinding(Float.class, String.class, new FloatStringBinding());
        registerBinding(InetAddress.class, String.class, new InetAddressStringBinding());
        registerBinding(Integer.class, String.class, new IntegerStringBinding());
        registerBinding(Locale.class, String.class, new LocaleStringBinding());
        registerBinding(Long.class, String.class, new LongStringBinding());
        registerBinding(Package.class, String.class, new PackageStringBinding());
        registerBinding(Short.class, String.class, new ShortStringBinding());
        registerBinding(StringBuffer.class, String.class, new StringBufferStringBinding());
        registerBinding(StringBuilder.class, String.class, new StringBuilderStringBinding());
        registerBinding(String.class, String.class, new StringStringBinding());
        registerBinding(TimeZone.class, String.class, new TimeZoneStringBinding());
        registerBinding(URI.class, String.class, new URIStringBinding());
        registerBinding(URL.class, String.class, new URLStringBinding());
        registerBinding(UUID.class, String.class, new UUIDStringBinding());
    }
 
    /**
     * Initialises bindings registered with the framework included bindings.xml
     * This includes the built in support for converters for annotation processing and Joda Convert annotations.
     */
    private void initBuiltInBindings() {

        final URL builtInBindingsUrl = getBuiltInBindingsURL();
        registerConfiguration(builtInBindingsUrl);
    }

	private URL getBuiltInBindingsURL() {

		String classResource = BasicBinder.class.getCanonicalName().replace('.', '/') + ".class";
        
        URL bindingClassUrl = Thread.currentThread().getContextClassLoader().getResource(classResource);
        
        String classPrefix = bindingClassUrl.toString().substring(0,
                bindingClassUrl.toString().indexOf(classResource));

        final URL builtInBindingsUrl;
        try {
            builtInBindingsUrl = new URL(classPrefix + "META-INF/bindings.xml");
        } catch (IOException e) {
            throw new IllegalStateException("Error registering bindings: " + e.getMessage(), e);
        }
		return builtInBindingsUrl;
	}
    
    /**
     * Initialises any bindings bundled with other (third-party / user) jars
     */
    private void initRegisteredBindings() {
        
        Enumeration<URL> bindingsConfiguration;
        try {
            bindingsConfiguration = ClassLoaderUtils.getClassLoader().getResources("META-INF/bindings.xml");
        } catch (IOException e) {
            throw new IllegalStateException("Error registering bindings: " + e.getMessage(), e);
        }

        registerConfigurations(bindingsConfiguration);
    }

    /**
     * Registers a set of configurations for the given list of URLs. 
     * This is typically used to process all the various bindings.xml files discovered in
     * jars on the classpath. It is given protected scope to allow subclasses to register
     * additional configurations
     * @param bindingsConfiguration An enumeration of the URLs to process
     * @param <X> Internally used type argument
     */
    protected <X> void registerConfigurations(Enumeration<URL> bindingsConfiguration) {
        List<BindingConfiguration> configs = new ArrayList<BindingConfiguration>();
        for (URL nextLocation : IterableEnumeration.wrapEnumeration(bindingsConfiguration)) {
            
        	// Filter built in bindings - these are already registered by calling registerConfiguration directly
        	URL builtIn = getBuiltInBindingsURL();
        	if (!builtIn.toString().equals(nextLocation.toString())) {
        		configs.add(BindingXmlLoader.load(nextLocation));
        	}
        }
        
        for (BindingConfiguration nextConfig : configs) {
                for (Provider nextProvider : nextConfig.getProviders()) {
            
	                try {
	                    registerConverterProvider(nextProvider.getProviderClass().newInstance());
	                } catch (InstantiationException e) {
	                    throw new IllegalStateException("Cannot instantiate binding provider class: " + nextProvider.getProviderClass().getName());
	                } catch (IllegalAccessException e) {
	                    throw new IllegalStateException("Cannot access binding provider class: " + nextProvider.getProviderClass().getName());
	                }
                }
                
                for (Extension<?> nextExtension : nextConfig.getExtensions()) {
            		try {
            			@SuppressWarnings("unchecked") Extension<X> myExtension = (Extension<X>)nextExtension;
            			@SuppressWarnings("unchecked") X myImplementation = (X) nextExtension.getImplementationClass().newInstance();
            			registerExtendedBinder(myExtension.getExtensionClass(), myImplementation);
	                } catch (InstantiationException e) {
	                    throw new IllegalStateException("Cannot instantiate binder extension class: " + nextExtension.getExtensionClass().getName());
	                } catch (IllegalAccessException e) {
	                    throw new IllegalStateException("Cannot access binder extension class: " + nextExtension.getExtensionClass().getName());
	                }
                }
        
            registerBindingConfigurationEntries(nextConfig.getBindingEntries());
        }
    }
    
    /**
     * Register the configuration file (bindings.xml) at the given URL 
     */
    public final void registerConfiguration(URL nextLocation) {
        
        BindingConfiguration configuration = BindingXmlLoader.load(nextLocation);

        for (Provider nextProvider : configuration.getProviders()) {
            
            try {
                registerConverterProvider(nextProvider.getProviderClass().newInstance());
            } catch (InstantiationException e) {
                throw new IllegalStateException("Cannot instantiate binding provider class: " + nextProvider.getProviderClass().getName());
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Cannot access binding provider class: " + nextProvider.getProviderClass().getName());
            }
        }
        
        registerBindingConfigurationEntries(configuration.getBindingEntries());
    }
    
    /**
     * Method used to register one or more converter providers. Converters extend the binding framework to provide 
     * additional mechanisms for discovering and registering bindings (e.g. using Joda Convert).
     * This method is protected so that subclasses can register additional providers.
     * @param providers A collection of providers
     */
    public void registerConverterProviders(ConverterProvider... providers) {

        for (ConverterProvider nextProvider : providers) {
            registerConverterProvider(nextProvider);
        }
    }
    
    /**
     * Register a single converter provider
     * @param provider The provider.
     */
    public void registerConverterProvider(ConverterProvider provider) {
        this.converterProviders.add(provider);
    }
    
    /**
     * Registers a list of binding configuration entries. A binding configuration entry described in a section of a bindings.xml file 
     * and describes the use of a particular method for databinding.
     * @param bindings The entries to register
     */
    protected void registerBindingConfigurationEntries(Iterable<BindingConfigurationEntry> bindings) {

        for (BindingConfigurationEntry nextBinding : bindings) {
        	try {
        		registerBindingConfigurationEntry(nextBinding);
        	} catch (IllegalStateException e) {
        		// Ignore this - it can happen when introspecting class mappings
        	}
        }
    }
    
    /**
     * Register a particular binding configuration entry.
     * @param theBinding The entry to be registered
     * @param <S> The Source type for the binding
     * @param <T> The Target type for the binding
     */
	protected <S,T> void registerBindingConfigurationEntry(BindingConfigurationEntry theBinding) {
		
        /*
         * BindingConfigurationEntry has two possible configurations:
         * 
         * bindingClass with an optional qualifier (this defaults to DefaultBinding)
         * 
         *  OR
         *  
         * sourceClass and
         * targetClass and
         * optional qualifier (defaults to DefaultBinding)
         * with at least one of either
         * toMethod and/or
         * fromMethod and/or
         * fromConstructor         
         * 
         * Depending on which components are populated the entry is interpreted differently.
         */
		
		if (Binding.class.isAssignableFrom(theBinding.getBindingClass())) {
        	
        	/*
         	 * If the binding class is an instance of the Binding interface then register it.
         	 * When the binding class is an interface, you must define source and target class if they cannot be
         	 * determined from introspecting the interface.
         	 * 
         	 * You can optionally supply a qualifier so that the binding is associated with a qualifier
         	 */
        	try {
        		@SuppressWarnings("unchecked")
				Binding<S,T> binding = (Binding<S,T>)theBinding.getBindingClass().newInstance();
                registerBinding(binding.getBoundClass(), binding.getTargetClass(), binding, theBinding.getQualifier());
            } catch (InstantiationException e) {
                throw new IllegalStateException("Cannot instantiate binding class: " + theBinding.getBindingClass().getName());
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Cannot access binding class: " + theBinding.getBindingClass().getName());
            }
        } else if (FromUnmarshaller.class.isAssignableFrom(theBinding.getBindingClass())) {
        	
        	/*
         	 * If the binding class is an instance of the FromUnmarshaller interface then register it.
         	 * When the class is an interface, you must define source and target class if they cannot be
         	 * determined from introspecting the interface.
         	 * 
         	 * You can optionally supply a qualifier so that the binding is associated with a qualifier
         	 */
        	try {
        		@SuppressWarnings("unchecked")
				FromUnmarshaller<S,T> fromUnmarshaller = (FromUnmarshaller<S,T>)theBinding.getBindingClass().newInstance();
                registerUnmarshaller(fromUnmarshaller.getBoundClass(), fromUnmarshaller.getTargetClass(), fromUnmarshaller, theBinding.getQualifier());
            } catch (InstantiationException e) {
                throw new IllegalStateException("Cannot instantiate binding class: " + theBinding.getBindingClass().getName());
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Cannot access binding class: " + theBinding.getBindingClass().getName());
            }
		} else if (ToMarshaller.class.isAssignableFrom(theBinding.getBindingClass())) {
        	
        	/*
         	 * If the binding class is an instance of the ToMarshaller interface then register it.
         	 * When the class is an interface, you must define source and target class if they cannot be
         	 * determined from introspecting the interface.
         	 * 
         	 * You can optionally supply a qualifier so that the binding is associated with a qualifier
         	 */
        	try {
        		@SuppressWarnings("unchecked")
				ToMarshaller<S,T> toMarshaller = (ToMarshaller<S,T>)theBinding.getBindingClass().newInstance();
                registerMarshaller(toMarshaller.getBoundClass(), toMarshaller.getTargetClass(), toMarshaller, theBinding.getQualifier());
            } catch (InstantiationException e) {
                throw new IllegalStateException("Cannot instantiate binding class: " + theBinding.getBindingClass().getName());
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Cannot access binding class: " + theBinding.getBindingClass().getName());
            }
		} else if (Converter.class.isAssignableFrom(theBinding.getBindingClass())) {
        	
        	/*
         	 * If the binding class is an instance of the Converter interface then register it.
         	 * When the class is an interface, you must define source and target class if they cannot be
         	 * determined from introspecting the interface.
         	 * 
         	 * You can optionally supply a qualifier so that the binding is associated with a qualifier
         	 */
        	try {
        		@SuppressWarnings("unchecked")
				Converter<S,T> converter = (Converter<S,T>)theBinding.getBindingClass().newInstance();
                registerConverter(converter.getInputClass(), converter.getOutputClass(), converter, theBinding.getQualifier());
            } catch (InstantiationException e) {
                throw new IllegalStateException("Cannot instantiate binding class: " + theBinding.getBindingClass().getName());
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Cannot access binding class: " + theBinding.getBindingClass().getName());
            }
		} else if (theBinding.getBindingClass() != null) {

    		/*
    		 * If only the binding class is supplied, then inspect it for bindings, gathering all bindings identified
    		 */
			registerAnnotatedClasses(theBinding.getBindingClass());
        } else {
        	
    		/*
    		 * Register the binding using the explicit method details provided 
    		 */
        	@SuppressWarnings("unchecked")
        	ConverterKey<S,T> converterKey = new ConverterKey<S,T>((Class<S>)theBinding.getSourceClass(), (Class<T>)theBinding.getTargetClass(), theBinding.getQualifier());
        	@SuppressWarnings("unchecked")
        	Constructor<S> fromConstructor = (Constructor<S>)theBinding.getFromConstructor();
        	
            registerForMethods(converterKey, theBinding.getToMethod(), theBinding.getFromMethod(), fromConstructor);
        }
    }
    
	/**
	 * Inspect each of the supplied classes, processing any of the annotated methods found
	 * @param classesToInspect The classes to inspect for annotations
	 */
	public void registerAnnotatedClasses(Class<?>... classesToInspect) {
		
		for (Class<?> nextClass : classesToInspect) {
			
			Class<?> loopClass = nextClass;
			while ((loopClass != Object.class) && (!inspectedClasses.contains(loopClass))) {
	
				attachForAnnotations(loopClass);
	
				loopClass = loopClass.getSuperclass();
			}		
		}
	}

    /**********************
     *                    *
     * Extended Binders   * 
     *                    *
     **********************/    
	
    /**
     * Register a custom, typesafe binder implementation which can be retrieved later
	 * @param iface The interface for the provider to be registered.
     * @param provider The implementation.
     * @param <I> The class to be registered
     * @param <T> Implementation of the binder type, I.
     */
    protected <I, T extends I> void registerExtendedBinder(Class<I> iface, T provider) {
        extendedBinders.put(iface, provider);
    }
    
    /**
     * Retrieves an extended binder
     * @param cls The implementation.
     * @param <I> Interface type for the extended binder
     * @return The found extended binder
     */
    @SuppressWarnings("unchecked")
	protected <I> I getExtendedBinder(Class<I> cls) {
        return (I) extendedBinders.get(cls);
    }
	
    /******************
     *                *
     * Registration   * 
     *                *
     ******************/
    
	/**
	 * Register a Binding with the given source and target class.
	 * A binding unifies a marshaller and an unmarshaller and both must be available to resolve a binding.
	 * 
	 * The source class is considered the owning class of the binding. The source can be marshalled
	 * into the target class. Similarly, the target can be unmarshalled to produce an instance of the source type.
	 * @param source The source (owning) class
	 * @param target The target (foreign) class
	 * @param converter The binding to be registered
	 */
	public final <S, T> void registerBinding(Class<S> source, Class<T> target, Binding<S, T> converter) {
        Class<? extends Annotation> scope = matchImplementationToScope(converter.getClass());
        registerBinding(new ConverterKey<S,T>(source, target, scope == null ? DefaultBinding.class : scope), converter);	
	}

	/**
	 * Register an UnMarshaller with the given source and target class.
	 * The unmarshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class
	 * @param converter The FromUnmarshaller to be registered  
	 */
	public final <S, T> void registerUnmarshaller(Class<S> source, Class<T> target, FromUnmarshaller<S, T> converter) {
        Class<? extends Annotation> scope = matchImplementationToScope(converter.getClass());
        registerUnmarshaller(new ConverterKey<S,T>(source, target, scope == null ? DefaultBinding.class : scope), converter);	
	}

	/**
	 * Register a Marshaller with the given source and target class.
	 * The marshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class
	 * @param converter The ToMarshaller to be registered
	 */
	public final <S, T> void registerMarshaller(Class<S> source, Class<T> target, ToMarshaller<S, T> converter) {
        Class<? extends Annotation> scope = matchImplementationToScope(converter.getClass());
        registerMarshaller(new ConverterKey<S,T>(source, target, scope == null ? DefaultBinding.class : scope), converter);	
	}
	
	/**
	 * Register a Converter with the given input and output classes. Instances of the input class can be converted into 
	 * instances of the output class
	 * @param input The input class
	 * @param output The output class
	 * @param converter The Converter to be registered   
	 */
	public final <S, T> void registerConverter(Class<S> input, Class<T> output, Converter<S, T> converter) {
        Class<? extends Annotation> scope = matchImplementationToScope(converter.getClass());
        registerConverter(new ConverterKey<S,T>(input, output, scope == null ? DefaultBinding.class : scope), converter);
    }

	/**
	 * Register a Binding with the given source and target class.
	 * A binding unifies a marshaller and an unmarshaller and both must be available to resolve a binding.
	 * 
	 * The source class is considered the owning class of the binding. The source can be marshalled
	 * into the target class. Similarly, the target can be unmarshalled to produce an instance of the source type.
	 * @param source The source (owning) class
	 * @param target The target (foreign) class
	 * @param converter The binding to be registered
	 * @param qualifier The qualifier for which the binding must be registered
	 */
	public final <S, T> void registerBinding(Class<S> source, Class<T> target, Binding<S, T> converter, Class<? extends Annotation> qualifier) {
		registerBinding(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier), converter);
	}

	/**
	 * Register a Binding with the given source and target class.
	 * A binding unifies a marshaller and an unmarshaller and both must be available to resolve a binding.
	 * 
	 * The source class is considered the owning class of the binding. The source can be marshalled
	 * into the target class. Similarly, the target can be unmarshalled to produce an instance of the source type.
	 * @param key Converter Key to use
	 * @param converter The binding to be registered
	 */
	public final <S, T> void registerBinding(ConverterKey<S,T> key, Binding<S, T> converter) {
		registerConverter(key.invert(), new FromUnmarshallerConverter<S,T>(converter));
		registerConverter(key, new ToMarshallerConverter<S,T>(converter));
	}

	
	/**
	 * Register an UnMarshaller with the given source and target class.
	 * The unmarshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class
	 * @param converter The FromUnmarshaller to be registered
	 * @param qualifier The qualifier for which the unmarshaller must be registered  
	 */
	public final <S, T> void registerUnmarshaller(Class<S> source, Class<T> target, FromUnmarshaller<S, T> converter, Class<? extends Annotation> qualifier) {
		registerUnmarshaller(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier), converter);
	}

	/**
	 * Register an UnMarshaller with the given source and target class.
	 * The unmarshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param key Converter Key to use
	 * @param converter The FromUnmarshaller to be registered
	 */
	public final <S, T> void registerUnmarshaller(ConverterKey<S,T> key, FromUnmarshaller<S, T> converter) {
		registerConverter(key.invert(), new FromUnmarshallerConverter<S,T>(converter));
	}
	
	/**
	 * Register a Marshaller with the given source and target class.
	 * The marshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class
	 * @param converter The ToMarshaller to be registered
	 * @param qualifier The qualifier for which the marshaller must be registered 
	 */
	public final <S, T> void registerMarshaller(Class<S> source, Class<T> target, ToMarshaller<S, T> converter, Class<? extends Annotation> qualifier) {
		registerMarshaller(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier), converter);
	}

	/**
	 * Register a Marshaller with the given source and target class.
	 * The marshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param key Converter Key to use
	 * @param converter The ToMarshaller to be registered
	 */
	public final <S, T> void registerMarshaller(ConverterKey<S,T> key, ToMarshaller<S, T> converter) {
		registerConverter(key, new ToMarshallerConverter<S,T>(converter));
	}
	
	/**
	 * Register a Converter with the given input and output classes. Instances of the input class can be converted into 
	 * instances of the output class
	 * @param input The input class
	 * @param output The output class
	 * @param converter The Converter to be registered
	 * @param qualifier The qualifier for which the converter must be registered   
	 */
	public final <S, T> void registerConverter(Class<S> input, Class<T> output, Converter<S, T> converter, Class<? extends Annotation> qualifier) {
		registerConverter(new ConverterKey<S,T>(input, output, qualifier == null ? DefaultBinding.class : qualifier), converter);
	}
		
	/**
	 * Register a Converter with the given input and output classes. Instances of the input class can be converted into 
	 * instances of the output class
	 * @param key Converter Key to use
	 * @param converter The Converter to be registered
	 */
	public final <S, T> void registerConverter(ConverterKey<S,T> key, Converter<S, T> converter) {	

        if (key.getInputClass() == null) {
            throw new IllegalArgumentException("Input Class must not be null");
        }
        if (key.getOutputClass() == null) {
            throw new IllegalArgumentException("Output Class must not be null");
        }
        if (converter == null) {
            throw new IllegalArgumentException("Converter must not be null");
        }

        if (key.getQualifierAnnotation() == null) {
            throw new IllegalArgumentException("Qualifier must not be null");
        }

        Converter<?,?> old = registeredConverters.putIfAbsent(key, converter);
        if (old != null && (!isSameConverter(old, converter))) {
        	throw new IllegalStateException("Converter already registered for key: " + key);
        }
    }
	
	@SuppressWarnings("unlikely-arg-type")
	private boolean isSameConverter(Converter<?,?> old, Converter<?,?> converter) {
		
		if (old.getClass().equals(converter.getClass())) {
			return true;
		}

		// Special cases which arises when processing the identity function
		if (old instanceof FromUnmarshallerConverter && converter instanceof ToMarshallerConverter
				&& (((FromUnmarshallerConverter<?, ?>)old).getUnmarshaller().equals(((ToMarshallerConverter<?, ?>)converter).getMarshaller()))) {
			return true;
		}
		if (old instanceof ToMarshallerConverter && converter instanceof FromUnmarshallerConverter
				&& (((ToMarshallerConverter<?, ?>)old).getMarshaller().equals(((FromUnmarshallerConverter<?, ?>)converter).getUnmarshaller()))) {
			return true;
		}
		return false;
	}
	
    /******************
     *                *
     * Conversion API * 
     *                *
     ******************/
	
	/**
	 * Convert an object to the given target class
	 * This method infers the source type for the conversion from the runtime type of object.
	 * @param output The target class to convert the object to
	 * @param object The object to be converted
	 */
	public <S, T> T convertTo(Class<T> output, Object object) {
		return convertTo(output, object, DefaultBinding.class);
	}
	
	/**
	 * Convert an object to the given target class
	 * This method infers the source type for the conversion from the runtime type of object.
	 * @param output The target class to convert the object to
	 * @param object The object to be converted
	 * @param qualifier The qualifier for which the binding must be registered
	 */
	public <S, T> T convertTo(Class<T> output, Object object, Class<? extends Annotation> qualifier) {

		if (object == null) {
			return null;
		}
		
		@SuppressWarnings("unchecked")
		Converter<S, T> conv = (Converter<S, T>) determineConverter(object.getClass(), output, qualifier == null ? DefaultBinding.class : qualifier);
		
		if (conv == null) {
			@SuppressWarnings("unchecked")
			Class<S> inputClass = (Class<S>)object.getClass();
			throw new NoConverterFoundException(new ConverterKey<S,T>(inputClass, output, qualifier == null ? DefaultBinding.class : qualifier));
		}
		
		@SuppressWarnings("unchecked")
		S myObject = (S)object;
		return conv.convert(myObject);
	}
		
	private <S, T> Converter<S, T> determineConverter(Class<S> candidateClass, Class<T> output, Class<? extends Annotation> qualifier) {
		
		if (!candidateClass.equals(Object.class)) {
			Converter<S, T> match = findConverter(candidateClass, output, qualifier);
			if (match != null) {
				return match;
			}

			@SuppressWarnings("unchecked")	
			Class<S>[] interfaces = (Class<S>[])candidateClass.getInterfaces();
			for (Class<S> candidateInterface : interfaces) {
				match = determineConverter(candidateInterface, output, qualifier);
				if (match != null) {
					return match;
				}	
			}
			
			Class<? super S> superClass = (Class<? super S>)candidateClass.getSuperclass();
			
			@SuppressWarnings("unchecked")
			Converter<S,T> superMatch = (Converter<S, T>) determineConverter(superClass, output, qualifier);
			return superMatch;
		} else {
			return null;
		}
	}
	
	/**
	 * Convert an object which is an instance of source class to the given target class
	 * @param input The class of the object to be converted
	 * @param output The target class to convert the object to
	 * @param object The object to be converted
	 */
	public <S, T> T convertTo(Class<S> input, Class<T> output, Object object) {
		return convertTo(new ConverterKey<S,T>(input, output, DefaultBinding.class), object);
	}

	/**
	 * Convert an object which is an instance of source class to the given target class
	 * @param input The class of the object to be converted
	 * @param output The target class to convert the object to
	 * @param object The object to be converted
	 * @param qualifier Match the converter with the given qualifier
	 */
	public <S, T> T convertTo(Class<S> input, Class<T> output, Object object, Class<? extends Annotation> qualifier) {

		return convertTo(new ConverterKey<S,T>(input, output, qualifier), object);
	}

	/**
	 * Convert an object which is an instance of source class to the given target class
	 * @param key The converter key to use
	 * @param object The object to be converted
	 */
	public <S, T> T convertTo(ConverterKey<S,T> key, Object object) {

		if (object == null) {
			return null;
		}
		
		Converter<S, T> conv = findConverter(key.getInputClass(), key.getOutputClass(), key.getQualifierAnnotation() == null ? DefaultBinding.class : key.getQualifierAnnotation());
		
		if (conv == null) {
			throw new NoConverterFoundException(key);
		}
		
		@SuppressWarnings("unchecked")
		S myObject = (S)object;
		return conv.convert(myObject);
	}
	
    /*************************
     *                       *
     * Conversion Resolution * 
     *                       *
     *************************/
	
	/**
	 * Resolve a Binding with the given source and target class.
	 * A binding unifies a marshaller and an unmarshaller and both must be available to resolve a binding.
	 * 
	 * The source class is considered the owning class of the binding. The source can be marshalled
	 * into the target class. Similarly, the target can be unmarshalled to produce an instance of the source type.
	 * @param source The source (owning) class
	 * @param target The target (foreign) class
	 */
	public <S, T> Binding<S, T> findBinding(Class<S> source, Class<T> target) {
		return findBinding(new ConverterKey<S,T>(source, target, DefaultBinding.class));
	}

	/**
	 * Resolve a Marshaller with the given source and target class.
	 * The marshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class 
	 */
	public <S, T> ToMarshaller<S, T> findMarshaller(Class<S> source, Class<T> target) {
		return findMarshaller(new ConverterKey<S,T>(source, target, DefaultBinding.class));
	}

	/**
	 * Resolve a Converter with the given input and output classes. Instances of the input class can be converted into 
	 * instances of the output class
	 * @param input The input class
	 * @param output The output class  
	 */
	public <S, T> Converter<S, T> findConverter(Class<S> input, Class<T> output) {
		return findConverter(new ConverterKey<S,T>(input, output, DefaultBinding.class));
	}

	/**
	 * Resolve an UnMarshaller with the given source and target class.
	 * The unmarshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class 
	 */
	public <S, T> FromUnmarshaller<S, T> findUnmarshaller(Class<S> source, Class<T> target) {
		return findUnmarshaller(new ConverterKey<S,T>(source, target, DefaultBinding.class));
	}

	/**
	 * Resolve a Binding with the given source and target class.
	 * A binding unifies a marshaller and an unmarshaller and both must be available to resolve a binding.
	 * 
	 * The source class is considered the owning class of the binding. The source can be marshalled
	 * into the target class. Similarly, the target can be unmarshalled to produce an instance of the source type.
	 * @param source The source (owning) class
	 * @param target The target (foreign) class
	 * @param qualifier The qualifier for which the binding must be registered
	 */
	public <S, T> Binding<S, T> findBinding(Class<S> source, Class<T> target, Class<? extends Annotation> qualifier) {
		return findBinding(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier));
	}
	
	/**
	 * Resolve a Binding with the given source and target class.
	 * A binding unifies a marshaller and an unmarshaller and both must be available to resolve a binding.
	 * 
	 * The source class is considered the owning class of the binding. The source can be marshalled
	 * into the target class. Similarly, the target can be unmarshalled to produce an instance of the source type.
	 * @param key The key to look up
	 */
	@SuppressWarnings("unlikely-arg-type")
	public <S, T> Binding<S, T> findBinding(ConverterKey<S,T> key) {

		FromUnmarshaller<?,?> fromUnmarshaller = null;
		ToMarshaller<?,?> toMarshaller = null;
		
		Converter<S, T> toTarget = findConverter(key);
		if (toTarget instanceof FromUnmarshallerConverter<?, ?>) {
			fromUnmarshaller = ((FromUnmarshallerConverter<?, ?>) toTarget).getUnmarshaller();
		} else if (toTarget instanceof ToMarshallerConverter<?, ?>) {
			toMarshaller = ((ToMarshallerConverter<?, ?>) toTarget).getMarshaller();
		}
		Converter<T, S> toSource = findConverter(key.invert());
		if (toSource instanceof FromUnmarshallerConverter<?, ?>) {
			fromUnmarshaller = ((FromUnmarshallerConverter<?, ?>) toSource).getUnmarshaller();
		} else if (toSource instanceof ToMarshallerConverter<?, ?>) {
			toMarshaller = ((ToMarshallerConverter<?, ?>) toSource).getMarshaller();
		}
		
		if (fromUnmarshaller != null && toMarshaller != null) {
			
			if (fromUnmarshaller.equals(toMarshaller) && Binding.class.isAssignableFrom(fromUnmarshaller.getClass())) {
				Binding<?,?> theBinding = (Binding<?,?>)fromUnmarshaller;
				if (theBinding.getBoundClass().equals(key.getInputClass())) {
					@SuppressWarnings("unchecked")
					final Binding<S, T>myBinding = (Binding<S, T>)theBinding;
					return myBinding;
				}
			}
			
			if (fromUnmarshaller.getBoundClass().equals(key.getInputClass())) {
				@SuppressWarnings("unchecked")
				final Binding<S, T>myBinding = new CompositeBinding<S,T>((ToMarshaller<S,T>)toMarshaller, (FromUnmarshaller<S,T>)fromUnmarshaller);
				return myBinding;
			} else {
				@SuppressWarnings("unchecked")
				final Binding<S, T> myBinding = new InverseCompositeBinding<S,T>((ToMarshaller<T,S>)toMarshaller, (FromUnmarshaller<T,S>)fromUnmarshaller);
				return myBinding;
			}
		}
		return null;
	}

	/**
	 * Resolve a Marshaller with the given source and target class.
	 * The marshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class
	 * @param qualifier The qualifier for which the marshaller must be registered 
	 */
	public <S, T> ToMarshaller<S, T> findMarshaller(Class<S> source, Class<T> target, Class<? extends Annotation> qualifier) {
		return findMarshaller(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier));
	}
	
	/**
	 * Resolve a Marshaller with the given source and target class.
	 * The marshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param key The key to look up
	 */
	public <S, T> ToMarshaller<S, T> findMarshaller(ConverterKey<S,T> key) {

		Converter<S,T> converter = findConverter(key);
		
		if (converter == null) {
			return null;
		}
		
		if (ToMarshallerConverter.class.isAssignableFrom(converter.getClass())) {
			return ((ToMarshallerConverter<S, T>)converter).getMarshaller();
		} else {
			return new ConverterToMarshaller<S, T>(converter);
		}
	}

	/**
	 * Resolve a Converter with the given input and output classes. Instances of the input class can be converted into 
	 * instances of the output class
	 * @param source The source class
	 * @param target The target class
	 * @param qualifier The qualifier for which the marshaller must be registered   
	 */
	public <S, T> Converter<S, T> findConverter(Class<S> source, Class<T> target, Class<? extends Annotation> qualifier) {
		return findConverter(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier));
	}
	
	/**
	 * Resolve a Converter with the given input and output classes. Instances of the input class can be converted into 
	 * instances of the output class
	 * @param key The key to look up
	 */
	public <S, T> Converter<S, T> findConverter(ConverterKey<S,T> key) {
		
		 // We check once before attempting introspection so we avoid that if possible
		@SuppressWarnings("unchecked")
		Converter<S,T> converter = (Converter<S, T>) registeredConverters.get(key);
		if (converter != null) {
			return converter;
		}
		
		// Now try introspecting the relevant class
		registerAnnotatedClasses(key.getInputClass(), key.getOutputClass());

		@SuppressWarnings("unchecked")
		Converter<S,T> myConverter = (Converter<S, T>) registeredConverters.get(key);
		return myConverter;
	}

	/**
	 * Resolve an UnMarshaller with the given source and target class.
	 * The unmarshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param source The source (input) class
	 * @param target The target (output) class 
	 * @param qualifier The qualifier for which the unmarshaller must be registered 
	 */
	public <S, T> FromUnmarshaller<S, T> findUnmarshaller(Class<S> source, Class<T> target, Class<? extends Annotation> qualifier) {
		return findUnmarshaller(new ConverterKey<S,T>(source, target, qualifier == null ? DefaultBinding.class : qualifier));
	}
	
	/**
	 * Resolve an UnMarshaller with the given source and target class.
	 * The unmarshaller is used as follows: Instances of the source can be marshalled into the target class.
	 * @param key The key to look up 
	 */
	public <S, T> FromUnmarshaller<S, T> findUnmarshaller(ConverterKey<S,T> key) {

		Converter<T,S> converter = findConverter(key.invert());
		
		if (converter == null) {
			return null;
		}
		
		if (FromUnmarshallerConverter.class.isAssignableFrom(converter.getClass())) {
			return ((FromUnmarshallerConverter<S, T>)converter).getUnmarshaller();
		} else {
			return new ConverterFromUnmarshaller<S, T>(converter);
		}
	}
	
	/**
	 * Return an iterable collection of ConverterKeys, one for each currently registered conversion
	 */
	public Iterable<ConverterKey<?,?>> getConverterEntries() {
		return registeredConverters.keySet();
	}

    /******************
     *                *
     * Helper Methods * 
     *                *
     ******************/

	/**
	 * This method assists in matching a given implementation class to its (programmer) defined scope.
	 * The method searches for an annotation on the class defined as a binding scope. If one is found 
	 * the implementation is matched to the scope. Note that the implementation scope can be overridden 
	 * using bindings.xml configuration
	 * @param implementation The class to examine
	 * @return The found scope annotation
	 */
    private <T> Class<? extends Annotation> matchImplementationToScope(Class<?> implementation) {

        for (Annotation next : implementation.getAnnotations()) {
            Class<? extends Annotation> nextType = next.annotationType();
            if (nextType.getAnnotation(BindingScope.class) != null) {
                return nextType;
            }
        }
        return null;
    }
	
	private <S, T> void attachForAnnotations(final Class<?> target) {

		// Only apply an annotation once
		synchronized(inspectedClasses) {
			if (inspectedClasses.contains(target)) {
				return;
			}
			inspectedClasses.add(target);
		}

		// NB Don't worry about superclasses - that happens elsewhere
		Set<ConverterKey<?,?>> previouslySeenKeys = new HashSet<ConverterKey<?,?>>();
		
		Map<ConverterKey<?,?>, Method> toMethods = new HashMap<ConverterKey<?,?>, Method>();
		Map<ConverterKey<?,?>, Method> fromMethods = new HashMap<ConverterKey<?,?>, Method>();
		Map<ConverterKey<?,?>, Constructor<?>> fromConstructors = new HashMap<ConverterKey<?,?>, Constructor<?>>();
		
		matchTo(target, previouslySeenKeys, toMethods);
		
		matchFrom(target, previouslySeenKeys, fromMethods, fromConstructors);
		
		// 2 Pass all the results into registerForMethods
		for (ConverterKey<?,?> next : previouslySeenKeys) {
			
			@SuppressWarnings("unchecked")
			ConverterKey<S,T> nextKey = (ConverterKey<S,T>)next;
			
			ConverterKey<?,?> inverse = next.invert();
			@SuppressWarnings("unchecked") Constructor<S> fromConstructor = (Constructor<S>) fromConstructors.get(inverse);
			
			registerForMethods(nextKey, toMethods.get(next), fromMethods.get(inverse), fromConstructor);
		}
	}

	private void matchTo(Class<?> target, Set<ConverterKey<?,?>> previouslySeenKeys, Map<ConverterKey<?,?>, Method> toMethods) {

		for (ConverterProvider nextConverter : converterProviders) {
			Map<ConverterKey<?,?>, Method> nextMethods = nextConverter.matchToMethods(target);
			for(ConverterKey<?,?> currentKey : nextMethods.keySet()) {
				if (previouslySeenKeys.contains(currentKey)) {
					throw new IllegalStateException("Method is resolved by two converters: " + currentKey.toString());
				}
				previouslySeenKeys.add(currentKey);
			}
			toMethods.putAll(nextMethods);
		}
	}
	
	private <T> void matchFrom(Class<T> target, Set<ConverterKey<?,?>> previouslySeenKeys, Map<ConverterKey<?,?>, Method> fromMethods, Map<ConverterKey<?,?>, Constructor<?>> fromConstructors) {

		for (ConverterProvider nextConverter : converterProviders) {
			Map<ConverterKey<?,?>, Method> nextMethods = nextConverter.matchFromMethods(target);
			for (ConverterKey<?,?> currentKey : nextMethods.keySet()) {
				if (previouslySeenKeys.contains(currentKey)) {
					throw new IllegalStateException("Method is resolved by two converters: " + currentKey.toString());
				}
				previouslySeenKeys.add(currentKey);
			}
			fromMethods.putAll(nextMethods);
			
			Map<ConverterKey<?,?>, Constructor<T>> nextConstructors = nextConverter.matchFromConstructors(target);
			for (ConverterKey<?,?> currentKey : nextConstructors.keySet()) {
				if (previouslySeenKeys.contains(currentKey)) {
					throw new IllegalStateException("ConverterKey is resolved by two converters: " + currentKey.toString());
				}
				previouslySeenKeys.add(currentKey);
			}
			fromConstructors.putAll(nextConstructors);
		}
	}
    
    private <I,O> void registerForMethods(ConverterKey<I,O> key, Method toMethod, Method fromMethod, Constructor<I> con) {
    
        if (toMethod != null) {
            
            MethodToMarshaller<I,O> toMarshaller = new MethodToMarshaller<I,O>(key.getInputClass(), key.getOutputClass(), toMethod);
            if (con != null) {
    
                ConstructorFromUnmarshaller<I,O> fromUnmarshaller = new ConstructorFromUnmarshaller<I,O>(con); 
                registerBinding(key.getInputClass(), key.getOutputClass(), new CompositeBinding<I,O>(toMarshaller, fromUnmarshaller), key.getQualifierAnnotation());               
            } else if (fromMethod != null) {
                
                MethodFromUnmarshaller<I,O> fromUnmarshaller = new MethodFromUnmarshaller<I,O>(key.getInputClass(), fromMethod);
                registerBinding(key.getInputClass(), key.getOutputClass(), new CompositeBinding<I,O>(toMarshaller, fromUnmarshaller), key.getQualifierAnnotation());
            } else {
                
            	registerMarshaller(key.getInputClass(), key.getOutputClass(), toMarshaller, key.getQualifierAnnotation());
            }
        } else {
            if (con != null) {
                
                registerUnmarshaller(key.getInputClass(), key.getOutputClass(), new ConstructorFromUnmarshaller<I,O>(con), key.getQualifierAnnotation());
            } else if (fromMethod != null) {
                
                registerUnmarshaller(key.getInputClass(), key.getOutputClass(), new MethodFromUnmarshaller<I,O>(key.getInputClass(), fromMethod), key.getQualifierAnnotation());
            }
        }
    }

    /******************
     *                *
     * String Binder  * 
     *                *
     ******************/
    
	public <T> T convertFromString(Class<T> output, String object) {
		return convertTo(String.class, output, object);
	}

	public <T> T convertFromString(Class<T> output, String object,
			Class<? extends Annotation> qualifier) {
		return convertTo(String.class, output, object, qualifier);
	}

	public String convertToString(Object object) {
		return convertTo(String.class, object);
	}

	public String convertToString(Object object,
			Class<? extends Annotation> qualifier) {
		return convertTo(String.class, object, qualifier);
	}

	public <S> String convertToString(Class<S> input, Object object) {
		return convertTo(input, String.class, object);
	}

	public <S> String convertToString(Class<S> input, Object object,
			Class<? extends Annotation> qualifier) {
		return convertTo(input, String.class, object, qualifier);
	}
}