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 }