From fd9d6c0b62a3372a41020eb328028279c7bab048 Mon Sep 17 00:00:00 2001 From: Nicolas Harrand Date: Mon, 26 Feb 2018 20:44:43 +0100 Subject: [PATCH] feat: add support for paths for all elements from model root (CtElement#getPath) (#1874) --- doc/path.md | 31 ++-- .../spoon/reflect/declaration/CtElement.java | 7 + .../reflect/path/CtElementPathBuilder.java | 97 ++++++++++++ src/main/java/spoon/reflect/path/CtPath.java | 9 +- .../reflect/path/CtPathStringBuilder.java | 3 +- .../spoon/reflect/path/impl/CtPathImpl.java | 6 +- .../reflect/path/impl/CtRolePathElement.java | 143 ++++++++++-------- .../reflect/declaration/CtElementImpl.java | 13 ++ src/test/java/spoon/test/main/MainTest.java | 43 +++++- src/test/java/spoon/test/path/Foo.java | 4 + src/test/java/spoon/test/path/PathTest.java | 69 ++++++++- 11 files changed, 330 insertions(+), 95 deletions(-) create mode 100644 src/main/java/spoon/reflect/path/CtElementPathBuilder.java diff --git a/doc/path.md b/doc/path.md index 93efbf2fc20..18bd415a89e 100644 --- a/doc/path.md +++ b/doc/path.md @@ -6,18 +6,27 @@ keywords: quering, query, path, ast, elements `CtPath` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/path/CtPath.html)) defines the path to a `CtElement` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/declaration/CtElement.html)) -in a model respectively to another element. A `CtPath` can also be used -to make a query to get elements and is based on three concepts: -names of elements, types of elements and roles of code elements. +in a model. For example, `.spoon.test.path.Foo.foo#body#statement[index=0]` represents the first statement of the body of method foo. -A role is a relation between two AST nodes, encoded as an AST node field. -For instance, a "then" branch in a if/then/else is a role (and not an node). +A `CtPath`is based on: names of elements (eg `foo`), and roles of elements with respect to their parent (eg `body`). +A role is a relation between two AST nodes. +For instance, a "then" branch in a if/then/else is a role (and not an node). All roles can be found in `CtRole`. In addition, each getter or setter in the metamodel is annotated with its role. -To build a path, you have two possibilities: `CtPathBuilder` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/path/CtPathBuilder.html)) -and `CtPathStringBuilder` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/path/CtPathStringBuilder.html)). -`CtPathBuilder` defines a fluent api to build your path. -`CtPathStringBuilder` creates a path object from a string according to a +To build a path, there are several possibilities: + +* method `getPath` in `CtElement` +* `CtPathStringBuilder` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/path/CtPathStringBuilder.html)). +it creates a path object from a string according to a syntax inspired from XPath and CSS selectors. +* the low-level `CtPathBuilder` ([javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/reflect/path/CtPathBuilder.html)), it defines a fluent api to build your path. + +To evaluate a path, ie getting the elements represented by it, use `evaluateOn(List)` + +```java +path = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body#statement[index=0]"); +List l = path.evaluateOn(root) +``` + ## CtPathStringBuilder @@ -30,7 +39,7 @@ For instance, if we want the first statement in the body of method `foo`, declar in the class `spoon.test.path.Foo`. ```java -new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body[index=0]"); +new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body#statement[index=0]"); ``` ## CtPathBuilder @@ -69,4 +78,4 @@ in your project according to the rest of your path request. ``` new CtPathBuilder().recursiveWildcard().name("toto") new CtPathBuilder().name("toto").recursiveWildcard() -``` \ No newline at end of file +``` diff --git a/src/main/java/spoon/reflect/declaration/CtElement.java b/src/main/java/spoon/reflect/declaration/CtElement.java index 2488f2b310c..51f95629093 100644 --- a/src/main/java/spoon/reflect/declaration/CtElement.java +++ b/src/main/java/spoon/reflect/declaration/CtElement.java @@ -19,6 +19,7 @@ import spoon.processing.FactoryAccessor; import spoon.reflect.code.CtComment; import spoon.reflect.cu.SourcePosition; +import spoon.reflect.path.CtPath; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtVisitable; @@ -349,4 +350,10 @@ List getAnnotatedChildren( * @param value to be assigned to this field. */ E setValueByRole(CtRole role, T value); + + /** + * Return the path from the model root to this CtElement, eg `.spoon.test.path.Foo.foo#body#statement[index=0]` + */ + CtPath getPath(); + } diff --git a/src/main/java/spoon/reflect/path/CtElementPathBuilder.java b/src/main/java/spoon/reflect/path/CtElementPathBuilder.java new file mode 100644 index 00000000000..d5bd8bc72f2 --- /dev/null +++ b/src/main/java/spoon/reflect/path/CtElementPathBuilder.java @@ -0,0 +1,97 @@ +package spoon.reflect.path; + +import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtNamedElement; +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.path.impl.CtPathElement; +import spoon.reflect.path.impl.CtPathImpl; +import spoon.reflect.path.impl.CtRolePathElement; +import spoon.reflect.reference.CtReference; + +import java.util.List; +import java.util.Map; + +/** + * This builder allow to create some CtPath from CtElements + * + * Created by nharrand on 21/02/2018. + */ +public class CtElementPathBuilder { + /** + * Build path to a CtElement el, from one of its parent. + * + * @throws CtPathException is thrown when root is not a parent of el. + * + * @param el : the element to which the CtPath leads to + * @param root : Starting point of the CtPath + * @return CtPath from root to el + */ + public CtPath fromElement(CtElement el, CtElement root) throws CtPathException { + CtPathImpl path = new CtPathImpl(); + CtElement cur = el; + while (cur != root) { + CtElement parent = cur.getParent(); + CtRole role = cur.getRoleInParent(); + if (role == null) { + throw new CtPathException(); + } + RoleHandler roleHandler = RoleHandlerHelper.getOptionalRoleHandler(parent.getClass(), role); + if (roleHandler == null) { + throw new CtPathException(); + } + CtPathElement pathElement = new CtRolePathElement(role); + switch (roleHandler.getContainerKind()) { + case SINGLE: + break; + + case LIST: + //Element needs to be differentiated from its brothers + List list = roleHandler.asList(parent); + //Assumes that List's order is deterministic. + //Can't be replaced by list.indexOf(cur) + //Because objects must be the same (and not just equals) + int index = 0; + for (Object o : list) { + if (o == cur) { + break; + } + index++; + } + pathElement.addArgument("index", index + ""); + break; + + case SET: + String name; + if (cur instanceof CtNamedElement) { + name = ((CtNamedElement) cur).getSimpleName(); + } else if (cur instanceof CtReference) { + name = ((CtReference) cur).getSimpleName(); + } else { + throw new CtPathException(); + } + pathElement.addArgument("name", name); + break; + + case MAP: + Map map = roleHandler.asMap(parent); + String key = null; + for (Object o : map.keySet()) { + if (map.get(o) == cur) { + key = (String) o; + break; + } + } + if (key == null) { + throw new CtPathException(); + } else { + pathElement.addArgument("key", key); + } + break; + } + cur = parent; + path.addFirst(pathElement); + } + return path; + } +} diff --git a/src/main/java/spoon/reflect/path/CtPath.java b/src/main/java/spoon/reflect/path/CtPath.java index dd2f3a37e24..98dbfc724a1 100644 --- a/src/main/java/spoon/reflect/path/CtPath.java +++ b/src/main/java/spoon/reflect/path/CtPath.java @@ -21,16 +21,13 @@ import java.util.Collection; /** - * A CtPath allow top define the path to a CtElement in the Spoon Model. + * A CtPath allows to define the path to a CtElement in the Spoon model, eg ".spoon.test.path.Foo.foo#body#statement[index=0]" */ public interface CtPath { /** - * Search some element matching this CtPatch from given nodes. - * - * @param startNode - * @return + * Search for elements matching this CtPatch from start nodes given as parameters. */ - Collection evaluateOn(Collection startNode); + Collection evaluateOn(CtElement... startNode); } diff --git a/src/main/java/spoon/reflect/path/CtPathStringBuilder.java b/src/main/java/spoon/reflect/path/CtPathStringBuilder.java index 96559ab0c18..9578258803f 100644 --- a/src/main/java/spoon/reflect/path/CtPathStringBuilder.java +++ b/src/main/java/spoon/reflect/path/CtPathStringBuilder.java @@ -16,11 +16,12 @@ */ package spoon.reflect.path; + import spoon.reflect.path.impl.CtNamedPathElement; import spoon.reflect.path.impl.CtPathElement; import spoon.reflect.path.impl.CtPathImpl; -import spoon.reflect.path.impl.CtRolePathElement; import spoon.reflect.path.impl.CtTypedNameElement; +import spoon.reflect.path.impl.CtRolePathElement; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/spoon/reflect/path/impl/CtPathImpl.java b/src/main/java/spoon/reflect/path/impl/CtPathImpl.java index ac9b821c9e2..f9c2542e608 100644 --- a/src/main/java/spoon/reflect/path/impl/CtPathImpl.java +++ b/src/main/java/spoon/reflect/path/impl/CtPathImpl.java @@ -19,7 +19,7 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.path.CtPath; -import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.List; @@ -36,8 +36,8 @@ public List getElements() { } @Override - public Collection evaluateOn(Collection startNode) { - Collection filtered = new ArrayList<>(startNode); + public Collection evaluateOn(CtElement... startNode) { + Collection filtered = Arrays.asList(startNode); for (CtPathElement element : elements) { filtered = element.getElements(filtered); } diff --git a/src/main/java/spoon/reflect/path/impl/CtRolePathElement.java b/src/main/java/spoon/reflect/path/impl/CtRolePathElement.java index 5885d5a1b3d..a0bf27b3f8d 100644 --- a/src/main/java/spoon/reflect/path/impl/CtRolePathElement.java +++ b/src/main/java/spoon/reflect/path/impl/CtRolePathElement.java @@ -16,15 +16,18 @@ */ package spoon.reflect.path.impl; -import spoon.reflect.code.CtIf; import spoon.reflect.declaration.CtElement; -import spoon.reflect.declaration.CtExecutable; -import spoon.reflect.declaration.CtField; +import spoon.reflect.declaration.CtNamedElement; +import spoon.reflect.meta.RoleHandler; +import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.path.CtPathException; import spoon.reflect.path.CtRole; -import spoon.reflect.visitor.CtInheritanceScanner; +import spoon.reflect.reference.CtReference; import java.util.Collection; import java.util.LinkedList; +import java.util.Map; +import java.util.Set; /** * A CtPathElement that define some roles for matching. @@ -40,60 +43,6 @@ public class CtRolePathElement extends AbstractPathElement public static final String STRING = "#"; - private class RoleVisitor extends CtInheritanceScanner { - private Collection matchs = new LinkedList<>(); - - private RoleVisitor() { - } - - @Override - public void scanCtExecutable(CtExecutable e) { - super.scanCtExecutable(e); - - switch (role) { - case BODY: - if (e.getBody() != null) { - if (getArguments().containsKey("index") - && e.getBody() - .getStatements() - .size() > Integer.parseInt( - getArguments().get("index"))) { - matchs.add(e.getBody().getStatements().get(Integer - .parseInt(getArguments().get("index")))); - } else { - matchs.addAll(e.getBody().getStatements()); - } - } - } - - } - - @Override - public void visitCtField(CtField e) { - super.visitCtField(e); - - if (role == CtRole.DEFAULT_EXPRESSION && e.getDefaultExpression() != null) { - matchs.add(e.getDefaultExpression()); - } - } - - @Override - public void visitCtIf(CtIf e) { - super.visitCtIf(e); - - switch (role) { - case THEN: - if (e.getThenStatement() != null) { - matchs.add(e.getThenStatement()); - } - case ELSE: - if (e.getElseStatement() != null) { - matchs.add(e.getElseStatement()); - } - } - } - } - private final CtRole role; public CtRolePathElement(CtRole role) { @@ -106,14 +55,82 @@ public CtRole getRole() { @Override public String toString() { - return STRING + role.toString() + getParamString(); + return STRING + getRole().toString() + getParamString(); + } + + private CtElement getFromSet(Set set, String name) throws CtPathException { + for (Object o: set) { + if (o instanceof CtNamedElement) { + if (((CtNamedElement) o).getSimpleName().equals(name)) { + return (CtElement) o; + } + } else if (o instanceof CtReference) { + if (((CtReference) o).getSimpleName().equals(name)) { + return (CtElement) o; + } + } else { + throw new CtPathException(); + } + } + //Element is not found in set. + return null; } @Override public Collection getElements(Collection roots) { - RoleVisitor visitor = new RoleVisitor(); - visitor.scan(roots); - return visitor.matchs; + Collection matchs = new LinkedList<>(); + for (CtElement root : roots) { + RoleHandler roleHandler = RoleHandlerHelper.getOptionalRoleHandler(root.getClass(), getRole()); + if (roleHandler != null) { + switch (roleHandler.getContainerKind()) { + case SINGLE: + if (roleHandler.getValue(root) != null) { + matchs.add(roleHandler.getValue(root)); + } + break; + + case LIST: + if (getArguments().containsKey("index")) { + int index = Integer.parseInt(getArguments().get("index")); + if (index < roleHandler.asList(root).size()) { + matchs.add((CtElement) roleHandler.asList(root).get(index)); + } + } else { + matchs.addAll(roleHandler.asList(root)); + } + break; + + case SET: + if (getArguments().containsKey("name")) { + String name = getArguments().get("name"); + try { + CtElement match = getFromSet(roleHandler.asSet(root), name); + if (match != null) { + matchs.add(match); + } + } catch (CtPathException e) { + //System.err.println("[ERROR] Element not found for name: " + name); + //No element found for name. + } + } else { + matchs.addAll(roleHandler.asSet(root)); + } + break; + + case MAP: + if (getArguments().containsKey("key")) { + String name = getArguments().get("key"); + if (roleHandler.asMap(root).containsKey(name)) { + matchs.add((CtElement) roleHandler.asMap(root).get(name)); + } + } else { + Map map = roleHandler.asMap(root); + matchs.addAll(map.values()); + } + break; + } + } + } + return matchs; } - } diff --git a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java index a0c67b854a8..73f534526db 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtElementImpl.java @@ -17,6 +17,8 @@ package spoon.support.reflect.declaration; import org.apache.log4j.Logger; +import spoon.SpoonException; +import spoon.reflect.CtModelImpl; import spoon.reflect.annotations.MetamodelPropertyField; import spoon.reflect.code.CtComment; import spoon.reflect.code.CtJavaDoc; @@ -31,6 +33,9 @@ import spoon.reflect.factory.FactoryImpl; import spoon.reflect.meta.RoleHandler; import spoon.reflect.meta.impl.RoleHandlerHelper; +import spoon.reflect.path.CtElementPathBuilder; +import spoon.reflect.path.CtPath; +import spoon.reflect.path.CtPathException; import spoon.reflect.path.CtRole; import spoon.reflect.declaration.CtImport; import spoon.reflect.reference.CtReference; @@ -536,4 +541,12 @@ public E setValueByRole(CtRole role, T value) { rh.setValue(this, value); return (E) this; } + + public CtPath getPath() { + try { + return new CtElementPathBuilder().fromElement(this, getParent(CtModelImpl.CtRootPackage.class)); + } catch (CtPathException e) { + throw new SpoonException(e); + } + } } diff --git a/src/test/java/spoon/test/main/MainTest.java b/src/test/java/spoon/test/main/MainTest.java index e1497778274..5ccfa2359ff 100644 --- a/src/test/java/spoon/test/main/MainTest.java +++ b/src/test/java/spoon/test/main/MainTest.java @@ -23,6 +23,9 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeParameter; import spoon.reflect.declaration.ParentNotInitializedException; +import spoon.reflect.path.CtPath; +import spoon.reflect.path.CtPathException; +import spoon.reflect.path.CtPathStringBuilder; import spoon.reflect.path.CtRole; import spoon.reflect.reference.CtArrayTypeReference; import spoon.reflect.reference.CtExecutableReference; @@ -40,13 +43,17 @@ import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.Deque; + +import java.util.Arrays; +import java.util.List; +import java.util.LinkedList; +import java.util.Set; import java.util.HashSet; -import java.util.IdentityHashMap; +import java.util.Deque; +import java.util.ArrayDeque; import java.util.Map; -import java.util.Set; +import java.util.IdentityHashMap; +import java.util.Collection; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -441,6 +448,32 @@ public void scan(CtRole role, CtElement element) { }); } + @Test + public void testElementToPathToElementEquivalency() { + + rootPackage.accept(new CtScanner() { + @Override + public void scan(CtElement element) { + if (element != null) { + CtPath path = element.getPath(); + String pathStr = path.toString(); + try { + CtPath pathRead = new CtPathStringBuilder().fromString(pathStr); + Collection returnedElements = pathRead.evaluateOn(rootPackage); + //contract: CtUniqueRolePathElement.evaluateOn() returns a unique elements if provided only a list of one inputs + assertEquals(returnedElements.size(), 1); + CtElement actualElement = (CtElement) returnedElements.toArray()[0]; + //contract: Element -> Path -> String -> Path -> Element leads to the original element + assertSame(element, actualElement); + } catch (CtPathException e) { + fail("Path is either incorrectly generated or incorrectly read"); + } + } + super.scan(element); + } + }); + } + @Test public void testElementIsContainedInAttributeOfItsParent() { rootPackage.accept(new CtScanner() { diff --git a/src/test/java/spoon/test/path/Foo.java b/src/test/java/spoon/test/path/Foo.java index 7cb37f85328..24f9493e147 100644 --- a/src/test/java/spoon/test/path/Foo.java +++ b/src/test/java/spoon/test/path/Foo.java @@ -18,8 +18,12 @@ void foo() { } } + @java.lang.SuppressWarnings("unchecked") void bar(int i, int j) { int x = 3; x = x + 1; + if (i > 5) { + x += 1; + } } } \ No newline at end of file diff --git a/src/test/java/spoon/test/path/PathTest.java b/src/test/java/spoon/test/path/PathTest.java index 3d6561d8f24..b7a145a2bf5 100644 --- a/src/test/java/spoon/test/path/PathTest.java +++ b/src/test/java/spoon/test/path/PathTest.java @@ -10,6 +10,7 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtMethod; import spoon.reflect.factory.Factory; +import spoon.reflect.path.CtElementPathBuilder; import spoon.reflect.path.CtPath; import spoon.reflect.path.CtPathBuilder; import spoon.reflect.path.CtPathException; @@ -18,11 +19,14 @@ import java.util.Arrays; import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Created by nicolas on 10/06/2015. @@ -43,13 +47,13 @@ public void setup() throws Exception { } private void equals(CtPath path, CtElement... elements) { - Collection result = path.evaluateOn(Arrays.asList(factory.Package().getRootPackage())); + Collection result = path.evaluateOn(factory.Package().getRootPackage()); assertEquals(elements.length, result.size()); assertArrayEquals(elements, result.toArray(new CtElement[0])); } private void equalsSet(CtPath path, Set elements) { - Collection result = path.evaluateOn(Arrays.asList(factory.Package().getRootPackage())); + Collection result = path.evaluateOn(factory.Package().getRootPackage()); assertEquals(elements.size(), result.size()); assertTrue(result.containsAll(elements)); } @@ -83,7 +87,7 @@ public void testBuilder() { public void testPathFromString() throws Exception { // match the first statement of Foo.foo() method equals( - new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body[index=0]"), + new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body#statement[index=0]"), factory.Package().get("spoon.test.path").getType("Foo").getMethod("foo").getBody() .getStatement(0)); @@ -100,10 +104,63 @@ public void testPathFromString() throws Exception { equals(new CtPathStringBuilder().fromString(".spoon.test.path.Foo.toto#defaultExpression"), literal); } + @Test + public void testMultiPathFromString() throws Exception { + // When role match a list but no index is provided, all of them must be returned + Collection results = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body#statement") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 3); + // When role match a set but no name is provided, all of them must be returned + results = new CtPathStringBuilder().fromString("#subPackage") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 1); + // When role match a map but no key is provided, all of them must be returned + results = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.bar##annotation[index=0]#value") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 1); + + } + + @Test + public void testIncorrectPathFromString() throws Exception { + // match the else part of the if in Foo.bar() method which does not exist (Test non existing unique element) + Collection results = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.bar#body#statement[index=2]#else") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 0); + // match the third statement of Foo.foo() method which does not exist (Test non existing element of a list) + results = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body#statement[index=3]") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 0); + // match an non existing package (Test non existing element of a set) + results = new CtPathStringBuilder().fromString("#subPackage[name=nonExistingPackage]") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 0); + //match a non existing field of an annotation (Test non existing element of a map) + results = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.bar##annotation[index=0]#value[key=misspelled]") + .evaluateOn(factory.getModel().getRootPackage()); + assertEquals(results.size(), 0); + } + + @Test + public void testGetPathFromNonParent() throws Exception { + CtMethod fooMethod = (CtMethod) new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo") + .evaluateOn(factory.getModel().getRootPackage()).iterator().next(); + CtMethod barMethod = (CtMethod) new CtPathStringBuilder().fromString(".spoon.test.path.Foo.bar") + .evaluateOn(factory.getModel().getRootPackage()).iterator().next(); + try { + new CtElementPathBuilder().fromElement(fooMethod,barMethod); + fail("No path should be found to .spoon.test.path.Foo.foo from .spoon.test.path.Foo.bar"); + } catch (CtPathException e) { + + } + } + @Test public void testWildcards() throws Exception { // get the first statements of all Foo methods - equals(new CtPathStringBuilder().fromString(".spoon.test.path.Foo.*#body[index=0]"), + List list = new LinkedList<>(); + list.add(factory.getModel().getRootPackage()); + equals(new CtPathStringBuilder().fromString(".spoon.test.path.Foo.*#body#statement[index=0]"), ((CtClass) factory.Package().get("spoon.test.path").getType("Foo")).getConstructor().getBody() .getStatement(0), factory.Package().get("spoon.test.path").getType("Foo").getMethod("foo").getBody() @@ -130,10 +187,10 @@ public void testRoles() throws Exception { @Test public void toStringTest() throws Exception { comparePath(".spoon.test.path.Foo/CtMethod"); - comparePath(".spoon.test.path.Foo.foo#body[index=0]"); + comparePath(".spoon.test.path.Foo.foo#body#statement[index=0]"); comparePath(".spoon.test.path.Foo.bar/CtParameter"); comparePath(".spoon.test.path.Foo.toto#defaultExpression"); - comparePath(".spoon.test.path.Foo.*#body[index=0]"); + comparePath(".spoon.test.path.Foo.*#body#statement[index=0]"); comparePath(".**/CtIf#else"); comparePath(".**#else"); }