Skip to content

Commit

Permalink
feat: add CtScanner#scan(Map) for a better EarlyTerminatingScanner (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky authored and monperrus committed Mar 3, 2018
1 parent ffd1db8 commit 86a4d25
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 19 deletions.
18 changes: 12 additions & 6 deletions src/main/java/spoon/reflect/visitor/CtScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ public void scan(CtRole role, Collection<? extends CtElement> elements) {
}
}
}
/**
* Generically scans a Map of meta-model elements.
*/
public void scan(CtRole role, Map<String, ? extends CtElement> elements) {
if (elements != null) {
for (CtElement obj : elements.values()) {
scan(role, obj);
}
}
}

/**
* Generically scans a collection of meta-model elements.
Expand Down Expand Up @@ -199,14 +209,10 @@ public void scan(CtRole role, Object o) {
scan(role, ((CtElement) (o)));
}
if (o instanceof Collection<?>) {
for (Object obj : ((Collection<?>) (o))) {
scan(role, obj);
}
scan(role, (Collection<? extends CtElement>) o);
}
if (o instanceof Map<?, ?>) {
for (Object obj : ((Map) (o)).values()) {
scan(role, obj);
}
scan(role, (Map<String, ? extends CtElement>) o);
}
}

Expand Down
47 changes: 43 additions & 4 deletions src/main/java/spoon/reflect/visitor/EarlyTerminatingScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class EarlyTerminatingScanner<T> extends CtScanner {
private boolean terminate = false;
private T result;
private CtScannerListener listener;
protected CtRole scannedRole;

protected void terminate() {
terminate = true;
Expand Down Expand Up @@ -93,6 +94,29 @@ public void scan(CtRole role, Collection<? extends CtElement> elements) {
}
}

@Override
public void scan(CtRole role, Map<String, ? extends CtElement> elements) {
if (isTerminated() || elements == null) {
return;
}
for (CtElement obj : elements.values()) {
scan(role, obj);
if (isTerminated()) {
return;
}
}
}

@Override
public void scan(CtRole role, CtElement element) {
scannedRole = role;
super.scan(role, element);
}

/*
* we cannot override scan(CtRole role, CtElement element) directly
* because some implementations needs scan(CtElement element), which must be called too
*/
@Override
public void scan(CtElement element) {
if (element == null || isTerminated()) {
Expand All @@ -101,13 +125,13 @@ public void scan(CtElement element) {
if (listener == null) {
//the listener is not defined
//visit this element and may be children
doScan(element, ScanningMode.NORMAL);
doScan(scannedRole, element, ScanningMode.NORMAL);
} else {
//the listener is defined, call it's enter method first
ScanningMode mode = listener.enter(element);
if (mode != ScanningMode.SKIP_ALL) {
//the listener decided to visit this element and may be children
doScan(element, mode);
doScan(scannedRole, element, mode);
//then call exit, only if enter returned true
listener.exit(element);
} //else the listener decided to skip this element and all children. Do not call exit.
Expand All @@ -118,8 +142,23 @@ public void scan(CtElement element) {
* This method is called ONLY when the listener decides that the current element and children should be visited.
* Subclasses can override it to react accordingly.
*/
protected void doScan(CtElement element, ScanningMode mode) {
super.scan(element);
protected void doScan(CtRole role, CtElement element, ScanningMode mode) {
//send input to output
if (mode.visitElement) {
onElement(role, element);
}
if (mode.visitChildren) {
//do not call scan(CtElement) nor scan(CtRole, CtElement), because they would cause StackOverflowError
element.accept(this);
}
}

/**
* Called for each scanned element. The call of this method is influenced by {@link ScanningMode} defined by {@link CtScannerListener}
* @param role a role of `element` in parent
* @param element a scanned element
*/
protected void onElement(CtRole role, CtElement element) {
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package spoon.reflect.visitor.filter;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.path.CtRole;
import spoon.reflect.visitor.EarlyTerminatingScanner;
import spoon.reflect.visitor.chain.CtConsumableFunction;
import spoon.reflect.visitor.chain.CtConsumer;
Expand Down Expand Up @@ -82,15 +83,11 @@ private static class Scanner extends EarlyTerminatingScanner<Void> {
private CtQuery query;

@Override
protected void doScan(CtElement element, ScanningMode mode) {
//send input to output
if (mode.visitElement) {
next.accept(element);
}
if (mode.visitChildren) {
element.accept(this);
}
protected void onElement(CtRole role, CtElement element) {
next.accept(element);
super.onElement(role, element);
}

/*
* override {@link EarlyTerminatingScanner#isTerminated()} and let it stop when query is terminated
*/
Expand Down
92 changes: 91 additions & 1 deletion src/test/java/spoon/reflect/visitor/CtScannerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.meta.ContainerKind;
import spoon.reflect.meta.RoleHandler;
import spoon.reflect.meta.impl.RoleHandlerHelper;
import spoon.reflect.path.CtRole;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
Expand All @@ -39,18 +42,23 @@
import spoon.test.metamodel.SpoonMetaModel;

import java.io.File;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand Down Expand Up @@ -226,6 +234,7 @@ class Counter {
int nExit=0;
int nObject=0;
int nElement=0;
Deque<CollectionContext> contexts = new ArrayDeque<>();
};
Counter counter = new Counter();
launcher.getModel().getRootPackage().accept(new CtScanner() {
Expand Down Expand Up @@ -258,5 +267,86 @@ public void exit(CtElement o) {
assertEquals(2396, counter.nEnter);
assertEquals(2396, counter.nExit);

// contract: all AST nodes which are part of Collection or Map are visited first by method "scan(Collection|Map)" and then by method "scan(CtElement)"
Counter counter2 = new Counter();
launcher.getModel().getRootPackage().accept(new CtScanner() {
@Override
public void scan(Object o) {
counter2.nObject++;
super.scan(o);
}
@Override
public void scan(CtRole role, CtElement o) {
if (o == null) {
//there is no collection involved in scanning of this single value NULL attribute
assertNull(counter2.contexts.peek().col);

} else {
RoleHandler rh = RoleHandlerHelper.getRoleHandler(o.getParent().getClass(), role);
if (rh.getContainerKind()==ContainerKind.SINGLE) {
//there is no collection involved in scanning of this single value attribute
assertNull(counter2.contexts.peek().col);
} else {
counter2.contexts.peek().assertRemoveSame(o);
}
}
counter2.nElement++;
super.scan(o);
}
@Override
public void scan(CtRole role, Collection<? extends CtElement> elements) {
//contract: before processed collection is finished before it starts with next collection
counter2.contexts.peek().initCollection(elements);
super.scan(role, elements);
//contract: all elements of collection are processed in previous super.scan call
counter2.contexts.peek().assertCollectionIsEmpty();
}
@Override
public void scan(CtRole role, Map<String, ? extends CtElement> elements) {
//contract: before processed collection is finished before it starts with next collection
counter2.contexts.peek().initCollection(elements.values());
super.scan(role, elements);
//contract: all elements of collection are processed in previous super.scan call
counter2.contexts.peek().assertCollectionIsEmpty();
}
@Override
public void enter(CtElement o) {
counter2.nEnter++;
counter2.contexts.push(new CollectionContext());
}
@Override
public void exit(CtElement o) {
counter2.nExit++;
counter2.contexts.peek().assertCollectionIsEmpty();
counter2.contexts.pop();
}
});
assertEquals(counter.nObject, counter2.nObject);
assertEquals(counter.nElement, counter2.nElement);
assertEquals(counter.nEnter, counter2.nEnter);
assertEquals(counter.nExit, counter2.nExit);
}
private static class CollectionContext {
Collection<CtElement> col;
void assertCollectionIsEmpty() {
assertTrue(col == null || col.isEmpty());
col = null;
}
public void initCollection(Collection<? extends CtElement> elements) {
assertCollectionIsEmpty();
col = new ArrayList<>(elements);
assertFalse(col.contains(null));
}
public void assertRemoveSame(CtElement o) {
assertNotNull(col);
for (Iterator iter = col.iterator(); iter.hasNext();) {
CtElement ctElement = (CtElement) iter.next();
if (o == ctElement) {
iter.remove();
return;
}
}
fail();
}
}
}

0 comments on commit 86a4d25

Please sign in to comment.