001 package com.hammurapi.convert;
002
003 import java.lang.reflect.InvocationTargetException;
004 import java.lang.reflect.Method;
005 import java.util.ArrayList;
006 import java.util.Collection;
007 import java.util.Collections;
008 import java.util.HashMap;
009 import java.util.HashSet;
010 import java.util.List;
011 import java.util.Map;
012 import java.util.ServiceLoader;
013 import java.util.Set;
014
015 import com.hammurapi.util.Adaptable;
016 import com.hammurapi.util.Context;
017
018 public class ConvertingService {
019
020 private interface ConvertersBucket<S, T> {
021
022 T convert(S source, Context context);
023 }
024
025 /**
026 * Converter which delegates to convert() method.
027 */
028 public static final Converter CONVERTER = new Converter() {
029
030 @SuppressWarnings("unchecked")
031 public Object convert(Object source, Class targetType, Context context) throws ConversionException {
032 return ConvertingService.convert(source, targetType, context);
033 }
034
035 };
036
037 /**
038 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
039 * If no converter is found, this method performs converter discovery through
040 * constructors if target type is a class and through duck conversion if target
041 * type is an interface.
042 * Invocation of this method is equivalent to invocation of
043 * <code>convert(source, targetType, false)</code>
044 * @param source Source object.
045 * @param targetType Target type.
046 * @return Source object converted to target type or null if no converter
047 * is available for source/targetType combination
048 * @throws ConversionException
049 */
050 public static <S, T> T convert(S source, Class<T> targetType) throws ConversionException {
051 return convert(source, targetType, null, false, null);
052 }
053
054 /**
055 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
056 * If no converter is found, this method performs converter discovery through
057 * constructors if target type is a class and through duck conversion if target
058 * type is an interface.
059 * Invocation of this method is equivalent to invocation of
060 * <code>convert(source, targetType, false)</code>
061 * @param source Source object.
062 * @param targetType Target type.
063 * @param context Conversion context.
064 * @return Source object converted to target type or null if no converter
065 * is available for source/targetType combination
066 * @throws ConversionException
067 */
068 public static <S, T> T convert(S source, Class<T> targetType, Context context) throws ConversionException {
069 return convert(source, targetType, context, false, null);
070 }
071
072 /**
073 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
074 * If no converter is found, this method performs converter discovery through
075 * constructors if target type is a class and through duck conversion if target
076 * type is an interface.
077 * Invocation of this method is equivalent to invocation of
078 * <code>convert(source, targetType, false)</code>
079 * @param source Source object.
080 * @param targetType Target type.
081 * @param classLoader Class loader, source of converters.
082 * @return Source object converted to target type or null if no converter
083 * is available for source/targetType combination
084 * @throws ConversionException
085 */
086 public static <S, T> T convert(S source, Class<T> targetType, ClassLoader classLoader) throws ConversionException {
087 return convert(source, targetType, null, false, classLoader);
088 }
089
090 private static class ConverterKey<S,T> {
091 Class<S> sourceType;
092 Class<T> targetType;
093
094 public ConverterKey(Class<S> sourceType, Class<T> targetType) {
095 super();
096 this.sourceType = sourceType;
097 this.targetType = targetType;
098 }
099
100 public int hashCode() {
101 final int prime = 31;
102 int result = 1;
103 result = prime * result + ((sourceType == null) ? 0 : sourceType.hashCode());
104 result = prime * result + ((targetType == null) ? 0 : targetType.hashCode());
105 return result;
106 }
107
108 @SuppressWarnings("unchecked")
109 public boolean equals(Object obj) {
110 return
111 sourceType == ((ConverterKey<S, T>) obj).sourceType
112 && targetType == ((ConverterKey<S, T>) obj).targetType;
113 }
114
115 }
116
117 private static class ClassLoaderConvertersEntry {
118 private static final String CONVERT = "convert";
119
120 private ClassLoader classLoader;
121
122 public ClassLoaderConvertersEntry(ClassLoader classLoader) {
123 this.classLoader = classLoader;
124 }
125
126 private Converter master = new Converter() {
127
128 @SuppressWarnings("unchecked")
129 public Object convert(Object source, Class targetType, Context context) throws ConversionException {
130 if (source==null) {
131 return null;
132 }
133
134 if (targetType.isInstance(source)) {
135 return source;
136 }
137
138 final ConvertersBucket<Object, Object> bucket = findConverter(source.getClass(), targetType);
139 return bucket==null ? null : bucket.convert(source, context);
140 }
141
142 };
143
144 // private Converter<?,?> chainingMaster = new Converter<?,?>() {
145 //
146 // public Object convert(Object source, Class targetType, Context context) throws ConversionException {
147 // if (source==null) {
148 // return null;
149 // }
150 //
151 // if (targetType.isInstance(source)) {
152 // return source;
153 // }
154 //
155 // ConvertersBucket bucket = findChainingConverter(source.getClass(), targetType);
156 // return bucket==null ? null : bucket.convert(source, context);
157 // }
158 //
159 // };
160
161 Collection<AtomicConverter<? extends Object,? extends Object>> atomicConverters = new ArrayList<AtomicConverter<? extends Object,? extends Object>>();
162
163 /**
164 * Map of resolved converters: Collection[source, target] -> ConverterClosure.
165 */
166 Map<ConverterKey<?,?>, Object> converters = new HashMap<ConverterKey<?,?>, Object>();
167
168 Set<Class<?>> introspectedTargetTypes = new HashSet<Class<?>>();
169
170 /**
171 * Map of resolved chaining converters: Collection[source, target] -> ConverterClosure.
172 */
173 // Map chainingConverters = new HashMap();
174
175 @SuppressWarnings("unchecked")
176 synchronized <S, T> ConvertersBucket<S, T> findConverter(Class<S> sourceType, Class<T> targetType) {
177 ConverterKey<S, T> key = new ConverterKey(sourceType, targetType);
178 Object ret = converters.get(key);
179 if (Boolean.FALSE.equals(ret)) { // Indicates that previous converter resolution didn't yield results.
180 return null;
181 }
182
183 if (ret == null) {
184 if (!targetType.isInterface() && introspectedTargetTypes.add(targetType)) {
185 atomicConverters.addAll(ReflectionConverter.discoverConstructorConverters(targetType));
186 }
187
188 class ConverterEntry implements Comparable<ConverterEntry> {
189 public ConverterEntry(AtomicConverter<? extends Object,? extends Object> converter, int affinity, int position) {
190 super();
191 this.converter = converter;
192 this.affinity = affinity;
193 this.position = position;
194 }
195 AtomicConverter<? extends Object, ? extends Object> converter;
196 int affinity;
197 int position;
198 public int compareTo(ConverterEntry obj) {
199 int cret = affinity - ((ConverterEntry) obj).affinity;
200 if (cret==0) {
201 return position - ((ConverterEntry) obj).position;
202 }
203 return cret;
204 }
205 }
206
207 final List<ConverterEntry> theConverters = new ArrayList<ConverterEntry>();
208
209 for (AtomicConverter<? extends Object,? extends Object> ac: atomicConverters) {
210 int position = 0;
211 if (ac.getSourceType().isAssignableFrom(sourceType) && targetType.isAssignableFrom(ac.getTargetType()) ) {
212 Integer newSourceAffinity = DuckConverterFactory.classAffinity(sourceType, ac.getSourceType());
213 Integer newTargetAffinity = DuckConverterFactory.classAffinity(ac.getTargetType(), targetType);
214 if (newSourceAffinity==null || newTargetAffinity==null) {
215 throw new IllegalArgumentException("Something is wrong - source and target affinities are null");
216 }
217 theConverters.add(new ConverterEntry(ac, newSourceAffinity.intValue()+newTargetAffinity.intValue(), ++position));
218 }
219 }
220 if (theConverters.isEmpty()) {
221 converters.put(key, Boolean.FALSE);
222 } else {
223 Collections.sort(theConverters);
224 ret = new ConvertersBucket<S,T>() {
225
226 public T convert(S source, Context context) {
227 for (ConverterEntry ce: theConverters) {
228 AtomicConverter<S, T> theConverter = (AtomicConverter<S, T>) ce.converter;
229 T ret = (T) theConverter.convert(source, master, context, classLoader);
230 if (ret!=null) {
231 return ret;
232 }
233 }
234 return null;
235 }
236
237 };
238 converters.put(key, ret);
239 }
240 }
241
242 return (ConvertersBucket<S, T>) ret;
243 }
244
245 synchronized <S, T> ConvertersBucket<S,T> findChainingConverter(Class<S> sourceType, Class<T> targetType) {
246 ConvertersBucket<S,T> ret = findConverter(sourceType, targetType);
247 if (ret!=null) {
248 return ret;
249 }
250
251 // TODO - Resolve chain.
252 return ret;
253 }
254
255 @SuppressWarnings("unchecked")
256 void addConverters(final Object provider) {
257 if (provider instanceof AtomicConverter) {
258 atomicConverters.add((AtomicConverter<Object, Object>) provider);
259 } else if (provider instanceof AtomicConvertersBundle) {
260 atomicConverters.addAll(((AtomicConvertersBundle) provider).getConverters());
261 } else if (provider instanceof Collection<?>) {
262 for (Object obj: (Collection<?>) provider) {
263 addConverters(obj);
264 }
265 } else { // Introspection
266 Class pClass = provider.getClass();
267 Method[] methods = pClass.getMethods();
268 for (int i=0; i<methods.length; ++i) {
269 final Method method = methods[i];
270 if (CONVERT.equals(method.getName()) && !void.class.equals(method.getReturnType())) {
271 Class[] pTypes = method.getParameterTypes();
272 if (pTypes.length == 1) {
273 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
274
275 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
276 try {
277 return method.invoke(provider, new Object[] {source});
278 } catch (IllegalAccessException e) {
279 throw new ConversionException(e);
280 } catch (InvocationTargetException e) {
281 throw new ConversionException(e);
282 }
283 }
284
285 });
286 } else if (pTypes.length == 2 && Converter.class.equals(pTypes[1])) {
287 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
288
289 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
290 try {
291 return method.invoke(provider, new Object[] {source, master});
292 } catch (IllegalAccessException e) {
293 throw new ConversionException(e);
294 } catch (InvocationTargetException e) {
295 throw new ConversionException(e);
296 }
297 }
298
299 });
300
301 } else if (pTypes.length == 2 && Context.class.equals(pTypes[1])) {
302 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
303
304 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
305 try {
306 return method.invoke(provider, new Object[] {source, context});
307 } catch (IllegalAccessException e) {
308 throw new ConversionException(e);
309 } catch (InvocationTargetException e) {
310 throw new ConversionException(e);
311 }
312 }
313
314 });
315
316 } else if (pTypes.length == 3 && Converter.class.equals(pTypes[1]) && Context.class.equals(pTypes[2])) {
317 atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
318
319 public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
320 try {
321 return method.invoke(provider, new Object[] {source, master, context});
322 } catch (IllegalAccessException e) {
323 throw new ConversionException(e);
324 } catch (InvocationTargetException e) {
325 throw new ConversionException(e);
326 }
327 }
328
329 });
330
331 }
332
333 }
334 }
335 }
336 }
337
338 }
339
340 /**
341 * Map of resolved converters: ClassLoader -> ClassLoaderConvertersEntry.
342 */
343 private static Map<ClassLoader, ClassLoaderConvertersEntry> classLoaderEntries = new HashMap<ClassLoader, ClassLoaderConvertersEntry>();
344
345 /**
346 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
347 * If no converter is found, this method performs converter discovery through
348 * constructors if target type is a class and through duck conversion if target
349 * type is an interface.
350 * @param source Source object.
351 * @param targetType Target type.
352 * @param context Conversion context.
353 * @param classLoader Class loader.
354 * @return Source object converted to target type or null if no converter
355 * is available for source/targetType combination
356 * @throws ConversionException
357 */
358 public static <S, T> T convert(S source, Class<T> targetType, Context context, ClassLoader classLoader) throws ConversionException {
359 return convert(source, targetType, context, false, classLoader);
360 }
361
362 /**
363 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
364 * If no converter is found, this method performs converter discovery through
365 * constructors if target type is a class and through duck conversion if target
366 * type is an interface.
367 * @param source Source object.
368 * @param targetType Target type.
369 * @param chain If true, the converter will attempt to build a chain of
370 * conversions from source type to target type.
371 * For example, StringBuffer doesn't have a constructor from Integer. If chain
372 * is <code>false</code> then this method will return <code>null</code>. If
373 * <code>chain</code> is true, then the converter will convert Integer to String
374 * and then will create StringBuffer from it. If multiple conversion chains
375 * are available, the shortest is chosen.
376 * @return Source object converted to target type or null if no converter
377 * is available for source/targetType combination
378 * @throws ConversionException
379 */
380 @SuppressWarnings("unchecked")
381 private static <S, T> T convert(S source, Class<T> targetType, Context context, boolean chain, ClassLoader classLoader) throws ConversionException {
382 if (source==null) {
383 return null;
384 }
385
386 if (targetType.isInstance(source)) {
387 return (T) source;
388 }
389
390 if (source instanceof Adaptable) {
391 T ret = ((Adaptable) source).getAdapter(targetType, context);
392 if (ret!=null) {
393 return ret;
394 }
395 }
396
397 ConverterClosure<S, T> converter = (ConverterClosure<S, T>) getConverter(source.getClass(), targetType, context, chain, classLoader);
398
399 if (converter!=null) {
400 return converter.convert(source);
401 }
402
403 ConverterClosure<S, Adaptable> adaptableConverter = (ConverterClosure<S, Adaptable>) getConverter(source.getClass(), Adaptable.class, context, chain, classLoader);
404 if (adaptableConverter==null) {
405 return null;
406 }
407
408 Adaptable adaptable = adaptableConverter.convert(source);
409 return adaptable==null ? null : adaptable.getAdapter(targetType, context);
410 }
411
412 /**
413 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
414 * If no converter is found, this method performs converter discovery through
415 * constructors if target type is a class and through duck conversion if target
416 * type is an interface.
417 * @param sourceType Source type.
418 * @param targetType Target type.
419 * @return Converter from source type to target type, or null if such converter
420 * is not available.
421 * @throws ConversionException
422 */
423 public static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType) {
424 return getConverter(sourceType, targetType, null, false, null);
425 }
426
427 /**
428 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
429 * If no converter is found, this method performs converter discovery through
430 * constructors if target type is a class and through duck conversion if target
431 * type is an interface.
432 * @param sourceType Source type.
433 * @param targetType Target type.
434 * @param classLoader Class loader to retrieve converters from.
435 * @return Converter from source type to target type, or null if such converter
436 * is not available.
437 * @throws ConversionException
438 */
439 public static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType, ClassLoader classLoader) {
440 return getConverter(sourceType, targetType, null, false, classLoader);
441 }
442
443 /**
444 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
445 * If no converter is found, this method performs converter discovery through
446 * constructors if target type is a class and through duck conversion if target
447 * type is an interface.
448 * @param sourceType Source type.
449 * @param targetType Target type.
450 * @param context Conversion context.
451 * @return Converter from source type to target type, or null if such converter
452 * is not available.
453 * @throws ConversionException
454 */
455 public static <S, T> ConverterClosure<S,T> getConverter(Class<S> sourceType, Class<T> targetType, Context context) {
456 return getConverter(sourceType, targetType, context, false, null);
457 }
458
459 /**
460 * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
461 * If no converter is found, this method performs converter discovery through
462 * constructors if target type is a class and through duck conversion if target
463 * type is an interface.
464 * @param sourceType Source type.
465 * @param targetType Target type.
466 * @param context Conversion context.
467 * @param classLoader Class loader to retrieve converters from.
468 * @return Converter from source type to target type, or null if such converter
469 * is not available.
470 * @throws ConversionException
471 */
472 public static <S, T> ConverterClosure<S,T> getConverter(Class<S> sourceType, Class<T> targetType, Context context, ClassLoader classLoader) {
473 return getConverter(sourceType, targetType, context, false, classLoader);
474 }
475
476 private static ClassLoader promote(ClassLoader present, ClassLoader candidate) {
477 ClassLoader ret = DuckConverterFactory.getChildClassLoader(present, candidate);
478 return ret==null ? present : ret;
479 }
480
481 /**
482 *
483 * @param sourceType
484 * @param targetType
485 * @param context
486 * @param chain If true, the converter will attempt to build a chain of
487 * conversions from source type to target type.
488 * For example, StringBuffer doesn't have a constructor from Integer. If chain
489 * is <code>false</code> then this method will return <code>null</code>. If
490 * <code>chain</code> is true, then the converter will convert Integer to String
491 * and then will create StringBuffer from it. If multiple conversion chains
492 * are available, the shortest is chosen.
493 * @param classLoader
494 * @return
495 */
496 @SuppressWarnings("unchecked")
497 private static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType, final Context context, boolean chain, ClassLoader pClassLoader) {
498 if (pClassLoader==null) {
499 pClassLoader = ConvertingService.class.getClassLoader();
500 }
501 ClassLoader classLoader = promote(pClassLoader, promote(targetType.getClassLoader(), sourceType.getClassLoader()));
502
503 ClassLoaderConvertersEntry clce;
504 synchronized (classLoaderEntries) {
505 clce = classLoaderEntries.get(classLoader);
506 if (clce == null) {
507 clce = new ClassLoaderConvertersEntry(classLoader);
508 for (Object provider: ServiceLoader.load(AtomicConvertersBundle.class, classLoader)) {
509 clce.addConverters(provider);
510 }
511 for (Object provider: ServiceLoader.load(AtomicConverter.class, classLoader)) {
512 clce.addConverters(provider);
513 }
514 for (Object provider: ServiceLoader.load(Converter.class, classLoader)) {
515 clce.addConverters(provider);
516 }
517
518 // TODO - Converter factories which may create converter (closure) or return null depending on context.
519 classLoaderEntries.put(classLoader, clce);
520 }
521 }
522 final ConvertersBucket bucket = chain ? clce.findChainingConverter(sourceType, targetType) : clce.findConverter(sourceType, targetType);
523 return bucket==null ? null : new ConverterClosure() {
524
525 public Object convert(Object source) {
526 return bucket.convert(source, context);
527 }
528
529 };
530 }
531 }