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 6 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
8 changes: 8 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,8 @@
import spoon.processing.FactoryAccessor;
import spoon.reflect.code.CtComment;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.path.CtPath;
import spoon.reflect.path.CtPathException;
import spoon.reflect.path.CtRole;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtVisitable;
Expand Down Expand Up @@ -349,4 +351,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);

/**
* Gets a unique path from models root to the CtElement.
*/
CtPath getPath();

}
67 changes: 67 additions & 0 deletions src/main/java/spoon/reflect/path/CtElementPathBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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.CtUniqueRolePathElement;
import spoon.reflect.reference.CtReference;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CtElementPathBuilder {
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 CtUniqueRolePathElement(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;
}
}
11 changes: 5 additions & 6 deletions src/main/java/spoon/reflect/path/CtPathStringBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@
*/
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.*;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand All @@ -30,7 +26,7 @@
*/
public class CtPathStringBuilder {

private final Pattern pathPattern = Pattern.compile("([/.#])([^/.#\\[]+)(\\[([^/.#]*)\\])?");
private final Pattern pathPattern = Pattern.compile("([/.#@])([^/.#@\\[]+)(\\[([^/.#@]*)\\])?");
private final Pattern argumentPattern = Pattern.compile("(\\w+)=([^=\\]]+)");


Expand Down Expand Up @@ -66,6 +62,7 @@ private Class load(String name) throws CtPathException {
* . : match with the given name
* # : match with a CtPathRole
* / : match with a element type (for example, to match all classes, use /CtClass
* @ : match with a CtUniqueRolePath
*/
public CtPath fromString(String pathStr) throws CtPathException {
Matcher matcher = pathPattern.matcher(pathStr);
Expand All @@ -81,6 +78,8 @@ public CtPath fromString(String pathStr) throws CtPathException {
pathElement = new CtTypedNameElement(load(matcher.group(2)));
} else if (CtRolePathElement.STRING.equals(kind)) {
pathElement = new CtRolePathElement(CtRole.fromName(matcher.group(2)));
} else if (CtUniqueRolePathElement.STRING.equals(kind)) {
pathElement = new CtUniqueRolePathElement(CtRole.fromName(matcher.group(2)));
}

String args = matcher.group(4);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/spoon/reflect/path/impl/CtPathElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package spoon.reflect.path.impl;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.path.CtPathException;

import java.util.Collection;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
import spoon.reflect.path.CtPathException;
import spoon.reflect.path.CtRole;
import spoon.reflect.visitor.CtInheritanceScanner;

Expand Down
66 changes: 66 additions & 0 deletions src/main/java/spoon/reflect/path/impl/CtUniqueRolePathElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package spoon.reflect.path.impl;

import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.path.CtPathException;
import spoon.reflect.path.CtRole;
import spoon.reflect.reference.CtReference;

import java.util.*;

public class CtUniqueRolePathElement extends CtRolePathElement {
public static final String STRING = "@";

public CtUniqueRolePathElement(CtRole role) {
super(role);
}

@Override
public String toString() {
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();
}

@Override
public Collection<CtElement> getElements(Collection<CtElement> roots) {
Collection<CtElement> matchs = new LinkedList<>();
for (CtElement root : roots) {
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 (Exception e) {
System.err.println("[ERROR] Element not found for name: " + 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);
}
}
return matchs;
}
}
12 changes: 12 additions & 0 deletions src/main/java/spoon/support/reflect/declaration/CtElementImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

why not removing the try/catch and let the exception go?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.
But still, we can let it go.

Copy link
Collaborator

Choose a reason for hiding this comment

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

"let it go" then :-)

Copy link
Collaborator Author

@nharrand nharrand Feb 25, 2018

Choose a reason for hiding this comment

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

I have a counter proposal: Why not catch CtPathException (which should not happen in that context) and throw a SpoonException to show that it's an spoon implementation error. Further more it would avoid that the user has to handle it.

try {
	return new CtElementPathBuilder().fromElement(this, getParent(CtModelImpl.CtRootPackage.class));
} catch (CtPathException e) {
	throw new SpoonException();
}

Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
}
}
39 changes: 32 additions & 7 deletions src/test/java/spoon/test/main/MainTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.ParentNotInitializedException;
import spoon.reflect.path.CtRole;
import spoon.reflect.path.*;
import spoon.reflect.reference.CtArrayTypeReference;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
Expand All @@ -40,13 +40,9 @@
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;

import static junit.framework.TestCase.fail;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
Expand Down Expand Up @@ -439,6 +435,35 @@ public void scan(CtRole role, CtElement element) {
});
}

@Test
public void testElementToPathToElementEquivalency() {

List<CtElement> list = new LinkedList<>();
list.add(rootPackage);

rootPackage.accept(new CtScanner() {
@Override
public void scan(CtRole role, CtElement element) {
if (element != null) {
CtPath path = element.getPath();
String pathStr = path.toString();
try {
CtPath pathRead = new CtPathStringBuilder().fromString(pathStr);
Collection<CtElement> returnedElements = pathRead.evaluateOn(list);
//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
assertEquals(element, actualElement);
Copy link
Collaborator

Choose a reason for hiding this comment

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

assertSame?

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, that sounds better. I'll fix it ASAP.

} catch (CtPathException e) {
fail();
}
}
super.scan(role, element);
}
});
}

@Test
public void testTest() throws Exception {
// the tests should be spoonable
Expand Down