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

feat: Adds unique serializable path for each CtElement from Root Element. #1874

Merged
merged 28 commits into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
24e6e0b
add unique path to element getter
nharrand Feb 21, 2018
5163384
refactor test unique path
nharrand Feb 21, 2018
d3bd277
refactor: remove unecessary exception for CtElement.getPath()
nharrand Feb 21, 2018
b1b806f
refactor: remove unecessary exception for CtElement.getPath()
nharrand Feb 21, 2018
64a2758
fix(CtPathStringBuilder): Now supports CtUniqueRolePath
nharrand Feb 21, 2018
eda3e7c
doc(MainTest.testElementToPathToElementEquivalency): adds contract info
nharrand Feb 21, 2018
afc30dd
refactor(MainTest): cosmetic
nharrand Feb 22, 2018
e3e66c4
merge
nharrand Feb 23, 2018
3c69d39
doc(CtElementPathBuilder): add missing javadoc
nharrand Feb 23, 2018
be56d34
refactor: fix coding style
nharrand Feb 23, 2018
1e604a7
fix: MainTest merging error
nharrand Feb 23, 2018
14e0c2c
Update CtPathStringBuilder.java
monperrus Feb 23, 2018
aa826b8
refactor(CtRolePathElement): Replace old behavior by new one from CtU…
nharrand Feb 24, 2018
7865217
refactor(CtRolePathElement): fix coding style
nharrand Feb 24, 2018
45d6820
doc(CtPathStringBuilder): fix javadoc
nharrand Feb 24, 2018
8bc9df3
fix(CtElementImpl.getPath()): unsilence exception.
nharrand Feb 25, 2018
e2b309c
Merge branch 'feature-unique-path' of github.com:nharrand/spoon into …
nharrand Feb 25, 2018
a7290f4
refactor(CtRolePathElement,CtElementPathBuilder): uses RoleHandlerHelper
nharrand Feb 25, 2018
b2266ae
refactor(CtRolePathElement): remove old commented implem
nharrand Feb 25, 2018
1aaef21
Update path.md
monperrus Feb 25, 2018
d846e6f
Update CtElement.java
monperrus Feb 25, 2018
047f914
adds super.scan because the diff coverage is strange
monperrus Feb 25, 2018
b1f2276
fix(CtElementPathBuilder): List.indexOf rely on equality not identity
nharrand Feb 25, 2018
786d411
refactor(CtElementPathBuilder): fix coding style
nharrand Feb 25, 2018
8f82a94
simplify api of evaluateOn
monperrus Feb 26, 2018
8cfc1ad
fix(CtRolePathElement): the evaluation of a path that leads nowhere r…
nharrand Feb 26, 2018
67ec78b
fix(CtRolePathElement): extends unspecified index behavior to sets an…
nharrand Feb 26, 2018
7ee9921
fix(CtRolePathElement): replace exception throwing with return null w…
nharrand Feb 26, 2018
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: 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();

}
94 changes: 94 additions & 0 deletions src/main/java/spoon/reflect/path/CtElementPathBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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();
RoleHandler roleHandler = RoleHandlerHelper.getOptionalRoleHandler(parent.getClass(), role);
if (role == null || 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
125 changes: 62 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,17 @@
*/
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.Set;

/**
* A CtPathElement that define some roles for matching.
Expand All @@ -40,60 +42,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 +54,65 @@ public CtRole getRole() {

@Override
public String toString() {
return STRING + role.toString() + getParamString();
return STRING + getRole().toString() + getParamString();
}

public CtElement getFromSet(Set set, String name) throws CtPathException {
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we need it public? Note: In Spoon we try to make public API as small as possible. It is easier to maintain and refactor then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, we don't. I'll change it.

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();
}
}
throw new CtPathException();
Copy link
Collaborator

Choose a reason for hiding this comment

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

throwing of exception as part of normal program flow is not a good idea. It has a performance impact. So it is much faster (main line debug mode), when you return null and check it in caller.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 7ee9921

}

@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:
matchs.add(roleHandler.getValue(root));
break;

case LIST:
if (getArguments().containsKey("index")) {
int index = Integer.parseInt(getArguments().get("index"));
matchs.add((CtElement) roleHandler.asList(root).get(index));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

else it should add all items of the into matchs. WDYT?

Copy link
Collaborator

Choose a reason for hiding this comment

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

and what about case when index >= size()?

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, you are right. I only had in mind my Unique path case in mind here, but yes. I'll fix this

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right, that also means that I should add negative test cases...

Copy link
Collaborator Author

@nharrand nharrand Feb 26, 2018

Choose a reason for hiding this comment

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

else it should add all items of the into matchs.

We should extends that behavior to sets and maps, don't you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I agree. For Set it should add all items of the Set. And for Map it should add all values of the map.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Implemented in 67ec78b

break;

case SET:
if (getArguments().containsKey("name")) {
String name = getArguments().get("name");
try {
matchs.add(getFromSet(roleHandler.asSet(root), name));
} catch (CtPathException e) {
//System.err.println("[ERROR] Element not found for name: " + name);
//No element found for name.
}
}
break;

case MAP:
if (getArguments().containsKey("key")) {
String name = getArguments().get("key");
matchs.add((CtElement) roleHandler.asMap(root).get(name));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess it is not correct to add null into matchs, when map doesn't contain a key

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed. I'll fix this too.

}
break;
}
}
}
return matchs;
}

}
Loading