1 /*******************************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *******************************************************************************/
19 package org.apache.commons.convert;
20
21 import java.lang.reflect.Modifier;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.Set;
26 import java.util.concurrent.ConcurrentHashMap;
27
28 import javax.imageio.spi.ServiceRegistry;
29
30 /** A <code>Converter</code> factory and utility class.
31 * <p>The factory loads {@link org.apache.commons.convert.Converter} instances
32 * via the Java service provider registry. Applications can extend the
33 * converter framework by:<br />
34 * <ul>
35 * <li>Creating classes that implement the
36 * {@link org.apache.commons.convert.Converter} class</li>
37 * <li>Create a {@link org.apache.commons.convert.ConverterLoader} class that
38 * registers those classes</li>
39 * <li>Create a <code>META-INF/services/org.apache.commons.convert.ConverterLoader</code>
40 * file that contains the {@link org.apache.commons.convert.ConverterLoader} class name(s)</li>
41 * </ul></p>
42 * <p>To convert an object, call the static {@link #getConverter(Class, Class)} method to
43 * get a {@link org.apache.commons.convert.Converter} instance, then call the converter's
44 * {@link org.apache.commons.convert.Converter#convert(Object)} method.
45 * </p>
46 */
47 public class Converters {
48 protected static final String DELIMITER = "->";
49 protected static final ConcurrentHashMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>();
50 protected static final Set<ConverterCreator> creators = Collections.synchronizedSet(new HashSet<ConverterCreator>());
51 protected static final Set<String> noConversions = Collections.synchronizedSet(new HashSet<String>());
52
53 static {
54 registerCreator(new PassThruConverterCreator());
55 ClassLoader loader = Thread.currentThread().getContextClassLoader();
56 Iterator<ConverterLoader> converterLoaders = ServiceRegistry.lookupProviders(ConverterLoader.class, loader);
57 while (converterLoaders.hasNext()) {
58 try {
59 ConverterLoader converterLoader = converterLoaders.next();
60 converterLoader.loadConverters();
61 } catch (Exception e) {
62 e.printStackTrace();
63 }
64 }
65 }
66
67 private Converters() {}
68
69
70 /** Returns an appropriate <code>Converter</code> instance for
71 * <code>sourceClass</code> and <code>targetClass</code>. If no matching
72 * <code>Converter</code> is found, the method throws
73 * <code>ClassNotFoundException</code>.
74 *
75 * <p>This method is intended to be used when the source or
76 * target <code>Object</code> types are unknown at compile time.
77 * If the source and target <code>Object</code> types are known
78 * at compile time, then the appropriate converter instance should be used.</p>
79 *
80 * @param sourceClass The object class to convert from
81 * @param targetClass The object class to convert to
82 * @return A matching <code>Converter</code> instance
83 * @throws ClassNotFoundException
84 */
85 public static <S, T> Converter<S, T> getConverter(Class<S> sourceClass, Class<T> targetClass) throws ClassNotFoundException {
86 String key = sourceClass.getName().concat(DELIMITER).concat(targetClass.getName());
87 OUTER:
88 do {
89 Converter<?, ?> result = converterMap.get(key);
90 if (result != null) {
91 return Util.cast(result);
92 }
93 if (noConversions.contains(key)) {
94 throw new ClassNotFoundException("No converter found for " + key);
95 }
96 Class<?> foundSourceClass = null;
97 Converter<?, ?> foundConverter = null;
98 for (Converter<?, ?> value : converterMap.values()) {
99 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 }