Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review: refactor MethodTypingContext#isOverriding moved to ClassTypingContext #1299

Merged
merged 2 commits into from
May 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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