Skip to content

Commit

Permalink
refactor: MethodTypingContext#isOverriding/isSameSignature/isSubSigna…
Browse files Browse the repository at this point in the history
…ture moved to ClassTypingContext (#1299)
  • Loading branch information
pvojtechovsky authored and monperrus committed May 15, 2017
1 parent e21a338 commit 0743b5a
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 211 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@

import spoon.reflect.declaration.CtMethod;
import spoon.reflect.visitor.Filter;
import spoon.support.visitor.MethodTypingContext;
import spoon.support.visitor.ClassTypingContext;

/**
* Gets all overridden method from the method given.
*/
public class OverriddenMethodFilter implements Filter<CtMethod<?>> {
private final CtMethod<?> method;
private final MethodTypingContext context;
private final ClassTypingContext context;
private boolean includingSelf = false;

/**
Expand All @@ -36,7 +36,7 @@ public class OverriddenMethodFilter implements Filter<CtMethod<?>> {
*/
public OverriddenMethodFilter(CtMethod<?> method) {
this.method = method;
context = new MethodTypingContext().setMethod(method);
context = new ClassTypingContext(method.getDeclaringType());
}

/**
Expand All @@ -53,6 +53,6 @@ public boolean matches(CtMethod<?> element) {
if (method == element) {
return this.includingSelf;
}
return context.isOverriding(element);
return context.isOverriding(method, element);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtVisitor;
import spoon.support.visitor.MethodTypingContext;
import spoon.support.visitor.ClassTypingContext;

import java.util.ArrayList;
import java.util.EnumSet;
Expand Down Expand Up @@ -189,7 +189,7 @@ public <R extends T> void replace(CtMethod<T> element) {

@Override
public boolean isOverriding(CtMethod<?> superMethod) {
return new MethodTypingContext().setMethod(this).isOverriding(superMethod);
return new ClassTypingContext(getDeclaringType()).isOverriding(this, superMethod);
}

boolean isShadow;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.reference.CtActualTypeContainer;
import spoon.reflect.reference.CtExecutableReference;
Expand All @@ -31,7 +32,7 @@
import spoon.reflect.visitor.filter.NameFilter;
import spoon.support.reflect.declaration.CtElementImpl;
import spoon.support.util.RtHelper;
import spoon.support.visitor.MethodTypingContext;
import spoon.support.visitor.ClassTypingContext;
import spoon.support.visitor.SignaturePrinter;

import java.lang.reflect.AnnotatedElement;
Expand Down Expand Up @@ -247,9 +248,8 @@ public boolean isOverriding(CtExecutableReference<?> executable) {
}
return true;
}
if (exec instanceof CtMethod<?>) {
CtMethod<?> method = (CtMethod<?>) exec;
return new MethodTypingContext().setExecutableReference(this).isOverriding(method);
if (exec instanceof CtMethod<?> && thisExec instanceof CtMethod<?>) {
return new ClassTypingContext(((CtTypeMember) thisExec).getDeclaringType()).isOverriding((CtMethod<?>) thisExec, (CtMethod<?>) exec);
}
//it is not a method. So we can return true only if it is reference to the this executable
return exec == getDeclaration();
Expand Down
143 changes: 143 additions & 0 deletions src/main/java/spoon/support/visitor/ClassTypingContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Set;

import spoon.SpoonException;
import spoon.reflect.declaration.CtConstructor;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtFormalTypeDeclarer;
Expand Down Expand Up @@ -264,6 +265,53 @@ public <T> CtMethod<T> adaptMethod(CtMethod<T> method) {
return adaptedMethod;
}

/**
* @param thatMethod - to be checked method
* @return true if scope method overrides `thatMethod`
*/
public boolean isOverriding(CtMethod<?> thisMethod, CtMethod<?> thatMethod) {
if (thisMethod == thatMethod) {
//method overrides itself in spoon model
return true;
}
CtType<?> thatDeclType = thatMethod.getDeclaringType();
CtType<?> thisDeclType = getAdaptationScope();
if (thatDeclType != thisDeclType) {
if (isSubtypeOf(thatDeclType.getReference()) == false) {
//the declaringType of that method must be superType of this scope type
return false;
}
}
//TODO check method visibility following https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.8.1
return isSubSignature(thisMethod, thatMethod);
}

/**
* scope method is subsignature of thatMethod if either
* A) scope method is same signature like thatMethod
* B) scope method is same signature like type erasure of thatMethod
* See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2
*
* @param thatMethod - the checked method
* @return true if scope method is subsignature of thatMethod
*/
public boolean isSubSignature(CtMethod<?> thisMethod, CtMethod<?> thatMethod) {
return isSameSignature(thisMethod, thatMethod, true);
}

/**
* The same signature is the necessary condition for method A overrides method B.
* @param thatExecutable - the checked method
* @return true if this method and `thatMethod` has same signature
*/
public boolean isSameSignature(CtExecutable<?> thisExecutable, CtMethod<?> thatExecutable) {
if ((thatExecutable instanceof CtMethod || thatExecutable instanceof CtConstructor) == false) {
//only method or constructor can have same signature
return false;
}
return isSameSignature(thisExecutable, thatExecutable, false);
}

@Override
public ClassTypingContext getEnclosingGenericTypeAdapter() {
return enclosingClassTypingContext;
Expand Down Expand Up @@ -563,4 +611,99 @@ private CtTypeReference<?> adaptTypeForNewMethod(CtTypeReference<?> typeRef) {
}
return adaptType(typeRef);
}

private boolean isSameSignature(CtExecutable<?> thisMethod, CtExecutable<?> thatMethod, boolean canTypeErasure) {
if (thisMethod == thatMethod) {
return true;
}
ExecutableContext mtc = new ExecutableContext();
mtc.setClassTypingContext(this);

if (thisMethod instanceof CtMethod) {
if (thatMethod instanceof CtMethod) {
mtc.setMethod((CtMethod<?>) thisMethod);
} else {
return false;
}
} else if (thisMethod instanceof CtConstructor) {
if (thatMethod instanceof CtConstructor) {
mtc.setConstructor((CtConstructor<?>) thisMethod);
} else {
return false;
}
} else {
//only method or constructor can compare signatures
return false;
}
return mtc.isSameSignatureLikeScopeMethod(thatMethod, canTypeErasure);
}

private static class ExecutableContext extends MethodTypingContext {
private boolean isSameSignatureLikeScopeMethod(CtExecutable<?> thatExecutable, boolean canTypeErasure) {
//https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2
CtFormalTypeDeclarer thatDeclarer = (CtFormalTypeDeclarer) thatExecutable;
CtFormalTypeDeclarer thisDeclarer = getAdaptationScope();
CtExecutable<?> thisExecutable = (CtExecutable<?>) thisDeclarer;
if (thatExecutable.getSimpleName().equals(thisExecutable.getSimpleName()) == false) {
return false;
}
if (thisExecutable.getParameters().size() != thatExecutable.getParameters().size()) {
//the executables has different count of parameters they cannot have same signature
return false;
}
List<CtTypeParameter> thisTypeParameters = thisDeclarer.getFormalCtTypeParameters();
List<CtTypeParameter> thatTypeParameters = thatDeclarer.getFormalCtTypeParameters();
boolean useTypeErasure = false;
if (thisTypeParameters.size() == thatTypeParameters.size()) {
//the methods has same count of formal parameters
//check that formal type parameters are same
if (hasSameMethodFormalTypeParameters((CtFormalTypeDeclarer) thatExecutable) == false) {
return false;
}
} else {
//the methods has different count of formal type parameters.
if (canTypeErasure == false) {
//type erasure is not allowed. So non-generic methods cannot match with generic methods
return false;
}
//non-generic method can override a generic one if type erasure is allowed
if (thisTypeParameters.isEmpty() == false) {
//scope methods has some parameters. It is generic too, it is not a subsignature of that method
return false;
}
//scope method has zero formal type parameters. It is not generic.
useTypeErasure = true;
}
List<CtTypeReference<?>> thisParameterTypes = getParameterTypes(thisExecutable.getParameters());
List<CtTypeReference<?>> thatParameterTypes = getParameterTypes(thatExecutable.getParameters());
//check that parameters are same after adapting to the same scope
for (int i = 0; i < thisParameterTypes.size(); i++) {
CtTypeReference<?> thisType = thisParameterTypes.get(i);
CtTypeReference<?> thatType = thatParameterTypes.get(i);
if (useTypeErasure) {
if (thatType instanceof CtTypeParameterReference) {
thatType = ((CtTypeParameterReference) thatType).getTypeErasure();
}
} else {
thatType = adaptType(thatType);
}
if (thatType == null) {
//the type cannot be adapted.
return false;
}
if (thisType.equals(thatType) == false) {
return false;
}
}
return true;
}

private static List<CtTypeReference<?>> getParameterTypes(List<CtParameter<?>> params) {
List<CtTypeReference<?>> types = new ArrayList<>(params.size());
for (CtParameter<?> param : params) {
types.add(param.getType());
}
return types;
}
}
}
Loading

0 comments on commit 0743b5a

Please sign in to comment.