-
-
Notifications
You must be signed in to change notification settings - Fork 351
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: new query architecture with support for Java8 queries #1018
Conversation
Nice. 👍 One day we will able to write modern code that uses spoon :D |
Excellent indeed! Could you add a presentation of this new feature in |
ok, but I would first like to finish real chaining of filters using a new QueryBuilder and PR #1017. The combination of these two things will start to be interesting. I will then document it together. |
606b470
to
5679c8d
Compare
Documentation added. |
Thanks Do you know that you can use java 8 features in the tests? |
yes, I found it when I added spoon into Eclipse. The spoon code is java 7 and tests are java 8 compatible |
I have found one problem of this PR. It is not backward compatible with existing filters. For example CtFieldReference<?> ref = ...
List<CtFieldAccess<?>> expressions = someClass.getElements(new FieldAccessFilter(ref)); will behave different when we improve FieldAccessFilter by way it implements CtFieldReference<?> ref = ...
List<CtFieldAccess<?>> expressions = ref.getElements(new FieldAccessFilter()); How to solve that? By the way, Travis failed. Should I change something? |
I have rolled back CtElement#getElements, because it is not backward compatible. I would actually prefer solution like this: CtFieldReference<?> ref = ...
List<CtFieldAccess<?>> expressions = ref.search(new FieldAccessFilter()).list();
//or optionally this should be supported too
ref.search(new FieldAccessFilter()).forEach(CtFieldAccess<?>fa->{...}); Note that If you like this approach, then it is question how to make a PR for that? |
I have added QueryBuilder to this PR. The QueryBuilder is now used in Query API too, so it is tested by all test code which uses Queries. The documentation does not contain QueryBuilder yet. I will update it probably tomorrow. This old code CtFieldReference<?> ref = ...
List<CtFieldAccess<?>> expressions = someClass.getElements(new FieldAccessFilter(ref)); can be written this way: CtFieldReference<?> ref = ...
List<CtFieldAccess<?>> expressions = ref.query(new FieldAccessFilter()).list(); after |
This new code needs further refactoring and comments, test, etc. But the idea is like this: class Context {
CtMethod method;
CtMethod overridenMethod;
}
Context context = new Context();
launcher.getFactory().Package().getRootPackage().query(new TypeFilter(CtMethod.class))
.then(new ElementPipeImpl<CtMethod,CtMethod>(){
@Override
protected void onAccept(CtMethod element) {
context.method = element;
}
}).then(new OverriddenMethodFilter())
.then(new ElementPipeImpl<CtMethod,CtMethod>(){
@Override
protected void onAccept(CtMethod element) {
context.overridenMethod = element;
...
}
}).list(); it searches for all methods and then for all overriden methods of found methods. The Context class is used just as example how client can collect data from the middle of the query processing. |
5c4d19c
to
80c7dc1
Compare
This PR now contains support for query step chain. I implemented it again since beginning, so please forget olders concepts and check if they are still used in this PR or not. See the first draft of documentation in doc/Filter.md. The code is not finished yet.
Please review the names of the new Classes, packages and methods. Let me know if something is not understandable or confusing. Do you have a better name for AsyncFunction? |
I am still not finished, but query like this works now. Do you know what it does?? launcher.getFactory().Package().getRootPackage().query().scan((CtClass<?> c)->{return true;})
.then((CtClass<?> c)->c.getSuperInterfaces())
.then((CtTypeReference<?> iface)->iface.getTypeDeclaration())
.then((CtType<?> iface)->iface.getAllMethods())
.then((CtMethod<?> method)->method.getSimpleName().equals("make"))
.then((CtMethod<?> m)->m.getType())
.then((CtTypeReference<?> t)->t.getTypeDeclaration())
.forEach((CtInterface<?> c)->{
assertEquals("ITostada", c.getSimpleName());
}); It scans AST from root package, accepts CtClasses, then it navigates to all super interfaces of found classes and searches there for each method whose name is equal to "make". If an method is found then it takes its return type and checks that it has found expected value. I have removed QueryStep#matches. It actually uses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks very promising, but the Javadoc is too scarce to 100% grasp the design (one line per class describing the responsibility and key collaborating classes -- callers, callees, hierarchy -- is usually enough is single responsibility is applied).
* @param <T> the type of the input to the function | ||
* @param <R> the type of the result of the function | ||
*/ | ||
public interface AsyncFunction<T, R> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name and javadoc not clear enough to convey the core design intuition and contract
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why async? executed asynchronously? or do you mean deferred?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that Async is not good name for that. Deferred has a time meaning too, but it is better then Async. The purpose of this function is to produce zero, one or more elements from one input element. May be names like
- ChainableFunction
- CallbackFunction
- ProductFunction
- ...
I like no one :-(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChainableFunction is better IMHO
|
||
import spoon.support.util.SafeInvoker; | ||
|
||
public class AsyncFunctionQueryStep<O> extends QueryStep<O> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
javadoc?
* @param <T> the type of the input to the function | ||
* @param <R> the type of the result of the function | ||
*/ | ||
public interface Function<T, R> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clear
/** | ||
* @param <T> the type of the input to the function | ||
*/ | ||
public interface Predicate<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the same as Filter? (but the generic T?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes generic.
- QueryStep chain can be used to process elements which are not inherited from CtElement. I am not sure whether we needs that.
- Spoon
Filter
has already old meaning like Filtering of children produced by CtScanner, I wanted something more generic, what can be used to decide whether input element should be sent to output or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, could you put this explanation in the Javadoc?
* @param <I> - input type | ||
* @param <O> - output type | ||
*/ | ||
public abstract class QueryStep<O> implements Consumer<Object> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about having an interface QueryStep
? (in Spoon, the design is very interface-oriented: most key concepts and key methods are defined in interfaces). The method then
seems indeed essential for the design.
import spoon.reflect.visitor.chain.AsyncFunction; | ||
import spoon.reflect.visitor.chain.Consumer; | ||
|
||
public class Scann extends CtScanner implements AsyncFunction<CtElement, CtElement> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does Scann
mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be used in the QueryStep chain to visit all the children of chain input element.
It is used automatically In combination with filter in method QuertStep#scan(Filter)
, but I guess it can be use independent using ...query().then(new Scann())....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and why this strange name?
*/ | ||
public static Class<?>[] getMethodParameterTypes(Class<?> clazz, String methodName, int numParams) { | ||
public static Method getMethod(Class<?> clazz, String methodName, int numParams) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non backward-compatible change on public method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method getMethodParameterTypes
is there only few days. I guess nobody is using yet. And because nobody needs it, I removed it to minimize amount of code.
But if you like this method, then I will put it back. No problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK if it's a recent one.
import spoon.SpoonException; | ||
|
||
/** | ||
* @param <T> the Type of the delegate object, which is target of method invocation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
contract? responsibility?
@@ -332,6 +335,8 @@ public void testOverriddenMethodFromAbstractClass() throws Exception { | |||
final CtClass<AbstractTostada> aClass = launcher.getFactory().Class().get(AbstractTostada.class); | |||
|
|||
assertEquals(0, Query.getElements(launcher.getFactory(), new OverriddenMethodFilter(aClass.getMethodsByName("prepare").get(0))).size()); | |||
assertEquals(0, Query.query().then(new OverriddenMethodFilter()).list(aClass.getMethodsByName("prepare").get(0)).size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hard to understand to start with a call to "then" with no element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Query.query().then(...)
yes, I do not like it too. But it was minimum code at the beginning. If you have better idea, then tell me please. Otherwise I suggest to wait little bit until I (or somebody else) start to use it and come with real experience and needs... I am also looking for a better way ...
QueryStep<CtClass<?>> l_qv = launcher.getFactory().getModel().getRootPackage().query().scan(new TypeFilter<>(CtClass.class)); | ||
|
||
assertEquals(0, context.counter); | ||
l_qv.forEach(cls->{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the foreach
very much
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see foreach
more often in Spoon API - even in core model ... I will make other PR. But later ...
|
||
launcher.getFactory().Package().getRootPackage().query().scan(new TypeFilter<CtMethod<?>>(CtMethod.class)) | ||
.thenConsume((CtMethod<?> method) -> {context.method = method;}) | ||
.then(new OverriddenMethodFilter()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what you call then
is a functional map
(https://en.wikipedia.org/wiki/Map_(higher-order_function))?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I need suggestions and links like that! I do not have theoretic training in these things
Context context = new Context(); | ||
|
||
launcher.getFactory().Package().getRootPackage().query().scan(new TypeFilter<CtMethod<?>>(CtMethod.class)) | ||
.thenConsume((CtMethod<?> method) -> {context.method = method;}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the difference between then
and thenConsume
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that we must visibly distinguish between
A) then(...a function which produces elements for next step...)
B) thenConsume(... a function which just eats elements and produces nothing....)
We cannot let this distinction on the java compiler, because it would be confusing. Example:
(element)->element.doSomething()
Is that Consumer, which produces nothing or is it a function which produces result? Sometime the client wants to call function which returns values as consumer too.
If you write code like
...query().then(X).then(Y).then(Z)
then it processes X and the result of X is sent to Y and result of Y is sent to Z
But if you write code like
...query().then(X).thenConsume(Y).then(Z)
then it processes X and the result of X is sent to Y, which produces nothing and result of X is sent to Z
I am again open to change function name to anything better. May be I should learn some RxJava API and take over the names from that. Is there anybody who knows that API and can suggest names which are generally used and understandable?
A) then(...a function which produces elements for next step...)
B) thenConsume(... a function which just eats elements and produces
nothing....)
A) map and B) forEach (or "iter")?
But if you write code like
|...query().then(X).thenConsume(Y).then(Z)|
then it processes X and the result of X is sent to Y, which produces
nothing and result of *X* is sent to Z
That's quite counter-intuitive.to me. Do we need/want this behavior?
|
I will replace
I wanted that to be able to collect the intermediate results of the query (model browsing). But it can be replaced by query().then((element)->{
...do required consuming...
return element;
}) so we have one query "command" (
I prefer |
17ffb07
to
c191d5d
Compare
... still not ready for merge. I have pushed it here to show the current state and to see whether it pass the tests. Feedback is welcome, of course. |
OK
|
c191d5d
to
10abd98
Compare
I have commented all the new code and did final simplifications and renames. I am satisfied with that version, but if you have any suggestions, or I forgot something then let me know please. The test cases are still missing. Please make a review of new API so I can finally document it in Filter.md. I will do that probably on Saturday evening. |
@@ -35,7 +36,7 @@ | |||
* element). | |||
*/ | |||
@Root | |||
public interface CtElement extends FactoryAccessor, CtVisitable, Cloneable { | |||
public interface CtElement extends FactoryAccessor, CtVisitable, Cloneable, QueryComposer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QueryComposer contains all the methods, which can begin with creation of some query. It is the replacement for CtElement#query()
. With QueryComposer you can easily start to query spoon model starting from any element.
Note: QueryComposer is implemented by QueryStep too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let's first agree on this. I'm not fan on this because you cannot really say that a CtElement is a QueryComposer. However, I understand the idea of having an interface here because you want to have a starting point from any element, and the methods could also be used for chaining.
So we have to find a better name that grasps the responsibility. What do you think of QueryStartingPoint
? (a CtElement is a query starting point).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I fully agree with you. New name QueryStartingPoint is good for CtElement. But it is not so good for QueryStep, which will inherit it too and will use these methods for building of next query step.
What about
class CtElement extends QueryStartingPoint
class QueryStartingPoint extends QueryComposer
class QuerStep extends QueryComposer
... it is a kins of compromise. The best would be to found a name which is good for both interfaces.
One related idea... There is Query
class, which has actualy static methods. It would be may be nice to change them to instance methods and to let Query extend QueryComposer too. So the name of the interface should fit to that idea too. But let's duscuss it later. This PR is complex enough...
* It is implemented 1) by {@link CtElement} to allow creation new query and its first step | ||
* 2) by {@link QueryStep} to allow creation of next query step | ||
*/ | ||
public interface QueryComposer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the name, what do you think of QueryResult
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is there to compose first or next queryStep. It is really not about result/evaluation of the query.
If you do not like QueryComposer, then alternatives might be
- QueryCreator
- QueryStepBuilder
- QueryFactory
- ... ?
* | ||
* @param <O> the type of the element produced by this QueryStep | ||
*/ | ||
public interface QueryStep<O> extends QueryComposer, Consumer<Object> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The role and methods of QueryStep and QueryComposer seem close. Does it make sense to merge them in a single interface?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think no. QueryComposer has only methods, which can create query step. But QueryStep has methods which can evaluate query too. It makes no sense to evaluate query on CtElement (which implements QueryComposer) directly, because there is no query yet. First you have to create one or more query steps (using QueryComposer) and then you can evaluate the query using methods, which are only on QueryStep only.
* @param <R> the type of the result produced by this function | ||
*/ | ||
public interface ChainableFunction<T, R> { | ||
void apply(T input, Consumer<R> output); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about extending Function
(intuitive for ChainableFunction
), and put the consumer as return type: Consumer<R> apply(T input
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not possible. It is different contract. Have a look at new class
src/main/java/spoon/reflect/visitor/filter/Scann.java
It implements ChainableFunction. How you would implement such thing using Consumer<R> apply(T input)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at Scann, basically Consumer is the implicit Function. What if Consumer extends Function (accept becomes apply) and then you only have one method map?
* @param queryStep | ||
* @return the entered queryStep, which is now the last step of the query | ||
*/ | ||
<R> QueryStep<R> map(QueryStep<R> queryStep); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all map functions are really close (same name, same return type). It suggests a common abstraction (and the proximity of Function and ChainableFunction goes in the same direction).
Is it possible to have a single map function with an appropriate common interface as parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to have a single map function with an appropriate common interface as parameter?
I do not know how it would look like. Please describe your idea in more details.
Why three functions are problem?
I need all three functions for these cases:
-
map(QueryStep) - it let's us to combine query chains together
-
map(Function) - it let's us to make a query based on standard functions of CtModel elements. It simply gets the input element and returned value is sent to next step. See javadoc description for details about 4 possible usages.
-
map(ChainableFunction) it let's us implement clever Filters, which automatically computes the scanning scope. For example FieldReferencesFilter, which searches for references to an field depending on field visibility.
Note: we cannot merge map(Function) and map(ChainableFunction) into one. Because Function#apply has 1 input parameter, while ChainableFunction#apply has 2 parameters. And if I would make one Interface, which would contains both apply
functions, then it would be not possible to implement it using java 8 lambda expression, because it expects only one non default method in the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why three functions are problem?
because it's not elegant and hints to a design issue.
added the requirements for this new feature at the top of this thread. |
Thanks for Your PR! It helped me to understand Your thinking. Now I can try to clarify the misunderstanding, which is there. You are trying to implement different feature then me. So I will comment your PR now and then merge it. |
Please see my comments in your PR, but do not fix anything in your PR. I will takeover it as it is and then, I will simplify it even more. So we will have first small step on which we both can agree on. What I will do? factory.Query().map(new FieldReferenceQuery()).forEach(resultConsumingFunction, inputElement
//OR
List list=factory.Query().map(new FieldReferenceQuery()).list(inputElement)
//OR
factory.Query().map(new FieldReferenceQuery()).map(resultConsumingFunction).list(inputElement) This approach is not so nice like current one, but it is simplest code and the most powerful and flexible way. Later we can use that low level code and to write some nicer facade about that, so the usability will improve too. One more idea: If we will not merge code of OverridenMethodFilter with OverridenMethodQuery, then we can even merge |
OK, excellent.
Sounds a cool idea
The big advantage of
Are you sure? It's a really meaningful, concise and coherent interface, perfectly fitting into |
I agree with you, but it is kind of facade over the internal query engine. And I have learned from this PR, that too much steps at once causes misunderstanding. So I want to have first low level query engine merged in Spoon and then we can discuss how to integrate it into CtElement. |
And I have learned from this PR, that too much steps at once causes
misunderstanding.
I learned that too. And also that writing requirements upfront is useful.
So I want to have first low level query engine merged in Spoon and
then we can discuss how to integrate it into CtElement.
OK.
|
I am not able to implement next version, because each idea ends with the progress killing thought: "Martin doesn't like CtConsumer, CtQueryStep, forEach".... List list = ...query.list();
for(item:list) {
... do something with each item
} spoon is already full of such code. Just search for usages of CtElement#getElements() |
Here is the exact definition what avoids future progress from my side: |
Let's focus on requirements and API.
It seems we now agree on a basic set of requirements (listed here) and
API (in pvojtechovsky#4).
Why about merging it first? What prevents this to your opinion?
For implementing scope-based query, we will probably either add new
methods in the query interfaces or new interfaces, but again, let's
first agree on the core design here.
|
query: design + javadoc
I merged your PR.
Query composition is not possible now with your PR.
ok, I suggest that You continue on future query development, until it gets at least to state, where it is possible to implement scope based queries.
this core design is quite limited now. I agree with anything what will cause a progress with this PR. But I will spent my effort on another Spoon features now, which are based on the state of this PR before your PR was merged. |
Query composition is not possible now with your PR.
renamed the title.
do you agree that the four listed requirements are well supported with
this PR?
|
yes
partially. You can chain implementations of CtFunction, but you cannot chain queries.
partially. The CtFunction is evaluated lazily for each input element, but it is not possible the apply lazy evaluation to output of that function, because function must return results using
yes But I have no problem if you merge it like it is and then continue with scope-based query. And before merge, I suggest you fix the little problems in your PR. See my comments in your PR. |
The idea is to be 100% on the same wavelength before merging
to me, "chaining" means writing This is completely supported, right? |
yes this is supported |
On lazy evaluationn you say
I don't understand. Could you elaborate? To me, in the current implementation, all queries are only performed by calling |
The implementation of ChildrenFilteringWrapper is correct, because it calls if (matches) {
//send input to output, because Fitler.matches returned true
next.accept(element);
} So it will work effectively for large amount of elements too. The implementation of FunctionWrapper is correct too, because CtFunction is intended for query steps, which will return relatively small number of results. So what is wrong? There is no way how to extend Query by way it works well for functions, which produces large result set. You mentioned it will be next step. OK. But as I mentioned until this feature is back again, I cannot continue, so I will continue on my old branch in the mean time.
Correct. Anyway thanks for Your effort Martin, to try to make it/keep it understandable! |
doc: improves doc of new Query API
@surli this PR is now ready for your review. there is no need to read and understand the whole discussion, the PR itself (doc, javadoc, tests) should now be self-contained. |
@@ -42,8 +46,8 @@ public QueryVisitor(Filter<T> filter) { | |||
if (filter instanceof AbstractFilter) { | |||
filteredType = ((AbstractFilter<T>) filter).getType(); | |||
} else { | |||
Class<?>[] params = RtHelper.getMethodParameterTypes(filter.getClass(), "matches", 1); | |||
filteredType = (Class<T>) params[0]; | |||
Method method = RtHelper.getMethod(filter.getClass(), "matches", 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You did not check here that method
can be null as you did in AbstractFilter
: it's on purpose because you assume it can never happens or is it missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot imagine situation when "matches" method is missing, because Filter interface has such method.
But I can add check there. Thank you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
}).list(); | ||
assertTrue(context.count==1); | ||
assertTrue(context.totalCount>1); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about what you're testing there, compared to the previous test.
Why don't you use a NameFilter here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right this test brings nothing new, The testFunctionQueryStep already tests the same. I will remove this test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have deleted the test
* | ||
* @param <T> - the type of accepted elements | ||
*/ | ||
private interface CtConsumer<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose exactly to distinguish this interface from Step? From my point of view, each time you use a CtConsumer it's in fact a Step you're using, am I wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need CtConsumer as public interface later. But Martin made it private in this first step, because after his simplifications/deleting of functionality this interface is not needed.
Requirements for a new query architecture:
filterChildren(x).filterChildren(y)...
ormap(z).map(t)...
or evenfilterChildren(p).map(q)...
)With this PR you can write code like
Which makes usage of complex filters more reliable, because these filters can automatically define correct searching context. For example
OverriddenMethodFilter
will search in root packagelocal variable reference
filter will search in scope of its CtBock elementmember variable reference
filter will search in scope of its Class and depending on visibility in all inherited class, in current package or in root packageor you can write
which 1) is shortest code and 2) which will let us chain filters (later) into complex filters chains, which are able to browse spoon model in sequence of searching steps. I will probably need something like this for refactoring methods, I am preparing.