Skip to content

Commit

Permalink
feature: CtTypeParameter(Reference)#getTypeErasure()
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Apr 2, 2017
1 parent 43c7e52 commit 8bbef27
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/main/java/spoon/reflect/declaration/CtTypeParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ public interface CtTypeParameter extends CtType<Object> {
@DerivedProperty
CtFormalTypeDeclarer getTypeParameterDeclarer();

/**
* @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();

// override the return type
@Override
CtTypeParameter clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,20 @@ public interface CtTypeParameterReference extends CtTypeReference<Object> {
@DerivedProperty
CtTypeParameter getDeclaration();

/**
* @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();

// overriding the return type
@Override
CtTypeParameterReference clone();

@Override
@UnsettableProperty
<T extends CtActualTypeContainer> T setActualTypeArguments(List<? extends CtTypeReference<?>> actualTypeArguments);

}
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,24 @@ public boolean isSubtypeOf(CtTypeReference<?> type) {
return getReference().isSubtypeOf(type);
}

@Override
public CtTypeReference<?> getTypeErasure() {
CtTypeReference<?> boundType = getBound(this);
if (boundType instanceof CtTypeParameterReference) {
CtTypeParameterReference typeRef = (CtTypeParameterReference) boundType;
return typeRef.getDeclaration().getTypeErasure();
}
return boundType;
}

private static CtTypeReference<?> getBound(CtTypeParameter typeParam) {
CtTypeReference<?> bound = typeParam.getSuperclass();
if (bound == null) {
bound = typeParam.getFactory().Type().OBJECT;
}
return bound;
}

@Override
@UnsettableProperty
public <M, C extends CtType<Object>> C addMethod(CtMethod<M> method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ public CtType<Object> getTypeDeclaration() {
return getDeclaration();
}

@Override
public CtTypeReference<?> getTypeErasure() {
return getDeclaration().getTypeErasure();
}

@Override
public CtTypeParameterReference clone() {
return (CtTypeParameterReference) super.clone();
Expand Down
129 changes: 129 additions & 0 deletions src/test/java/spoon/test/ctType/CtTypeParameterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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<CtTypeParameter> 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().getQualifiedName());
}

private void checkParameterErasureOfExecutable(CtParameter<?> param, CtExecutable<?> exec) {
CtTypeReference<?> paramTypeRef = param.getType();
if (paramTypeRef instanceof CtTypeParameterReference) {
CtTypeParameterReference typeParamRef = (CtTypeParameterReference) paramTypeRef;
paramTypeRef = typeParamRef.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("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();
}
}
46 changes: 46 additions & 0 deletions src/test/java/spoon/test/ctType/testclasses/ErasureModelA.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package spoon.test.ctType.testclasses;

import java.io.Serializable;
import java.util.List;

public class ErasureModelA<A, B extends Exception, C extends B, D extends List<B>> {

A paramA;
B paramB;
C paramC;
D paramD;

public <I, J extends C> ErasureModelA(I paramI, J paramJ, D paramD) {
}

public <I, J extends C, K extends ErasureModelA<A,B,C,D>&Serializable> void method(I paramI, J paramJ, D paramD, K paramK) {
}

public <I> void wildCardMethod(I paramI, ErasureModelA<? extends I, B, C, D> extendsI) {
}

static class ModelB<A2,B2 extends Exception, C2 extends B2, D2 extends List<B2>> extends ErasureModelA<A2,B2,C2,D2> {
A2 paramA2;
B2 paramB2;
C2 paramC2;
D2 paramD2;

public <I, J extends C2> ModelB(I paramI, J paramJ, D2 paramD2) {
super(paramI, paramJ, paramD2);
}

@Override
public <I, J extends C2, K2 extends ErasureModelA<A2,B2,C2,D2>&Serializable> void method(I paramI, J paramJ, D2 paramD2, K2 paramK2) {
}
}

static class ModelC extends ErasureModelA<Integer, RuntimeException, IllegalArgumentException, List<RuntimeException>> {

public ModelC(Float paramI, IllegalArgumentException paramJ, ModelC paramK) {
super(paramI, paramJ, null);
}

public void method(Float paramI, IllegalArgumentException paramJ, ModelC paramK) {
}
}
}

0 comments on commit 8bbef27

Please sign in to comment.