-
-
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
feat: Adds unique serializable path for each CtElement from Root Element. #1874
Changes from 15 commits
24e6e0b
5163384
d3bd277
b1b806f
64a2758
eda3e7c
afc30dd
e3e66c4
3c69d39
be56d34
1e604a7
14e0c2c
aa826b8
7865217
45d6820
8bc9df3
e2b309c
a7290f4
b2266ae
1aaef21
d846e6f
047f914
b1f2276
786d411
8f82a94
8cfc1ad
67ec78b
7ee9921
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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.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; | ||
import java.util.Set; | ||
|
||
/** | ||
* 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 (parent == null || role == null) { | ||
throw new CtPathException(); | ||
} | ||
CtPathElement pathElement = new CtRolePathElement(role); | ||
if (parent.getValueByRole(role) instanceof List) { | ||
//Element needs to be differentiated from its brothers | ||
List list = parent.getValueByRole(role); | ||
//Assumes that List's order is deterministic. | ||
int index = 0; | ||
for (Object o : list) { | ||
if (o == cur) { | ||
break; | ||
} | ||
index++; | ||
} | ||
pathElement.addArgument("index", index + ""); | ||
} else if (parent.getValueByRole(role) instanceof Set) { | ||
if (!(cur instanceof CtNamedElement) && !(cur instanceof CtReference)) { | ||
throw new CtPathException(); | ||
} | ||
//Element needs to be differentiated from its brothers | ||
Set set = parent.getValueByRole(role); | ||
String name = null; | ||
for (Object o : set) { | ||
if (o == cur) { | ||
if (cur instanceof CtNamedElement) { | ||
name = ((CtNamedElement) cur).getSimpleName(); | ||
} else { | ||
name = ((CtReference) cur).getSimpleName(); | ||
} | ||
break; | ||
} | ||
} | ||
if (name == null) { | ||
throw new CtPathException(); | ||
} else { | ||
pathElement.addArgument("name", name); | ||
} | ||
|
||
} else if (parent.getValueByRole(role) instanceof Map) { | ||
Map map = parent.getValueByRole(role); | ||
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); | ||
} | ||
} | ||
cur = parent; | ||
path.addFirst(pathElement); | ||
} | ||
return path; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,15 +16,18 @@ | |
*/ | ||
package spoon.reflect.path.impl; | ||
|
||
import spoon.reflect.code.CtIf; | ||
import spoon.SpoonException; | ||
import spoon.reflect.declaration.CtElement; | ||
import spoon.reflect.declaration.CtExecutable; | ||
import spoon.reflect.declaration.CtField; | ||
import spoon.reflect.declaration.CtNamedElement; | ||
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.List; | ||
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<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) { | ||
|
@@ -106,14 +55,57 @@ 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 { | ||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
try { | ||
if (root.getValueByRole(getRole()) instanceof List) { | ||
if (getArguments().containsKey("index")) { | ||
int index = Integer.parseInt(getArguments().get("index")); | ||
matchs.add((CtElement) ((List) root.getValueByRole(getRole())).get(index)); | ||
} | ||
} else if (root.getValueByRole(getRole()) instanceof Set) { | ||
if (getArguments().containsKey("name")) { | ||
String name = getArguments().get("name"); | ||
try { | ||
matchs.add(getFromSet(root.getValueByRole(getRole()), name)); | ||
} catch (CtPathException e) { | ||
//System.err.println("[ERROR] Element not found for name: " + name); | ||
//No element found for name. | ||
} | ||
} | ||
} else if (root.getValueByRole(getRole()) instanceof Map) { | ||
if (getArguments().containsKey("key")) { | ||
String name = getArguments().get("key"); | ||
matchs.add((CtElement) ((Map) root.getValueByRole(getRole())).get(name)); | ||
} | ||
} else { | ||
CtElement el = root.getValueByRole(getRole()); | ||
matchs.add(el); | ||
} | ||
} catch (SpoonException e) { } //When no element are found for a given role, return empty list. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same thing here, do we want this exception to be silent? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, imo, because as it is, if we do not silence this exception, wildcards are broken. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess one alternative would be adding a method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is already there. See There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @pvojtechovsky Thanks for the tip. |
||
} | ||
return matchs; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
package spoon.support.reflect.declaration; | ||
|
||
import org.apache.log4j.Logger; | ||
import spoon.reflect.CtModelImpl; | ||
import spoon.reflect.annotations.MetamodelPropertyField; | ||
import spoon.reflect.code.CtComment; | ||
import spoon.reflect.code.CtJavaDoc; | ||
|
@@ -31,6 +32,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 +540,12 @@ public <E extends CtElement, T> E setValueByRole(CtRole role, T value) { | |
rh.setValue(this, value); | ||
return (E) this; | ||
} | ||
|
||
public CtPath getPath() { | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not removing the try/catch and let the exception go? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exception is supposed to be risen from CtElementPathBuilder().fromElement(CtElement el, CtElement from) when from is not a parent of el. So in this case, I think it can't happen and I didn't want to annoy the user with the handling of an exception which is not supposed to happen. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "let it go" then :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a counter proposal: Why not catch try {
return new CtElementPathBuilder().fromElement(this, getParent(CtModelImpl.CtRootPackage.class));
} catch (CtPathException e) {
throw new SpoonException();
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK for me. |
||
return new CtElementPathBuilder().fromElement(this, getParent(CtModelImpl.CtRootPackage.class)); | ||
} catch (CtPathException e) { | ||
} | ||
return null; | ||
} | ||
} |
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.
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.
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.
No, we don't. I'll change it.