diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 6bdb05661..e0f2c6ac5 100644 --- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -266,7 +266,7 @@ public final class DefaultTypeManager implements TypeManager { * can have many classes if it uses method overrides and we need to register all * the variations/classes for the enum. */ - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public void addEnumType(ScalarType scalarType, Class enumClass) { @@ -339,7 +339,7 @@ public final class DefaultTypeManager implements TypeManager { Type valueType = getValueType(genericType); if (type.equals(List.class)) { if (arrayTypeListFactory != null) { - if(isEnumType(valueType)) { + if (isEnumType(valueType)) { return arrayTypeListFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), EnumType.STRING)); } return arrayTypeListFactory.typeFor(valueType); @@ -348,7 +348,7 @@ public final class DefaultTypeManager implements TypeManager { return new ScalarTypeJsonList.Varchar(getDocType(valueType)); } else if (type.equals(Set.class)) { if (arrayTypeSetFactory != null) { - if(isEnumType(valueType)) { + if (isEnumType(valueType)) { return arrayTypeSetFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), EnumType.STRING)); } return arrayTypeSetFactory.typeFor(valueType); @@ -359,13 +359,12 @@ public final class DefaultTypeManager implements TypeManager { throw new IllegalStateException("Type [" + type + "] not supported for @DbArray"); } - @SuppressWarnings("unchecked") private Class> asEnumClass(Type valueType) { - return (Class>)valueType; + return TypeReflectHelper.asEnumClass(valueType); } private boolean isEnumType(Type valueType) { - return valueType instanceof Class && ((Class)valueType).isEnum(); + return TypeReflectHelper.isEnumType(valueType); } @Override @@ -430,14 +429,13 @@ public final class DefaultTypeManager implements TypeManager { /** * Return true if value parameter type of the map is Object. */ - private boolean isValueTypeSimple(Type genericType) { - Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments(); - return String.class.equals(typeArgs[0]) || Long.class.equals(typeArgs[0]); + private boolean isValueTypeSimple(Type collectionType) { + Type typeArg = TypeReflectHelper.getValueType(collectionType); + return String.class.equals(typeArg) || Long.class.equals(typeArg); } - private Type getValueType(Type genericType) { - Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments(); - return typeArgs[0]; + private Type getValueType(Type collectionType) { + return TypeReflectHelper.getValueType(collectionType); } /** @@ -593,7 +591,7 @@ public final class DefaultTypeManager implements TypeManager { ScalarTypeEnum scalarType = (ScalarTypeEnum) getScalarType(enumType); if (scalarType != null && !scalarType.isOverrideBy(type)) { if (type != null && !scalarType.isCompatible(type)) { - throw new IllegalStateException("Error mapping Enum type:"+enumType+" It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)"); + throw new IllegalStateException("Error mapping Enum type:" + enumType + " It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)"); } return scalarType; } diff --git a/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java b/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java index a69dcd75d..392c0829b 100644 --- a/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java +++ b/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.type; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; public class TypeReflectHelper { @@ -9,14 +10,48 @@ public class TypeReflectHelper { return TypeResolver.resolveRawArgs(matchRawType, cls); } - public static Class getClass(Type type) { + /** + * Return the enum class for this type taking into account wildcard type. + */ + @SuppressWarnings("unchecked") + public static Class> asEnumClass(Type valueType) { + Class enumClass = getClass(valueType); + return (Class>) enumClass; + } + + /** + * Return true if the type is an enum. + */ + public static boolean isEnumType(Type valueType) { + try { + return getClass(valueType).isEnum(); + } catch (ClassCastException | IllegalArgumentException e) { + return false; + } + } + + /** + * Return the value type of a collection type (list, set, map values). + */ + public static Type getValueType(Type collectionType) { + Type[] typeArgs = ((ParameterizedType) collectionType).getActualTypeArguments(); + return typeArgs[0]; + } + + private static Class getClass(Type type) { while (true) { if (type instanceof ParameterizedType) { type = ((ParameterizedType) type).getRawType(); continue; } - + if (type instanceof WildcardType) { + Type[] upperBounds = ((WildcardType) type).getUpperBounds(); + if (upperBounds != null && upperBounds.length == 1) { + return getClass(upperBounds[0]); + } + throw new IllegalArgumentException("Don't know how to determine Class from Type [" + type + "]"); + } return (Class) type; } } diff --git a/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java b/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java index fe815617d..8ba6d612f 100644 --- a/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java +++ b/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.type; import io.ebean.config.ScalarTypeConverter; import org.junit.Test; +import org.tests.model.basic.Order; import org.tests.model.ivo.Money; import org.tests.model.ivo.Oid; import org.tests.model.ivo.SysTime; @@ -10,8 +11,13 @@ import org.tests.model.ivo.converter.OidTypeConverter; import org.tests.model.ivo.converter.SysTimeConverter; import javax.persistence.AttributeConverter; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -57,13 +63,63 @@ public class TypeReflectHelperTest { assertThat(params[1]).isEqualTo(byte[].class); } - static class RichText { + @Test + public void isEnumType_wildCard() throws NoSuchFieldException { + + Field wildOrderStatus = Some.class.getDeclaredField("wildOrderStatus"); + assertThat(TypeReflectHelper.isEnumType(getValueType(wildOrderStatus.getGenericType()))).isTrue(); + + Class aClass = TypeReflectHelper.asEnumClass(getValueType(wildOrderStatus.getGenericType())); + assertThat(aClass).isEqualTo(Order.Status.class); + } + + + @Test + public void isEnumType_simpleType() throws NoSuchFieldException { + + Field orderStatus = Some.class.getDeclaredField("orderStatus"); + + assertThat(TypeReflectHelper.isEnumType(getValueType(orderStatus.getGenericType()))).isTrue(); + + Class aClass = TypeReflectHelper.asEnumClass(getValueType(orderStatus.getGenericType())); + assertThat(aClass).isEqualTo(Order.Status.class); + } + + @Test + public void getValueType_simpleType() throws NoSuchFieldException { + + Field orderStatus = Some.class.getDeclaredField("orderStatus"); + + Type expected = getValueType(orderStatus.getGenericType()); + assertThat(TypeReflectHelper.getValueType(orderStatus.getGenericType())).isEqualTo(expected); + } + + @Test + public void getValueType_wildcardType() throws NoSuchFieldException { + + Field orderStatus = Some.class.getDeclaredField("wildOrderStatus"); + + Type expected = getValueType(orderStatus.getGenericType()); + assertThat(TypeReflectHelper.getValueType(orderStatus.getGenericType())).isEqualTo(expected); + } + + private Type getValueType(Type genericType) { + return ((ParameterizedType) genericType).getActualTypeArguments()[0]; + } + + private static class Some { + + List wildOrderStatus = new ArrayList<>(); + List orderStatus = new ArrayList<>(); + } + + private static class RichText { } - static class RichTextConverter extends Direct {} + private static class RichTextConverter extends Direct {} - static class Direct implements ScalarTypeConverter { + private static class Direct implements ScalarTypeConverter { @Override public M getNullValue() {