diff --git a/doc/path.md b/doc/path.md index 578651040b2..0c8281ec2a3 100644 --- a/doc/path.md +++ b/doc/path.md @@ -20,6 +20,15 @@ path = new CtPathStringBuilder().fromString(".spoon.test.path.testclasses.Foo.fo List l = path.evaluateOn(root) ``` +If the root parameter is omitted, spoon will try to find shadow elements (e.g. jdk classes). + +> Querying shadow elements currently only supports types, methods and fields. + +```java +path = new CtPathStringBuilder().fromString("#subPackage[name=java]#subPackage[name=util]#containedType[name=HashSet]"); +List l = path.evaluateOn() +``` + ### Creating AST paths #### From an existing element diff --git a/src/main/java/spoon/reflect/declaration/CtClass.java b/src/main/java/spoon/reflect/declaration/CtClass.java index 02d4948031b..19fe72dd52b 100644 --- a/src/main/java/spoon/reflect/declaration/CtClass.java +++ b/src/main/java/spoon/reflect/declaration/CtClass.java @@ -49,6 +49,18 @@ public interface CtClass extends CtType, CtStatement, CtSealable { @PropertyGetter(role = CONSTRUCTOR) CtConstructor getConstructor(CtTypeReference... parameterTypes); + /** + * Returns the constructor of the class that takes the given signature. + * e.g. java.util.HashSet has constructor with no parameters `()` + * e.g. java.util.HashSet has constructor with a int and a float parameters `(int,float)` + * e.g. java.util.HashSet has constructor with a java.util.Collection parameter `(java.util.Collection)` + * + * Derived from {@link #getTypeMembers()} + */ + @DerivedProperty + @PropertyGetter(role = CONSTRUCTOR) + CtConstructor getConstructorBySignature(String signature); + /** * Returns the constructors of this class. This includes the default * constructor if this class has no constructors explicitly declared. diff --git a/src/main/java/spoon/reflect/declaration/CtType.java b/src/main/java/spoon/reflect/declaration/CtType.java index c8d0f55d049..7b9c0ea4d4e 100644 --- a/src/main/java/spoon/reflect/declaration/CtType.java +++ b/src/main/java/spoon/reflect/declaration/CtType.java @@ -234,6 +234,14 @@ public interface CtType extends CtNamedElement, CtTypeInformation, CtTypeMemb @PropertyGetter(role = METHOD) CtMethod getMethod(String name, CtTypeReference... parameterTypes); + /** + * Gets a method from its signature. + * + * @return null if does not exit + */ + @PropertyGetter(role = METHOD) + CtMethod getMethodBySignature(String signature); + /** * Returns the methods that are directly declared by this class or * interface. diff --git a/src/main/java/spoon/reflect/path/CtPath.java b/src/main/java/spoon/reflect/path/CtPath.java index e50a5d95530..adaf1a2c0b5 100644 --- a/src/main/java/spoon/reflect/path/CtPath.java +++ b/src/main/java/spoon/reflect/path/CtPath.java @@ -21,6 +21,11 @@ public interface CtPath { */ List evaluateOn(CtElement... startNode); + /** + * Search for elements matching this CtPatch from shadow model. + */ + CtElement evaluateOnShadowModel(); + /** * * Returns the path that is relative to the given element (subpath from it to the end of the path). diff --git a/src/main/java/spoon/reflect/path/CtPathStringBuilder.java b/src/main/java/spoon/reflect/path/CtPathStringBuilder.java index 7082db76d81..7f1de58fff3 100644 --- a/src/main/java/spoon/reflect/path/CtPathStringBuilder.java +++ b/src/main/java/spoon/reflect/path/CtPathStringBuilder.java @@ -13,8 +13,8 @@ import spoon.reflect.path.impl.CtNamedPathElement; import spoon.reflect.path.impl.CtPathElement; import spoon.reflect.path.impl.CtPathImpl; -import spoon.reflect.path.impl.CtTypedNameElement; import spoon.reflect.path.impl.CtRolePathElement; +import spoon.reflect.path.impl.CtTypedNameElement; import java.util.ArrayDeque; import java.util.Deque; @@ -145,7 +145,8 @@ private String parseArgumentValue(Tokenizer tokenizer, String argName, CtPathEle } } else if ("]".equals(token) || ";".equals(token)) { //finished reading of argument value - pathElement.addArgument(argName, argValue.toString()); + //[fix bug]:AbstractPathElement.getArguments([constructor with no parameters]) + pathElement.addArgument(argName, argValue.toString().replace("())", "()")); return token; } argValue.append(token); diff --git a/src/main/java/spoon/reflect/path/impl/CtPathImpl.java b/src/main/java/spoon/reflect/path/impl/CtPathImpl.java index a178f68c433..ab78cd72469 100644 --- a/src/main/java/spoon/reflect/path/impl/CtPathImpl.java +++ b/src/main/java/spoon/reflect/path/impl/CtPathImpl.java @@ -7,8 +7,12 @@ */ package spoon.reflect.path.impl; +import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; +import spoon.reflect.declaration.CtType; +import spoon.reflect.factory.TypeFactory; import spoon.reflect.path.CtPath; +import spoon.reflect.path.CtRole; import java.util.ArrayList; import java.util.Arrays; @@ -36,6 +40,70 @@ public List evaluateOn(CtElement... startNode) { return (List) filtered; } + @Override + public CtElement evaluateOnShadowModel() { + List classRoleNameList = new LinkedList<>(); + CtType ctType = null; + for (CtPathElement element : elements) { + if (element instanceof CtRolePathElement) { // search by CtRolePathElement + Collection values = ((CtRolePathElement) element).getArguments().values(); + String val = null; + if (values.iterator().hasNext()) { + val = values.iterator().next(); + } + if (val != null) { + if (CtRole.SUB_PACKAGE.equals(((CtRolePathElement) element).getRole()) + || CtRole.CONTAINED_TYPE.equals(((CtRolePathElement) element).getRole())) { + classRoleNameList.add(val); + } + Class cls = getJdkClass(String.join(".", classRoleNameList)); + if (cls != null) { + if (ctType == null) { + ctType = new TypeFactory().get(cls); + } else { + if (CtRole.METHOD.equals(((CtRolePathElement) element).getRole())) { + return ctType.getMethodBySignature(val); + } + if (CtRole.CONSTRUCTOR.equals(((CtRolePathElement) element).getRole())) { + return ((CtClass) ctType).getConstructorBySignature(val); + } + if (CtRole.FIELD.equals(((CtRolePathElement) element).getRole())) { + return ctType.getField(val); + } + } + } + } + } + } + return ctType; + } + + private Class getJdkClass(String name) { + name = name.replaceAll("[\\[\\]]", ""); + switch (name) { + case "byte": + return byte.class; + case "int": + return int.class; + case "long": + return long.class; + case "float": + return float.class; + case "double": + return double.class; + case "char": + return char.class; + case "boolean": + return boolean.class; + default: + try { + return Class.forName(name); + } catch (ClassNotFoundException e) { + } + } + return null; + } + @Override public CtPath relativePath(CtElement parent) { List roots = new ArrayList<>(); diff --git a/src/main/java/spoon/support/reflect/declaration/CtClassImpl.java b/src/main/java/spoon/support/reflect/declaration/CtClassImpl.java index c3272176f72..24a98356364 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtClassImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtClassImpl.java @@ -88,6 +88,20 @@ public CtConstructor getConstructor(CtTypeReference... parameterTypes) { return null; } + @Override + public CtConstructor getConstructorBySignature(String signature) { + for (CtTypeMember typeMember : getTypeMembers()) { + if (!(typeMember instanceof CtConstructor)) { + continue; + } + CtConstructor c = (CtConstructor) typeMember; + if (c.getSignature().replaceAll(c.getDeclaringType().getQualifiedName(), "").equals(signature)) { + return c; + } + } + return null; + } + @Override public Set> getConstructors() { Set> constructors = new SignatureBasedSortedSet<>(); diff --git a/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java b/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java index 8e4a5804c7f..818f72b2e3a 100644 --- a/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java +++ b/src/main/java/spoon/support/reflect/declaration/CtTypeImpl.java @@ -723,6 +723,21 @@ public CtMethod getMethod(String name, CtTypeReference... parameterTyp return null; } + @Override + @SuppressWarnings("unchecked") + public CtMethod getMethodBySignature(String signature) { + if (signature == null) { + return null; + } + String name = signature.substring(0, signature.indexOf('(')); + for (CtMethod candidate : getMethodsByName(name)) { + if (candidate.getSignature().equals(signature)) { + return (CtMethod) candidate; + } + } + return null; + } + protected boolean hasSameParameters(CtExecutable candidate, CtTypeReference... parameterTypes) { if (candidate.getParameters().size() != parameterTypes.length) { return false; diff --git a/src/test/java/spoon/test/path/PathTest.java b/src/test/java/spoon/test/path/PathTest.java index eccb2340265..d0b9041f27f 100644 --- a/src/test/java/spoon/test/path/PathTest.java +++ b/src/test/java/spoon/test/path/PathTest.java @@ -39,6 +39,7 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.CtTypeMember; import spoon.reflect.factory.Factory; +import spoon.reflect.factory.TypeFactory; import spoon.reflect.path.CtElementPathBuilder; import spoon.reflect.path.CtPath; import spoon.reflect.path.CtPathBuilder; @@ -55,6 +56,7 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Created by nicolas on 10/06/2015. @@ -409,4 +411,31 @@ public void testFieldOfArrayType() { assertEquals(1, result.size()); assertSame(argType, result.get(0)); } + + @Test + public void testGetJdkElementByCtPathString() { + CtPath path; + // test class + path = new CtPathStringBuilder().fromString("#subPackage[name=java]#subPackage[name=util]#containedType[name=HashSet]"); + assertNotNull(path.evaluateOnShadowModel()); + CtType class_HashSet = new TypeFactory().get(java.util.HashSet.class); + assertEquals(path.evaluateOnShadowModel(), class_HashSet); + // if unable to get the method or the field, it will try to return the class. + // test method + path = new CtPathStringBuilder().fromString("#subPackage[name=java]#subPackage[name=util]#containedType[name=HashSet]#method[signature=contains(java.lang.Object)]"); + assertNotNull(path.evaluateOnShadowModel()); + assertEquals(path.evaluateOnShadowModel(), class_HashSet.getMethodBySignature("contains(java.lang.Object)")); + // test constructor + path = new CtPathStringBuilder().fromString("#subPackage[name=java]#subPackage[name=util]#containedType[name=HashSet]#constructor[signature=()]"); + assertNotNull(path.evaluateOnShadowModel()); + assertEquals(path.evaluateOnShadowModel(), ((CtClass) class_HashSet).getConstructorBySignature("()")); + + path = new CtPathStringBuilder().fromString("#subPackage[name=java]#subPackage[name=util]#containedType[name=HashSet]#constructor[signature=(int)]"); + assertNotNull(path.evaluateOnShadowModel()); + assertEquals(path.evaluateOnShadowModel(), ((CtClass) class_HashSet).getConstructorBySignature("(int)")); + // test field + path = new CtPathStringBuilder().fromString("#subPackage[name=java]#subPackage[name=util]#containedType[name=HashSet]#field[name=map]"); + assertNotNull(path.evaluateOnShadowModel()); + assertEquals(path.evaluateOnShadowModel(), class_HashSet.getField("map")); + } } \ No newline at end of file