From ac36172cc61e38b1f982135cc2ab72965d7da0f5 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 16 Feb 2018 12:47:10 +1300 Subject: [PATCH] #997 - TypeReflectHelper does not resolve type variables correctly in non-trivial cases Part 2 - Fix the failing test, swap in use of TypeResolver and remove all the unused features. --- .../server/type/TypeReflectHelper.java | 24 +- .../server/type/TypeResolver.java | 421 +++--------------- .../server/type/TypeReflectHelperTest.java | 21 - 3 files changed, 75 insertions(+), 391 deletions(-) diff --git a/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java b/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java index 95cc0529f..a69dcd75d 100644 --- a/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java +++ b/src/main/java/io/ebeaninternal/server/type/TypeReflectHelper.java @@ -6,13 +6,7 @@ import java.lang.reflect.Type; public class TypeReflectHelper { public static Class[] getParams(Class cls, Class matchRawType) { - - Type[] types = getParamType(cls, matchRawType); - Class[] result = new Class[types.length]; - for (int i = 0; i < result.length; i++) { - result[i] = getClass(types[i]); - } - return result; + return TypeResolver.resolveRawArgs(matchRawType, cls); } public static Class getClass(Type type) { @@ -26,20 +20,4 @@ public class TypeReflectHelper { return (Class) type; } } - - private static Type[] getParamType(Class cls, Class matchRawType) { - - Type[] gis = cls.getGenericInterfaces(); - for (Type type : gis) { - if (type instanceof ParameterizedType) { - ParameterizedType paramType = (ParameterizedType) type; - Type rawType = paramType.getRawType(); - if (rawType.equals(matchRawType)) { - - return paramType.getActualTypeArguments(); - } - } - } - return null; - } } diff --git a/src/main/java/io/ebeaninternal/server/type/TypeResolver.java b/src/main/java/io/ebeaninternal/server/type/TypeResolver.java index 46b91a00a..2fecd6a4b 100644 --- a/src/main/java/io/ebeaninternal/server/type/TypeResolver.java +++ b/src/main/java/io/ebeaninternal/server/type/TypeResolver.java @@ -1,10 +1,6 @@ package io.ebeaninternal.server.type; -//import java.lang.ref.Reference; -//import java.lang.ref.WeakReference; -//import java.lang.reflect.AccessibleObject; - import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; @@ -13,87 +9,16 @@ import java.lang.reflect.TypeVariable; import java.util.HashMap; import java.util.Map; -//import java.lang.reflect.Constructor; -//import java.lang.reflect.Field; -//import java.lang.reflect.Member; -//import java.lang.reflect.Method; -//import java.lang.reflect.Modifier; -//import java.security.AccessController; -//import java.security.PrivilegedExceptionAction; -//import java.util.Arrays; -//import java.util.Collections; -//import java.util.WeakHashMap; - -//import sun.misc.Unsafe; - -public class TypeResolver { - - /** Cache of type variable/argument pairs */ -// private static final Map, Reference, Type>>> TYPE_VARIABLE_CACHE = Collections -// .synchronizedMap(new WeakHashMap, Reference, Type>>>()); -// private static volatile boolean CACHE_ENABLED = true; -// private static boolean RESOLVES_LAMBDAS; -// private static Method GET_CONSTANT_POOL; -// private static Method GET_CONSTANT_POOL_SIZE; -// private static Method GET_CONSTANT_POOL_METHOD_AT; -// private static final Map OBJECT_METHODS = new HashMap(); -// private static final Map, Class> PRIMITIVE_WRAPPERS; -// private static final Double JAVA_VERSION; -// -// static { -// JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version", "0")); -// -// try { -//// Unsafe unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction() { -//// @Override -//// public Unsafe run() throws Exception { -//// final Field f = Unsafe.class.getDeclaredField("theUnsafe"); -//// f.setAccessible(true); -//// -//// return (Unsafe) f.get(null); -//// } -//// }); -// -//// GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool"); -//// String constantPoolName = JAVA_VERSION < 9 ? "sun.reflect.ConstantPool" : "jdk.internal.reflect.ConstantPool"; -//// Class constantPoolClass = Class.forName(constantPoolName); -//// GET_CONSTANT_POOL_SIZE = constantPoolClass.getDeclaredMethod("getSize"); -//// GET_CONSTANT_POOL_METHOD_AT = constantPoolClass.getDeclaredMethod("getMethodAt", int.class); -// -// // setting the methods as accessible -//// Field overrideField = AccessibleObject.class.getDeclaredField("override"); -//// long overrideFieldOffset = unsafe.objectFieldOffset(overrideField); -//// unsafe.putBoolean(GET_CONSTANT_POOL, overrideFieldOffset, true); -//// unsafe.putBoolean(GET_CONSTANT_POOL_SIZE, overrideFieldOffset, true); -//// unsafe.putBoolean(GET_CONSTANT_POOL_METHOD_AT, overrideFieldOffset, true); -// -// // additional checks - make sure we get a result when invoking the Class::getConstantPool and -// // ConstantPool::getSize on a class -//// Object constantPool = GET_CONSTANT_POOL.invoke(Object.class); -//// GET_CONSTANT_POOL_SIZE.invoke(constantPool); -// -//// for (Method method : Object.class.getDeclaredMethods()) -//// OBJECT_METHODS.put(method.getName(), method); -// -//// RESOLVES_LAMBDAS = true; -// } catch (Exception ignore) { -// } -// -//// Map, Class> types = new HashMap, Class>(); -//// types.put(boolean.class, Boolean.class); -//// types.put(byte.class, Byte.class); -//// types.put(char.class, Character.class); -//// types.put(double.class, Double.class); -//// types.put(float.class, Float.class); -//// types.put(int.class, Integer.class); -//// types.put(long.class, Long.class); -//// types.put(short.class, Short.class); -//// types.put(void.class, Void.class); -//// PRIMITIVE_WRAPPERS = Collections.unmodifiableMap(types); -// } +/** + * This class is a modified version of TypeResolver from https://github.com/jhalterman/typetools + * which is Apache 2 license. + * + * It is a cut down version removing the lambda support and related sun.misc.Unsafe use etc. + */ +class TypeResolver { /** An unknown type. */ - public static final class Unknown { + private static final class Unknown { private Unknown() { } } @@ -101,56 +26,6 @@ public class TypeResolver { private TypeResolver() { } -// /** -// * Enables the internal caching of resolved TypeVariables. -// */ -// public static void enableCache() { -// CACHE_ENABLED = true; -// } -// -// /** -// * Disables the internal caching of resolved TypeVariables. -// */ -// public static void disableCache() { -// TYPE_VARIABLE_CACHE.clear(); -// CACHE_ENABLED = false; -// } - - /** - * Returns the raw class representing the argument for the {@code type} using type variable information from the - * {@code subType}. If no arguments can be resolved then {@code Unknown.class} is returned. - * - * @param type to resolve argument for - * @param subType to extract type variable information from - * @return argument for {@code type} else {@link Unknown}.class if no type arguments are declared - * @throws IllegalArgumentException if more or less than one argument is resolved for the {@code type} - */ - public static Class resolveRawArgument(Class type, Class subType) { - return resolveRawArgument(resolveGenericType(type, subType), subType); - } - - /** - * Returns the raw class representing the argument for the {@code genericType} using type variable information from - * the {@code subType}. If {@code genericType} is an instance of class, then {@code genericType} is returned. If no - * arguments can be resolved then {@code Unknown.class} is returned. - * - * @param genericType to resolve argument for - * @param subType to extract type variable information from - * @return argument for {@code genericType} else {@link Unknown}.class if no type arguments are declared - * @throws IllegalArgumentException if more or less than one argument is resolved for the {@code genericType} - */ - public static Class resolveRawArgument(Type genericType, Class subType) { - Class[] arguments = resolveRawArguments(genericType, subType); - if (arguments == null) - return Unknown.class; - - if (arguments.length != 1) - throw new IllegalArgumentException( - "Expected 1 argument for generic type " + genericType + " but found " + arguments.length); - - return arguments[0]; - } - /** * Returns an array of raw classes representing arguments for the {@code type} using type variable information from * the {@code subType}. Arguments for {@code type} that cannot be resolved are returned as {@code Unknown.class}. If @@ -161,7 +36,7 @@ public class TypeResolver { * @return array of raw classes representing arguments for the {@code type} else {@code null} if no type arguments are * declared */ - public static Class[] resolveRawArgs(Class type, Class subType) { + static Class[] resolveRawArgs(Class type, Class subType) { return resolveRawArguments(resolveGenericType(type, subType), subType); } @@ -175,34 +50,27 @@ public class TypeResolver { * @return array of raw classes representing arguments for the {@code genericType} else {@code null} if no type * arguments are declared */ - public static Class[] resolveRawArguments(Type genericType, Class subType) { + private static Class[] resolveRawArguments(Type genericType, Class subType) { Class[] result = null; - Class functionalInterface = null; - -// // Handle lambdas -// if (RESOLVES_LAMBDAS && subType.isSynthetic()) { -// Class fi = genericType instanceof ParameterizedType -// && ((ParameterizedType) genericType).getRawType() instanceof Class -// ? (Class) ((ParameterizedType) genericType).getRawType() -// : genericType instanceof Class ? (Class) genericType : null; -// if (fi != null && fi.isInterface()) -// functionalInterface = fi; -// } if (genericType instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) genericType; Type[] arguments = paramType.getActualTypeArguments(); result = new Class[arguments.length]; - for (int i = 0; i < arguments.length; i++) - result[i] = resolveRawClass(arguments[i], subType, functionalInterface); + for (int i = 0; i < arguments.length; i++) { + result[i] = resolveRawClass(arguments[i], subType); + } + } else if (genericType instanceof TypeVariable) { result = new Class[1]; - result[0] = resolveRawClass(genericType, subType, functionalInterface); + result[0] = resolveRawClass(genericType, subType); + } else if (genericType instanceof Class) { TypeVariable[] typeParams = ((Class) genericType).getTypeParameters(); result = new Class[typeParams.length]; - for (int i = 0; i < typeParams.length; i++) - result[i] = resolveRawClass(typeParams[i], subType, functionalInterface); + for (int i = 0; i < typeParams.length; i++) { + result[i] = resolveRawClass(typeParams[i], subType); + } } return result; @@ -216,15 +84,17 @@ public class TypeResolver { * @param subType to extract type variable information from * @return generic {@code type} else {@code null} if it cannot be resolved */ - public static Type resolveGenericType(Class type, Type subType) { + private static Type resolveGenericType(Class type, Type subType) { Class rawType; - if (subType instanceof ParameterizedType) + if (subType instanceof ParameterizedType) { rawType = (Class) ((ParameterizedType) subType).getRawType(); - else + } else { rawType = (Class) subType; + } - if (type.equals(rawType)) + if (type.equals(rawType)) { return subType; + } Type result; if (type.isInterface()) { @@ -235,83 +105,65 @@ public class TypeResolver { } Type superClass = rawType.getGenericSuperclass(); - if (superClass != null && !superClass.equals(Object.class)) - if ((result = resolveGenericType(type, superClass)) != null) + if (superClass != null && !superClass.equals(Object.class)) { + if ((result = resolveGenericType(type, superClass)) != null) { return result; + } + } return null; } - /** - * Resolves the raw class for the {@code genericType}, using the type variable information from the {@code subType} - * else {@link Unknown} if the raw class cannot be resolved. - * - * @param genericType to resolve raw class for - * @param subType to extract type variable information from - * @return raw class for the {@code genericType} else {@link Unknown} if it cannot be resolved - */ - public static Class resolveRawClass(Type genericType, Class subType) { - return resolveRawClass(genericType, subType, null); - } - - private static Class resolveRawClass(Type genericType, Class subType, Class functionalInterface) { + private static Class resolveRawClass(Type genericType, Class subType) { if (genericType instanceof Class) { return (Class) genericType; + } else if (genericType instanceof ParameterizedType) { - return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType, functionalInterface); + return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType); + } else if (genericType instanceof GenericArrayType) { GenericArrayType arrayType = (GenericArrayType) genericType; - Class component = resolveRawClass(arrayType.getGenericComponentType(), subType, functionalInterface); + Class component = resolveRawClass(arrayType.getGenericComponentType(), subType); return Array.newInstance(component, 0).getClass(); + } else if (genericType instanceof TypeVariable) { TypeVariable variable = (TypeVariable) genericType; - genericType = getTypeVariableMap(subType, functionalInterface).get(variable); + genericType = getTypeVariableMap(subType).get(variable); genericType = genericType == null ? resolveBound(variable) - : resolveRawClass(genericType, subType, functionalInterface); + : resolveRawClass(genericType, subType); } return genericType instanceof Class ? (Class) genericType : Unknown.class; } - private static Map, Type> getTypeVariableMap(final Class targetType, Class functionalInterface) { + private static Map, Type> getTypeVariableMap(final Class targetType) { - //Reference, Type>> ref = TYPE_VARIABLE_CACHE.get(targetType); - Map, Type> map = null;//ref != null ? ref.get() : null; + Map, Type> map = new HashMap<>(); - if (map == null) { - map = new HashMap, Type>(); + // Populate interfaces + populateSuperTypeArgs(targetType.getGenericInterfaces(), map); -// // Populate lambdas -// if (functionalInterface != null) -// populateLambdaArgs(functionalInterface, targetType, map); - - // Populate interfaces - populateSuperTypeArgs(targetType.getGenericInterfaces(), map, functionalInterface != null); - - // Populate super classes and interfaces - Type genericType = targetType.getGenericSuperclass(); - Class type = targetType.getSuperclass(); - while (type != null && !Object.class.equals(type)) { - if (genericType instanceof ParameterizedType) - populateTypeArgs((ParameterizedType) genericType, map, false); - populateSuperTypeArgs(type.getGenericInterfaces(), map, false); - - genericType = type.getGenericSuperclass(); - type = type.getSuperclass(); + // Populate super classes and interfaces + Type genericType = targetType.getGenericSuperclass(); + Class type = targetType.getSuperclass(); + while (type != null && !Object.class.equals(type)) { + if (genericType instanceof ParameterizedType) { + populateTypeArgs((ParameterizedType) genericType, map); } + populateSuperTypeArgs(type.getGenericInterfaces(), map); - // Populate enclosing classes - type = targetType; - while (type.isMemberClass()) { - genericType = type.getGenericSuperclass(); - if (genericType instanceof ParameterizedType) - populateTypeArgs((ParameterizedType) genericType, map, functionalInterface != null); + genericType = type.getGenericSuperclass(); + type = type.getSuperclass(); + } - type = type.getEnclosingClass(); + // Populate enclosing classes + type = targetType; + while (type.isMemberClass()) { + genericType = type.getGenericSuperclass(); + if (genericType instanceof ParameterizedType) { + populateTypeArgs((ParameterizedType) genericType, map); } - -// if (CACHE_ENABLED) -// TYPE_VARIABLE_CACHE.put(targetType, new WeakReference, Type>>(map)); + type = type.getEnclosingClass(); } return map; @@ -320,20 +172,18 @@ public class TypeResolver { /** * Populates the {@code map} with with variable/argument pairs for the given {@code types}. */ - private static void populateSuperTypeArgs(final Type[] types, final Map, Type> map, - boolean depthFirst) { + private static void populateSuperTypeArgs(final Type[] types, final Map, Type> map) { + for (Type type : types) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; - if (!depthFirst) - populateTypeArgs(parameterizedType, map, depthFirst); + populateTypeArgs(parameterizedType, map); Type rawType = parameterizedType.getRawType(); - if (rawType instanceof Class) - populateSuperTypeArgs(((Class) rawType).getGenericInterfaces(), map, depthFirst); - if (depthFirst) - populateTypeArgs(parameterizedType, map, depthFirst); + if (rawType instanceof Class) { + populateSuperTypeArgs(((Class) rawType).getGenericInterfaces(), map); + } } else if (type instanceof Class) { - populateSuperTypeArgs(((Class) type).getGenericInterfaces(), map, depthFirst); + populateSuperTypeArgs(((Class) type).getGenericInterfaces(), map); } } } @@ -341,15 +191,16 @@ public class TypeResolver { /** * Populates the {@code map} with variable/argument pairs for the given {@code type}. */ - private static void populateTypeArgs(ParameterizedType type, Map, Type> map, boolean depthFirst) { + private static void populateTypeArgs(ParameterizedType type, Map, Type> map) { if (type.getRawType() instanceof Class) { TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters(); Type[] typeArguments = type.getActualTypeArguments(); if (type.getOwnerType() != null) { Type owner = type.getOwnerType(); - if (owner instanceof ParameterizedType) - populateTypeArgs((ParameterizedType) owner, map, depthFirst); + if (owner instanceof ParameterizedType) { + populateTypeArgs((ParameterizedType) owner, map); + } } for (int i = 0; i < typeArguments.length; i++) { @@ -364,14 +215,6 @@ public class TypeResolver { map.put(variable, typeArgument); } else if (typeArgument instanceof TypeVariable) { TypeVariable typeVariableArgument = (TypeVariable) typeArgument; - if (depthFirst) { - Type existingType = map.get(variable); - if (existingType != null) { - map.put(typeVariableArgument, existingType); - continue; - } - } - Type resolvedType = map.get(typeVariableArgument); if (resolvedType == null) resolvedType = resolveBound(typeVariableArgument); @@ -384,134 +227,18 @@ public class TypeResolver { /** * Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved. */ - public static Type resolveBound(TypeVariable typeVariable) { + private static Type resolveBound(TypeVariable typeVariable) { Type[] bounds = typeVariable.getBounds(); - if (bounds.length == 0) + if (bounds.length == 0) { return Unknown.class; + } Type bound = bounds[0]; - if (bound instanceof TypeVariable) + if (bound instanceof TypeVariable) { bound = resolveBound((TypeVariable) bound); + } return bound == Object.class ? Unknown.class : bound; } -// /** -// * Populates the {@code map} with variable/argument pairs for the {@code functionalInterface}. -// */ -// private static void populateLambdaArgs(Class functionalInterface, final Class lambdaType, -// Map, Type> map) { -// if (RESOLVES_LAMBDAS) { -// // Find SAM -// for (Method m : functionalInterface.getMethods()) { -// if (!isDefaultMethod(m) && !Modifier.isStatic(m.getModifiers()) && !m.isBridge()) { -// // Skip methods that override Object.class -// Method objectMethod = OBJECT_METHODS.get(m.getName()); -// if (objectMethod != null && Arrays.equals(m.getTypeParameters(), objectMethod.getTypeParameters())) -// continue; -// -// // Get functional interface's type params -// Type returnTypeVar = m.getGenericReturnType(); -// Type[] paramTypeVars = m.getGenericParameterTypes(); -// -// Member member = getMemberRef(lambdaType); -// if (member == null) -// return; -// -// // Populate return type argument -// if (returnTypeVar instanceof TypeVariable) { -// Class returnType = member instanceof Method ? ((Method) member).getReturnType() -// : ((Constructor) member).getDeclaringClass(); -// returnType = wrapPrimitives(returnType); -// if (!returnType.equals(Void.class)) -// map.put((TypeVariable) returnTypeVar, returnType); -// } -// -// Class[] arguments = member instanceof Method ? ((Method) member).getParameterTypes() -// : ((Constructor) member).getParameterTypes(); -// -// // Populate object type from arbitrary object method reference -// int paramOffset = 0; -// if (paramTypeVars.length > 0 && paramTypeVars[0] instanceof TypeVariable -// && paramTypeVars.length == arguments.length + 1) { -// Class instanceType = member.getDeclaringClass(); -// map.put((TypeVariable) paramTypeVars[0], instanceType); -// paramOffset = 1; -// } -// -// // Handle additional arguments that are captured from the lambda's enclosing scope -// int argOffset = 0; -// if (paramTypeVars.length < arguments.length) { -// argOffset = arguments.length - paramTypeVars.length; -// } -// -// // Populate type arguments -// for (int i = 0; i + argOffset < arguments.length; i++) { -// if (paramTypeVars[i] instanceof TypeVariable) -// map.put((TypeVariable) paramTypeVars[i + paramOffset], wrapPrimitives(arguments[i + argOffset])); -// } -// -// return; -// } -// } -// } -// } -// -// private static boolean isDefaultMethod(Method m) { -// return JAVA_VERSION >= 1.8 && m.isDefault(); -// } -// -// private static Member getMemberRef(Class type) { -// Object constantPool; -// try { -// constantPool = GET_CONSTANT_POOL.invoke(type); -// } catch (Exception ignore) { -// return null; -// } -// -// Member result = null; -// for (int i = getConstantPoolSize(constantPool) - 1; i >= 0; i--) { -// Member member = getConstantPoolMethodAt(constantPool, i); -// // Skip SerializedLambda constructors and members of the "type" class -// if (member == null -// || (member instanceof Constructor -// && member.getDeclaringClass().getName().equals("java.lang.invoke.SerializedLambda")) -// || member.getDeclaringClass().isAssignableFrom(type)) -// continue; -// -// result = member; -// -// // Return if not valueOf method -// if (!(member instanceof Method) || !isAutoBoxingMethod((Method) member)) -// break; -// } -// -// return result; -// } -// -// private static boolean isAutoBoxingMethod(Method method) { -// Class[] parameters = method.getParameterTypes(); -// return method.getName().equals("valueOf") && parameters.length == 1 && parameters[0].isPrimitive() -// && wrapPrimitives(parameters[0]).equals(method.getDeclaringClass()); -// } -// -// private static Class wrapPrimitives(Class clazz) { -// return clazz.isPrimitive() ? PRIMITIVE_WRAPPERS.get(clazz) : clazz; -// } -// -// private static int getConstantPoolSize(Object constantPool) { -// try { -// return (Integer) GET_CONSTANT_POOL_SIZE.invoke(constantPool); -// } catch (Exception ignore) { -// return 0; -// } -// } -// -// private static Member getConstantPoolMethodAt(Object constantPool, int i) { -// try { -// return (Member) GET_CONSTANT_POOL_METHOD_AT.invoke(constantPool, i); -// } catch (Exception ignore) { -// return null; -// } -// } } diff --git a/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java b/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java index 2197ce6d2..fe815617d 100644 --- a/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java +++ b/src/test/java/io/ebeaninternal/server/type/TypeReflectHelperTest.java @@ -20,57 +20,36 @@ public class TypeReflectHelperTest { @Test public void getParams_MoneyTypeConverter() { - Class[] params0 = TypeResolver.resolveRawArgs(AttributeConverter.class, MoneyTypeConverter.class); - Class[] params = TypeReflectHelper.getParams(MoneyTypeConverter.class, AttributeConverter.class); assertThat(params.length).isEqualTo(2); assertThat(params[0]).isEqualTo(Money.class); assertThat(params[1]).isEqualTo(BigDecimal.class); - - assertThat(params0[0]).isEqualTo(Money.class); - assertThat(params0[1]).isEqualTo(BigDecimal.class); } @Test public void getParams_OidTypeConverter() { - Class[] params0 = TypeResolver.resolveRawArgs(ScalarTypeConverter.class, OidTypeConverter.class); - Class[] params = TypeReflectHelper.getParams(OidTypeConverter.class, ScalarTypeConverter.class); assertThat(params.length).isEqualTo(2); assertThat(params[0]).isEqualTo(Oid.class); assertThat(params[1]).isEqualTo(Long.class); - - assertThat(params0[0]).isEqualTo(Oid.class); - assertThat(params0[1]).isEqualTo(Long.class); } @Test public void getParams_SysTimeConverter() { - Class[] params0 = TypeResolver.resolveRawArgs(ScalarTypeConverter.class, SysTimeConverter.class); - Class[] params = TypeReflectHelper.getParams(SysTimeConverter.class, ScalarTypeConverter.class); assertThat(params.length).isEqualTo(2); assertThat(params[0]).isEqualTo(SysTime.class); assertThat(params[1]).isEqualTo(Timestamp.class); - - assertThat(params0[0]).isEqualTo(SysTime.class); - assertThat(params0[1]).isEqualTo(Timestamp.class); } @Test public void getParams_RichTextConverter() { - Class[] params0 = TypeResolver.resolveRawArgs(ScalarTypeConverter.class, RichTextConverter.class); - assertThat(params0.length).isEqualTo(2); - assertThat(params0[0]).isEqualTo(RichText.class); - assertThat(params0[1]).isEqualTo(byte[].class); - - // failing test here with NPE Class[] params = TypeReflectHelper.getParams(RichTextConverter.class, ScalarTypeConverter.class); assertThat(params.length).isEqualTo(2);