001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.core.convert.support;
018
019import java.lang.reflect.Array;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.LinkedHashMap;
024import java.util.LinkedHashSet;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import org.springframework.core.DecoratingProxy;
031import org.springframework.core.ResolvableType;
032import org.springframework.core.convert.ConversionException;
033import org.springframework.core.convert.ConversionFailedException;
034import org.springframework.core.convert.ConversionService;
035import org.springframework.core.convert.ConverterNotFoundException;
036import org.springframework.core.convert.TypeDescriptor;
037import org.springframework.core.convert.converter.ConditionalConverter;
038import org.springframework.core.convert.converter.ConditionalGenericConverter;
039import org.springframework.core.convert.converter.Converter;
040import org.springframework.core.convert.converter.ConverterFactory;
041import org.springframework.core.convert.converter.ConverterRegistry;
042import org.springframework.core.convert.converter.GenericConverter;
043import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
044import org.springframework.util.Assert;
045import org.springframework.util.ClassUtils;
046import org.springframework.util.ConcurrentReferenceHashMap;
047import org.springframework.util.ObjectUtils;
048import org.springframework.util.StringUtils;
049
050/**
051 * Base {@link ConversionService} implementation suitable for use in most environments.
052 * Indirectly implements {@link ConverterRegistry} as registration API through the
053 * {@link ConfigurableConversionService} interface.
054 *
055 * @author Keith Donald
056 * @author Juergen Hoeller
057 * @author Chris Beams
058 * @author Phillip Webb
059 * @author David Haraburda
060 * @since 3.0
061 */
062public class GenericConversionService implements ConfigurableConversionService {
063
064        /**
065         * General NO-OP converter used when conversion is not required.
066         */
067        private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");
068
069        /**
070         * Used as a cache entry when no converter is available.
071         * This converter is never returned.
072         */
073        private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH");
074
075
076        /** Java 8's java.util.Optional.empty() */
077        private static Object javaUtilOptionalEmpty = null;
078
079        static {
080                try {
081                        Class<?> clazz = ClassUtils.forName("java.util.Optional", GenericConversionService.class.getClassLoader());
082                        javaUtilOptionalEmpty = ClassUtils.getMethod(clazz, "empty").invoke(null);
083                }
084                catch (Exception ex) {
085                        // Java 8 not available - conversion to Optional not supported then.
086                }
087        }
088
089
090        private final Converters converters = new Converters();
091
092        private final Map<ConverterCacheKey, GenericConverter> converterCache =
093                        new ConcurrentReferenceHashMap<ConverterCacheKey, GenericConverter>(64);
094
095
096        // ConverterRegistry implementation
097
098        @Override
099        public void addConverter(Converter<?, ?> converter) {
100                ResolvableType[] typeInfo = getRequiredTypeInfo(converter.getClass(), Converter.class);
101                if (typeInfo == null && converter instanceof DecoratingProxy) {
102                        typeInfo = getRequiredTypeInfo(((DecoratingProxy) converter).getDecoratedClass(), Converter.class);
103                }
104                if (typeInfo == null) {
105                        throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " +
106                                        "Converter [" + converter.getClass().getName() + "]; does the class parameterize those types?");
107                }
108                addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
109        }
110
111        @Override
112        public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
113                addConverter(new ConverterAdapter(
114                                converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
115        }
116
117        @Override
118        public void addConverter(GenericConverter converter) {
119                this.converters.add(converter);
120                invalidateCache();
121        }
122
123        @Override
124        public void addConverterFactory(ConverterFactory<?, ?> factory) {
125                ResolvableType[] typeInfo = getRequiredTypeInfo(factory.getClass(), ConverterFactory.class);
126                if (typeInfo == null && factory instanceof DecoratingProxy) {
127                        typeInfo = getRequiredTypeInfo(((DecoratingProxy) factory).getDecoratedClass(), ConverterFactory.class);
128                }
129                if (typeInfo == null) {
130                        throw new IllegalArgumentException("Unable to determine source type <S> and target type <T> for your " +
131                                        "ConverterFactory [" + factory.getClass().getName() + "]; does the class parameterize those types?");
132                }
133                addConverter(new ConverterFactoryAdapter(factory,
134                                new ConvertiblePair(typeInfo[0].resolve(), typeInfo[1].resolve())));
135        }
136
137        @Override
138        public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
139                this.converters.remove(sourceType, targetType);
140                invalidateCache();
141        }
142
143
144        // ConversionService implementation
145
146        @Override
147        public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
148                Assert.notNull(targetType, "Target type to convert to cannot be null");
149                return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null),
150                                TypeDescriptor.valueOf(targetType));
151        }
152
153        @Override
154        public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
155                Assert.notNull(targetType, "Target type to convert to cannot be null");
156                if (sourceType == null) {
157                        return true;
158                }
159                GenericConverter converter = getConverter(sourceType, targetType);
160                return (converter != null);
161        }
162
163        /**
164         * Return whether conversion between the source type and the target type can be bypassed.
165         * <p>More precisely, this method will return true if objects of sourceType can be
166         * converted to the target type by returning the source object unchanged.
167         * @param sourceType context about the source type to convert from
168         * (may be {@code null} if source is {@code null})
169         * @param targetType context about the target type to convert to (required)
170         * @return {@code true} if conversion can be bypassed; {@code false} otherwise
171         * @throws IllegalArgumentException if targetType is {@code null}
172         * @since 3.2
173         */
174        public boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
175                Assert.notNull(targetType, "Target type to convert to cannot be null");
176                if (sourceType == null) {
177                        return true;
178                }
179                GenericConverter converter = getConverter(sourceType, targetType);
180                return (converter == NO_OP_CONVERTER);
181        }
182
183        @Override
184        @SuppressWarnings("unchecked")
185        public <T> T convert(Object source, Class<T> targetType) {
186                Assert.notNull(targetType, "Target type to convert to cannot be null");
187                return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
188        }
189
190        @Override
191        public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
192                Assert.notNull(targetType, "Target type to convert to cannot be null");
193                if (sourceType == null) {
194                        Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
195                        return handleResult(null, targetType, convertNullSource(null, targetType));
196                }
197                if (source != null && !sourceType.getObjectType().isInstance(source)) {
198                        throw new IllegalArgumentException("Source to convert from must be an instance of [" +
199                                        sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
200                }
201                GenericConverter converter = getConverter(sourceType, targetType);
202                if (converter != null) {
203                        Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
204                        return handleResult(sourceType, targetType, result);
205                }
206                return handleConverterNotFound(source, sourceType, targetType);
207        }
208
209        /**
210         * Convenience operation for converting a source object to the specified targetType,
211         * where the target type is a descriptor that provides additional conversion context.
212         * Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and
213         * encapsulates the construction of the source type descriptor using
214         * {@link TypeDescriptor#forObject(Object)}.
215         * @param source the source object
216         * @param targetType the target type
217         * @return the converted value
218         * @throws ConversionException if a conversion exception occurred
219         * @throws IllegalArgumentException if targetType is {@code null},
220         * or sourceType is {@code null} but source is not {@code null}
221         */
222        public Object convert(Object source, TypeDescriptor targetType) {
223                return convert(source, TypeDescriptor.forObject(source), targetType);
224        }
225
226        @Override
227        public String toString() {
228                return this.converters.toString();
229        }
230
231
232        // Protected template methods
233
234        /**
235         * Template method to convert a {@code null} source.
236         * <p>The default implementation returns {@code null} or the Java 8
237         * {@link java.util.Optional#empty()} instance if the target type is
238         * {@code java.util.Optional}. Subclasses may override this to return
239         * custom {@code null} objects for specific target types.
240         * @param sourceType the source type to convert from
241         * @param targetType the target type to convert to
242         * @return the converted null object
243         */
244        protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
245                if (javaUtilOptionalEmpty != null && targetType.getObjectType() == javaUtilOptionalEmpty.getClass()) {
246                        return javaUtilOptionalEmpty;
247                }
248                return null;
249        }
250
251        /**
252         * Hook method to lookup the converter for a given sourceType/targetType pair.
253         * First queries this ConversionService's converter cache.
254         * On a cache miss, then performs an exhaustive search for a matching converter.
255         * If no converter matches, returns the default converter.
256         * @param sourceType the source type to convert from
257         * @param targetType the target type to convert to
258         * @return the generic converter that will perform the conversion,
259         * or {@code null} if no suitable converter was found
260         * @see #getDefaultConverter(TypeDescriptor, TypeDescriptor)
261         */
262        protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
263                ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
264                GenericConverter converter = this.converterCache.get(key);
265                if (converter != null) {
266                        return (converter != NO_MATCH ? converter : null);
267                }
268
269                converter = this.converters.find(sourceType, targetType);
270                if (converter == null) {
271                        converter = getDefaultConverter(sourceType, targetType);
272                }
273
274                if (converter != null) {
275                        this.converterCache.put(key, converter);
276                        return converter;
277                }
278
279                this.converterCache.put(key, NO_MATCH);
280                return null;
281        }
282
283        /**
284         * Return the default converter if no converter is found for the given sourceType/targetType pair.
285         * <p>Returns a NO_OP Converter if the source type is assignable to the target type.
286         * Returns {@code null} otherwise, indicating no suitable converter could be found.
287         * @param sourceType the source type to convert from
288         * @param targetType the target type to convert to
289         * @return the default generic converter that will perform the conversion
290         */
291        protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
292                return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
293        }
294
295
296        // Internal helpers
297
298        private ResolvableType[] getRequiredTypeInfo(Class<?> converterClass, Class<?> genericIfc) {
299                ResolvableType resolvableType = ResolvableType.forClass(converterClass).as(genericIfc);
300                ResolvableType[] generics = resolvableType.getGenerics();
301                if (generics.length < 2) {
302                        return null;
303                }
304                Class<?> sourceType = generics[0].resolve();
305                Class<?> targetType = generics[1].resolve();
306                if (sourceType == null || targetType == null) {
307                        return null;
308                }
309                return generics;
310        }
311
312        private void invalidateCache() {
313                this.converterCache.clear();
314        }
315
316        private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
317                if (source == null) {
318                        assertNotPrimitiveTargetType(sourceType, targetType);
319                        return null;
320                }
321                if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) {
322                        return source;
323                }
324                throw new ConverterNotFoundException(sourceType, targetType);
325        }
326
327        private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) {
328                if (result == null) {
329                        assertNotPrimitiveTargetType(sourceType, targetType);
330                }
331                return result;
332        }
333
334        private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) {
335                if (targetType.isPrimitive()) {
336                        throw new ConversionFailedException(sourceType, targetType, null,
337                                        new IllegalArgumentException("A null value cannot be assigned to a primitive type"));
338                }
339        }
340
341
342        /**
343         * Adapts a {@link Converter} to a {@link GenericConverter}.
344         */
345        @SuppressWarnings("unchecked")
346        private final class ConverterAdapter implements ConditionalGenericConverter {
347
348                private final Converter<Object, Object> converter;
349
350                private final ConvertiblePair typeInfo;
351
352                private final ResolvableType targetType;
353
354                public ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
355                        this.converter = (Converter<Object, Object>) converter;
356                        this.typeInfo = new ConvertiblePair(sourceType.resolve(Object.class), targetType.resolve(Object.class));
357                        this.targetType = targetType;
358                }
359
360                @Override
361                public Set<ConvertiblePair> getConvertibleTypes() {
362                        return Collections.singleton(this.typeInfo);
363                }
364
365                @Override
366                public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
367                        // Check raw type first...
368                        if (this.typeInfo.getTargetType() != targetType.getObjectType()) {
369                                return false;
370                        }
371                        // Full check for complex generic type match required?
372                        ResolvableType rt = targetType.getResolvableType();
373                        if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) &&
374                                        !this.targetType.hasUnresolvableGenerics()) {
375                                return false;
376                        }
377                        return !(this.converter instanceof ConditionalConverter) ||
378                                        ((ConditionalConverter) this.converter).matches(sourceType, targetType);
379                }
380
381                @Override
382                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
383                        if (source == null) {
384                                return convertNullSource(sourceType, targetType);
385                        }
386                        return this.converter.convert(source);
387                }
388
389                @Override
390                public String toString() {
391                        return (this.typeInfo + " : " + this.converter);
392                }
393        }
394
395
396        /**
397         * Adapts a {@link ConverterFactory} to a {@link GenericConverter}.
398         */
399        @SuppressWarnings("unchecked")
400        private final class ConverterFactoryAdapter implements ConditionalGenericConverter {
401
402                private final ConverterFactory<Object, Object> converterFactory;
403
404                private final ConvertiblePair typeInfo;
405
406                public ConverterFactoryAdapter(ConverterFactory<?, ?> converterFactory, ConvertiblePair typeInfo) {
407                        this.converterFactory = (ConverterFactory<Object, Object>) converterFactory;
408                        this.typeInfo = typeInfo;
409                }
410
411                @Override
412                public Set<ConvertiblePair> getConvertibleTypes() {
413                        return Collections.singleton(this.typeInfo);
414                }
415
416                @Override
417                public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
418                        boolean matches = true;
419                        if (this.converterFactory instanceof ConditionalConverter) {
420                                matches = ((ConditionalConverter) this.converterFactory).matches(sourceType, targetType);
421                        }
422                        if (matches) {
423                                Converter<?, ?> converter = this.converterFactory.getConverter(targetType.getType());
424                                if (converter instanceof ConditionalConverter) {
425                                        matches = ((ConditionalConverter) converter).matches(sourceType, targetType);
426                                }
427                        }
428                        return matches;
429                }
430
431                @Override
432                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
433                        if (source == null) {
434                                return convertNullSource(sourceType, targetType);
435                        }
436                        return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
437                }
438
439                @Override
440                public String toString() {
441                        return (this.typeInfo + " : " + this.converterFactory);
442                }
443        }
444
445
446        /**
447         * Key for use with the converter cache.
448         */
449        private static final class ConverterCacheKey implements Comparable<ConverterCacheKey> {
450
451                private final TypeDescriptor sourceType;
452
453                private final TypeDescriptor targetType;
454
455                public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) {
456                        this.sourceType = sourceType;
457                        this.targetType = targetType;
458                }
459
460                @Override
461                public boolean equals(Object other) {
462                        if (this == other) {
463                                return true;
464                        }
465                        if (!(other instanceof ConverterCacheKey)) {
466                                return false;
467                        }
468                        ConverterCacheKey otherKey = (ConverterCacheKey) other;
469                        return (ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType) &&
470                                        ObjectUtils.nullSafeEquals(this.targetType, otherKey.targetType));
471                }
472
473                @Override
474                public int hashCode() {
475                        return (ObjectUtils.nullSafeHashCode(this.sourceType) * 29 +
476                                        ObjectUtils.nullSafeHashCode(this.targetType));
477                }
478
479                @Override
480                public String toString() {
481                        return ("ConverterCacheKey [sourceType = " + this.sourceType +
482                                        ", targetType = " + this.targetType + "]");
483                }
484
485                @Override
486                public int compareTo(ConverterCacheKey other) {
487                        int result = this.sourceType.getResolvableType().toString().compareTo(
488                                        other.sourceType.getResolvableType().toString());
489                        if (result == 0) {
490                                result = this.targetType.getResolvableType().toString().compareTo(
491                                                other.targetType.getResolvableType().toString());
492                        }
493                        return result;
494                }
495        }
496
497
498        /**
499         * Manages all converters registered with the service.
500         */
501        private static class Converters {
502
503                private final Set<GenericConverter> globalConverters = new LinkedHashSet<GenericConverter>();
504
505                private final Map<ConvertiblePair, ConvertersForPair> converters =
506                                new LinkedHashMap<ConvertiblePair, ConvertersForPair>(256);
507
508                public void add(GenericConverter converter) {
509                        Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
510                        if (convertibleTypes == null) {
511                                Assert.state(converter instanceof ConditionalConverter,
512                                                "Only conditional converters may return null convertible types");
513                                this.globalConverters.add(converter);
514                        }
515                        else {
516                                for (ConvertiblePair convertiblePair : convertibleTypes) {
517                                        getMatchableConverters(convertiblePair).add(converter);
518                                }
519                        }
520                }
521
522                private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
523                        ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
524                        if (convertersForPair == null) {
525                                convertersForPair = new ConvertersForPair();
526                                this.converters.put(convertiblePair, convertersForPair);
527                        }
528                        return convertersForPair;
529                }
530
531                public void remove(Class<?> sourceType, Class<?> targetType) {
532                        this.converters.remove(new ConvertiblePair(sourceType, targetType));
533                }
534
535                /**
536                 * Find a {@link GenericConverter} given a source and target type.
537                 * <p>This method will attempt to match all possible converters by working
538                 * through the class and interface hierarchy of the types.
539                 * @param sourceType the source type
540                 * @param targetType the target type
541                 * @return a matching {@link GenericConverter}, or {@code null} if none found
542                 */
543                public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
544                        // Search the full type hierarchy
545                        List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
546                        List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
547                        for (Class<?> sourceCandidate : sourceCandidates) {
548                                for (Class<?> targetCandidate : targetCandidates) {
549                                        ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
550                                        GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
551                                        if (converter != null) {
552                                                return converter;
553                                        }
554                                }
555                        }
556                        return null;
557                }
558
559                private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
560                                TypeDescriptor targetType, ConvertiblePair convertiblePair) {
561
562                        // Check specifically registered converters
563                        ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
564                        if (convertersForPair != null) {
565                                GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
566                                if (converter != null) {
567                                        return converter;
568                                }
569                        }
570                        // Check ConditionalConverters for a dynamic match
571                        for (GenericConverter globalConverter : this.globalConverters) {
572                                if (((ConditionalConverter) globalConverter).matches(sourceType, targetType)) {
573                                        return globalConverter;
574                                }
575                        }
576                        return null;
577                }
578
579                /**
580                 * Returns an ordered class hierarchy for the given type.
581                 * @param type the type
582                 * @return an ordered list of all classes that the given type extends or implements
583                 */
584                private List<Class<?>> getClassHierarchy(Class<?> type) {
585                        List<Class<?>> hierarchy = new ArrayList<Class<?>>(20);
586                        Set<Class<?>> visited = new HashSet<Class<?>>(20);
587                        addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited);
588                        boolean array = type.isArray();
589
590                        int i = 0;
591                        while (i < hierarchy.size()) {
592                                Class<?> candidate = hierarchy.get(i);
593                                candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
594                                Class<?> superclass = candidate.getSuperclass();
595                                if (superclass != null && superclass != Object.class && superclass != Enum.class) {
596                                        addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
597                                }
598                                addInterfacesToClassHierarchy(candidate, array, hierarchy, visited);
599                                i++;
600                        }
601
602                        if (Enum.class.isAssignableFrom(type)) {
603                                addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited);
604                                addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited);
605                                addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited);
606                        }
607
608                        addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
609                        addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
610                        return hierarchy;
611                }
612
613                private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray,
614                                List<Class<?>> hierarchy, Set<Class<?>> visited) {
615
616                        for (Class<?> implementedInterface : type.getInterfaces()) {
617                                addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
618                        }
619                }
620
621                private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
622                                List<Class<?>> hierarchy, Set<Class<?>> visited) {
623
624                        if (asArray) {
625                                type = Array.newInstance(type, 0).getClass();
626                        }
627                        if (visited.add(type)) {
628                                hierarchy.add(index, type);
629                        }
630                }
631
632                @Override
633                public String toString() {
634                        StringBuilder builder = new StringBuilder();
635                        builder.append("ConversionService converters =\n");
636                        for (String converterString : getConverterStrings()) {
637                                builder.append('\t').append(converterString).append('\n');
638                        }
639                        return builder.toString();
640                }
641
642                private List<String> getConverterStrings() {
643                        List<String> converterStrings = new ArrayList<String>();
644                        for (ConvertersForPair convertersForPair : converters.values()) {
645                                converterStrings.add(convertersForPair.toString());
646                        }
647                        Collections.sort(converterStrings);
648                        return converterStrings;
649                }
650        }
651
652
653        /**
654         * Manages converters registered with a specific {@link ConvertiblePair}.
655         */
656        private static class ConvertersForPair {
657
658                private final LinkedList<GenericConverter> converters = new LinkedList<GenericConverter>();
659
660                public void add(GenericConverter converter) {
661                        this.converters.addFirst(converter);
662                }
663
664                public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
665                        for (GenericConverter converter : this.converters) {
666                                if (!(converter instanceof ConditionalGenericConverter) ||
667                                                ((ConditionalGenericConverter) converter).matches(sourceType, targetType)) {
668                                        return converter;
669                                }
670                        }
671                        return null;
672                }
673
674                @Override
675                public String toString() {
676                        return StringUtils.collectionToCommaDelimitedString(this.converters);
677                }
678        }
679
680
681        /**
682         * Internal converter that performs no operation.
683         */
684        private static class NoOpConverter implements GenericConverter {
685
686                private final String name;
687
688                public NoOpConverter(String name) {
689                        this.name = name;
690                }
691
692                @Override
693                public Set<ConvertiblePair> getConvertibleTypes() {
694                        return null;
695                }
696
697                @Override
698                public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
699                        return source;
700                }
701
702                @Override
703                public String toString() {
704                        return this.name;
705                }
706        }
707
708}