001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005  This file is part of Granite Data Services.
006
007  Granite Data Services is free software; you can redistribute it and/or modify
008  it under the terms of the GNU Library General Public License as published by
009  the Free Software Foundation; either version 2 of the License, or (at your
010  option) any later version.
011
012  Granite Data Services is distributed in the hope that it will be useful, but
013  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015  for more details.
016
017  You should have received a copy of the GNU Library General Public License
018  along with this library; if not, see <http://www.gnu.org/licenses/>.
019*/
020
021package org.granite.messaging.amf.io.convert;
022
023import java.lang.reflect.Constructor;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Type;
026import java.lang.reflect.TypeVariable;
027import java.util.ArrayList;
028import java.util.List;
029
030import org.granite.util.TypeUtil;
031
032/**
033 * @author Franck WOLFF
034 *
035 * @see Converter
036 * @see Reverter
037 */
038public class Converters {
039
040    /** Array of all configured converters */
041    private Converter[] converters;
042
043    /** Array of all configured reverters */
044    private Reverter[] reverters;
045
046    /**
047     * Constructs a new Converters instance with the supplied list of converters (possibly reverters).
048     *
049     * @param converterClasses the list of all used converters.
050     * @throws NoSuchMethodException if one of the Converter does not have a constructor with a
051     *          Converters parameter.
052     * @throws IllegalAccessException if something goes wrong when creating an instance of one
053     *          of the supplied Converter classes.
054     * @throws InvocationTargetException if something goes wrong when creating an instance of one
055     *          of the supplied Converter classes.
056     * @throws InstantiationException if something goes wrong when creating an instance of one
057     *          of the supplied Converter classes.
058     */
059    public Converters(List<Class<? extends Converter>> converterClasses)
060        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
061
062        List<Converter> converters = new ArrayList<Converter>();
063        List<Reverter> reverters = new ArrayList<Reverter>();
064
065        if (converterClasses != null) {
066            for (Class<? extends Converter> converterClass : converterClasses) {
067                Constructor<? extends Converter> constructor = converterClass.getConstructor(Converters.class);
068                Converter converter = constructor.newInstance(this);
069                converters.add(converter);
070                if (converter instanceof Reverter)
071                    reverters.add((Reverter)converter);
072            }
073        }
074
075        this.converters = converters.toArray(new Converter[converters.size()]);
076        this.reverters = reverters.toArray(new Reverter[reverters.size()]);
077    }
078    
079    public void addConverter(Class<? extends Converter> converterClass) 
080        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
081        
082        Converter[] converters = new Converter[this.converters.length+1];
083        System.arraycopy(this.converters, 0, converters, 1, this.converters.length);
084        Constructor<? extends Converter> constructor = converterClass.getConstructor(Converters.class);
085        converters[0] = constructor.newInstance(this);
086        this.converters = converters;
087        
088        if (converters[0] instanceof Reverter) {
089                Reverter[] reverters = new Reverter[this.reverters.length+1];
090                System.arraycopy(this.reverters, 0, reverters, 1, this.reverters.length);
091                reverters[0] = (Reverter)converters[0];
092                this.reverters = reverters;
093        }
094    }
095
096    /**
097     * Returns a suitable converter for supplied parameters or null if no converter
098     * can be found. This method is equivalent to the
099     * {@link Converters#getConverter(Object, Type, boolean)} method with the
100     * throwNotFoundException parameter set to false.
101     *
102     * @param value the value to be converted
103     * @param targetType the type of the converted value
104     * @return a Converter instance or null if no suitable converter can be found
105     */
106    public Converter getConverter(Object value, Type targetType) {
107        return getConverter(value, targetType, false);
108    }
109
110    /**
111     * Returns a suitable converter for supplied parameters or either returns null if no converter
112     * can be found or throws a {@link NoConverterFoundException}.
113     *
114     * @param value the value to be converted
115     * @param targetType the type of the converted value
116     * @param throwNotFoundException should an exception be thrown if no converter is found?
117     * @return a Converter instance or null if no suitable converter can be found
118     * @throws NoConverterFoundException if the throwNotFoundException parameter is set to true
119     *          and no converter can be found.
120     */
121    public Converter getConverter(Object value, Type targetType, boolean throwNotFoundException)
122        throws NoConverterFoundException {
123        
124        // Small optimization: this avoids to make TypeVariable conversion in all converters...
125        if (targetType instanceof TypeVariable<?>)
126                targetType = TypeUtil.getBoundType((TypeVariable<?>)targetType);
127        
128        for (Converter converter : converters) {
129            if (converter.canConvert(value, targetType))
130                return converter;
131        }
132
133        if (!throwNotFoundException)
134            return null;
135
136        throw new NoConverterFoundException(value, targetType);
137    }
138
139    /**
140     * Converts the supplied object to the supplied target type. This method is
141     * a simple shortcut for: <tt>this.getConverter(value, target, true).convert(value, targetType)</tt>.
142     *
143     * @param value the object to be converted.
144     * @param targetType the target type.
145     * @return the converted object.
146     * @throws NoConverterFoundException if no suitable converter can be found.
147     */
148    public Object convert(Object value, Type targetType) throws NoConverterFoundException {
149        return getConverter(value, targetType, true).convert(value, targetType);
150    }
151
152    /**
153     * Returns true if at least one reverter is configured for this Converters instance.
154     *
155     * @return true if at least one reverter is configured for this Converters instance.
156     */
157    public boolean hasReverters() {
158        return reverters.length > 0;
159    }
160
161    /**
162     * Revert back to standard, AMF3 known Java type the supplied value. This method iterates
163     * on all configured Reverters and returns the {@link Reverter#revert(Object)} method result
164     * if the {@link Reverter#canRevert(Object)} method returns true for the current Reverter
165     * instance.
166     *
167     * @param value the value to be reverted.
168     * @return the reverted value (same instance if none of the configured reverters apply).
169     */
170    public Object revert(Object value) {
171        for (Reverter reverter : reverters) {
172            if (reverter.canRevert(value))
173                return reverter.revert(value);
174        }
175        return value;
176    }
177    
178    public Converter[] getConverters() {
179        Converter[] copy = new Converter[converters.length];
180        System.arraycopy(converters, 0, copy, 0, converters.length);
181        return copy;
182    }
183}