Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: reusable and efficient CtQueries #1090

Merged
merged 28 commits into from
Jan 12, 2017
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b8f0ba4
Support of CtScanner based queries
pvojtechovsky Dec 26, 2016
e7ff290
added concept diagram
pvojtechovsky Jan 1, 2017
7a8b8e5
Simplified and thread safe implementation of CtBaseQueryImpl
pvojtechovsky Jan 3, 2017
8673d06
fix exception handling. Improve tests
pvojtechovsky Jan 3, 2017
065a7c1
Remove generic from CtQueryable, add CtQuery#list(Class)
pvojtechovsky Jan 5, 2017
ca635d1
rename CtBaseQuery#apply to evaluate
pvojtechovsky Jan 5, 2017
15f1560
added sample query: OverridenMethodQuery + tests
pvojtechovsky Jan 5, 2017
9d2d62f
fix compilation error
pvojtechovsky Jan 5, 2017
a2ef090
fix compilation error
pvojtechovsky Jan 5, 2017
3a8859a
add missing javadoc and CtQuery#setInput
pvojtechovsky Jan 5, 2017
a525092
fix return type of setInput
pvojtechovsky Jan 5, 2017
67e1c92
merge CtBaseQueryImpl into CtQueryImpl
pvojtechovsky Jan 5, 2017
33a92fb
removes Step + doc
monperrus Jan 6, 2017
741c6b6
improve query test
pvojtechovsky Jan 8, 2017
e242717
return CtQueryImpl instead of CtQuery
pvojtechovsky Jan 8, 2017
0fcdde1
fix checkstyle problem
pvojtechovsky Jan 8, 2017
6f00843
remove by mistake committed test
pvojtechovsky Jan 8, 2017
62b9a03
pass IntercessionTest
pvojtechovsky Jan 8, 2017
bb0bc1e
improve tests and fix NPE
pvojtechovsky Jan 8, 2017
7317b9a
<R extends Object> List<R> list()
pvojtechovsky Jan 8, 2017
7868e2d
remove useless generic <T extends CtQuery> T setInput(Object... input);
pvojtechovsky Jan 8, 2017
9d0f355
remove query-elements.png
pvojtechovsky Jan 8, 2017
ff8a250
add useless generic to pass the test
pvojtechovsky Jan 8, 2017
f95defb
update tests
monperrus Jan 8, 2017
fd13bbb
update doc
monperrus Jan 9, 2017
323ea3a
fix the documentation
pvojtechovsky Jan 10, 2017
954c371
javaDoc JDK 8 compatibility
pvojtechovsky Jan 11, 2017
7705aa0
improve CtQueryImpl code and fix documentation
pvojtechovsky Jan 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions doc/filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,35 +39,41 @@ list3 = rootPackage.filterChildren(
return field.getModifiers.contains(ModifierKind.PUBLIC);
}
}
);
).list();
```

Queries
-------

The Query, introduced in Spoon 5.5 by Pavel Vojtechovsky, is an improved filter mechanism:

* matching can be done with a Java 8 lambda
* queries can be done with a Java 8 lambda
* queries can be chained
* queries can be reused on multiple input elements

`CtQueryable#filterChildren(Filter)` is a filtering query that can be chained:
**Queries with Java8 lambdas**: `CtQueryable#map(CtFunction)`enables you to give Java 8 lambda as query.

```java
// returns a list of String
list = package.map((CtClass c) -> c.getSimpleName()).list();
```

**Compatibility with existing filters** `CtQueryable#filterChildren(Filter)` is a filtering query that can be chained:

```java
// collecting all methods of deprecated classes
list2 = rootPackage
.filterChildren(new AnnotationFilter(Deprecated.class))
.filterChildren(new TypeFilter(CtMethod.class)).list();
.filterChildren(new AnnotationFilter(Deprecated.class)).list()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you keep an example witch chaining queries here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess @monperrus removed it.

```

`CtQueryable#map(CtFunction)`enables you to give Java 8 lambda as query.
A boolean return value of the lambda tells whether the elements are selected for inclusion or not.

```java
// creating a custom filter to select all public fields using java 8 lambda
list3 = rootPackage.filterChildren((CtField field)->field.getModifiers.contains(ModifierKind.PUBLIC)).list();
```

If the CtFunction returns an object, this object is given as result to the query:
**Chaining** If the CtFunction returns an object, this object is given as result to the next step of the query:

```java
// a query which processes all not deprecated methods of all deprecated classes
Expand All @@ -81,3 +87,14 @@ rootPackage

Finally, if the CtFunction returns and Iterable or an Array then each item of the collection/array is sent to next query step or result.

**Query reuse**. Method `setInput` allows you to reuse the same query over multiple inputs.

```java
// here is the query
CtQuery q = new CtQueryImpl().map((CtClass c) -> c.getSimpleName());
// using it on a first input
String s1 = q.setInput(cls).list().get(0);
// using it on a second input
String s2 = q.setInput(cls2).list().get(0);
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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.chain;

/**
* Represents a function, as {@link CtFunction}. However, the main difference is that
* while a {@link CtFunction} returns something with a standard Java return keyword,
* a {@link CtConsumableFunction} returns something by passing the returned object
* as parameter to the given outpuConsumer#accept. This enables to write efficient and concise code in certain situations.
* It also enables one to emulate several returns, by simply calling several times accept, while not paying
* the code or performance price of creating a list or an iterable object.
*
* It is typically used as parameter of {@link CtQueryable#map(CtConsumableFunction)}, can be written as one-liners
* with Java8 lambdas:.`cls.map((CtClass&t;?> c, CtConsumer&t;Object> out)->out.accept(c.getParent()))`
*
* @param <T> the type of the input to the function
*/
public interface CtConsumableFunction<T> {
/**
* Evaluates the function on the given input.
* @param input the input of the function
* @param outputConsumer the consumer which accepts the results of this function.
*/
void apply(T input, CtConsumer<Object> outputConsumer);
}
27 changes: 27 additions & 0 deletions src/main/java/spoon/reflect/visitor/chain/CtConsumer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 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.chain;

/**
* The functional interface used to receive objects.
* It is used for example to receive results of the query in {@link CtQuery#forEach(CtConsumer)}
*
* @param <T> - the type of accepted elements
*/
public interface CtConsumer<T> {
void accept(T t);
}
2 changes: 1 addition & 1 deletion src/main/java/spoon/reflect/visitor/chain/CtFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/**
* Abstraction for functions in the Spoon realm.
* It is used in the query stack, for example by {@link CtQueryable#map(CtFunction)}
* Compatible with Java 8 lambdas, hence enable to write queries with lambdas.
* It is compatible with Java 8 lambdas, hence enable to write one-liner queries with lambdas.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
Expand Down
110 changes: 89 additions & 21 deletions src/main/java/spoon/reflect/visitor/chain/CtQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,124 @@
*/
package spoon.reflect.visitor.chain;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.Filter;

import java.util.List;

/**
* CtQuery represents a query, which can be used to traverse a spoon model and collect
* children elements in several ways.<br>
* children elements in several ways.<br/>
*
* <br>
* Use {@link CtQueryable#map(CtFunction)}} or {@link CtQueryable#filterChildren(Filter)} to create a new query starting from an existing element.<br>
* Creation: A query is created either from a {@link CtElement}, or it can be defined first from {@link CtQueryImpl} and bound to root elements
* afterwards using {@link CtQuery#setInput(Object...)}.<br/>
*
* The main methods are:
* <ul>
* <li> {@link CtQueryable#map(CtFunction)} - uses a lambda expression to return any model elements that are directly accessible from an input element.
* <li> {@link CtQueryable#filterChildren(Filter)} - uses {@link Filter} instances to filter children of an element
* <li> {@link #list()} - to evaluate the query and return a list of elements produced by this query.
* </ul>
* The query can be used several times.<br>
* A CtQuery is lazily evaluated once {{@link #list()}} is called.
* Usually a new query is created each time when one needs to query something.
* However, reusing a {@link CtQuery} instance makes sense when the same query has to be evaluated
* several times in a loop.
* Chaining: In a query several steps can be chained, by chaining calls to map functions. The non-null outputs of one step
* are given as input to the next step. An iterable or array output is considered as a set of different inputs for the next step.
*
* Evaluation: A CtQuery is lazily evaluated once {@link CtQuery#list()} or {@link CtQuery#forEach(CtConsumer)} are called.<br/>
*
* @param <O> the type of the element produced by this query
*/
public interface CtQuery<O> extends CtQueryable {
public interface CtQuery extends CtQueryable {

/**
* Recursively scans all children elements of an input element.
* The matched child element for which (filter.matches(element)==true) are sent to the next query step.
* Essentially the same as {@link CtElement#getElements(Filter)} but more powerful, because it
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure here: when you say filtered elements are sent to the next query step, it's not mandatory: you can just get the list of filtered elements through list() right? The meaning of your comment here is that the filterChildren can be used in the chain as it returns a CtQuery?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the filterChildren can be used in the chain as it returns a CtQuery?

I do not understand

* can be chained with other subsequent queries.
*
* Note: the input element (the root of the query, `this` if you're in {@link CtElement}) is also checked and may thus be also sent to the next step.
* The elements which throw {@link ClassCastException} during {@link Filter#matches(CtElement)}
* are considered as **not matching**, ie. are excluded.
*
* @param filter used to filter scanned children elements of the AST tree
* @return this to support fluent API
*/
@Override
<R extends CtElement> CtQuery filterChildren(Filter<R> filter);


/**
* Query elements based on a function, the behavior depends on the return type of the function.
* <table>
* <tr><td><b>Return type of `function`</b><td><b>Behavior</b>
* <tr><td>{@link Boolean}<td>Select elements if the returned value of `function` is true (as for {@link Filter}).
* <tr><td>? extends {@link Object}<td>Send the returned value of `function` to the next step
* <tr><td>{@link Iterable}<td>Send each item of the collection to the next step
* <tr><td>{@link Object[]}<td>Send each item of the array to the next step
* </table><br>
*
* @param function a Function with one parameter of type I returning a value of type R
* @return this to support fluent API
*/
@Override
<I, R> CtQuery map(CtFunction<I, R> function);

/**
* Sets (binds) the input of the query.
* If the query is created by {@link CtElement#map} or {@link CtElement#filterChildren(Filter)},
* then the query is already bound to this element.
* A new call of {@link CtQuery#setInput(Object...)} is always possible, it resets the current binding and sets the new one.
*
* @param input
* @return this to support fluent API
*/
<T extends CtQuery> T setInput(Object... input);

/**
* Actually evaluates the query and for each produced output element of the last step,
* calls `consumer.accept(outputElement)`.
*
* This avoids to create useless intermediate lists.
*
* @param consumer The consumer which accepts the results of the query
*/
<R> void forEach(CtConsumer<R> consumer);

/**
* Actually evaluates the query and returns all the elements produced in the last step.<br>
* Note: The type R of the list is not checked by the query. So use the type, which matches the results of your query,
* otherwise the ClassCastException will be thrown when reading the list.
* @return the list of elements collected by the query.
* @see #forEach(CtConsumer) for an efficient way of manipulating the elements without creating an intermediate list.
*/
<R extends Object> List<R> list();

/**
* actually evaluates the query and returns all the produced elements collected in a List
* Same as {@link CtQuery#list()}, but with static typing on the return type
* and the final filtering, which matches only results, which are assignable from that return type.
*
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So classes which are not assignable for that return type are just ignored and you cannot have a ClassCastException?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, exactly

* @return the list of elements collected by the query.
*/
List<O> list();
<R> List<R> list(Class<R> itemClass);

/**
* Defines whether this query will throw {@link ClassCastException}
* when the output of the previous step cannot be cast to type of input of next step.
* The default value is {@link QueryFailurePolicy#FAIL}<br>
* The default value is {@link QueryFailurePolicy#FAIL}, which means than exception is thrown when there is a mismatch<br>
*
* Note: The {@link CtQueryable#filterChildren(Filter)} step never throws {@link ClassCastException}
*
* @param policy the policy
* @return this to support fluent API
*/
CtQuery<O> failurePolicy(QueryFailurePolicy policy);
CtQuery failurePolicy(QueryFailurePolicy policy);

/**
* Sets the name of current query, to identify the current step during debugging of a query
* @param name
* @return this to support fluent API
*/
CtQuery<O> name(String name);
CtQuery name(String name);

/**
* Same as {@link CtQuery#map(CtFunction)}, but the returned object is not handled
* by java's return statement, but by a call to {@link CtConsumer#accept(Object)}, this
* allows efficient and easy to write chained processing, see {@link CtConsumableFunction}.
*
* @param queryStep
* @return this to support fluent API
*/
@Override
<I> CtQuery map(CtConsumableFunction<I> queryStep);
}
Loading