001    /*
002     *  Copyright 2004 The Apache Software Foundation
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.apache.commons.convert;
017    
018    import java.util.HashMap;
019    import java.util.Map;
020    
021    import org.apache.commons.convert.conversion.ClassToStringConversion;
022    import org.apache.commons.convert.conversion.ObjectToStringConversionFactory;
023    import org.apache.commons.convert.conversion.TimeZoneToStringConversionFactory;
024    
025    /**
026     * ConversionRegistry manages the <code>Conversion</code> and
027     * <code>ConversionFactory</code> objects.
028     * <p>
029     * This class allows conversions to be added, removed and looked up.
030     * This implementation is fully synchronized.
031     * <pre>
032     * Converter converter = new Converter();
033     * converter.getRegistry().addDefaultConversions();
034     * converter.getRegistry().addConversion(new MyNewConversion());
035     * </pre>
036     *
037     * @author Stephen Colebourne
038     * @version $Id: ConversionRegistry.java 155441 2005-02-26 13:19:22Z dirkv $
039     * @since 1.0
040     */
041    public class ConversionRegistry {
042    
043        /** Map of from class to conversion */
044        protected Map iConversions = new HashMap();
045        /** Array of conversion factories */
046        protected ConversionFactory[] iFactories = new ConversionFactory[0];
047        /** Object to synchronize on for factories */
048        protected final Object iFactoryLock = new Object();
049    
050        /**
051         * Restricted constructor, use Converter.
052         */
053        protected ConversionRegistry() {
054            super();
055        }
056    
057        //-----------------------------------------------------------------------
058        /**
059         * Add the default set of conversions to the registry.
060         */
061        public void addDefaultConversions() {
062            synchronized (iConversions) {
063                addConversion(ClassToStringConversion.INSTANCE);
064            }
065            synchronized (iFactoryLock) {
066                addConversionFactory(ObjectToStringConversionFactory.INSTANCE);
067                addConversionFactory(TimeZoneToStringConversionFactory.INSTANCE);
068            }
069        }
070    
071        //-----------------------------------------------------------------------
072        /**
073         * Adds a Conversion to the map of known conversions.
074         * Any previous conversion for this from-to pair is replaced.
075         * 
076         * @param conv  the conversion to add
077         */
078        public void addConversion(Conversion conv) {
079            if (conv != null) {
080                synchronized (iConversions) {
081                    Map map = (Map) iConversions.get(conv.getFromType());
082                    if (map == null) {
083                        map = new HashMap();
084                        iConversions.put(conv.getFromType(), map);
085                    }
086                    map.put(conv.getToType(), conv);
087                }
088            }
089        }
090    
091        /**
092         * Adds a ConversionFactory to the set of known factories.
093         * Any previous factory that matches by <code>equals()</code> is replaced.
094         * 
095         * @param factory  the factory to add
096         */
097        public void addConversionFactory(ConversionFactory factory) {
098            if (factory != null) {
099                synchronized (iFactoryLock) {
100                    ConversionFactory[] oldFactories = iFactories;
101                    for (int i = 0; i < oldFactories.length; i++) {
102                        if (oldFactories[i].equals(factory)) {
103                            iFactories[i] = factory;
104                            return;
105                        }
106                    }
107                    ConversionFactory[] newFactories = new ConversionFactory[oldFactories.length + 1];
108                    System.arraycopy(oldFactories, 0, newFactories, 0, oldFactories.length);
109                    newFactories[oldFactories.length] = factory;
110                    iFactories = newFactories;
111                }
112            }
113        }
114    
115        //-----------------------------------------------------------------------
116        /**
117         * Gets the conversion object that best matches the from and to types.
118         * <p>
119         * The lookup first examines the known conversions. If none is found, the
120         * factories are used to search for and create a conversion. If no suitable
121         * factory is found then <code>null</code> is returned.
122         * 
123         * @param value  the value that will be converted, read only and not to be stored
124         * @param fromType  the type to convert from
125         * @param toType  the type to convert to
126         * @return the best matching conversion, null if no match
127         */
128        public Conversion getConversion(Object value, Class fromType, Class toType) {
129            // try known conversions
130            synchronized (iConversions) {
131                Map map = (Map) iConversions.get(fromType);
132                if (map != null) {
133                    Conversion conv = (Conversion) map.get(toType);
134                    if (conv != null) {
135                        return conv;
136                    }
137                }
138            }
139    
140            // try factories
141            int max = 0;
142            ConversionFactory maxFactory = null;
143            synchronized (iFactoryLock) {
144                ConversionFactory[] factories = iFactories;
145                for (int i = 0; i < factories.length; i++) {
146                    int match = factories[i].getMatchPercent(value, fromType, toType);
147                    if (match > max) {
148                        max = match;
149                        maxFactory = factories[i];
150                    }
151                }
152            }
153            if (maxFactory != null) {
154                Conversion conv = maxFactory.getInstance(value, fromType, toType);
155                addConversion(conv);
156                return conv;
157            }
158    
159            // no match
160            return null;
161        }
162    
163        //-----------------------------------------------------------------------
164        /**
165         * Returns a string describing this object.
166         *
167         * @return a string describing this object
168         */
169        public String toString() {
170            return "ConverterRegistry";
171        }
172    
173    }