Skip to content

Commit

Permalink
feat: add support for paths for all elements from model root (CtEleme…
Browse files Browse the repository at this point in the history
…nt#getPath) (#1874)
  • Loading branch information
nharrand authored and monperrus committed Feb 26, 2018
1 parent 1030f7c commit fd9d6c0
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 95 deletions.
31 changes: 20 additions & 11 deletions doc/path.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<CtElement>)`

```java
path = new CtPathStringBuilder().fromString(".spoon.test.path.Foo.foo#body#statement[index=0]");
List<CtElement> l = path.evaluateOn(root)
```


## CtPathStringBuilder

Expand All @@ -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
Expand Down Expand Up @@ -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()
```
```
7 changes: 7 additions & 0 deletions src/main/java/spoon/reflect/declaration/CtElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -349,4 +350,10 @@ <E extends CtElement> List<E> getAnnotatedChildren(
* @param value to be assigned to this field.
*/
<E extends CtElement, T> 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();

}
97 changes: 97 additions & 0 deletions src/main/java/spoon/reflect/path/CtElementPathBuilder.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
9 changes: 3 additions & 6 deletions src/main/java/spoon/reflect/path/CtPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
<T extends CtElement> Collection<T> evaluateOn(Collection<? extends CtElement> startNode);
<T extends CtElement> Collection<T> evaluateOn(CtElement... startNode);

}
3 changes: 2 additions & 1 deletion src/main/java/spoon/reflect/path/CtPathStringBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/spoon/reflect/path/impl/CtPathImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,8 +36,8 @@ public List<CtPathElement> getElements() {
}

@Override
public <T extends CtElement> Collection<T> evaluateOn(Collection<? extends CtElement> startNode) {
Collection<CtElement> filtered = new ArrayList<>(startNode);
public <T extends CtElement> Collection<T> evaluateOn(CtElement... startNode) {
Collection<CtElement> filtered = Arrays.asList(startNode);
for (CtPathElement element : elements) {
filtered = element.getElements(filtered);
}
Expand Down
143 changes: 80 additions & 63 deletions src/main/java/spoon/reflect/path/impl/CtRolePathElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -40,60 +43,6 @@ public class CtRolePathElement extends AbstractPathElement<CtElement, CtElement>

public static final String STRING = "#";

private class RoleVisitor extends CtInheritanceScanner {
private Collection<CtElement> matchs = new LinkedList<>();

private RoleVisitor() {
}

@Override
public <R> void scanCtExecutable(CtExecutable<R> 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 <T> void visitCtField(CtField<T> 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) {
Expand All @@ -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<CtElement> getElements(Collection<CtElement> roots) {
RoleVisitor visitor = new RoleVisitor();
visitor.scan(roots);
return visitor.matchs;
Collection<CtElement> 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<String, CtElement> map = roleHandler.asMap(root);
matchs.addAll(map.values());
}
break;
}
}
}
return matchs;
}

}
Loading

0 comments on commit fd9d6c0

Please sign in to comment.