Skip to content

Commit

Permalink
fix: bugs related to continue algorithm of ClassTypingContext (#1379)
Browse files Browse the repository at this point in the history
* test continue in hierarchy scanning of ClassTypingContext

* fix continue algorithm of ClassTypingContext
  • Loading branch information
pvojtechovsky authored and surli committed Jun 8, 2017
1 parent 3ae28a1 commit 40326ca
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 6 deletions.
50 changes: 44 additions & 6 deletions src/main/java/spoon/support/visitor/ClassTypingContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import spoon.reflect.visitor.chain.CtConsumer;
import spoon.reflect.visitor.chain.ScanningMode;
import spoon.reflect.visitor.filter.SuperInheritanceHierarchyFunction;
import spoon.support.SpoonClassNotFoundException;

/**
* Helper class created from type X or reference to X.
Expand Down Expand Up @@ -188,6 +189,11 @@ public List<CtTypeReference<?>> resolveActualTypeArgumentsOf(CtTypeReference<?>
return null;
}
final HierarchyListener listener = new HierarchyListener(getVisitedSet());
/*
* remove last resolved class from the list of visited,
* because it would avoid visiting it's super hierarchy
*/
getVisitedSet().remove(lastResolvedSuperclass.getQualifiedName());
/*
* visit super inheritance class hierarchy of lastResolve type of level of `type` to found it's actual type arguments.
*/
Expand All @@ -204,7 +210,29 @@ public void accept(CtTypeReference<?> typeRef) {
* which are going to be substituted as arguments to formal type parameters of super type
*/
String superTypeQualifiedName = typeRef.getQualifiedName();
List<CtTypeReference<?>> superTypeActualTypeArgumentsResolvedFromSubType = resolveTypeParameters(typeRef.getActualTypeArguments());
List<CtTypeReference<?>> actualTypeArguments = typeRef.getActualTypeArguments();
if (actualTypeArguments.isEmpty()) {
//may be they are not set - check whether type declares some generic parameters
List<CtTypeParameter> typeParams;
try {
CtType<?> type = typeRef.getTypeDeclaration();
typeParams = type.getFormalCtTypeParameters();
} catch (final SpoonClassNotFoundException e) {
if (typeRef.getFactory().getEnvironment().getNoClasspath()) {
typeParams = Collections.emptyList();
} else {
throw e;
}
}
if (typeParams.size() > 0) {
//yes, there are generic type parameters. Reference should use actualTypeArguments computed from their bounds
actualTypeArguments = new ArrayList<>(typeParams.size());
for (CtTypeParameter typeParam : typeParams) {
actualTypeArguments.add(typeParam.getTypeErasure());
}
}
}
List<CtTypeReference<?>> superTypeActualTypeArgumentsResolvedFromSubType = resolveTypeParameters(actualTypeArguments);
//Remember actual type arguments of `type`
typeToArguments.put(superTypeQualifiedName, superTypeActualTypeArgumentsResolvedFromSubType);
if (typeQualifiedName.equals(superTypeQualifiedName)) {
Expand All @@ -216,6 +244,12 @@ public void accept(CtTypeReference<?> typeRef) {
}
}
});
if (listener.foundArguments == null) {
/*
* superclass was not found. We have scanned whole hierarchy
*/
lastResolvedSuperclass = null;
}
return listener.foundArguments;
}

Expand Down Expand Up @@ -427,12 +461,11 @@ private class HierarchyListener extends SuperInheritanceHierarchyFunction.Distin
}
@Override
public ScanningMode enter(CtTypeReference<?> typeRef, boolean isClass) {
ScanningMode mode = super.enter(typeRef);
if (mode == ScanningMode.SKIP_ALL) {
//this interface was already visited. Do not visit it again
return mode;
}
if (isClass) {
/*
* test foundArguments and skip all before call of super.enter,
* which would add that not visited type into visitedSet
*/
if (foundArguments != null) {
//we have found result then we can finish before entering super class. All interfaces of found type should be still visited
//skip before super class (and it's interfaces) of found type is visited
Expand All @@ -445,6 +478,11 @@ public ScanningMode enter(CtTypeReference<?> typeRef, boolean isClass) {
*/
lastResolvedSuperclass = typeRef;
}
ScanningMode mode = super.enter(typeRef);
if (mode == ScanningMode.SKIP_ALL) {
//this interface was already visited. Do not visit it again
return mode;
}
//this type was not visited yet. Visit it normally
return ScanningMode.NORMAL;
}
Expand Down
101 changes: 101 additions & 0 deletions src/test/java/spoon/reflect/declaration/CtTypeInformationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import static org.junit.Assert.assertEquals;
import static spoon.testing.utils.ModelUtils.build;

import java.lang.reflect.Field;
import java.util.AbstractCollection;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.RandomAccess;
import java.util.Set;

import org.junit.Assert;
Expand All @@ -15,6 +20,7 @@
import spoon.reflect.declaration.testclasses.TestInterface;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtTypeReference;
import spoon.support.visitor.ClassTypingContext;

public class CtTypeInformationTest {
private Factory factory;
Expand All @@ -24,6 +30,101 @@ public void setUp() throws Exception {
factory = build(ExtendsObject.class, Subclass.class, Subinterface.class, TestInterface.class);
}

@Test
public void testClassTypingContextContinueScanning() throws Exception {

final CtType<?> subClass = this.factory.Type().get(Subclass.class);
final CtTypeReference<?> subinterface = this.factory.Type().createReference(Subinterface.class);
final CtTypeReference<?> testInterface = this.factory.Type().createReference(TestInterface.class);
final CtTypeReference<?> extendObject = this.factory.Type().createReference(ExtendsObject.class);
final CtTypeReference<?> arrayList = this.factory.Type().createReference(ArrayList.class);
final CtTypeReference<?> abstractList = this.factory.Type().createReference(AbstractList.class);
final CtTypeReference<?> abstractCollection = this.factory.Type().createReference(AbstractCollection.class);
final CtTypeReference<?> object = this.factory.Type().createReference(Object.class);

{
final ClassTypingContext ctc = (ClassTypingContext) this.factory.createTypeAdapter(subClass);
//contract: at the beginning, the last resolved class is a subClass
Assert.assertEquals(subClass.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

//contract: this.isSubttypeOf(this) == true
Assert.assertTrue(ctc.isSubtypeOf(subClass.getReference()));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(subClass.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(subinterface));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(subClass.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(testInterface));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(subClass.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(factory.createCtTypeReference(Comparable.class)));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(subClass.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(extendObject));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(extendObject.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(arrayList));
//contract: ClassTypingContext#isSubtypeOf returns always the same results
Assert.assertTrue(ctc.isSubtypeOf(extendObject));
Assert.assertTrue(ctc.isSubtypeOf(subClass.getReference()));

//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(arrayList.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(factory.createCtTypeReference(RandomAccess.class)));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(arrayList.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(abstractList));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(abstractList.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(abstractCollection));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class, which was needed to agree on isSubtypeOf
Assert.assertEquals(abstractCollection.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

Assert.assertTrue(ctc.isSubtypeOf(object));
//contract: ClassTypingContext did not scanned whole type hierarchy. It stopped on last class - even on java.lang.Object, which was needed to agree on isSubtypeOf
Assert.assertEquals(object.getQualifiedName(), getLastResolvedSuperclass(ctc).getQualifiedName());

//contract: ClassTypingContext returns false on a type which is not a sub type of ctc scope.
Assert.assertFalse(ctc.isSubtypeOf(factory.Type().createReference("java.io.InputStream")));
//contract: ClassTypingContext must scans whole type hierarchy if detecting subtypeof on type which is not a supertype
Assert.assertNull(getLastResolvedSuperclass(ctc));
//contract: ClassTypingContext#isSubtypeOf returns always the same results
Assert.assertTrue(ctc.isSubtypeOf(arrayList));
Assert.assertTrue(ctc.isSubtypeOf(extendObject));
Assert.assertTrue(ctc.isSubtypeOf(subClass.getReference()));
}

{
//now try directly a type which is not a supertype
final ClassTypingContext ctc2 = (ClassTypingContext) this.factory.createTypeAdapter(subClass);
//contract: at the beginning, the last resolved class is a subClass
Assert.assertEquals(subClass.getQualifiedName(), getLastResolvedSuperclass(ctc2).getQualifiedName());
//contract: ClassTypingContext returns false on a type which is not a sub type of ctc scope.
Assert.assertFalse(ctc2.isSubtypeOf(factory.Type().createReference("java.io.InputStream")));
//contract: ClassTypingContext must scans whole type hierarchy if detecting subtypeof on type which is not a supertype
Assert.assertNull(getLastResolvedSuperclass(ctc2));

//contract: ClassTypingContext#isSubtypeOf returns always the same results
Assert.assertTrue(ctc2.isSubtypeOf(arrayList));
Assert.assertTrue(ctc2.isSubtypeOf(extendObject));
Assert.assertTrue(ctc2.isSubtypeOf(subClass.getReference()));
}
}

private CtTypeInformation getLastResolvedSuperclass(ClassTypingContext ctc) throws Exception {
Field f = ClassTypingContext.class.getDeclaredField("lastResolvedSuperclass");
f.setAccessible(true);
return (CtTypeInformation) f.get(ctc);
}

@Test
public void testGetSuperclass() throws Exception {

Expand Down

0 comments on commit 40326ca

Please sign in to comment.