View Javadoc

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 }