Skip to content

Commit

Permalink
added QueryBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Dec 1, 2016
1 parent 46a125b commit a71e8a0
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 8 deletions.
11 changes: 11 additions & 0 deletions src/main/java/spoon/reflect/declaration/CtElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtVisitable;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.QueryBuilder;
import spoon.reflect.visitor.Root;
import spoon.support.DerivedProperty;

Expand Down Expand Up @@ -169,6 +170,16 @@ <E extends CtElement> List<E> getAnnotatedChildren(
*/
<E extends CtElement> List<E> getElements(Filter<E> filter);

/**
* Returns QueryBuilder which will process query starting on this Element using Filter filter
* Use the {@QueryBuilder} fluent API to build more complex filter or use
* <ul>
* <li>{@QueryBuilder#list()} to return List of all matches
* <li>{@QueryBuilder#forEach(consumer)} to call consumer for each match
* </ul>
*/
<E extends CtElement> QueryBuilder<E> query(Filter<E> filter);

/**
* @param filter
* @return
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/spoon/reflect/visitor/ElementConsumer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright (C) 2006-2016 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.reflect.visitor;

import spoon.reflect.declaration.CtElement;

/**
* Functional interface used to accept elements.
* It is used for example by {@link QueryBuilder} and {@link CtElement#query(Filter)}
*
* @param <T> - the type of accepted element
*/
public interface ElementConsumer<T extends CtElement> {
void accept(T t);
}
7 changes: 3 additions & 4 deletions src/main/java/spoon/reflect/visitor/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private Query() {
* the filter which defines search scope and the matching criteria
*/
public static <E extends CtElement> List<E> getElements(ScopeAwareFilter<E> filter) {
return getElements(filter.getSearchScope(), filter);
return new QueryBuilder<>().then(filter).list();
}


Expand Down Expand Up @@ -76,9 +76,7 @@ public static <E extends CtElement> List<E> getElements(Factory factory,
*/
public static <E extends CtElement> List<E> getElements(
CtElement rootElement, Filter<E> filter) {
QueryVisitor<E> visitor = new QueryVisitor<>(filter);
visitor.scan(rootElement);
return visitor.getResult();
return new QueryBuilder<>().onInput(rootElement).then(filter).list();
}

/**
Expand Down Expand Up @@ -118,4 +116,5 @@ public static <R extends CtReference> List<R> getReferences(
return getElements(factory, filter);
}


}
161 changes: 161 additions & 0 deletions src/main/java/spoon/reflect/visitor/QueryBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* Copyright (C) 2006-2016 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.reflect.visitor;

import java.util.ArrayList;
import java.util.List;

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

/**
* Builder which can be used to build chain of Filters. The query built using this QueryBuilder uses this algorithm:
* <ol>
* <li>all the input elements registered by {@link #onInput(CtElement...)} are used to start query on first filter of the chain. See details below.
* <li>if there is next filter, then all matching elements, which pass filter X will be collected as input for the next filter
* <li>if there is no next filter, then all matching elements are sent to outputConsumer
* </ol>
*
* The input elements are used in query of filter like this:
* <ul>
* <li>In case of Filter which implements {@link ChainableFilter}, the input will be set as input parameter of the filter and then the Filter computes starting element of AST traversal
* <li>In case of Filter which implements {@link ScopeAwareFilter}, the input will be ignored, but this Filter computes starting element of AST traversal
* <li>In other cases, the input will be used as starting element of AST traversal
* </ul>
*
* Example of usage:
* <br>
* List&lt;Y> list = new QueryBuilder<>().onInput(anElement).then(filter1&lt;X>).then(filterN&lt;Y>).list();
* <br>
* new QueryBuilder<>().onInput(anElement).then(filter1&lt;X>).then(filterN&lt;Y>).forEach(Y element -> ..process elements...);
*
* Note: If the first filter implements {@link ScopeAwareFilter}, then you can run query without previous call of {@link #onInput(CtElement...)}.
* The search scope will be computed by first filter in such case.
*
* @param <T> defines type of returned elements. It is equal to type of last filter in the chain
*/
public class QueryBuilder<T extends CtElement> {

private ArrayList<CtElement> inputs = new ArrayList<>();
private List<Filter<?>> chainItems = new ArrayList<>();

public QueryBuilder() {
}

/**
* @param input - one or more input elements
* @return this instance, so it can be used for next query building.
*/
public QueryBuilder<T> onInput(CtElement... input) {
for (CtElement element : input) {
inputs.add(element);
}
return this;
}

/**
* Adds next filter into chain of filters.
*
* @param filter
* @return this instance, so it can be used for next query building.
*/
@SuppressWarnings("unchecked")
public <U extends CtElement> QueryBuilder<U> then(Filter<U> filter) {
chainItems.add(filter);
return (QueryBuilder<U>) this;
}

/**
* @return ElementConsumer which contains whole chain of built filters and which finally will call outputConsumer
*/
public ElementConsumer<CtElement> build(ElementConsumer<T> outputConsumer) {
return build(outputConsumer, 0);
}

/**
* @param outputConsumer - the consumer which should be used as last one in the chain
* @param level
* @return the ElementConsumer of the level level.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected ElementConsumer<CtElement> build(ElementConsumer<T> outputConsumer, int level) {
if (chainItems.size() == level) {
//there is no filter for this level. return outputConsumer
return (ElementConsumer<CtElement>) outputConsumer;
}
final Filter filter = (Filter) chainItems.get(level);
final QueryVisitor queryVisitor = new QueryVisitor(filter, build(outputConsumer, level + 1));
return new ElementConsumer<CtElement>() {
@Override
public void accept(CtElement inputElement) {
if (filter instanceof ChainableFilter && inputElement != null) {
try {
((ChainableFilter<CtElement, CtElement>) filter).setFilterInput(inputElement);
} catch (ClassCastException e) {
//the filter does not accepts this as input. Ignore this not matching element
return;
}
}
if (filter instanceof ScopeAwareFilter) {
queryVisitor.scan(((ScopeAwareFilter) filter).getSearchScope());
} else {
queryVisitor.scan(inputElement);
}
}
};
}

/**
* Process all registered filters on all input elements and return list of matching elements
* @return the result of the query as a List of elements
*/
public List<T> list() {
final List<T> list = new ArrayList<>();
forEach(new ElementConsumer<T>() {
@Override
public void accept(T element) {
list.add(element);
}
});
return list;
}

/**
* Process all registered filters on all input elements and send matching elements to consumer
* @param consumer
*/
public void forEach(ElementConsumer<T> consumer) {
ElementConsumer<CtElement> myConsumer = build(consumer);
if (inputs.isEmpty()) {
//there is no input element defined
if (chainItems.isEmpty()) {
//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)");
}
} else {
for (CtElement element : inputs) {
myConsumer.accept(element);
}
}
}
}
32 changes: 28 additions & 4 deletions src/main/java/spoon/reflect/visitor/QueryVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.List;

import spoon.SpoonException;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.filter.AbstractFilter;
import spoon.support.util.RtHelper;
Expand All @@ -30,13 +31,21 @@
public class QueryVisitor<T extends CtElement> extends CtScanner {
private final Filter<T> filter;
private final Class<T> filteredType;
private final List<T> result = new ArrayList<>();
private final ElementConsumer<T> consumer;

/**
* Constructs a query visitor with a given filter.
* @deprecated use {@link QueryBuilder} instead.
*/
@SuppressWarnings("unchecked")
@Deprecated
public QueryVisitor(Filter<T> filter) {
this(filter, new ArrayConsumer<T>());
}

/**
* Constructs a query visitor with a given filter and Consumer.
*/
public QueryVisitor(Filter<T> filter, ElementConsumer<T> consumer) {
super();
this.filter = filter;
if (filter instanceof AbstractFilter) {
Expand All @@ -45,13 +54,28 @@ public QueryVisitor(Filter<T> filter) {
Class<?>[] params = RtHelper.getMethodParameterTypes(filter.getClass(), "matches", 1);
filteredType = (Class<T>) params[0];
}
this.consumer = consumer;
}

//TODO remove with deprecated methods
private static class ArrayConsumer<T extends CtElement> implements ElementConsumer<T> {
final List<T> result = new ArrayList<>();
@Override
public void accept(T e) {
result.add(e);
}
}

/**
* Gets the result (elements matching the filter).
* @deprecated use {@link QueryBuilder} instead.
*/
@Deprecated
public List<T> getResult() {
return result;
if (consumer instanceof ArrayConsumer) {
return ((ArrayConsumer<T>) consumer).result;
}
throw new SpoonException("getResult() cannot be used on QueryVistor with consumer");
}

@SuppressWarnings("unchecked")
Expand All @@ -63,7 +87,7 @@ public void scan(CtElement element) {
try {
if ((filteredType == null || filteredType.isAssignableFrom(element.getClass()))) {
if (filter.matches((T) element)) {
result.add((T) element);
consumer.accept((T) element);
}
}
} catch (ClassCastException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.ModelConsistencyChecker;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.QueryBuilder;
import spoon.reflect.visitor.filter.AnnotationFilter;
import spoon.support.util.EmptyClearableList;
import spoon.support.util.EmptyClearableSet;
Expand Down Expand Up @@ -254,6 +255,11 @@ public <E extends CtElement> List<E> getElements(Filter<E> filter) {
return Query.getElements(this, filter);
}

@Override
public <E extends CtElement> QueryBuilder<E> query(Filter<E> filter) {
return new QueryBuilder<>().onInput(this).then(filter);
}

public <T extends CtReference> List<T> getReferences(Filter<T> filter) {
return getElements(filter);
}
Expand Down

0 comments on commit a71e8a0

Please sign in to comment.