Skip to content

Commit

Permalink
feature: CtTypeParamer#getTypeErasure, getTypeAdaptedTo
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Mar 12, 2017
1 parent b715c39 commit bb7752f
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/main/java/spoon/reflect/declaration/CtTypeParameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ public interface CtTypeParameter extends CtType<Object> {
@DerivedProperty
CtTypeParameterReference getReference();

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

/**
* @param targetType - the scope where this type parameter is needed
* @return type reference adapted from origin scope to scope of `targetType`
*/
CtTypeReference<?> getTypeAdaptedTo(CtType<?> targetType);

// override the return type
@Override
CtTypeParameter clone();
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/spoon/reflect/factory/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ public CtTypeParameterReference createReference(CtTypeParameter type) {
ref.addAnnotation(ctAnnotation.clone());
}
ref.setSimpleName(type.getSimpleName());
//TypeParameter reference without parent is unusable. It lost information about typed of parameter
ref.setParent(type);
return ref;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
*/
package spoon.support.reflect.declaration;

import spoon.SpoonException;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtFormalTypeDeclarer;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtPackage;
Expand Down Expand Up @@ -237,6 +240,113 @@ public boolean isSubtypeOf(CtTypeReference<?> type) {
return getReference().isSubtypeOf(type);
}

@Override
public CtTypeReference<?> getTypeErasure() {
CtTypeReference<?> boundType = getSuperclass();
if (boundType == null) {
return getFactory().Type().OBJECT;
}
CtTypeReference<?> typeErasure = getReferenceInScope(boundType, getDeclaringType());
if (typeErasure instanceof CtTypeParameterReference) {
CtTypeParameterReference typeRef = (CtTypeParameterReference) typeErasure;
return ((CtTypeParameter) typeRef.getTypeDeclaration()).getTypeErasure();
}
return typeErasure;
}

@Override
public CtTypeReference<?> getTypeAdaptedTo(CtType<?> targetType) {
CtFormalTypeDeclarer scope = getParent(CtFormalTypeDeclarer.class);
if (scope == null) {
throw new SpoonException("Can't adapt type param to another scope if it has no current scope (no parent formal type declarer).");
}
if (targetType == scope) {
//type parameter is declared in required scope. Just return it.
return getReference();
}
if (scope instanceof CtType) {
return getReferenceInTypeScope(this, (CtType<?>) targetType, (CtType<?>) scope);
}
//cannot adapt type parameters of methods or constructors. It is only possible to decide whether two type parameters of two methods are same.
throw new SpoonException("Cannot adapt type parameter defined in scope of method or constructor.");
}

/**
* Converts generic parameters from namespace of some super class to namespace of `targetScope`
* @param type - the reference to to be converted parameter (generic) type or normal type.
* @param targetScope - the type which represents target scope of the generic parameter.
* @return a `type` converted into scope of `targetType`.
*/
private static CtTypeReference<?> getReferenceInScope(CtTypeReference<?> type, CtType<?> targetScope) {
if (type instanceof CtTypeParameterReference) {
CtTypeParameterReference typeParamRef = (CtTypeParameterReference) type;
CtTypeParameter typeParam = typeParamRef.getDeclaration();
CtFormalTypeDeclarer scope = typeParam.getParent(CtFormalTypeDeclarer.class);
if (scope == null) {
throw new SpoonException("Can't conver scope of type param if it has no parent formal type declarer.");
}
if (targetScope == scope) {
//type parameter is declared in required scope. Just return it.
return typeParamRef;
}
if (scope instanceof CtType) {
//this type reference is declared in scope of different type. Compute how it is declared in `targetType`
return getReferenceInTypeScope(typeParam, targetScope, (CtType<?>) scope);
} else if (scope instanceof CtMethod) {

} else if (scope instanceof CtConstructor) {

} else {
throw new SpoonException("Unexpected scope of type parameter of type: " + scope.getClass().getName());
}
}
return type;
}

/**
* Converts generic `typeParam` in scope of type `sourceScope` to namespace of `targetScope`
* @param typeParam the to be converted type parameter
* @param targetScope - type of target scope, which should be a sub type of source scope
* @param sourceScope - type of source scope
* @return reference to type converted into targetScope
*/
private static CtTypeReference<?> getReferenceInTypeScope(CtTypeParameter typeParam, CtType<?> targetScope, CtType<?> sourceScope) {
if (targetScope == sourceScope) {
return typeParam.getReference();
}
CtTypeReference<?> superTypeRef = targetScope.getSuperclass();
if (superTypeRef == null) {
return null;
}
CtType<?> superType = superTypeRef.getTypeDeclaration();
if (superType == null) {
return null;
}
CtTypeReference<?> superTypeParamRef = getReferenceInTypeScope(typeParam, superType, sourceScope);
if (superTypeParamRef instanceof CtTypeParameterReference) {
CtTypeParameter superTypeParam = ((CtTypeParameterReference) superTypeParamRef).getDeclaration();
//convert typeParam of super type to typeParam of `targetType`
int paramTypeIdxInSuperType = superType.getFormalCtTypeParameters().indexOf(superTypeParam);
if (paramTypeIdxInSuperType == -1) {
throw new SpoonException("Type parameter not found");
}
List<CtTypeReference<?>> actTRs = superTypeRef.getActualTypeArguments();
if (paramTypeIdxInSuperType < actTRs.size()) {
//the actual type argument is defined in `targetType`
return actTRs.get(paramTypeIdxInSuperType);
}
//the super type actual type arguments are not defined in sub class
if (typeParam.getSuperclass() == null) {
//The super type param has no bound. So it is a `Object`
return typeParam.getFactory().Code().createCtTypeReference(Object.class);
}
//The super type param is bound. So it is a bounding type
return getReferenceInScope(typeParam.getSuperclass(), targetScope);
}
//else it is normal type reference.
return superTypeParamRef;
}

@Override
@UnsettableProperty
public <M, C extends CtType<Object>> C addMethod(CtMethod<M> method) {
Expand Down
111 changes: 111 additions & 0 deletions src/test/java/spoon/test/ctType/CtTypeParameterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package spoon.test.ctType;

import java.lang.reflect.Executable;
import java.lang.reflect.Field;
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.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 {
CtClass<?> ctModel = (CtClass<?>) ModelUtils.buildClass(ErasureModelA.class);
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);
}
} 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 = declClass.getDeclaredMethods()[0];
}
Class<?> paramType = declExec.getParameterTypes()[paramIdx];
assertEquals("TypeErasure of executable param "+getTypeParamIdentification(typeParam), paramType.getName(), typeParam.getTypeErasure().getQualifiedName());
}

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();
}

@Test
public void testTypeAdapted() throws Exception {
CtClass<?> ctModel = (CtClass<?>) ModelUtils.buildClass(ErasureModelA.class);
CtTypeParameter tpA = ctModel.getFormalCtTypeParameters().get(0);
CtTypeParameter tpB = ctModel.getFormalCtTypeParameters().get(1);
CtTypeParameter tpC = ctModel.getFormalCtTypeParameters().get(2);
CtTypeParameter tpD = ctModel.getFormalCtTypeParameters().get(3);

CtClass<?> ctModelB = ctModel.filterChildren(new NameFilter<>("ModelB")).first();
assertEquals("A2", tpA.getTypeAdaptedTo(ctModelB).getQualifiedName());
assertEquals("B2", tpB.getTypeAdaptedTo(ctModelB).getQualifiedName());
assertEquals("C2", tpC.getTypeAdaptedTo(ctModelB).getQualifiedName());
assertEquals("D2", tpD.getTypeAdaptedTo(ctModelB).getQualifiedName());

CtClass<?> ctModelC = ctModel.filterChildren(new NameFilter<>("ModelC")).first();
assertEquals("java.lang.Integer", tpA.getTypeAdaptedTo(ctModelC).getQualifiedName());
assertEquals("java.lang.RuntimeException", tpB.getTypeAdaptedTo(ctModelC).getQualifiedName());
assertEquals("java.lang.IllegalArgumentException", tpC.getTypeAdaptedTo(ctModelC).getQualifiedName());
assertEquals("spoon.test.ctType.testclasses.ErasureModelA$ModelC", tpD.getTypeAdaptedTo(ctModelC).getQualifiedName());
}
}
40 changes: 40 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,40 @@
package spoon.test.ctType.testclasses;

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

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> void method(I paramI, J paramJ, D paramD) {
}

static class ModelB<A2,B2 extends Exception, C2 extends B2, D2 extends ErasureModelA<A2,B2,C2,D2>> 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> void method(I paramI, J paramJ, D2 paramD2) {
}
}

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

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 bb7752f

Please sign in to comment.