001package com.hammurapi.convert;
002
003import java.lang.reflect.InvocationTargetException;
004import java.lang.reflect.Method;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Map;
012import java.util.ServiceLoader;
013import java.util.Set;
014
015import com.hammurapi.common.Adaptable;
016import com.hammurapi.common.Context;
017
018public 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                                if (bucket!=null) {
140                                        Object ret = bucket.convert(source, context);
141                                        if (ret!=null) {
142                                                return ret;
143                                        }
144                                }
145                                
146                                for (ConverterPart part: parts) {
147                                        Object ret = part.convert(source, targetType, context, this);
148                                        if (ret!=null) {
149                                                return ret;
150                                        }
151                                }
152
153                                for (Converter subConverter: subConverters) {
154                                        Object ret = subConverter.convert(source, targetType, context);
155                                        if (ret!=null) {
156                                                return ret;
157                                        }
158                                }
159                                
160                                return null;
161                        }
162                        
163                };
164
165//              private Converter<?,?> chainingMaster = new Converter<?,?>() {
166//
167//                      public Object convert(Object source, Class targetType, Context context) throws ConversionException {
168//                              if (source==null) {
169//                                      return null;
170//                              }
171//                              
172//                              if (targetType.isInstance(source)) {
173//                                      return source;
174//                              }
175//                              
176//                              ConvertersBucket bucket = findChainingConverter(source.getClass(), targetType);
177//                              return bucket==null ? null : bucket.convert(source, context);
178//                      }
179//                      
180//              };
181
182                Collection<AtomicConverter<? extends Object,? extends Object>> atomicConverters = new ArrayList<AtomicConverter<? extends Object,? extends Object>>();
183                
184                /**
185                 * Map of resolved converters: Collection[source, target] -&gt; ConverterClosure. 
186                 */
187                Map<ConverterKey<?,?>, Object> converters = new HashMap<ConverterKey<?,?>, Object>(); 
188                
189                Set<Class<?>> introspectedTargetTypes = new HashSet<Class<?>>();
190
191                Set<Class<?>> introspectedSourceTypes = new HashSet<Class<?>>();
192                
193                Collection<Converter> subConverters = new ArrayList<Converter>();
194                Collection<ConverterPart> parts = new ArrayList<ConverterPart>();
195                
196                /**
197                 * Map of resolved chaining converters: Collection[source, target] -&gt; ConverterClosure. 
198                 */
199//              Map chainingConverters = new HashMap();
200                
201                @SuppressWarnings("unchecked")
202                synchronized <S, T> ConvertersBucket<S, T> findConverter(Class<S> sourceType, final Class<T> targetType) {
203                        ConverterKey<S, T> key = new ConverterKey(sourceType, targetType);
204                        Object ret = converters.get(key);
205                        if (Boolean.FALSE.equals(ret)) { // Indicates that previous converter resolution didn't yield results.
206                                return null;
207                        }
208                        
209                        if (ret == null) {
210                                if (!targetType.isInterface() && introspectedTargetTypes.add(targetType)) {
211                                        atomicConverters.addAll(ReflectionConverter.discoverConstructorConverters(targetType));
212                                }
213                                
214                                class ConverterEntry implements Comparable<ConverterEntry> {
215                                        public ConverterEntry(AtomicConverter<? extends Object,? extends Object> converter, int affinity, int position) {
216                                                super();
217                                                this.converter = converter;
218                                                this.affinity = affinity;
219                                                this.position = position;
220                                        }
221                                        AtomicConverter<? extends Object, ? extends Object> converter;
222                                        int affinity;
223                                        int position;
224                                        public int compareTo(ConverterEntry obj) {
225                                                int cret = affinity - ((ConverterEntry) obj).affinity;
226                                                if (cret==0) {
227                                                        return position - ((ConverterEntry) obj).position;
228                                                }
229                                                return cret;
230                                        }
231                                }
232                                
233                                final List<ConverterEntry> theConverters  = new ArrayList<ConverterEntry>();
234                                
235                                int position = 0;
236                                for (AtomicConverter<? extends Object,? extends Object> ac: atomicConverters) {
237                                        if (ac.getSourceType().isAssignableFrom(sourceType) && targetType.isAssignableFrom(ac.getTargetType()) ) {
238                                                Integer newSourceAffinity = DuckConverterFactory.classAffinity(sourceType, ac.getSourceType());
239                                                Integer newTargetAffinity = DuckConverterFactory.classAffinity(ac.getTargetType(), targetType);
240                                                if (newSourceAffinity==null || newTargetAffinity==null) {
241                                                        throw new IllegalArgumentException("Something is wrong - source and target affinities are null");
242                                                }
243                                                theConverters.add(new ConverterEntry(ac, newSourceAffinity.intValue()+newTargetAffinity.intValue(), ++position));
244                                        }
245                                }
246                                if (theConverters.isEmpty() && parts.isEmpty() && subConverters.isEmpty()) {
247                                        converters.put(key, Boolean.FALSE);
248                                } else {
249                                        Collections.sort(theConverters);
250                                        ret = new ConvertersBucket<S,T>() {
251
252                                                public T convert(S source, Context context) {
253                                                        for (ConverterEntry ce: theConverters) {
254                                                                AtomicConverter<S, T> theConverter = (AtomicConverter<S, T>) ce.converter;
255                                                                T ret = (T) theConverter.convert(source, master, context, classLoader);
256                                                                if (ret!=null) {
257                                                                        return ret;
258                                                                }                                                                                                                               
259                                                        }
260                                                        
261                                                        for (ConverterPart part: parts) {
262                                                                Object ret = part.convert(source, targetType, context, master);
263                                                                if (ret!=null) {
264                                                                        return (T) ret;
265                                                                }
266                                                        }
267
268                                                        for (Converter subConverter: subConverters) {
269                                                                Object ret = subConverter.convert(source, targetType, context);
270                                                                if (ret!=null) {
271                                                                        return (T) ret;
272                                                                }
273                                                        }
274                                                                                                                
275                                                        return null;
276                                                }
277                                                
278                                        };
279                                        converters.put(key, ret);
280                                }
281                        }
282                        
283                        return (ConvertersBucket<S, T>) ret;
284                }
285
286                synchronized <S, T> ConvertersBucket<S,T> findChainingConverter(Class<S> sourceType, Class<T> targetType) {
287                        ConvertersBucket<S,T> ret = findConverter(sourceType, targetType);
288                        if (ret!=null) {
289                                return ret;
290                        }
291                        
292                        // TODO - Resolve chain.
293                        return ret;
294                }
295                
296                <S,T> void addAnnotationConverters(Class<S> source) {
297                        if (introspectedSourceTypes.add(source)) {
298                                Z: for (Method m: source.getMethods()) {                                                
299                                        if (m.getAnnotation(ConverterMethod.class)!=null && !void.class.equals(m.getReturnType())) {
300                                                for (Class<?> pt: m.getParameterTypes()) {
301                                                        if (!(Context.class.equals(pt) || Converter.class.equals(pt))) {
302                                                                continue Z;
303                                                        }
304                                                }
305                                                final Method converterMethod = m;
306                                                @SuppressWarnings("unchecked")
307                                                AtomicConverterBase<S, T> converter = new AtomicConverterBase<S, T>(source, (Class<T>) m.getReturnType()) {
308
309                                                        @Override
310                                                        public T convert(S source, Converter master, Context context, ClassLoader classLoader) {
311                                                                try {
312                                                                        Class<?>[] pt = converterMethod.getParameterTypes();
313                                                                        Object[] args = new Object[pt.length];
314                                                                        for (int i=0; i<pt.length; ++i) {
315                                                                                if (Context.class.equals(pt[i])) {
316                                                                                        args[i] = context;
317                                                                                } else if (Converter.class.equals(pt[i])) {
318                                                                                        args[i] = master;
319                                                                                }                                                                                       
320                                                                        }
321                                                                        return (T) converterMethod.invoke(source, args);
322                                                                } catch (Exception e) {
323                                                                        throw new ConversionException(e);
324                                                                }
325                                                        }
326                                                };
327                                                atomicConverters.add(converter);
328                                        }
329                                }
330                        }
331                }
332                
333                @SuppressWarnings("unchecked")
334                void addConverters(final Object provider) {
335                        if (provider instanceof AtomicConverter) {
336                                atomicConverters.add((AtomicConverter<Object, Object>) provider);
337                        } else if (provider instanceof AtomicConvertersBundle) {
338                                atomicConverters.addAll(((AtomicConvertersBundle) provider).getConverters());
339                        } else if (provider instanceof Collection<?>) {
340                                for (Object obj: (Collection<?>) provider) {
341                                        addConverters(obj);
342                                }
343                        } else if (provider instanceof Converter) {
344                                subConverters.add((Converter) provider);
345                        } else if (provider instanceof ConverterPart) {
346                                parts.add((ConverterPart) provider);
347                        } else { // Introspection
348                                Class pClass = provider.getClass();
349                                Method[] methods = pClass.getMethods();
350                                for (int i=0; i<methods.length; ++i) {
351                                        final Method method = methods[i];
352                                        if (CONVERT.equals(method.getName()) && !void.class.equals(method.getReturnType())) {
353                                                Class[] pTypes = method.getParameterTypes();
354                                                if (pTypes.length == 1) {
355                                                        atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
356
357                                                                public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
358                                                                        try {
359                                                                                return method.invoke(provider, new Object[] {source});
360                                                                        } catch (IllegalAccessException e) {
361                                                                                throw new ConversionException(e);
362                                                                        } catch (InvocationTargetException e) {
363                                                                                throw new ConversionException(e);
364                                                                        }
365                                                                }
366                                                                
367                                                        });
368                                                } else if (pTypes.length == 2 && Converter.class.equals(pTypes[1])) {
369                                                        atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
370
371                                                                public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
372                                                                        try {
373                                                                                return method.invoke(provider, new Object[] {source, master});
374                                                                        } catch (IllegalAccessException e) {
375                                                                                throw new ConversionException(e);
376                                                                        } catch (InvocationTargetException e) {
377                                                                                throw new ConversionException(e);
378                                                                        }
379                                                                }
380                                                                
381                                                        });
382                                                        
383                                                } else if (pTypes.length == 2 && Context.class.equals(pTypes[1])) {
384                                                        atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
385
386                                                                public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
387                                                                        try {
388                                                                                return method.invoke(provider, new Object[] {source, context});
389                                                                        } catch (IllegalAccessException e) {
390                                                                                throw new ConversionException(e);
391                                                                        } catch (InvocationTargetException e) {
392                                                                                throw new ConversionException(e);
393                                                                        }
394                                                                }
395                                                                
396                                                        });
397                                                        
398                                                } else if (pTypes.length == 3 && Converter.class.equals(pTypes[1]) && Context.class.equals(pTypes[2])) {
399                                                        atomicConverters.add(new AtomicConverterBase(pTypes[0], method.getReturnType()) {
400
401                                                                public Object convert(Object source, Converter master, Context context, ClassLoader classLoader) {
402                                                                        try {
403                                                                                return method.invoke(provider, new Object[] {source, master, context});
404                                                                        } catch (IllegalAccessException e) {
405                                                                                throw new ConversionException(e);
406                                                                        } catch (InvocationTargetException e) {
407                                                                                throw new ConversionException(e);
408                                                                        }
409                                                                }
410                                                                
411                                                        });
412                                                        
413                                                }
414                                                
415                                        }
416                                }
417                        }
418                }
419                
420        }
421        
422        /**
423         * Map of resolved converters: ClassLoader -&gt; ClassLoaderConvertersEntry. 
424         */
425        private static Map<ClassLoader, ClassLoaderConvertersEntry> classLoaderEntries = new HashMap<ClassLoader, ClassLoaderConvertersEntry>();            
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 source Source object.
433         * @param targetType Target type.
434         * @param context Conversion context.
435         * @param classLoader Class loader.
436         * @return Source object converted to target type or null if no converter
437         * is available for source/targetType combination
438         * @throws ConversionException
439         */
440        public static <S, T> T convert(S source, Class<T> targetType, Context context, ClassLoader classLoader) throws ConversionException {
441                return convert(source, targetType, context, false, classLoader);
442        }
443        
444        /**
445         * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
446         * If no converter is found, this method performs converter discovery through
447         * constructors if target type is a class and through duck conversion if target
448         * type is an interface. 
449         * @param source Source object.
450         * @param targetType Target type.
451         * @param chain If true, the converter will attempt to build a chain of 
452         * conversions from source type to target type. 
453         * For example, StringBuffer doesn't have a constructor from Integer. If chain
454         * is <code>false</code> then this method will return <code>null</code>. If 
455         * <code>chain</code> is true, then the converter will convert Integer to String
456         * and then will create StringBuffer from it. If multiple conversion chains 
457         * are available, the shortest is chosen.
458         * @return Source object converted to target type or null if no converter
459         * is available for source/targetType combination
460         * @throws ConversionException
461         */
462        @SuppressWarnings("unchecked")
463        private static <S, T> T convert(S source, Class<T> targetType, Context context, boolean chain, ClassLoader classLoader) throws ConversionException {
464                if (source==null) {
465                        return null;
466                }
467                
468                if (targetType.isInstance(source)) {
469                        return (T) source;
470                }
471                
472                if (source instanceof Adaptable) {
473                        T ret = ((Adaptable) source).getAdapter(targetType, context);
474                        if (ret!=null) {
475                                return ret;
476                        }
477                }
478                
479                ConverterClosure<S, T> converter = (ConverterClosure<S, T>) getConverter(source.getClass(), targetType, context, chain, classLoader);
480                
481                if (converter!=null) {
482                        return converter.convert(source);
483                }
484                
485                ConverterClosure<S, Adaptable> adaptableConverter = (ConverterClosure<S, Adaptable>) getConverter(source.getClass(), Adaptable.class, context, chain, classLoader);
486                if (adaptableConverter==null) {
487                        return null;
488                }
489                
490                Adaptable adaptable = adaptableConverter.convert(source);               
491                return adaptable==null ? null : adaptable.getAdapter(targetType, context);              
492        }
493        
494        /**
495         * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
496         * If no converter is found, this method performs converter discovery through
497         * constructors if target type is a class and through duck conversion if target
498         * type is an interface. 
499         * @param sourceType Source type.
500         * @param targetType Target type.
501         * @return Converter from source type to target type, or null if such converter
502         * is not available.
503         * @throws ConversionException
504         */     
505        public static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType) { 
506                return getConverter(sourceType, targetType, null, false, null);
507        }
508        
509        /**
510         * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
511         * If no converter is found, this method performs converter discovery through
512         * constructors if target type is a class and through duck conversion if target
513         * type is an interface. 
514         * @param sourceType Source type.
515         * @param targetType Target type.
516         * @param classLoader Class loader to retrieve converters from.
517         * @return Converter from source type to target type, or null if such converter
518         * is not available.
519         * @throws ConversionException
520         */     
521        public static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType, ClassLoader classLoader) { 
522                return getConverter(sourceType, targetType, null, false, classLoader);
523        }
524        
525        /**
526         * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
527         * If no converter is found, this method performs converter discovery through
528         * constructors if target type is a class and through duck conversion if target
529         * type is an interface. 
530         * @param sourceType Source type.
531         * @param targetType Target type.
532         * @param context Conversion context.
533         * @return Converter from source type to target type, or null if such converter
534         * is not available.
535         * @throws ConversionException
536         */     
537        public static <S, T> ConverterClosure<S,T> getConverter(Class<S> sourceType, Class<T> targetType, Context context) { 
538                return getConverter(sourceType, targetType, context, false, null);
539        }
540        
541        /**
542         * Converts source to target type using Java 6 service loading for AtomicConverterBundle, AtomicConverter, and Converter services
543         * If no converter is found, this method performs converter discovery through
544         * constructors if target type is a class and through duck conversion if target
545         * type is an interface. 
546         * @param sourceType Source type.
547         * @param targetType Target type.
548         * @param context Conversion context.
549         * @param classLoader Class loader to retrieve converters from.
550         * @return Converter from source type to target type, or null if such converter
551         * is not available.
552         * @throws ConversionException
553         */     
554        public static <S, T> ConverterClosure<S,T> getConverter(Class<S> sourceType, Class<T> targetType, Context context, ClassLoader classLoader) { 
555                return getConverter(sourceType, targetType, context, false, classLoader);
556        }
557        
558        private static ClassLoader promote(ClassLoader present, ClassLoader candidate) {
559                ClassLoader ret = DuckConverterFactory.getChildClassLoader(present, candidate);
560                return ret==null ? present : ret;
561        }
562        
563        /**
564         * 
565         * @param sourceType
566         * @param targetType
567         * @param context
568         * @param chain If true, the converter will attempt to build a chain of 
569         * conversions from source type to target type. 
570         * For example, StringBuffer doesn't have a constructor from Integer. If chain
571         * is <code>false</code> then this method will return <code>null</code>. If 
572         * <code>chain</code> is true, then the converter will convert Integer to String
573         * and then will create StringBuffer from it. If multiple conversion chains 
574         * are available, the shortest is chosen.
575         * @param classLoader
576         * @return
577         */
578        @SuppressWarnings("unchecked")
579        private static <S, T> ConverterClosure<S, T> getConverter(Class<S> sourceType, Class<T> targetType, final Context context, boolean chain, ClassLoader pClassLoader) {
580                if (pClassLoader==null) {
581                        pClassLoader = ConvertingService.class.getClassLoader();
582                }
583                ClassLoader classLoader = promote(pClassLoader, promote(targetType.getClassLoader(), sourceType.getClassLoader()));
584                                        
585                ClassLoaderConvertersEntry clce;
586                synchronized (classLoaderEntries) {
587                        clce = classLoaderEntries.get(classLoader);
588                        if (clce == null) {
589                                clce = new ClassLoaderConvertersEntry(classLoader);
590                                for (Object provider: ServiceLoader.load(AtomicConvertersBundle.class, classLoader)) {
591                                        clce.addConverters(provider);
592                                }
593                                for (Object provider: ServiceLoader.load(AtomicConverter.class, classLoader)) {
594                                        clce.addConverters(provider);
595                                }
596                                for (Object provider: ServiceLoader.load(Converter.class, classLoader)) {
597                                        clce.addConverters(provider);
598                                }
599                                
600                                for (Object provider: ServiceLoader.load(ConverterPart.class, classLoader)) {
601                                        clce.addConverters(provider);
602                                }
603                                // TODO - Converter factories which may create converter (closure) or return null depending on context.
604                                classLoaderEntries.put(classLoader, clce);
605                        }       
606                        clce.addAnnotationConverters(sourceType);
607                }
608                final ConvertersBucket bucket = chain ? clce.findChainingConverter(sourceType, targetType) : clce.findConverter(sourceType, targetType);
609                return bucket==null ? null : new ConverterClosure() {
610
611                        public Object convert(Object source) {
612                                return bucket.convert(source, context);
613                        }
614                        
615                };
616        }
617}