Skip to content

Commit

Permalink
feature CtTypeParameter#isSubtypeOf
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Mar 14, 2017
1 parent 059792f commit 23ace58
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 41 deletions.
9 changes: 9 additions & 0 deletions src/main/java/spoon/reflect/declaration/CtMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
* This element defines a method declaration.
*/
public interface CtMethod<T> extends CtExecutable<T>, CtTypeMember, CtFormalTypeDeclarer, CtShadowable {

/**
* The same signature is the necessary condition for method A overrides method B.
* @param thatMethod - the compared method
* @param canTypeErasure - use true if the type erasure can be used to match signatures
* @return true if this method and `thatMethod` has same signature
*/
boolean isSameSignature(CtMethod<?> thatMethod, boolean canTypeErasure);

/**
* Checks if the method is a default method. Default method can be in interfaces from
* Java 8: http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html.
Expand Down
96 changes: 94 additions & 2 deletions src/main/java/spoon/support/reflect/declaration/CtMethodImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import spoon.reflect.declaration.CtFormalTypeDeclarer;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtShadowable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.declaration.ModifierKind;
Expand All @@ -47,8 +49,6 @@ public class CtMethodImpl<T> extends CtExecutableImpl<T> implements CtMethod<T>

boolean defaultMethod = false;

List<CtTypeParameterReference> formalTypeParameters = emptyList();

List<CtTypeParameter> formalCtTypeParameters = emptyList();

Set<ModifierKind> modifiers = CtElementImpl.emptySet();
Expand Down Expand Up @@ -189,6 +189,98 @@ public <R extends T> void replace(CtMethod<T> element) {
replace((CtElement) element);
}

@Override
public boolean isSameSignature(CtMethod<?> thatMethod, boolean canTypeErasure) {
//https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2
if (this == thatMethod) {
return true;
}
if ((thatMethod instanceof CtMethod) == false) {
return false;
}
if (thatMethod.getSimpleName().equals(getSimpleName()) == false) {
return false;
}
List<CtParameter<?>> thatParameters = thatMethod.getParameters();
if (parameters.size() != thatParameters.size()) {
//the methods has different count of parameters they cannot override each other
return false;
}
List<CtTypeParameter> thatTypeParameters = thatMethod.getFormalCtTypeParameters();
if (formalCtTypeParameters.size() == thatTypeParameters.size()) {
//the methods has same count of formal parameters
//check that formal type parameters are same
for (int i = 0; i < formalCtTypeParameters.size(); i++) {
if (CtTypeParameterImpl.isSame(formalCtTypeParameters.get(i).getReference(), thatTypeParameters.get(i).getReference(), canTypeErasure, false) == false) {
return false;
}
}
} else {
//the methods has different count of formal type parameters. It still can override when one of the method is without type parameters = is not generic
if (formalCtTypeParameters.isEmpty() == false && thatTypeParameters.isEmpty() == false) {
//Both methods has some parameters, but different. The signature is not same
return false;
}
}
CtType<?> thisDeclType = getDeclaringType();
CtType<?> thatDeclType = thatMethod.getDeclaringType();
CtTypeReference<?>[] thisParameterTypes = getParameterTypes(parameters);
CtTypeReference<?>[] thatParameterTypes = getParameterTypes(thatParameters);
if (thisDeclType != thatDeclType) {
if (thisDeclType.isSubtypeOf(thatDeclType.getReference())) {
//adapt parameter types of thatMethod to thisDeclType
if (adaptTypes(thatParameterTypes, this) == false) {
//cannot adapt types, the signature is not same
return false;
}
} else {
//adapt parameter types of this method to thatDeclType
if (adaptTypes(thisParameterTypes, thatMethod) == false) {
//cannot adapt types, the signature is not same
return false;
}
}
}
//check that parameters are same after adapted to same scope
for (int i = 0; i < thisParameterTypes.length; i++) {
CtTypeReference<?> thisType = thisParameterTypes[i];
CtTypeReference<?> thatType = thatParameterTypes[i];
if (CtTypeParameterImpl.isSame(thisType, thatType, canTypeErasure, false) == false) {
return false;
}
}
return true;
}

/**
* adapt all CtTypeParameterReference to targetType
* @param types list of to be adapted types
* @param targetType target type
* @return false if adaptation is not possible
*/
private static boolean adaptTypes(CtTypeReference<?>[] types, CtFormalTypeDeclarer targetType) {
for (int i = 0; i < types.length; i++) {
CtTypeReference<?> ref = types[i];
if (ref instanceof CtTypeParameterReference) {
CtTypeParameterReference typeParamRef = (CtTypeParameterReference) ref;
CtTypeReference<?> adaptedType = typeParamRef.getDeclaration().getTypeAdaptedTo(targetType);
if (adaptedType == null) {
return false;
}
types[i] = adaptedType;
}
}
return true;
}

private static CtTypeReference<?>[] getParameterTypes(List<CtParameter<?>> params) {
CtTypeReference<?>[] types = new CtTypeReference[params.size()];
for (int i = 0; i < types.length; i++) {
types[i] = params.get(i).getType();
}
return types;
}

boolean isShadow;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,34 @@ public List<CtFieldReference<?>> getDeclaredFields() {

@Override
public boolean isSubtypeOf(CtTypeReference<?> type) {
return getReference().isSubtypeOf(type);
if (type instanceof CtTypeParameterReference) {
//the type is type parameter too. Use appropriate sub type checking algorithm
CtTypeParameter subTypeParam = (CtTypeParameter) type.getDeclaration();
return isSubtypeOf(this, subTypeParam);
}
//type is normal type
return getTypeErasure().isSubtypeOf(type);
}

private static boolean isSubtypeOf(CtTypeParameter superTypeParam, CtTypeParameter subTypeParam) {
while (superTypeParam != null) {
if (isSame(superTypeParam, subTypeParam, false, true)) {
//both type params are same
return true;
}
CtTypeReference<?> superType = superTypeParam.getSuperclass();
if (superType == null) {
//there is no super type defined
return false;
}
if (superType instanceof CtTypeParameterReference) {
superTypeParam = ((CtTypeParameterReference) superType).getDeclaration();
} else {
//the super type is not type parameter
return false;
}
}
return false;
}

@Override
Expand Down Expand Up @@ -303,6 +330,146 @@ public CtTypeReference<?> getTypeAdaptedTo(CtFormalTypeDeclarer targetScope) {
}
}

static boolean isSame(CtTypeParameter thisType, CtTypeParameter thatType, boolean canTypeErasure, boolean checkMethodOverrides) {
CtFormalTypeDeclarer thisTypeScope = thisType.getTypeParameterDeclarer();
if (thisTypeScope == null) {
throw new SpoonException("This type parameter has no parent formal type declarer.");
}
CtFormalTypeDeclarer thatTypeScope = thatType.getTypeParameterDeclarer();
if (thatTypeScope == null) {
throw new SpoonException("The type parameter has no parent formal type declarer.");
}
if (thisTypeScope == thatTypeScope) {
//the scopes are same. Just check if types are same
return isSameInSameScope(thisType, thatType);
}
//scopes are not same.
if (thisTypeScope instanceof CtType) {
if (thatTypeScope instanceof CtType) {
//both are TypeParameters defined in scope of type.
if (((CtType<?>) thisTypeScope).isSubtypeOf(((CtType<?>) thatTypeScope).getReference())) {
//translate thatType parameter type to scope of thisTypeScope
CtTypeReference<?> targetTypeRef = getReferenceInScope(thatType.getReference(), (CtType<?>) thisTypeScope);
//and then check if they are same
return isSameInSameScope(thisType, targetTypeRef);
} else {
//translate this type parameter type to scope of thatTypeScope
CtTypeReference<?> targetTypeRef = getReferenceInScope(thisType.getReference(), (CtType<?>) thatTypeScope);
//and then check if they are same
return isSameInSameScope(thatType, targetTypeRef);
}
}
return false;
}
if (thisTypeScope instanceof CtConstructor || thatTypeScope instanceof CtConstructor) {
//the type parameters declared in different constructors are never same
return false;
}
if (thisTypeScope instanceof CtMethod && thatTypeScope instanceof CtMethod) {
CtMethod<?> thisMethod = (CtMethod<?>) thisTypeScope;
CtMethod<?> thatMethod = (CtMethod<?>) thatTypeScope;
/*
* The type parameters of generic executables are same if
* 1) the methods are same or if methods overrides each other.
* 2) they are declared on same position
* 3) they have same bound
* See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.4
*/
if (checkMethodOverrides) {
if (thisMethod.isSameSignature(thatMethod, false) == false) {
//the signature of methods is not same
return false;
}
CtType<?> thisDeclType = thisMethod.getDeclaringType();
CtType<?> thatDeclType = thatMethod.getDeclaringType();
if (thisDeclType == thatDeclType) {
//if two different methods are declared in the same type then they cannot override
return false;
}
if (thisDeclType.isSubtypeOf(thatDeclType.getReference()) == false && thatDeclType.isSubtypeOf(thisDeclType.getReference()) == false) {
//if two different methods are declared in the types which does not inherit then they cannot override
return false;
}
}
int thisPosition = getTypeParameterPosition(thisType, thisTypeScope);
int thatPosition = getTypeParameterPosition(thatType, thatTypeScope);
if (thisPosition != thatPosition) {
return false;
}
CtTypeReference<?> thisBound = getBound(thisType);
CtTypeReference<?> thatBound = getBound(thatType);
return isSame(thisBound, thatBound, canTypeErasure, checkMethodOverrides);
}
return false;
}

/**
* @param thisType to be compared type 1
* @param thatType to be compared type 2
* @param canTypeErasure if true and types are not same, then that type erasure is applied to types before they checked if same
* @param checkMethodOverrides TODO
* @return true if thisType and thatType are same
*/
static boolean isSame(CtTypeReference<?> thisType, CtTypeReference<?> thatType, boolean canTypeErasure, boolean checkMethodOverrides) {
if (thisType instanceof CtTypeParameterReference) {
CtTypeParameterReference thisTypeParamRef = (CtTypeParameterReference) thisType;
CtTypeParameter thisTypeParam = thisTypeParamRef.getDeclaration();
if (thisTypeParam == null) {
return false;
}
if (thatType instanceof CtTypeParameterReference) {
CtTypeParameterReference thatTypeParamRef = (CtTypeParameterReference) thatType;
CtTypeParameter thatTypeParam = thatTypeParamRef.getDeclaration();
if (thatTypeParam == null) {
return false;
}
if (isSame(thisTypeParam, thatTypeParam, canTypeErasure, checkMethodOverrides)) {
return true;
}
if (canTypeErasure == false) {
return false;
}
thatType = thatTypeParam.getTypeErasure();
} else if (canTypeErasure == false) {
return false;
}
thisType = thisTypeParam.getTypeErasure();
}
if (canTypeErasure && thatType instanceof CtTypeParameterReference) {
CtTypeParameter thatTypeParam = ((CtTypeParameterReference) thatType).getDeclaration();
if (thatTypeParam == null) {
return false;
}
thatType = thatTypeParam.getTypeErasure();
}
return thisType.getQualifiedName().equals(thatType.getQualifiedName());
}

/**
* Note: This method expects that both arguments are already adapted to the same scope
* @param typeParam a type param 1
* @param typeRef a reference to some type 2
* @return true if typeParam and typeRef represents same type parameter.
*/
private static boolean isSameInSameScope(CtTypeParameter typeParam, CtTypeReference<?> typeRef) {
if (typeRef instanceof CtTypeParameterReference) {
return typeParam.getSimpleName().equals(((CtTypeParameterReference) typeRef).getSimpleName());
}
return false;
}
/**
* Note: This method expects that both arguments are already adapted to the same scope
* @param typeParam a type param 1
* @param type a type 2
* @return true if typeParam and type represents same type parameter.
*/
private static boolean isSameInSameScope(CtTypeParameter typeParam, CtType<?> type) {
if (type instanceof CtTypeParameter) {
return typeParam.getSimpleName().equals(((CtTypeParameter) type).getSimpleName());
}
return false;
}

/**
* Converts potentially 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.
Expand Down
Loading

0 comments on commit 23ace58

Please sign in to comment.