    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}