From ccb80c7a31372b8ea25e20e1405b9d00f6f443ff Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Mon, 3 Apr 2017 19:48:51 +0200 Subject: [PATCH 1/2] feature: CtTypeInformation#getTypeErasure() --- .../declaration/CtTypeInformation.java | 7 + .../reflect/declaration/CtTypeImpl.java | 5 + .../declaration/CtTypeParameterImpl.java | 14 ++ .../CtIntersectionTypeReferenceImpl.java | 8 ++ .../CtTypeParameterReferenceImpl.java | 5 + .../reference/CtTypeReferenceImpl.java | 10 ++ .../test/ctType/CtTypeParameterTest.java | 127 ++++++++++++++++++ .../ctType/testclasses/ErasureModelA.java | 52 +++++++ 8 files changed, 228 insertions(+) create mode 100644 src/test/java/spoon/test/ctType/CtTypeParameterTest.java create mode 100644 src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java diff --git a/src/main/java/spoon/reflect/declaration/CtTypeInformation.java b/src/main/java/spoon/reflect/declaration/CtTypeInformation.java index 9e82f234d20..341398724fe 100644 --- a/src/main/java/spoon/reflect/declaration/CtTypeInformation.java +++ b/src/main/java/spoon/reflect/declaration/CtTypeInformation.java @@ -160,4 +160,11 @@ public interface CtTypeInformation { @DerivedProperty Collection> getAllExecutables(); + /** + * @return type (not generic one), which is used by java compiler to ensure that no new classes are created for parameterized types; + * consequently, generics incur no runtime overhead. + * See https://docs.oracle.com/javase/tutorial/java/generics/erasure.html + */ + @DerivedProperty + CtTypeReference getTypeErasure(); } diff --git a/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java b/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java index 14588555ae3..297b2dd5050 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java @@ -880,6 +880,11 @@ public void accept(CtMethod method) { return Collections.unmodifiableSet(l); } + @Override + public CtTypeReference getTypeErasure() { + return getReference(); + } + boolean isShadow; @Override diff --git a/src/main/java/spoon/support/reflect/declaration/CtTypeParameterImpl.java b/src/main/java/spoon/support/reflect/declaration/CtTypeParameterImpl.java index af54b4c9cf1..decc3995c61 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtTypeParameterImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtTypeParameterImpl.java @@ -248,6 +248,20 @@ public boolean isSubtypeOf(CtTypeReference type) { return getReference().isSubtypeOf(type); } + @Override + public CtTypeReference getTypeErasure() { + CtTypeReference boundType = getBound(this); + return boundType.getTypeErasure(); + } + + private static CtTypeReference getBound(CtTypeParameter typeParam) { + CtTypeReference bound = typeParam.getSuperclass(); + if (bound == null) { + bound = typeParam.getFactory().Type().OBJECT; + } + return bound; + } + @Override @UnsettableProperty public > C addMethod(CtMethod method) { diff --git a/src/main/java/spoon/support/reflect/reference/CtIntersectionTypeReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtIntersectionTypeReferenceImpl.java index adb2a9f6344..2a7fe2ee989 100644 --- a/src/main/java/spoon/support/reflect/reference/CtIntersectionTypeReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtIntersectionTypeReferenceImpl.java @@ -73,6 +73,14 @@ public boolean removeBound(CtTypeReference bound) { return bounds != CtElementImpl.>emptyList() && bounds.remove(bound); } + @Override + public CtTypeReference getTypeErasure() { + if (bounds == null || bounds.isEmpty()) { + return getFactory().Type().OBJECT; + } + return bounds.get(0).getTypeErasure(); + } + @Override public CtIntersectionTypeReference clone() { return (CtIntersectionTypeReference) super.clone(); diff --git a/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java index 8eb41eca8a1..42627c0552c 100644 --- a/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtTypeParameterReferenceImpl.java @@ -199,6 +199,11 @@ public CtType getTypeDeclaration() { return getDeclaration(); } + @Override + public CtTypeReference getTypeErasure() { + return getDeclaration().getTypeErasure(); + } + @Override public CtTypeParameterReference clone() { return (CtTypeParameterReference) super.clone(); diff --git a/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java b/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java index c8acfe3e6ef..2dfad82a0bb 100644 --- a/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java +++ b/src/main/java/spoon/support/reflect/reference/CtTypeReferenceImpl.java @@ -833,4 +833,14 @@ public CtTypeParameter getTypeParameterDeclaration() { private CtTypeParameter findTypeParamDeclarationByPosition(CtFormalTypeDeclarer type, int position) { return type.getFormalCtTypeParameters().get(position); } + + @Override + public CtTypeReference getTypeErasure() { + if (getActualTypeArguments().isEmpty()) { + return this; + } + CtTypeReference erasedRef = clone(); + erasedRef.getActualTypeArguments().clear(); + return erasedRef; + } } diff --git a/src/test/java/spoon/test/ctType/CtTypeParameterTest.java b/src/test/java/spoon/test/ctType/CtTypeParameterTest.java new file mode 100644 index 00000000000..50ebf8395eb --- /dev/null +++ b/src/test/java/spoon/test/ctType/CtTypeParameterTest.java @@ -0,0 +1,127 @@ +package spoon.test.ctType; + +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.Test; + +import spoon.reflect.declaration.CtClass; +import spoon.reflect.declaration.CtConstructor; +import spoon.reflect.declaration.CtExecutable; +import spoon.reflect.declaration.CtFormalTypeDeclarer; +import spoon.reflect.declaration.CtMethod; +import spoon.reflect.declaration.CtParameter; +import spoon.reflect.declaration.CtType; +import spoon.reflect.declaration.CtTypeMember; +import spoon.reflect.declaration.CtTypeParameter; +import spoon.reflect.reference.CtTypeParameterReference; +import spoon.reflect.reference.CtTypeReference; +import spoon.reflect.visitor.filter.NameFilter; +import spoon.test.ctType.testclasses.ErasureModelA; +import spoon.testing.utils.ModelUtils; + +import static org.junit.Assert.*; + +public class CtTypeParameterTest { + + @Test + public void testTypeErasure() throws Exception { + //contract: the erasure type computed by CtParameterType(Reference)#getTypeErasure is same like the type of method parameter made by java compiler + CtClass ctModel = (CtClass) ModelUtils.buildClass(ErasureModelA.class); + //visit all methods of type ctModel + //visit all inner types and their methods recursively `getTypeErasure` returns expected value + //for each formal type parameter or method parameter check if + checkType(ctModel); + } + + private void checkType(CtType type) throws NoSuchFieldException, SecurityException { + List l_typeParams = type.getFormalCtTypeParameters(); + for (CtTypeParameter ctTypeParameter : l_typeParams) { + checkTypeParamErasureOfType(ctTypeParameter, type.getActualClass()); + } + + for (CtTypeMember typeMemeber : type.getTypeMembers()) { + if (typeMemeber instanceof CtFormalTypeDeclarer) { + CtFormalTypeDeclarer ftDecl = (CtFormalTypeDeclarer) typeMemeber; + l_typeParams = ftDecl.getFormalCtTypeParameters(); + if (typeMemeber instanceof CtExecutable) { + CtExecutable exec = (CtExecutable) typeMemeber; + for (CtTypeParameter ctTypeParameter : l_typeParams) { + checkTypeParamErasureOfExecutable(ctTypeParameter, exec); + } + for (CtParameter param : exec.getParameters()) { + checkParameterErasureOfExecutable(param, exec); + } + } else if (typeMemeber instanceof CtType) { + CtType nestedType = (CtType) typeMemeber; + checkType(nestedType); + } + } + } + } + + private void checkTypeParamErasureOfType(CtTypeParameter typeParam, Class clazz) throws NoSuchFieldException, SecurityException { + Field field = clazz.getDeclaredField("param"+typeParam.getSimpleName()); + assertEquals("TypeErasure of type param "+getTypeParamIdentification(typeParam), field.getType().getName(), typeParam.getTypeErasure().getQualifiedName()); + } + + private void checkTypeParamErasureOfExecutable(CtTypeParameter typeParam, CtExecutable exec) throws NoSuchFieldException, SecurityException { + CtParameter param = exec.filterChildren(new NameFilter<>("param"+typeParam.getSimpleName())).first(); + assertNotNull("Missing param"+typeParam.getSimpleName() + " in "+ exec.getSignature(), param); + int paramIdx = exec.getParameters().indexOf(param); + Class declClass = exec.getParent(CtType.class).getActualClass(); + Executable declExec; + if (exec instanceof CtConstructor) { + declExec = declClass.getDeclaredConstructors()[0]; + } else { + declExec = getMethodByName(declClass, exec.getSimpleName()); + } + Class paramType = declExec.getParameterTypes()[paramIdx]; + assertEquals("TypeErasure of executable param "+getTypeParamIdentification(typeParam), paramType.getName(), typeParam.getTypeErasure().toString()); + } + + private void checkParameterErasureOfExecutable(CtParameter param, CtExecutable exec) { + CtTypeReference paramTypeRef = param.getType(); + paramTypeRef = paramTypeRef.getTypeErasure(); + int paramIdx = exec.getParameters().indexOf(param); + Class declClass = exec.getParent(CtType.class).getActualClass(); + Executable declExec; + if (exec instanceof CtConstructor) { + declExec = declClass.getDeclaredConstructors()[0]; + } else { + declExec = getMethodByName(declClass, exec.getSimpleName()); + } + Class paramType = declExec.getParameterTypes()[paramIdx]; + assertEquals(0, paramTypeRef.getActualTypeArguments().size()); + assertEquals("TypeErasure of executable "+exec.getSignature()+" parameter "+param.getSimpleName(), paramType.getName(), paramTypeRef.getQualifiedName()); + } + + + private Executable getMethodByName(Class declClass, String simpleName) { + for (Method method : declClass.getDeclaredMethods()) { + if(method.getName().equals(simpleName)) { + return method; + } + } + fail("Method "+simpleName+" not found in "+declClass.getName()); + return null; + } + + private String getTypeParamIdentification(CtTypeParameter typeParam) { + String result = "<"+typeParam.getSimpleName()+">"; + CtFormalTypeDeclarer l_decl = typeParam.getParent(CtFormalTypeDeclarer.class); + if (l_decl instanceof CtType) { + return ((CtType) l_decl).getQualifiedName()+result; + } + if (l_decl instanceof CtExecutable) { + CtExecutable exec = (CtExecutable) l_decl; + if (exec instanceof CtMethod) { + result=exec.getSignature()+result; + } + return exec.getParent(CtType.class).getQualifiedName()+"#"+result; + } + throw new AssertionError(); + } +} diff --git a/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java b/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java new file mode 100644 index 00000000000..b14710a1aa2 --- /dev/null +++ b/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java @@ -0,0 +1,52 @@ +package spoon.test.ctType.testclasses; + +import java.io.Serializable; +import java.util.List; + +public class ErasureModelA> { + + A paramA; + B paramB; + C paramC; + D paramD; + + public ErasureModelA(I paramI, J paramJ, D paramD) { + } + + public void method(I paramI, J paramJ, D paramD) { + } + + public void method2(I paramI, J paramJ, D paramD) { + } + + public &Serializable> void method3(I paramI, J paramJ, D paramD, K paramK) { + } + + public void wildCardMethod(I paramI, ErasureModelA extendsI) { + } + + static class ModelB> extends ErasureModelA { + A2 paramA2; + B2 paramB2; + C2 paramC2; + D2 paramD2; + + public ModelB(I paramI, J paramJ, D2 paramD2) { + super(paramI, paramJ, paramD2); + } + + @Override + public void method(I paramI, J paramJ, D2 paramD2) { + } + } + + static class ModelC extends ErasureModelA> { + + public ModelC(Float paramI, IllegalArgumentException paramJ, ModelC paramK) { + super(paramI, paramJ, null); + } + + public void method(Float paramI, IllegalArgumentException paramJ, ModelC paramK) { + } + } +} From 6ad597407cd45179af8f501d855ed2da937b1474 Mon Sep 17 00:00:00 2001 From: Martin Monperrus Date: Mon, 3 Apr 2017 21:48:51 +0200 Subject: [PATCH 2/2] improve documentation --- .../declaration/CtTypeInformation.java | 3 +- .../test/ctType/CtTypeParameterTest.java | 44 ++++++++++--------- .../ctType/testclasses/ErasureModelA.java | 6 ++- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/main/java/spoon/reflect/declaration/CtTypeInformation.java b/src/main/java/spoon/reflect/declaration/CtTypeInformation.java index 341398724fe..a2cf9a4aac7 100644 --- a/src/main/java/spoon/reflect/declaration/CtTypeInformation.java +++ b/src/main/java/spoon/reflect/declaration/CtTypeInformation.java @@ -161,8 +161,7 @@ public interface CtTypeInformation { Collection> getAllExecutables(); /** - * @return type (not generic one), which is used by java compiler to ensure that no new classes are created for parameterized types; - * consequently, generics incur no runtime overhead. + * @return the type erasure, which is computed by the java compiler to ensure that no new classes are created for parametrized types so that generics incur no runtime overhead. * See https://docs.oracle.com/javase/tutorial/java/generics/erasure.html */ @DerivedProperty diff --git a/src/test/java/spoon/test/ctType/CtTypeParameterTest.java b/src/test/java/spoon/test/ctType/CtTypeParameterTest.java index 50ebf8395eb..2109545643d 100644 --- a/src/test/java/spoon/test/ctType/CtTypeParameterTest.java +++ b/src/test/java/spoon/test/ctType/CtTypeParameterTest.java @@ -28,7 +28,7 @@ public class CtTypeParameterTest { @Test public void testTypeErasure() throws Exception { - //contract: the erasure type computed by CtParameterType(Reference)#getTypeErasure is same like the type of method parameter made by java compiler + //contract: the type erasure computed by getTypeErasure is same as the one computed by the Java compiler CtClass ctModel = (CtClass) ModelUtils.buildClass(ErasureModelA.class); //visit all methods of type ctModel //visit all inner types and their methods recursively `getTypeErasure` returns expected value @@ -37,25 +37,26 @@ public void testTypeErasure() throws Exception { } private void checkType(CtType type) throws NoSuchFieldException, SecurityException { - List l_typeParams = type.getFormalCtTypeParameters(); - for (CtTypeParameter ctTypeParameter : l_typeParams) { + List formalTypeParameters = type.getFormalCtTypeParameters(); + for (CtTypeParameter ctTypeParameter : formalTypeParameters) { checkTypeParamErasureOfType(ctTypeParameter, type.getActualClass()); } - for (CtTypeMember typeMemeber : type.getTypeMembers()) { - if (typeMemeber instanceof CtFormalTypeDeclarer) { - CtFormalTypeDeclarer ftDecl = (CtFormalTypeDeclarer) typeMemeber; - l_typeParams = ftDecl.getFormalCtTypeParameters(); - if (typeMemeber instanceof CtExecutable) { - CtExecutable exec = (CtExecutable) typeMemeber; - for (CtTypeParameter ctTypeParameter : l_typeParams) { - checkTypeParamErasureOfExecutable(ctTypeParameter, exec); + for (CtTypeMember member : type.getTypeMembers()) { + if (member instanceof CtFormalTypeDeclarer) { + CtFormalTypeDeclarer ftDecl = (CtFormalTypeDeclarer) member; + formalTypeParameters = ftDecl.getFormalCtTypeParameters(); + if (member instanceof CtExecutable) { + CtExecutable exec = (CtExecutable) member; + for (CtTypeParameter ctTypeParameter : formalTypeParameters) { + checkTypeParamErasureOfExecutable(ctTypeParameter); } for (CtParameter param : exec.getParameters()) { - checkParameterErasureOfExecutable(param, exec); + checkParameterErasureOfExecutable(param); } - } else if (typeMemeber instanceof CtType) { - CtType nestedType = (CtType) typeMemeber; + } else if (member instanceof CtType) { + CtType nestedType = (CtType) member; + // recursive call for nested type checkType(nestedType); } } @@ -67,7 +68,8 @@ private void checkTypeParamErasureOfType(CtTypeParameter typeParam, Class cla assertEquals("TypeErasure of type param "+getTypeParamIdentification(typeParam), field.getType().getName(), typeParam.getTypeErasure().getQualifiedName()); } - private void checkTypeParamErasureOfExecutable(CtTypeParameter typeParam, CtExecutable exec) throws NoSuchFieldException, SecurityException { + private void checkTypeParamErasureOfExecutable(CtTypeParameter typeParam) throws NoSuchFieldException, SecurityException { + CtExecutable exec = (CtExecutable) typeParam.getParent(); CtParameter param = exec.filterChildren(new NameFilter<>("param"+typeParam.getSimpleName())).first(); assertNotNull("Missing param"+typeParam.getSimpleName() + " in "+ exec.getSignature(), param); int paramIdx = exec.getParameters().indexOf(param); @@ -79,12 +81,13 @@ private void checkTypeParamErasureOfExecutable(CtTypeParameter typeParam, CtExec declExec = getMethodByName(declClass, exec.getSimpleName()); } Class paramType = declExec.getParameterTypes()[paramIdx]; + // contract the type erasure given with Java reflection is the same as the one computed by spoon assertEquals("TypeErasure of executable param "+getTypeParamIdentification(typeParam), paramType.getName(), typeParam.getTypeErasure().toString()); } - private void checkParameterErasureOfExecutable(CtParameter param, CtExecutable exec) { - CtTypeReference paramTypeRef = param.getType(); - paramTypeRef = paramTypeRef.getTypeErasure(); + private void checkParameterErasureOfExecutable(CtParameter param) { + CtExecutable exec = param.getParent(); + CtTypeReference typeErasure = param.getType().getTypeErasure(); int paramIdx = exec.getParameters().indexOf(param); Class declClass = exec.getParent(CtType.class).getActualClass(); Executable declExec; @@ -94,8 +97,9 @@ private void checkParameterErasureOfExecutable(CtParameter param, CtExecutabl declExec = getMethodByName(declClass, exec.getSimpleName()); } Class paramType = declExec.getParameterTypes()[paramIdx]; - assertEquals(0, paramTypeRef.getActualTypeArguments().size()); - assertEquals("TypeErasure of executable "+exec.getSignature()+" parameter "+param.getSimpleName(), paramType.getName(), paramTypeRef.getQualifiedName()); + assertEquals(0, typeErasure.getActualTypeArguments().size()); + // contract the type erasure of the method parameter given with Java reflection is the same as the one computed by spoon + assertEquals("TypeErasure of executable "+exec.getSignature()+" parameter "+param.getSimpleName(), paramType.getName(), typeErasure.getQualifiedName()); } diff --git a/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java b/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java index b14710a1aa2..3a8cdc2bba5 100644 --- a/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java +++ b/src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java @@ -24,7 +24,11 @@ public &Serializable> void meth public void wildCardMethod(I paramI, ErasureModelA extendsI) { } - + + // simple case + public void list(List x, List> y, List z) { + } + static class ModelB> extends ErasureModelA { A2 paramA2; B2 paramB2;