001    /*******************************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     *******************************************************************************/
019    package org.apache.commons.convert;
020    
021    import java.lang.reflect.Modifier;
022    import java.util.Collections;
023    import java.util.HashSet;
024    import java.util.Iterator;
025    import java.util.Set;
026    import java.util.concurrent.ConcurrentHashMap;
027    
028    import javax.imageio.spi.ServiceRegistry;
029    
030    /** A <code>Converter</code> factory and utility class.
031     * <p>The factory loads {@link org.apache.commons.convert.Converter} instances
032     * via the Java service provider registry. Applications can extend the
033     * converter framework by:<br />
034     * <ul>
035     * <li>Creating classes that implement the
036     * {@link org.apache.commons.convert.Converter} class</li>
037     * <li>Create a {@link org.apache.commons.convert.ConverterLoader} class that
038     * registers those classes</li>
039     * <li>Create a <code>META-INF/services/org.apache.commons.convert.ConverterLoader</code>
040     * file that contains the {@link org.apache.commons.convert.ConverterLoader} class name(s)</li>
041     * </ul></p>
042     * <p>To convert an object, call the static {@link #getConverter(Class, Class)} method to
043     * get a {@link org.apache.commons.convert.Converter} instance, then call the converter's
044     * {@link org.apache.commons.convert.Converter#convert(Object)} method.
045     * </p>
046     */
047    public class Converters {
048        protected static final String DELIMITER = "->";
049        protected static final ConcurrentHashMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>();
050        protected static final Set<ConverterCreator> creators = Collections.synchronizedSet(new HashSet<ConverterCreator>());
051        protected static final Set<String> noConversions = Collections.synchronizedSet(new HashSet<String>());
052    
053        static {
054            registerCreator(new PassThruConverterCreator());
055            ClassLoader loader = Thread.currentThread().getContextClassLoader();
056            Iterator<ConverterLoader> converterLoaders = ServiceRegistry.lookupProviders(ConverterLoader.class, loader);
057            while (converterLoaders.hasNext()) {
058                try {
059                    ConverterLoader converterLoader = converterLoaders.next();
060                    converterLoader.loadConverters();
061                } catch (Exception e) {
062                    e.printStackTrace();
063                }
064            }
065        }
066    
067        private Converters() {}
068    
069    
070        /** Returns an appropriate <code>Converter</code> instance for
071         * <code>sourceClass</code> and <code>targetClass</code>. If no matching
072         * <code>Converter</code> is found, the method throws
073         * <code>ClassNotFoundException</code>.
074         *
075         * <p>This method is intended to be used when the source or
076         * target <code>Object</code> types are unknown at compile time.
077         * If the source and target <code>Object</code> types are known
078         * at compile time, then the appropriate converter instance should be used.</p>
079         *
080         * @param sourceClass The object class to convert from
081         * @param targetClass The object class to convert to
082         * @return A matching <code>Converter</code> instance
083         * @throws ClassNotFoundException
084         */
085        public static <S, T> Converter<S, T> getConverter(Class<S> sourceClass, Class<T> targetClass) throws ClassNotFoundException {
086            String key = sourceClass.getName().concat(DELIMITER).concat(targetClass.getName());
087            OUTER:
088                do {
089                    Converter<?, ?> result = converterMap.get(key);
090                    if (result != null) {
091                        return Util.cast(result);
092                    }
093                    if (noConversions.contains(key)) {
094                        throw new ClassNotFoundException("No converter found for " + key);
095                    }
096                    Class<?> foundSourceClass = null;
097                    Converter<?, ?> foundConverter = null;
098                    for (Converter<?, ?> value : converterMap.values()) {
099                        if (value.canConvert(sourceClass, targetClass)) {
100                            // this converter can deal with the source/target pair
101                            if (foundSourceClass == null || foundSourceClass.isAssignableFrom(value.getSourceClass())) {
102                                // remember the current target source class; if we find another converter, check
103                                // to see if it's source class is assignable to this one, and if so, it means it's
104                                // a child class, so we'll then take that converter.
105                                foundSourceClass = value.getSourceClass();
106                                foundConverter = value;
107                            }
108                        }
109                    }
110                    if (foundConverter != null) {
111                        converterMap.putIfAbsent(key, foundConverter);
112                        continue OUTER;
113                    }
114                    for (ConverterCreator value : creators) {
115                        result = createConverter(value, sourceClass, targetClass);
116                        if (result != null) {
117                            converterMap.putIfAbsent(key, result);
118                            continue OUTER;
119                        }
120                    }
121                    noConversions.add(key);
122                    throw new ClassNotFoundException("No converter found for " + key);
123                } while (true);
124        }
125    
126        private static <S, SS extends S, T, TT extends T> Converter<SS, TT> createConverter(ConverterCreator creater, Class<SS> sourceClass, Class<TT> targetClass) {
127            return creater.createConverter(sourceClass, targetClass);
128        }
129    
130        /** Load all classes that implement {@link org.apache.commons.convert.Converter} and are
131         * contained in <code>containerClass</code>.
132         *
133         * @param containerClass A class that contains {@link org.apache.commons.convert.Converter}
134         * implementations
135         */
136        public static void loadContainedConverters(Class<?> containerClass) {
137            // This only returns public classes and interfaces
138            for (Class<?> clz: containerClass.getClasses()) {
139                try {
140                    // non-abstract, which means no interfaces or abstract classes
141                    if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) {
142                        Object value;
143                        try {
144                            value = clz.getConstructor().newInstance();
145                        } catch (NoSuchMethodException e) {
146                            // ignore this, as this class might be some other helper class,
147                            // with a non-pubilc constructor
148                            continue;
149                        }
150                        if (value instanceof ConverterLoader) {
151                            ConverterLoader loader = (ConverterLoader) value;
152                            loader.loadConverters();
153                        }
154                    }
155                } catch (Exception e) {
156                    e.printStackTrace();
157                }
158            }
159        }
160    
161        /** Registers a <code>ConverterCreator</code> instance to be used by the
162         * {@link org.apache.commons.convert.Converters#getConverter(Class, Class)}
163         * method, when a converter can't be found.
164         *
165         * @param <S> The source object type
166         * @param <T> The target object type
167         * @param creator The <code>ConverterCreator</code> instance to register
168         */
169        public static <S, T> void registerCreator(ConverterCreator creator) {
170            creators.add(creator);
171        }
172    
173        /** Registers a <code>Converter</code> instance to be used by the
174         * {@link org.apache.commons.convert.Converters#getConverter(Class, Class)}
175         * method.
176         *
177         * @param <S> The source object type
178         * @param <T> The target object type
179         * @param converter The <code>Converter</code> instance to register
180         */
181        public static <S, T> void registerConverter(Converter<S, T> converter) {
182            registerConverter(converter, converter.getSourceClass(), converter.getTargetClass());
183        }
184    
185        /** Registers a <code>Converter</code> instance to be used by the
186         * {@link org.apache.commons.convert.Converters#getConverter(Class, Class)}
187         * method.
188         * 
189         * @param <S> The source object type
190         * @param <T> The target object type
191         * @param converter
192         * @param sourceClass
193         * @param targetClass
194         */
195        public static <S, T> void registerConverter(Converter<S, T> converter, Class<?> sourceClass, Class<?> targetClass) {
196            StringBuilder sb = new StringBuilder();
197            if (sourceClass != null) {
198                sb.append(sourceClass.getName());
199            } else {
200                sb.append("<null>");
201            }
202            sb.append(DELIMITER);
203            if (targetClass != null) {
204                sb.append(targetClass.getName());
205            } else {
206                sb.append("<null>");
207            }
208            String key = sb.toString();
209            converterMap.putIfAbsent(key, converter);
210        }
211    
212        protected static class PassThruConverterCreator implements ConverterCreator{
213            protected PassThruConverterCreator() {
214            }
215    
216            public <S, T> Converter<S, T> createConverter(Class<S> sourceClass, Class<T> targetClass) {
217                if (sourceClass == targetClass || targetClass == Object.class || Util.instanceOf(sourceClass, targetClass)) {
218                    return new PassThruConverter<S, T>(sourceClass, targetClass);
219                } else {
220                    return null;
221                }
222            }
223        }
224    
225        /** Pass-through converter used when the source and target java object
226         * types are the same. The <code>convert</code> method returns the
227         * source object.
228         *
229         */
230        protected static class PassThruConverter<S, T> implements Converter<S, T> {
231            private final Class<S> sourceClass;
232            private final Class<T> targetClass;
233    
234            public PassThruConverter(Class<S> sourceClass, Class<T> targetClass) {
235                this.sourceClass = sourceClass;
236                this.targetClass = targetClass;
237            }
238    
239            public boolean canConvert(Class<?> sourceClass, Class<?> targetClass) {
240                return this.sourceClass == sourceClass && this.targetClass == targetClass;
241            }
242    
243            @SuppressWarnings("unchecked")
244            public T convert(S obj) throws ConversionException {
245                return (T) obj;
246            }
247    
248            public Class<?> getSourceClass() {
249                return sourceClass;
250            }
251    
252            public Class<?> getTargetClass() {
253                return targetClass;
254            }
255        }
256    }