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

Query factory #1093

Merged
merged 6 commits into from
Jan 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions doc/filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ 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.
**Query reuse**. Method `setInput` allows you to reuse the same query over multiple inputs.
In such case it makes sense to create unbound query using `Factory#createQuery()`.

```java
// here is the query
CtQuery q = new CtQueryImpl().map((CtClass c) -> c.getSimpleName());
CtQuery q = factory.createQuery().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
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/spoon/reflect/factory/Factory.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import spoon.reflect.reference.CtUnboundVariableReference;
import spoon.reflect.reference.CtVariableReference;
import spoon.reflect.reference.CtWildcardReference;
import spoon.reflect.visitor.chain.CtQuery;

import java.lang.annotation.Annotation;
import java.util.List;
Expand Down Expand Up @@ -150,6 +151,8 @@ public interface Factory {

ConstructorFactory Constructor(); // used 3 times

QueryFactory Query();

/**
* @see CodeFactory#createAnnotation(CtTypeReference)
*/
Expand Down Expand Up @@ -762,4 +765,13 @@ public interface Factory {
*/
CtTypeParameterReference createTypeParameterReference(String name);

/**
* @see QueryFactory#createQuery()
*/
CtQuery createQuery();

/**
* @see QueryFactory#createQuery(Object)
*/
CtQuery createQuery(Object input);
}
23 changes: 23 additions & 0 deletions src/main/java/spoon/reflect/factory/FactoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
import spoon.reflect.reference.CtUnboundVariableReference;
import spoon.reflect.reference.CtVariableReference;
import spoon.reflect.reference.CtWildcardReference;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.support.DefaultCoreFactory;
import spoon.support.StandardEnvironment;

Expand Down Expand Up @@ -335,6 +336,19 @@ public TypeFactory Type() {
return type;
}

private transient QueryFactory query;

/**
* The query sub-factory.
*/
@Override
public QueryFactory Query() {
if (query == null) {
query = new QueryFactory(this);
}
return query;
}

/**
* A constructor that takes the parent factory
*/
Expand Down Expand Up @@ -1024,4 +1038,13 @@ public CtTypeParameterReference createTypeParameterReference(String name) {
return Type().createTypeParameterReference(name);
}

@Override
public CtQuery createQuery() {
return Query().createQuery();
}

@Override
public CtQuery createQuery(Object input) {
return Query().createQuery(input);
}
}
51 changes: 51 additions & 0 deletions src/main/java/spoon/reflect/factory/QueryFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 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.factory;

import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.chain.CtQueryImpl;

/**
* A factory to create some queries on the Spoon metamodel.
*/
public class QueryFactory extends SubFactory {

/**
* Creates the evaluation factory.
*/
public QueryFactory(Factory factory) {
super(factory);
}

/**
* Creates a unbound query. Use {@link CtQuery#setInput(Object...)}
* before {@link CtQuery#forEach(spoon.reflect.visitor.chain.CtConsumer)}
* or {@link CtQuery#list()} is called
*/
public CtQuery createQuery() {
return new CtQueryImpl();
}

/**
* Creates a bound query. Use directly
* {@link CtQuery#forEach(spoon.reflect.visitor.chain.CtConsumer)}
* or {@link CtQuery#list()} to evaluate the query
*/
public CtQuery createQuery(Object input) {
return new CtQueryImpl(input);
}
}
3 changes: 2 additions & 1 deletion src/main/java/spoon/reflect/visitor/chain/CtQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package spoon.reflect.visitor.chain;

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

import java.util.List;
Expand All @@ -25,7 +26,7 @@
* <p>CtQuery represents a query, which can be used to traverse a spoon model and collect
* children elements in several ways.</p>
*
* <p>Creation: A query is created either from a {@link CtElement}, or it can be defined first from {@link CtQueryImpl} and bound to root elements
* <p>Creation: A query is created either from a {@link CtElement}, or it can be defined first from {@link Factory#createQuery()} and bound to root elements
* afterwards using {@link CtQuery#setInput(Object...)}.</p>
*
* <p>Chaining: In a query several steps can be chained, by chaining calls to map functions. The non-null outputs of one step
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/spoon/support/SerializationModelStreamer.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import spoon.reflect.ModelStreamer;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.Filter;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -51,13 +51,15 @@ public Factory load(InputStream in) throws IOException {
try {
ObjectInputStream ois = new ObjectInputStream(in);
final Factory f = (Factory) ois.readObject();
new CtScanner() {
//create query using factory directly
//because any try to call CtElement#map or CtElement#filterChildren will fail on uninitialized factory
f.createQuery(f.getModel().getRootPackage()).filterChildren(new Filter<CtElement>() {
@Override
public void enter(CtElement e) {
public boolean matches(CtElement e) {
e.setFactory(f);
super.enter(e);
return false;
}
}.scan(f.Package().getAll());
}).list();
ois.close();
return f;
} catch (ClassNotFoundException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.ModelConsistencyChecker;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.chain.CtQueryImpl;
import spoon.reflect.visitor.chain.CtFunction;
import spoon.reflect.visitor.chain.CtConsumableFunction;
import spoon.reflect.visitor.chain.CtQuery;
Expand Down Expand Up @@ -261,17 +260,17 @@ public <E extends CtElement> List<E> getElements(Filter<E> filter) {

@Override
public <I> CtQuery map(CtConsumableFunction<I> queryStep) {
return new CtQueryImpl(this).map(queryStep);
return factory.Query().createQuery(this).map(queryStep);
}

@Override
public <I, R> CtQuery map(CtFunction<I, R> function) {
return new CtQueryImpl(this).map(function);
return factory.Query().createQuery(this).map(function);
}

@Override
public <P extends CtElement> CtQuery filterChildren(Filter<P> predicate) {
return new CtQueryImpl(this).filterChildren(predicate);
return factory.Query().createQuery(this).filterChildren(predicate);
}

public <T extends CtReference> List<T> getReferences(Filter<T> filter) {
Expand Down
9 changes: 3 additions & 6 deletions src/main/java/spoon/template/TemplateMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ private List<CtFieldReference<?>> getVarargs(CtClass<? extends Template<?>> root
/**
* Constructs a matcher for a given template.
*
* @param templateRoot the template to match against
*
*/
@SuppressWarnings("unchecked")
public TemplateMatcher(CtElement templateRoot) {
Expand Down Expand Up @@ -446,16 +448,11 @@ private boolean isCurrentTemplate(Object object, CtElement inMulti) {
}

/**
* Matches a target program sub-tree against a template. Once this method
* has been called, {@link #getMatches()} will give the matching parts if
* any.
* Matches a target program sub-tree against a template.
*
* @param targetRoot
* the target to be tested for match
* @param templateRoot
* the template to match against
* @return true if matches
* @see #getMatches()
*/
@Override
public boolean matches(CtElement targetRoot) {
Expand Down
42 changes: 36 additions & 6 deletions src/test/java/spoon/test/filters/FilterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ public void testElementMapFunction() throws Exception {
public void testElementMapFunctionOtherContracts() throws Exception {
// contract: when a function returns an array, all non-null values are sent to the next step
final Launcher launcher = new Launcher();
CtQueryImpl q = new CtQueryImpl().map((String s)->new String[]{"a", null, s});
CtQuery q = launcher.getFactory().Query().createQuery().map((String s)->new String[]{"a", null, s});
List<String> list = q.setInput(null).list();
assertEquals(0, list.size());

Expand All @@ -642,14 +642,14 @@ public void testElementMapFunctionOtherContracts() throws Exception {
assertEquals("c", list.get(1));

// contract: when input is null then the query function is not called at all.
CtQueryImpl q2 = new CtQueryImpl().map((String s)->{ throw new AssertionError();});
CtQuery q2 = launcher.getFactory().Query().createQuery().map((String s)->{ throw new AssertionError();});
assertEquals(0, q2.setInput(null).list().size());
}
@Test
public void testElementMapFunctionNull() throws Exception {
// contract: when a function returns null, it is discarded at the next step
final Launcher launcher = new Launcher();
CtQueryImpl q = new CtQueryImpl().map((String s)->null);
CtQuery q = launcher.getFactory().Query().createQuery().map((String s)->null);
List<String> list = q.setInput("c").list();
assertEquals(0, list.size());
}
Expand Down Expand Up @@ -692,7 +692,7 @@ public void testReuseOfBaseQuery() throws Exception {
CtClass<?> cls2 = launcher.getFactory().Class().get(Tostada.class);

// here is the query
CtQuery q = new CtQueryImpl().map((CtClass c) -> c.getSimpleName());
CtQuery q = launcher.getFactory().Query().createQuery().map((CtClass c) -> c.getSimpleName());
// using it on a first input
assertEquals("Tacos", q.setInput(cls).list().get(0));
// using it on a second input
Expand Down Expand Up @@ -744,7 +744,7 @@ class Context {
CtClass<?> cls = launcher.getFactory().Class().get(Tacos.class);

// first query
CtQuery allChildPublicClasses = new CtQueryImpl().filterChildren((CtClass clazz)->clazz.hasModifier(ModifierKind.PUBLIC));
CtQuery allChildPublicClasses = launcher.getFactory().Query().createQuery().filterChildren((CtClass clazz)->clazz.hasModifier(ModifierKind.PUBLIC));

// second query,involving the first query
CtQuery q = launcher.getFactory().Package().getRootPackage().map((CtElement in)->allChildPublicClasses.setInput(in).list());
Expand All @@ -759,7 +759,7 @@ class Context {
context.count=0; //reset

// again second query, but now with CtConsumableFunction
CtQuery q2 = launcher.getFactory().Package().getRootPackage().map((CtElement in, CtConsumer<Object> out)->allChildPublicClasses.setInput(in).forEach(x -> out.accept(x)));
CtQuery q2 = launcher.getFactory().Package().getRootPackage().map((CtElement in, CtConsumer<Object> out)->allChildPublicClasses.setInput(in).forEach(out));

// now the assertions
q2.forEach((CtElement clazz)->{
Expand All @@ -780,7 +780,37 @@ class Context {
assertTrue(((CtClass<?>)clazz).hasModifier(ModifierKind.PUBLIC));
});
assertEquals(6, context.count);
}

@Test
public void testEmptyQuery() throws Exception {
// contract: unbound or empty query

final Launcher launcher = new Launcher();

//contract: empty query returns no element
assertEquals(0, launcher.getFactory().createQuery().list().size());
assertEquals(0, launcher.getFactory().createQuery(null).list().size());
//contract: empty query returns no element
launcher.getFactory().createQuery().forEach(x->fail());
launcher.getFactory().createQuery(null).forEach(x->fail());
//contract: empty query calls no mapping
assertEquals(0, launcher.getFactory().createQuery().map(x->{fail();return true;}).list().size());
assertEquals(0, launcher.getFactory().createQuery(null).map(x->{fail();return true;}).list().size());
//contract: empty query calls no filterChildren
assertEquals(0, launcher.getFactory().createQuery().filterChildren(x->{fail();return true;}).list().size());
assertEquals(0, launcher.getFactory().createQuery(null).filterChildren(x->{fail();return true;}).list().size());
}

@Test
public void testBoundQuery() throws Exception {
// contract: bound query, without any mapping

final Launcher launcher = new Launcher();

//contract: bound query returns bound element
List<String> list = launcher.getFactory().createQuery("x").list();
assertEquals(1, list.size());
assertEquals("x", list.get(0));
}
}