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] -&gt; 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] -&gt; 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 -&gt; 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    }