Skip to content

Commit

Permalink
feat(filter): Template matchers can be used as filter (#1110)
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky authored and monperrus committed Jan 12, 2017
1 parent 30540e5 commit df55265
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 33 deletions.
6 changes: 3 additions & 3 deletions doc/matcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ To define a template matcher one must:

1. specify the "holes" of the template matcher
1. write the matcher in a dedicated method
1. instantiate TemplateMatcher and call method `find`.
1. instantiate TemplateMatcher and call method `find` or use it as Filter of a query.

Taking again the same example.

Expand All @@ -53,9 +53,9 @@ CtClass<?> templateKlass = factory.Class().get(CheckBoundMatcher.class);
CtIf templateRoot = (CtIf) ((CtMethod) templateKlass.getElements(new NameFilter("matcher1")).get(0)).getBody().getStatement(0);
TemplateMatcher matcher = new TemplateMatcher(templateRoot);
for (CtElement elems : matcher.find(aPackage)) { ... };
//or TemplateMatcher as a Filter of query
aPackage.filterChildren(matcher).forEach((CtElement elem)->{ ... });
```

For named elements, a wildcard can be specified: if the named element (eg a method) to be matched is called `f` and the template matcher class contains a template parameter called `f` (of type Object), all methods starting by `f` will be matched.


43 changes: 15 additions & 28 deletions src/main/java/spoon/template/TemplateMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import spoon.reflect.reference.CtTypeParameterReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.InvocationFilter;
import spoon.support.template.DefaultParameterMatcher;
Expand All @@ -58,7 +59,7 @@
/**
* This class defines an engine for matching a template to pieces of code.
*/
public class TemplateMatcher {
public class TemplateMatcher implements Filter<CtElement> {

private List<CtInvocation<?>> getMethods(CtClass<? extends Template<?>> root) {
CtExecutableReference<?> methodRef = root.getFactory().Executable()
Expand Down Expand Up @@ -132,14 +133,18 @@ private List<CtFieldReference<?>> getVarargs(CtClass<? extends Template<?>> root
* Constructs a matcher for a given template.
*
*/
@SuppressWarnings("unchecked")
public TemplateMatcher(CtElement templateRoot) {
this.templateType = templateRoot.getParent(CtClass.class);
this.templateRoot = templateRoot;
variables = getMethods(templateType);
typeVariables = getTemplateTypeParameters(templateType);
names = getTemplateNameParameters(templateType);
varArgs = getVarargs(templateType, variables);
this.templateType = templateType;
//check that template matches itself
if (helperMatch(this.templateRoot, this.templateRoot) == false) {
throw new SpoonException("TemplateMatcher was unable to find itself, it certainly indicates a bug. Please revise your template or report an issue.");
}
}

private boolean addMatch(Object template, Object target) {
Expand Down Expand Up @@ -177,31 +182,7 @@ private CtElement checkListStatements(List<?> teList) {
* @return the matched elements
*/
public <T extends CtElement> List<T> find(final CtElement targetRoot) {
CtScanner scanner = new CtScanner() {
@Override
public void scan(CtElement element) {
if (match(element, templateRoot)) {
finds.add(element);
// matches.clear();
}
super.scan(element);
}
};

scanner.scan(templateRoot);
if (!finds.contains(templateRoot)) {
throw new SpoonException("TemplateMatcher was unable to find itself, it certainly indicates a bug. Please revise your template or report an issue.");
}
finds.clear();

scanner.scan(targetRoot);

// This case can occur when we are scanning the entire package for example see TemplateTest#testTemplateMatcherWithWholePackage
if (finds.contains(templateRoot)) {
finds.remove(templateRoot);
}

return (List<T>) finds;
return targetRoot.filterChildren(this).list();
}

/**
Expand Down Expand Up @@ -476,7 +457,13 @@ private boolean isCurrentTemplate(Object object, CtElement inMulti) {
* @return true if matches
* @see #getMatches()
*/
private boolean match(CtElement targetRoot, CtElement templateRoot) {
@Override
public boolean matches(CtElement targetRoot) {
if (targetRoot == templateRoot) {
// This case can occur when we are scanning the entire package for example see TemplateTest#testTemplateMatcherWithWholePackage
// Correct template matches itself of course, but client does not want that
return false;
}
return helperMatch(targetRoot, templateRoot);
}

Expand Down
13 changes: 11 additions & 2 deletions src/test/java/spoon/test/template/TemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import spoon.reflect.visitor.filter.NameFilter;
import spoon.support.compiler.FileSystemFile;
import spoon.support.template.Parameters;
import spoon.template.Substitution;
import spoon.template.TemplateMatcher;
import spoon.test.template.testclasses.SecurityCheckerTemplate;

Expand All @@ -28,7 +27,6 @@
import java.rmi.Remote;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import static org.junit.Assert.assertEquals;
Expand Down Expand Up @@ -418,6 +416,7 @@ public void testTemplateMatcherMatchTwoSnippets() throws Exception {
CtIf templateRoot = (CtIf) templateMethod.getBody().getStatement(0);
TemplateMatcher matcher = new TemplateMatcher(templateRoot);

//match using legacy TemplateMatcher#find method
List<CtElement> matches = matcher.find(factory.getModel().getRootPackage());

assertEquals(2, matches.size());
Expand All @@ -426,5 +425,15 @@ public void testTemplateMatcherMatchTwoSnippets() throws Exception {
CtElement match2 = matches.get(1);

assertTrue(match1.equals(match2));

//match using TemplateMatcher#matches method and query filter
matches = factory.getModel().getRootPackage().filterChildren(matcher).list();

assertEquals(2, matches.size());

match1 = matches.get(0);
match2 = matches.get(1);

assertTrue(match1.equals(match2));
}
}

0 comments on commit df55265

Please sign in to comment.