Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Converters |
|
| 2.7857142857142856;2.786 | ||||
Converters$PassThruConverter |
|
| 2.7857142857142856;2.786 | ||||
Converters$PassThruConverterCreator |
|
| 2.7857142857142856;2.786 |
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 | 1 | protected static final ConcurrentHashMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>(); |
50 | 1 | protected static final Set<ConverterCreator> creators = Collections.synchronizedSet(new HashSet<ConverterCreator>()); |
51 | 1 | protected static final Set<String> noConversions = Collections.synchronizedSet(new HashSet<String>()); |
52 | ||
53 | static { | |
54 | 1 | registerCreator(new PassThruConverterCreator()); |
55 | 1 | ClassLoader loader = Thread.currentThread().getContextClassLoader(); |
56 | 1 | Iterator<ConverterLoader> converterLoaders = ServiceRegistry.lookupProviders(ConverterLoader.class, loader); |
57 | 7 | while (converterLoaders.hasNext()) { |
58 | try { | |
59 | 6 | ConverterLoader converterLoader = converterLoaders.next(); |
60 | 6 | converterLoader.loadConverters(); |
61 | 0 | } catch (Exception e) { |
62 | 0 | e.printStackTrace(); |
63 | 6 | } |
64 | } | |
65 | 1 | } |
66 | ||
67 | 1 | 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 | 332 | String key = sourceClass.getName().concat(DELIMITER).concat(targetClass.getName()); |
87 | OUTER: | |
88 | do { | |
89 | 390 | Converter<?, ?> result = converterMap.get(key); |
90 | 390 | if (result != null) { |
91 | 329 | return Util.cast(result); |
92 | } | |
93 | 61 | if (noConversions.contains(key)) { |
94 | 1 | throw new ClassNotFoundException("No converter found for " + key); |
95 | } | |
96 | 60 | Class<?> foundSourceClass = null; |
97 | 60 | Converter<?, ?> foundConverter = null; |
98 | 60 | for (Converter<?, ?> value : converterMap.values()) { |
99 | 12169 | if (value.canConvert(sourceClass, targetClass)) { |
100 | // this converter can deal with the source/target pair | |
101 | 21 | 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 | 21 | foundSourceClass = value.getSourceClass(); |
106 | 21 | foundConverter = value; |
107 | } | |
108 | } | |
109 | } | |
110 | 60 | if (foundConverter != null) { |
111 | 20 | converterMap.putIfAbsent(key, foundConverter); |
112 | 20 | continue OUTER; |
113 | } | |
114 | 40 | for (ConverterCreator value : creators) { |
115 | 173 | result = createConverter(value, sourceClass, targetClass); |
116 | 173 | if (result != null) { |
117 | 38 | converterMap.putIfAbsent(key, result); |
118 | 38 | continue OUTER; |
119 | } | |
120 | } | |
121 | 2 | noConversions.add(key); |
122 | 2 | 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 | 173 | 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 | 171 | for (Class<?> clz: containerClass.getClasses()) { |
139 | try { | |
140 | // non-abstract, which means no interfaces or abstract classes | |
141 | 159 | if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) { |
142 | Object value; | |
143 | try { | |
144 | 153 | value = clz.getConstructor().newInstance(); |
145 | 19 | } catch (NoSuchMethodException e) { |
146 | // ignore this, as this class might be some other helper class, | |
147 | // with a non-pubilc constructor | |
148 | 19 | continue; |
149 | 134 | } |
150 | 134 | if (value instanceof ConverterLoader) { |
151 | 134 | ConverterLoader loader = (ConverterLoader) value; |
152 | 134 | loader.loadConverters(); |
153 | } | |
154 | } | |
155 | 1 | } catch (Exception e) { |
156 | 1 | e.printStackTrace(); |
157 | 139 | } |
158 | } | |
159 | 12 | } |
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 | 8 | creators.add(creator); |
171 | 8 | } |
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 | 333 | registerConverter(converter, converter.getSourceClass(), converter.getTargetClass()); |
183 | 333 | } |
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 | 333 | StringBuilder sb = new StringBuilder(); |
197 | 333 | if (sourceClass != null) { |
198 | 332 | sb.append(sourceClass.getName()); |
199 | } else { | |
200 | 1 | sb.append("<null>"); |
201 | } | |
202 | 333 | sb.append(DELIMITER); |
203 | 333 | if (targetClass != null) { |
204 | 333 | sb.append(targetClass.getName()); |
205 | } else { | |
206 | 0 | sb.append("<null>"); |
207 | } | |
208 | 333 | String key = sb.toString(); |
209 | 333 | converterMap.putIfAbsent(key, converter); |
210 | 333 | } |
211 | ||
212 | protected static class PassThruConverterCreator implements ConverterCreator{ | |
213 | 1 | protected PassThruConverterCreator() { |
214 | 1 | } |
215 | ||
216 | public <S, T> Converter<S, T> createConverter(Class<S> sourceClass, Class<T> targetClass) { | |
217 | 29 | if (sourceClass == targetClass || targetClass == Object.class || Util.instanceOf(sourceClass, targetClass)) { |
218 | 15 | return new PassThruConverter<S, T>(sourceClass, targetClass); |
219 | } else { | |
220 | 14 | 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 | 15 | public PassThruConverter(Class<S> sourceClass, Class<T> targetClass) { |
235 | 15 | this.sourceClass = sourceClass; |
236 | 15 | this.targetClass = targetClass; |
237 | 15 | } |
238 | ||
239 | public boolean canConvert(Class<?> sourceClass, Class<?> targetClass) { | |
240 | 166 | return this.sourceClass == sourceClass && this.targetClass == targetClass; |
241 | } | |
242 | ||
243 | @SuppressWarnings("unchecked") | |
244 | public T convert(S obj) throws ConversionException { | |
245 | 16 | return (T) obj; |
246 | } | |
247 | ||
248 | public Class<?> getSourceClass() { | |
249 | 7 | return sourceClass; |
250 | } | |
251 | ||
252 | public Class<?> getTargetClass() { | |
253 | 7 | return targetClass; |
254 | } | |
255 | } | |
256 | } |