-
-
Notifications
You must be signed in to change notification settings - Fork 351
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
46a125b
commit a71e8a0
Showing
6 changed files
with
238 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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<Y> list = new QueryBuilder<>().onInput(anElement).then(filter1<X>).then(filterN<Y>).list(); | ||
* <br> | ||
* new QueryBuilder<>().onInput(anElement).then(filter1<X>).then(filterN<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); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters