diff --git a/spock-core/src/main/java/org/spockframework/gentyref/GenericTypeReflector.java b/spock-core/src/main/java/org/spockframework/gentyref/GenericTypeReflector.java index e4a42382af..c2f1dd1832 100644 --- a/spock-core/src/main/java/org/spockframework/gentyref/GenericTypeReflector.java +++ b/spock-core/src/main/java/org/spockframework/gentyref/GenericTypeReflector.java @@ -47,18 +47,40 @@ private static Type mapTypeParameters(Type toMapType, Type typeAndParams) { return erase(toMapType); } else { VarMap varMap = new VarMap(); - Type handlingTypeAndParams = typeAndParams; - while(handlingTypeAndParams instanceof ParameterizedType) { - ParameterizedType pType = (ParameterizedType)handlingTypeAndParams; - Class clazz = (Class)pType.getRawType(); // getRawType should always be Class - varMap.addAll(clazz.getTypeParameters(), pType.getActualTypeArguments()); - handlingTypeAndParams = pType.getOwnerType(); - } - return varMap.map(toMapType); - } - } + collectVarMapEntriesForTypeVariable(toMapType, varMap); + collectVarMapEntriesForType(typeAndParams, varMap); + return varMap.map(toMapType); + } + } - /** + /** + * This will collect the {@link TypeVariable} bounds of toMapType into the varMap, + * if the toMapType is a {@link TypeVariable} and has exactly one type bound ({@link TypeVariable#getBounds()}. + * + * @param toMapType Type possibly containing type arguments + * @param varMap the {@link VarMap} to save resolve variables into. Caution: This will be modified + */ + private static void collectVarMapEntriesForTypeVariable(Type toMapType, VarMap varMap) { + if (toMapType instanceof TypeVariable) { + final TypeVariable t = (TypeVariable) toMapType; + final Type[] bounds = t.getBounds(); + if (bounds.length == 1) { + varMap.add(t, bounds[0]); + } + } + } + + private static void collectVarMapEntriesForType(Type typeAndParams, VarMap varMap) { + Type handlingTypeAndParams = typeAndParams; + while (handlingTypeAndParams instanceof ParameterizedType) { + ParameterizedType pType = (ParameterizedType) handlingTypeAndParams; + Class clazz = (Class) pType.getRawType(); // getRawType should always be Class + varMap.addAll(clazz.getTypeParameters(), pType.getActualTypeArguments()); + handlingTypeAndParams = pType.getOwnerType(); + } + } + + /** * Checks if the given type is a class that is supposed to have type parameters, but doesn't. * In other words, if it's a really raw type. */ diff --git a/spock-specs/src/test/groovy/org/spockframework/gentyref/GenericTypeReflectorSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/gentyref/GenericTypeReflectorSpec.groovy index 0c2ab1b472..20a7ba4814 100644 --- a/spock-specs/src/test/groovy/org/spockframework/gentyref/GenericTypeReflectorSpec.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/gentyref/GenericTypeReflectorSpec.groovy @@ -16,8 +16,14 @@ package org.spockframework.gentyref +import org.spockframework.runtime.model.NodeInfo import spock.lang.Specification +import java.lang.annotation.Annotation +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.Method +import java.lang.reflect.Type + class GenericTypeReflectorSpec extends Specification { def "can get exact parameter types for java.lang.Object method"() { when: @@ -51,4 +57,24 @@ class GenericTypeReflectorSpec extends Specification { notThrown(NullPointerException) type == Object } + + def "getExactReturnType"(Class clazz, String methodName, Type expectedType) { + given: + def method = clazz.getMethods().find { it.name == methodName } + expect: + clazz && methodName && method != null + GenericTypeReflector.getExactReturnType(method, clazz) == expectedType + + where: + clazz | methodName | expectedType + NodeInfo | "getAnnotation" | Annotation + NodeInfo | "getName" | String + NodeInfo | "getReflection" | AnnotatedElement + TestNodeInfo | "getReflection" | Method + NodeInfo | "getParent" | NodeInfo + TestNodeInfo | "getParent" | TestNodeInfo + ArrayList | "get" | Object + } + + private abstract class TestNodeInfo extends NodeInfo {} } diff --git a/spock-specs/src/test/groovy/org/spockframework/mock/MockSpecInfoAnnotationSpec.groovy b/spock-specs/src/test/groovy/org/spockframework/mock/MockSpecInfoAnnotationSpec.groovy new file mode 100644 index 0000000000..1bb50a1577 --- /dev/null +++ b/spock-specs/src/test/groovy/org/spockframework/mock/MockSpecInfoAnnotationSpec.groovy @@ -0,0 +1,34 @@ +package org.spockframework.mock + +import org.spockframework.runtime.model.SpecInfo +import org.spockframework.util.Nullable +import spock.lang.Specification + +import java.lang.annotation.Annotation + +class MockSpecInfoAnnotationSpec extends Specification { + + def "NodeInfo.getAnnotation() shall return valid Stub for Stub Issue #1163"() { + given: + def mockUtil = new MockUtil() + def spec = Stub(SpecInfo) + + when: + def t = spec.getAnnotation(Nullable) + + then: + t instanceof Annotation + mockUtil.isMock(t) + } + + def "NodeInfo.getAnnotation() shall return null for Mock Issue #1163"() { + given: + def spec = Mock(SpecInfo) + + when: + def t = spec.getAnnotation(Nullable) + + then: + t == null + } +}