Skip to content

Commit

Permalink
feature: add CtMethod#isOverriding(CtMethod) (#1220)
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky authored and monperrus committed May 4, 2017
1 parent 8ca2213 commit 9e5e8a8
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 3 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 {
/**
* @param superMethod to be checked method
* @return true if this method overrides `superMethod`.<br>
* Returns true for itself too.
* <pre>
* assertTrue(this.isOverriding(this))
* </pre>
*/
boolean isOverriding(CtMethod<?> superMethod);
/**
* 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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtVisitor;
import spoon.support.visitor.MethodTypingContext;

import java.util.ArrayList;
import java.util.EnumSet;
Expand Down Expand Up @@ -186,6 +187,11 @@ public <R extends T> void replace(CtMethod<T> element) {
replace((CtElement) element);
}

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

boolean isShadow;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ public <S extends T> CtExecutableReference<S> getOverridingExecutable(

@Override
public boolean isOverriding(CtExecutableReference<?> executable) {
CtExecutable<?> exec = executable.getDeclaration();
if (exec == null) {
CtExecutable<?> exec = executable.getExecutableDeclaration();
CtExecutable<?> thisExec = getExecutableDeclaration();
if (exec == null || thisExec == null) {
//the declaration of this executable is not in spoon model
//use light detection algorithm, which ignores generic types
final boolean isSame = getSimpleName().equals(executable.getSimpleName()) && getParameters().equals(executable.getParameters()) && getActualTypeArguments().equals(executable.getActualTypeArguments());
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/spoon/support/visitor/MethodTypingContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public MethodTypingContext setInvocation(CtInvocation<?> invocation) {

public MethodTypingContext setExecutableReference(CtExecutableReference<?> execRef) {
this.actualTypeArguments = execRef.getActualTypeArguments();
this.scopeMethod = (CtMethod<?>) execRef.getDeclaration();
this.scopeMethod = execRef.getExecutableDeclaration();
if (classTypingContext == null) {
CtTypeReference<?> declaringTypeRef = execRef.getDeclaringType();
if (declaringTypeRef != null) {
Expand All @@ -105,7 +105,16 @@ public MethodTypingContext setExecutableReference(CtExecutableReference<?> execR
* @return true if scope method overrides `thatMethod`
*/
public boolean isOverriding(CtMethod<?> thatMethod) {
if (scopeMethod == thatMethod) {
//method overrides itself in spoon model
return true;
}
CtType<?> thatDeclType = thatMethod.getDeclaringType();
CtType<?> thisDeclType = ((CtTypeMember) scopeMethod).getDeclaringType();
if (thatDeclType == thisDeclType) {
//both methods has same declarer, they cannot override.
return false;
}
if (getEnclosingGenericTypeAdapter().isSubtypeOf(thatDeclType.getReference()) == false) {
return false;
}
Expand Down
1 change: 1 addition & 0 deletions src/test/java/spoon/test/filters/FilterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ class Context {
// actual evaluation
q.forEach((CtMethod<?> method) -> {
assertTrue(context.method.getReference().isOverriding(method.getReference()));
assertTrue(context.method.isOverriding(method));
context.count++;
});
// sanity check
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package spoon.test.method_overriding;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import org.junit.Test;

import spoon.reflect.declaration.CtMethod;
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.testing.utils.ModelUtils;

import static org.junit.Assert.*;

public class MethodOverriddingTest {

@Test
public void testMethodOverride() {
checkMethodOverride((m1, m2)->m1.isOverriding(m2));
}
@Test
public void testMethodOverrideByReference() {
checkMethodOverride((m1, m2)->m1.getReference().isOverriding(m2.getReference()));
}

private void checkMethodOverride(BiFunction<CtMethod<?>, CtMethod<?>, Boolean> isOverriding) {
Factory factory = ModelUtils.build(new File("src/test/java/spoon/test/method_overriding/testclasses").listFiles());
Map<String, List<CtMethod>> methodsByName = new HashMap<>();
factory.getModel().getRootPackage().filterChildren(new TypeFilter<>(CtMethod.class)).forEach((CtMethod m)->{
List<CtMethod> methods = methodsByName.get(m.getSimpleName());
if(methods==null) {
methods = new ArrayList<>();
methodsByName.put(m.getSimpleName(), methods);
}
methods.add(m);
});
assertTrue(methodsByName.size()>0);
for (Map.Entry<String, List<CtMethod>> e : methodsByName.entrySet()) {
combine(e.getValue(), 0, isOverriding);
}
}

private void combine(List<CtMethod> value, int start, BiFunction<CtMethod<?>, CtMethod<?>, Boolean> isOverriding) {
CtMethod m1 = value.get(start);
if(start+1<value.size()) {
for (CtMethod m2 : value.subList(start+1, value.size())) {
if(m1.getDeclaringType().isSubtypeOf(m2.getDeclaringType().getReference())) {
checkOverride(m1, m2, isOverriding);
} else if(m2.getDeclaringType().isSubtypeOf(m1.getDeclaringType().getReference())) {
checkOverride(m2, m1, isOverriding);
} else {
checkNotOverride(m1, m2, isOverriding);
}
}
combine(value, start+1, isOverriding);
}
}

private void checkOverride(CtMethod m1, CtMethod m2, BiFunction<CtMethod<?>, CtMethod<?>, Boolean> isOverriding) {
assertTrue(descr(m1)+" overriding "+descr(m2), isOverriding.apply(m1, m2));
assertFalse(descr(m2)+" NOT overriding "+descr(m1), isOverriding.apply(m2, m1));
}
private void checkNotOverride(CtMethod m1, CtMethod m2, BiFunction<CtMethod<?>, CtMethod<?>, Boolean> isOverriding) {
assertFalse(descr(m1)+" NOT overriding "+descr(m2), isOverriding.apply(m1, m2));
assertFalse(descr(m2)+" NOT overriding "+descr(m1), isOverriding.apply(m2, m1));
}

private String descr(CtMethod m) {
return m.getDeclaringType().getSimpleName()+"#"+m.getSimpleName();
}
}
24 changes: 24 additions & 0 deletions src/test/java/spoon/test/method_overriding/testclasses/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package spoon.test.method_overriding.testclasses;

import java.util.List;

public class A<U> {

public A() {
}

A<U> m1(C c){
return null;
}

<T extends A<U>> T m2(C c){
return null;
}

void m3(List<? super C> c){
}
void m4(List<? extends A<U>> c){
}
void m5(U u) {
}
}
32 changes: 32 additions & 0 deletions src/test/java/spoon/test/method_overriding/testclasses/B.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package spoon.test.method_overriding.testclasses;

import java.util.List;

public class B<S, R extends S> extends A<S> {

public B() {
}

@Override
B<S, R> m1(C c){
return null;
}

@Override
<T extends A<S>> T m2(C c){
return null;
}

@Override
void m3(List<? super C> c){
}

@Override
void m5(S u) {
super.m5(u);
}

@Override
void m4(List<? extends A<S>> c) {
}
}
37 changes: 37 additions & 0 deletions src/test/java/spoon/test/method_overriding/testclasses/C.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package spoon.test.method_overriding.testclasses;

import java.io.FilterInputStream;
import java.io.InputStream;
import java.util.List;

public class C extends B<InputStream, FilterInputStream> {

public C() {
}

@Override
B m1(C c){
return null;
}

@Override
B<InputStream, FilterInputStream> m2(C c){
return null;
}

@Override
void m3(List<? super C> c) {
// TODO Auto-generated method stub
super.m3(c);
}

@Override
void m4(List<? extends A<InputStream>> c){
}

@Override
void m5(InputStream u) {
super.m5(u);
}

}
24 changes: 24 additions & 0 deletions src/test/java/spoon/test/method_overriding/testclasses/D.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package spoon.test.method_overriding.testclasses;

import java.util.List;

public class D extends B<Integer, Integer> {

public D() {
}

@Override
B<Integer, Integer> m1(C c){
return null;
}

@Override
D m2(C c){
return null;
}

@Override
void m4(List<? extends A<Integer>> c){
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package spoon.test.method_overriding.testclasses;

public interface IA {

}

0 comments on commit 9e5e8a8

Please sign in to comment.