Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package org.springframework.core.convert.support;

import java.lang.reflect.Array;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
Expand Down Expand Up @@ -518,8 +519,8 @@ public void remove(Class<?> sourceType, Class<?> targetType) {
*/
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Search the full type hierarchy
List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
Iterable<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
Iterable<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
for (Class<?> sourceCandidate : sourceCandidates) {
for (Class<?> targetCandidate : targetCandidates) {
ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
Expand Down Expand Up @@ -555,41 +556,49 @@ private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
/**
* Returns an ordered class hierarchy for the given type.
* @param type the type
* @return an ordered list of all classes that the given type extends or implements
* @return an Iterable of all classes that the given type extends or implements
*/
private List<Class<?>> getClassHierarchy(Class<?> type) {
List<Class<?>> hierarchy = new ArrayList<Class<?>>(20);
Set<Class<?>> visited = new HashSet<Class<?>>(20);
addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited);
private Iterable<Class<?>> getClassHierarchy(Class<?> type) {
Deque<Class<?>> classStack = new ArrayDeque<Class<?>>(20);
LinkedHashSet<Class<?>> hierarchy = new LinkedHashSet<Class<?>>(20);
boolean array = type.isArray();
int i = 0;
while (i < hierarchy.size()) {
Class<?> candidate = hierarchy.get(i);
candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));

classStack.push(ClassUtils.resolvePrimitiveIfNecessary(type));

Class<?> candidate = null;
while((candidate = classStack.pollFirst()) != null) {
candidate = ClassUtils.resolvePrimitiveIfNecessary(candidate);
hierarchy.add(!candidate.isArray() && array ? Array.newInstance(candidate, 0).getClass() : candidate);

Class<?> superclass = candidate.getSuperclass();
if (candidate.getSuperclass() != null && superclass != Object.class) {
addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
if (superclass != null && superclass != Object.class && superclass != Enum.class) {
classStack.push(superclass);
}
for (Class<?> implementedInterface : candidate.getInterfaces()) {
addToClassHierarchy(hierarchy.size(), implementedInterface, array, hierarchy, visited);

for(Class<?> implementedInterface : candidate.getInterfaces()) {
// add interfaces to the other end of the queue, so that
// concrete classes always come first in the hierarchy
classStack.addLast(implementedInterface);
}
i++;
}
addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
return hierarchy;
}

private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
List<Class<?>> hierarchy, Set<Class<?>> visited) {
if (asArray) {
type = Array.newInstance(type, 0).getClass();

// make sure Enum comes at the "end" of the hierarchy (if necessary)
if(type.isEnum()) {
if(array) {
hierarchy.add(Array.newInstance(Enum.class, 0).getClass());
}
hierarchy.add(Enum.class);
}
if (visited.add(type)) {
hierarchy.add(index, type);

// always add Object to hierarchy
if(array) {
hierarchy.add(Array.newInstance(Object.class, 0).getClass());
}
hierarchy.add(Object.class);

return hierarchy;
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,21 @@ public void testEnumWithInterfaceToStringConversion() {
assertEquals("1", result);
}

@Test
public void testStringToEnumWithInterfaceConversion() {
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverterFactory(new StringToMyEnumInterfaceConverterFactory());
assertEquals(MyEnum.A, conversionService.convert("1", MyEnum.class));
}

@Test
public void testStringToEnumWithBaseInterfaceConversion() {
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverterFactory(new StringToMyEnumBaseInterfaceConverterFactory());
assertEquals(MyEnum.A, conversionService.convert("base1", MyEnum.class));
}


@Test
public void convertNullAnnotatedStringToString() throws Exception {
DefaultConversionService.addDefaultConverters(conversionService);
Expand Down Expand Up @@ -930,20 +945,35 @@ public int getNestedMatchAttempts() {
}
}


interface MyEnumInterface {

interface MyEnumBaseInterface {
String getBaseCode();
}

interface MyEnumInterface extends MyEnumBaseInterface {
String getCode();
}

public static enum MyEnum implements MyEnumInterface {

A {
@Override
public String getCode() {
return "1";
}
}
A("1"),
B("2"),
C("3");

private String code;

MyEnum(String code) {
this.code = code;
}

@Override
public String getCode() {
return code;
}

@Override
public String getBaseCode() {
return "base" + code;
}
}


Expand All @@ -970,6 +1000,59 @@ public String convert(T source) {
return source.getCode();
}
}

private static class StringToMyEnumInterfaceConverterFactory implements ConverterFactory<String, MyEnumInterface> {

@SuppressWarnings("unchecked")
public <T extends MyEnumInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToMyEnumInterfaceConverter(targetType);
}

private static class StringToMyEnumInterfaceConverter<T extends Enum<?> & MyEnumInterface> implements Converter<String, T> {
private final Class<T> enumType;

public StringToMyEnumInterfaceConverter(Class<T> enumType) {
this.enumType = enumType;
}

public T convert(String source) {
for (T value : enumType.getEnumConstants()) {
if (value.getCode().equals(source)) {
return value;
}
}
return null;
}
}

}

private static class StringToMyEnumBaseInterfaceConverterFactory implements ConverterFactory<String, MyEnumBaseInterface> {

@SuppressWarnings("unchecked")
public <T extends MyEnumBaseInterface> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToMyEnumBaseInterfaceConverter(targetType);
}

private static class StringToMyEnumBaseInterfaceConverter<T extends Enum<?> & MyEnumBaseInterface> implements Converter<String, T> {
private final Class<T> enumType;

public StringToMyEnumBaseInterfaceConverter(Class<T> enumType) {
this.enumType = enumType;
}

public T convert(String source) {
for (T value : enumType.getEnumConstants()) {
if (value.getBaseCode().equals(source)) {
return value;
}
}
return null;
}
}

}


public static class MyStringToStringCollectionConverter implements Converter<String, Collection<String>> {

Expand Down