Skip to content

Commit

Permalink
queryPipes
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Dec 3, 2016
1 parent cb4deb3 commit 5c4d19c
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 156 deletions.
98 changes: 46 additions & 52 deletions src/main/java/spoon/reflect/visitor/FilterPipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@
import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.chain.ElementPipeImpl;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.support.util.RtHelper;
import spoon.support.util.SafeInvoker;

/**
* if the input element is matching the filter (or there is no filter),
* then element is sent to output consumer
*
* @param <T> type of element produced by this pipe
* @param <T> type of element produced by this pipe - matched by the filter
*/
public class FilterPipe<T extends CtElement> extends ElementPipeImpl<T> {
public class FilterPipe<T extends CtElement> extends ElementPipeImpl<CtElement, T> {

private Filter<? super CtElement> filter;
private SafeInvoker<Filter<? super CtElement>> invoke_matches = new SafeInvoker<>(CtElement.class, "matches");
private SafeInvoker<Filter<? super CtElement>> invoke_setFilterInput = new SafeInvoker<>(CtElement.class, "setFilterInput");
/*
* Type which is accepted by the Filter#matches()
*/
private Class<? super CtElement> filterType;
private SafeInvoker<Filter<? super CtElement>> invoke_matches = new SafeInvoker<>("matches");
private SafeInvoker<Filter<? super CtElement>> invoke_setFilterInput = new SafeInvoker<>("setFilterInput");

public FilterPipe() {
}
Expand All @@ -28,27 +25,31 @@ public FilterPipe(Filter<? super CtElement> filter) {
}

@Override
public void accept(CtElement element) {
if (element == null) {
return;
public boolean matches(CtElement element) {
if (filter == null) {
return true;
}
//first check if filter matches
boolean matches = true;
if (filter != null) {
try {
if ((filterType == null || filterType.isAssignableFrom(element.getClass()))) {
matches = filter.matches(element);
}
} catch (ClassCastException e) {
// expected, some elements are not of type T
// Still need to protect from CCE, if users extend Filter (instead of AbstractFilter) directly,
// but with concrete type parameter
matches = false;
if (filter instanceof AbstractFilter) {
//Check if required type of AbstractFilter is matching
@SuppressWarnings("rawtypes")
Class<?> filterType = ((AbstractFilter) filter).getType();
if (filterType != null && filterType.isAssignableFrom(element.getClass()) == false) {
//required type is defined and it is not matching
return false;
}
}
if (matches) {
//it matches. Send the element to output
fireElement(element);
if (invoke_matches.isParameterTypeAssignableFrom(element) == false) {
//the element is not matching with parameter type of Filter#matches method
return false;
}
try {
return filter.matches(element);
} catch (ClassCastException e) {
// expected, some elements are not of type T
// Still need to protect from CCE, if users extend Filter (instead of AbstractFilter) directly,
// but with concrete type parameter
return false;
}
}

Expand All @@ -59,38 +60,31 @@ public Filter<? super CtElement> getFilter() {
@SuppressWarnings("unchecked")
public void setFilter(Filter<? super CtElement> filter) {
this.filter = filter;
if (filter != null) {
if (filter instanceof AbstractFilter) {
filterType = ((AbstractFilter<? super CtElement>) filter).getType();
} else {
Class<?>[] params = RtHelper.getMethodParameterTypes(filter.getClass(), "matches", 1);
filterType = (Class<? super CtElement>) params[0];
}
} else {
filterType = null;
}
invoke_matches.setDelegate(filter);
invoke_setFilterInput.setDelegate(filter);
}

public Class<? super CtElement> getFilterType() {
return filterType;
}

public void runOnScanner(CtElement inputElement, QueryVisitor<T> p_queryVisitor) {
if (filter != null) {
if (filter instanceof ChainableFilter && inputElement != null) {
public CtElement getSearchScopeOfElement(CtElement inputElement) {
if (filter == null) {
//there is no filter, all the elements starting from inputElement has to be visited
return inputElement;
}
if (invoke_setFilterInput.hasMethod()) {
//the filter has method setFilterInput
if (invoke_setFilterInput.isParameterTypeAssignableFrom(inputElement)) {
//the inputElement may be matching with setFilterInput parameter type
try {
((ChainableFilter<CtElement, CtElement>) filter).setFilterInput(inputElement);
invoke_setFilterInput.invoke(inputElement);
} catch (ClassCastException e) {
//the filter does not accepts this as input. Ignore this not matching element
return;
//the filter does not accepts this as input. Ignore this not matching element.
//Do not run scanner on it
return null;
}
}
if (filter instanceof ScopeAwareFilter) {
queryVisitor.scan(((ScopeAwareFilter) filter).getSearchScope());
} else {
queryVisitor.scan(inputElement);
}
}
scan(element);
if (filter instanceof ScopeAwareFilter) {
return ((ScopeAwareFilter<? super CtElement>) filter).getSearchScope();
}
return inputElement;
}
}
7 changes: 5 additions & 2 deletions src/main/java/spoon/reflect/visitor/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ public static <E extends CtElement> List<E> getElements(Factory factory,
}

/**
* Returns all the program elements that match the filter.
* Returns all the program elements that match the filter starting from defined rootElement.
* This method ignores search scope provided by {@link ScopeAwareFilter#getSearchScope()},
* so normally it is better to call {@link #getElements(ScopeAwareFilter)}
* or {@link CtElement#query(Filter)}
*
* @param <E>
* the type of the sought program elements
Expand All @@ -77,7 +80,7 @@ public static <E extends CtElement> List<E> getElements(Factory factory,
*/
public static <E extends CtElement> List<E> getElements(
CtElement rootElement, Filter<E> filter) {
List<E> list = new ArrayList<>();
final List<E> list = new ArrayList<>();
QueryVisitor<E> visitor = new QueryVisitor<>(filter, new ElementConsumer<E>() {
@Override
public void accept(E element) {
Expand Down
13 changes: 5 additions & 8 deletions src/main/java/spoon/reflect/visitor/QueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.ArrayList;
import java.util.List;

import spoon.SpoonException;
import spoon.reflect.declaration.CtElement;

/**
Expand Down Expand Up @@ -84,7 +83,8 @@ public <U extends CtElement> QueryBuilder<U> then(Filter<U> filter) {
* and it's output is registered to call next level of the chain
* @return this instance, so it can be used for next query building.
*/
public <U extends CtElement> QueryBuilder<U> then(ElementPipe<U> pipe) {
@SuppressWarnings("unchecked")
public <U extends CtElement> QueryBuilder<U> then(ElementPipe<? super CtElement, U> pipe) {
chainItems.add(pipe);
return (QueryBuilder<U>) this;
}
Expand All @@ -94,6 +94,7 @@ public <U extends CtElement> QueryBuilder<U> then(ElementPipe<U> pipe) {
* If the consumer does not implements {@link ElementSupplier}, then it is terminal consumer of this chain
* @return this instance, so it can be used for next query building.
*/
@SuppressWarnings("unchecked")
public <U extends CtElement> QueryBuilder<U> then(ElementConsumer<? super CtElement> consumer) {
chainItems.add(consumer);
return (QueryBuilder<U>) this;
Expand Down Expand Up @@ -152,12 +153,8 @@ public void forEach(ElementConsumer<T> consumer) {
//there is no filter defined. Do not call consumer
return;
}
if (chainItems.get(0) instanceof ScopeAwareFilter) {
//first filter is ScopeAwareFilter, so it computes start element of AST traversal. Use null as start element
myConsumer.accept(null);
} else {
throw new SpoonException("The QueryBuilder is missing the input element. Call QueryBuilder.onInput(element)");
}
//some filters can define starting point of AST traversal, so null makes sense for them
myConsumer.accept(null);
} else {
for (CtElement element : inputs) {
myConsumer.accept(element);
Expand Down
24 changes: 10 additions & 14 deletions src/main/java/spoon/reflect/visitor/QueryVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package spoon.reflect.visitor;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.chain.SafeElementConsumerWrapper;

/**
* A simple visitor that takes a filter and returns all the elements that match
Expand Down Expand Up @@ -46,26 +45,29 @@ public QueryVisitor(ElementConsumer<T> visitedElementConsumer) {
* Constructs a query visitor with a given filter.
*/
@SuppressWarnings("unchecked")
public QueryVisitor(Filter<? super CtElement> filter) {
filterPipe.setFilter(filter);
public QueryVisitor(Filter<T> filter) {
filterPipe.setFilter((Filter<? super CtElement>) filter);
}

/**
* Constructs a query visitor with a given filter and Consumer.
*/
@SuppressWarnings("unchecked")
public QueryVisitor(Filter<? super CtElement> filter, ElementConsumer<T> outputConsumer) {
public QueryVisitor(Filter<T> filter, ElementConsumer<T> outputConsumer) {
this(filter);
setOutput(outputConsumer);
}


/**
* starts traversal of AST from element. Same like {@link #scan(CtElement))}
* starts traversal of AST from element, but comparing to {@link #scan(CtElement)} The configured filter may influence the starting point
*/
@Override
public void accept(CtElement inputElement) {
filterPipe.runOnScanner(inputElement, this);
CtElement startElement = filterPipe.getSearchScopeOfElement(inputElement);
if (startElement != null) {
scan(startElement);
}
}

/**
Expand All @@ -74,18 +76,12 @@ public void accept(CtElement inputElement) {
@SuppressWarnings("unchecked")
@Override
public void setOutput(ElementConsumer<T> visitedElementsConsumer) {
//use SafeElementConsumerWrapper to effectively call the consumer only with the elements which it can accept
//without unwanted CCE
if (visitedElementsConsumer instanceof SafeElementConsumerWrapper) {
this.visitedElementConsumer = (SafeElementConsumerWrapper<T>) visitedElementsConsumer;
} else {
this.visitedElementConsumer = new SafeElementConsumerWrapper<>(visitedElementsConsumer);
}
filterPipe.setOutput(visitedElementsConsumer);
}

@Override
public void scan(CtElement element) {
visitedElementConsumer.accept(element);
filterPipe.accept(element);
super.scan(element);
}
}
28 changes: 27 additions & 1 deletion src/main/java/spoon/reflect/visitor/chain/ElementPipeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,37 @@ public void setOutput(ElementConsumer<O> outputConsumer) {

@Override
public void accept(I element) {
if (element == null) {
return;
}
onAccept(element);
boolean matches;
try {
matches = matches(element);
} catch (ClassCastException e) {
// expected, some elements are not of type T
// Still need to protect from CCE, if users extend Filter (instead of AbstractFilter) directly,
// but with concrete type parameter
matches = false;
}
if (matches) {
//it matches. Send the element to output
onMatches(element);
}
}

protected void onAccept(I p_element) {
}

protected void onMatches(I element) {
fireElement(element);
}

public boolean matches(I element) {
return true;
}

protected void fireElement(CtElement element) {
outputConsumer.accept(element);
}

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package spoon.reflect.visitor.chain;

import spoon.Launcher;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.ElementConsumer;
import spoon.reflect.visitor.ElementPipe;
import spoon.support.util.SafeInvoker;

/**
* wraps another {@link ElementConsumer} and silently catches all ClassCastExceptions,
* which would be thrown by calling of ElementConsumer which accepts different class
* wraps another {@link ElementConsumer} and silently catches all ClassCastExceptions,
* which would be thrown by calling of ElementConsumer which accepts different class
*
* @param <T> type of elements accepted by this ElementConsumer
*/
public class SafeElementConsumerWrapper<T extends CtElement> implements ElementPipe<T, T> {
private SafeInvoker<ElementConsumer<T>> safeInvoker = new SafeInvoker<>(CtElement.class, "accept");

private SafeInvoker<ElementConsumer<T>> invoke_accept = new SafeInvoker<>("accept");

public SafeElementConsumerWrapper() {
}

Expand All @@ -25,21 +26,28 @@ public SafeElementConsumerWrapper(ElementConsumer<T> delegate) {

@Override
public void accept(CtElement element) {
safeInvoker.invoke(element);
try {
invoke_accept.invoke(element);
} catch (ClassCastException e) {
// expected, some elements are not of type T
// Still need to protect from CCE, if client uses Lambda expression which does not provide real parameter type.
//log this exception with lower level, because it can be expected behavior that it fails here...
//but log it anyway to make it possible to debug cases when it really fails because of unexpected reasons
Launcher.LOGGER.trace("ElementConsumer does not accepts such elements. It may be expected behavior.", e);
}
}

@Override
public void setOutput(ElementConsumer<T> delegate)
{
this.safeInvoker.setDelegate(delegate);
public void setOutput(ElementConsumer<T> delegate) {
this.invoke_accept.setDelegate(delegate);
}

public ElementConsumer<T> getOutput() {
return safeInvoker.getDelegate();
return invoke_accept.getDelegate();
}

@SuppressWarnings("unchecked")
public Class<T> getOutputType() {
return (Class<T>) safeInvoker.getParamType();
return (Class<T>) invoke_accept.getParamType();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package spoon.reflect.visitor.filter;

import java.lang.reflect.Method;

import spoon.SpoonException;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.Filter;
Expand Down Expand Up @@ -43,11 +45,11 @@ public AbstractFilter(Class<? super T> type) {
*/
@SuppressWarnings("unchecked")
public AbstractFilter() {
Class<?>[] params = RtHelper.getMethodParameterTypes(getClass(), "matches", 1);
if (params == null) {
Method method = RtHelper.getMethod(getClass(), "matches", 1);
if (method == null) {
throw new SpoonException("The method matches with one parameter was not found on the class " + getClass().getName());
}
this.type = (Class<T>) params[0];
this.type = (Class<T>) method.getParameterTypes()[0];
}

public Class<T> getType() {
Expand Down
1 change: 0 additions & 1 deletion src/main/java/spoon/support/util/RtHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package spoon.support.util;

import spoon.SpoonException;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLiteral;
Expand Down
Loading

0 comments on commit 5c4d19c

Please sign in to comment.