diff --git a/ide-test/org.codehaus.groovy.eclipse.dsl.tests/src/org/codehaus/groovy/eclipse/dsl/tests/PointcutEvaluationTests.groovy b/ide-test/org.codehaus.groovy.eclipse.dsl.tests/src/org/codehaus/groovy/eclipse/dsl/tests/PointcutEvaluationTests.groovy index d7b5299e82..2580182a6b 100644 --- a/ide-test/org.codehaus.groovy.eclipse.dsl.tests/src/org/codehaus/groovy/eclipse/dsl/tests/PointcutEvaluationTests.groovy +++ b/ide-test/org.codehaus.groovy.eclipse.dsl.tests/src/org/codehaus/groovy/eclipse/dsl/tests/PointcutEvaluationTests.groovy @@ -208,6 +208,36 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { doTestOfLastMatch('2', 'currentType(methods("intValue"))', 'java.lang.Integer') } + @Test + void testCurrentTypeHasConstructor1() { + doTestOfLastMatch('2', 'currentType(hasConstructor("int"))', 'java.lang.Integer') + } + + @Test + void testCurrentTypeHasConstructor2() { + doTestOfLastMatch('2', 'currentType(hasConstructor("String"))', 'java.lang.Integer') + } + + @Test + void testCurrentTypeHasConstructor3() { + doTestOfLastMatch('""', 'currentType(hasConstructor("char[],int,int"))', 'java.lang.String') + } + + @Test + void testCurrentTypeHasConstructor4() { + doTestOfLastMatch('2', 'currentType(hasConstructor("Unknown"))', null) + } + + @Test + void testCurrentTypeHasConstructor5() { + doTestOfLastMatch('2', 'currentType(hasConstructor(isPublic()))', 'java.lang.Integer') + } + + @Test + void testCurrentTypeHasConstructor6() { + doTestOfLastMatch('2', 'currentType(hasConstructor(isPrivate()))', null) + } + @Test void testCurrentTypeFieldsAndMethods1() { doTestOfLastMatch('2', 'currentType(fields("value") & methods("intValue"))', 'java.lang.Integer') @@ -641,9 +671,9 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { void testNestedCalls1() { doTestOfLastBindingSet( 'bar {\n' + - ' foo {\n' + - ' XXX\n' + - ' }\n' + + ' foo {\n' + + ' XXX\n' + + ' }\n' + '}', 'bind( x: enclosingCall()) & bind(y: currentIdentifier("XXX"))', new BindingResult('x', 'foo(), bar()'), new BindingResult('y', 'Var: XXX')) @@ -653,9 +683,9 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { void testNestedCalls2() { doTestOfLastBindingSet( 'foo {\n' + - ' bar {\n' + - ' XXX\n' + - ' }\n' + + ' bar {\n' + + ' XXX\n' + + ' }\n' + '}', 'bind( x: enclosingCall()) & bind(y: currentIdentifier("XXX"))', new BindingResult('x', 'bar(), foo()'), new BindingResult('y', 'Var: XXX')) @@ -665,9 +695,9 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { void testNestedCalls3() { doTestOfLastBindingSet( 'foo {\n' + - ' foo {\n' + - ' XXX\n' + - ' }\n' + + ' foo {\n' + + ' XXX\n' + + ' }\n' + '}', 'bind( x: enclosingCall()) & bind(y: currentIdentifier("XXX"))', new BindingResult('x', 'foo(), foo()'), new BindingResult('y', 'Var: XXX')) @@ -677,9 +707,9 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { void testNestedCallNames1() { doTestOfLastBindingSet( 'bar {\n' + - ' foo {\n' + - ' XXX\n' + - ' }\n' + + ' foo {\n' + + ' XXX\n' + + ' }\n' + '}', 'bind( x: enclosingCallName()) & bind(y: currentIdentifier("XXX"))', new BindingResult('x', 'foo, bar'), new BindingResult('y', 'Var: XXX')) @@ -689,9 +719,9 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { void testNestedCallNames2() { doTestOfLastBindingSet( 'foo {\n' + - ' bar {\n' + - ' XXX\n' + - ' }\n' + + ' bar {\n' + + ' XXX\n' + + ' }\n' + '}', 'bind( x: enclosingCallName()) & bind(y: currentIdentifier("XXX"))', new BindingResult('x', 'bar, foo'), new BindingResult('y', 'Var: XXX')) @@ -701,9 +731,9 @@ final class PointcutEvaluationTests extends GroovyEclipseTestSuite { void testNestedCallsName3() { doTestOfLastBindingSet( 'foo {\n' + - ' foo {\n' + - ' XXX\n' + - ' }\n' + + ' foo {\n' + + ' XXX\n' + + ' }\n' + '}', 'bind( x: enclosingCallName()) & bind(y: currentIdentifier("XXX"))', diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FilteringPointcut.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FilteringPointcut.java index fc96ffd446..af9b7b58f6 100644 --- a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FilteringPointcut.java +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FilteringPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ */ public abstract class FilteringPointcut extends AbstractPointcut { - private final Class filterBy; + protected final Class filterBy; public FilteringPointcut(IStorage containerIdentifier, String pointcutName, Class filterBy) { super(containerIdentifier, pointcutName); diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindASTPointcut.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindASTPointcut.java new file mode 100644 index 0000000000..76b1b9d39a --- /dev/null +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindASTPointcut.java @@ -0,0 +1,68 @@ +/* + * Copyright 2009-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codehaus.groovy.eclipse.dsl.pointcuts.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import java.util.function.Function; + +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.eclipse.dsl.pointcuts.GroovyDSLDContext; +import org.eclipse.core.resources.IStorage; +import org.eclipse.jdt.groovy.core.util.GroovyUtils; + +public abstract class FindASTPointcut extends FilteringPointcut { + + private final Function> exploder; + private final Function stringify; + + public FindASTPointcut(IStorage containerIdentifier, String pointcutName, Class filterBy, + Function> exploder, Function stringify) { + super(containerIdentifier, pointcutName, filterBy); + this.exploder = Objects.requireNonNull(exploder); + this.stringify = Objects.requireNonNull(stringify); + } + + @Override + protected final Collection explodeObject(Object object) { + Collection result = new ArrayList<>(); + explodeObject(object, result); + return result; + } + + protected void explodeObject(Object object, Collection result) { + if (filterBy.isInstance(object)) { + @SuppressWarnings("unchecked") + T node = (T) object; + result.add(node); + } else if (object instanceof ClassNode) { + result.addAll(exploder.apply( + GroovyUtils.getWrapperTypeIfPrimitive((ClassNode) object))); + } else if (object instanceof Collection) { + ((Collection) object).forEach(item -> explodeObject(item, result)); + } + } + + @Override + protected T filterObject(T result, GroovyDSLDContext context, String firstArgAsString) { + if (firstArgAsString == null || firstArgAsString.equals(stringify.apply(result))) { + return result; + } + return null; + } +} diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindCtorPointcut.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindCtorPointcut.java new file mode 100644 index 0000000000..a1eca39e45 --- /dev/null +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindCtorPointcut.java @@ -0,0 +1,56 @@ +/* + * Copyright 2009-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.codehaus.groovy.eclipse.dsl.pointcuts.impl; + +import java.util.List; +import java.util.stream.Collectors; + +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.ConstructorNode; +import org.eclipse.core.resources.IStorage; +import org.eclipse.jdt.groovy.core.util.GroovyUtils; + +/** + * Matches if the input has a constructor with the supplied characteristics -- + * either a signature (comma-separated simple type names) or another pointcut + * like {@code hasAnnotation}. + */ +public class FindCtorPointcut extends FindASTPointcut { + + public FindCtorPointcut(IStorage containerIdentifier, String pointcutName) { + super(containerIdentifier, pointcutName, ConstructorNode.class, ClassNode::getDeclaredConstructors, ctor -> { + List paramTypes = GroovyUtils.getParameterTypes(ctor.getParameters()); + return paramTypes.stream().map(FindCtorPointcut::getSimpleTypeName) + .map(name -> name.substring(name.lastIndexOf('$') + 1)) + .collect(Collectors.joining(",")); + }); + } + + private static String getSimpleTypeName(ClassNode type) { + int dims = 0; + while (type.isArray()) { + dims += 1; + type = type.getComponentType(); + } + + String name = type.getNameWithoutPackage(); + while (dims > 0) { + name += "[]"; + dims -= 1; + } + return name; + } +} diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindFieldPointcut.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindFieldPointcut.java index 611fb59d9e..52f34b995e 100644 --- a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindFieldPointcut.java +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindFieldPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2018 the original author or authors. + * Copyright 2009-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,60 +15,17 @@ */ package org.codehaus.groovy.eclipse.dsl.pointcuts.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; -import org.codehaus.groovy.eclipse.dsl.pointcuts.GroovyDSLDContext; import org.eclipse.core.resources.IStorage; -import org.eclipse.jdt.groovy.core.util.GroovyUtils; /** - * the match returns true if the pattern passed in has a field with the - * supplied characteristics (either a name, or another pointcut such as hasAnnotation). + * Matches if the input has a field with the supplied characteristics -- either + * a name or another pointcut like {@code hasAnnotation}. */ -public class FindFieldPointcut extends FilteringPointcut { +public class FindFieldPointcut extends FindASTPointcut { public FindFieldPointcut(IStorage containerIdentifier, String pointcutName) { - super(containerIdentifier, pointcutName, FieldNode.class); - } - - /** - * Converts toMatch to a collection of field nodes. Might be null or empty list - * In either of these cases, this is considered a non-match - * @param toMatch the object to explode - */ - @Override - protected Collection explodeObject(Object toMatch) { - if (toMatch instanceof Collection) { - Collection fields = new ArrayList<>(); - for (Object obj : (Collection) toMatch) { - if (obj instanceof FieldNode) { - fields.add((FieldNode) obj); - } else if (obj instanceof ClassNode) { - fields.addAll(((ClassNode) obj).getFields()); - } - } - return fields; - } else if (toMatch instanceof FieldNode) { - return Collections.singleton((FieldNode) toMatch); - } else if (toMatch instanceof ClassNode) { - return new ArrayList<>(GroovyUtils.getWrapperTypeIfPrimitive((ClassNode) toMatch).getFields()); - } - return null; - } - - /** - * This gets called if the pointcut argument is a String argument - */ - @Override - protected FieldNode filterObject(FieldNode result, GroovyDSLDContext context, String firstArgAsString) { - if (firstArgAsString == null || result.getName().equals(firstArgAsString)) { - return result; - } else { - return null; - } + super(containerIdentifier, pointcutName, FieldNode.class, ClassNode::getFields, FieldNode::getName); } } diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindMethodPointcut.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindMethodPointcut.java index ab3431d0f8..64159ee972 100644 --- a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindMethodPointcut.java +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindMethodPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,56 +15,17 @@ */ package org.codehaus.groovy.eclipse.dsl.pointcuts.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; -import org.codehaus.groovy.eclipse.dsl.pointcuts.GroovyDSLDContext; import org.eclipse.core.resources.IStorage; /** - * the match returns true if the pattern passed in has a method with the - * supplied characteristics (either a name, or another pointcut such as hasAnnotation). + * Matches if the input has a method with the supplied characteristics -- either + * a name or another pointcut like {@code hasAnnotation}. */ -public class FindMethodPointcut extends FilteringPointcut { +public class FindMethodPointcut extends FindASTPointcut { public FindMethodPointcut(IStorage containerIdentifier, String pointcutName) { - super(containerIdentifier, pointcutName, MethodNode.class); - } - - /** - * Converts toMatch to a collection of method nodes. Might be null or empty list - * In either of these cases, this is considered a non-match - * @param toMatch the object to explode - */ - @Override - protected Collection explodeObject(Object toMatch) { - if (toMatch instanceof Collection) { - Collection methods = new ArrayList<>(); - for (Object obj : (Collection) toMatch) { - if (obj instanceof MethodNode) { - methods.add((MethodNode) obj); - } else if (obj instanceof ClassNode) { - methods.addAll(((ClassNode) obj).getMethods()); - } - } - return methods; - } else if (toMatch instanceof MethodNode) { - return Collections.singleton((MethodNode) toMatch); - } else if (toMatch instanceof ClassNode) { - return new ArrayList<>(((ClassNode) toMatch).getMethods()); - } - return null; - } - - @Override - protected MethodNode filterObject(MethodNode result, GroovyDSLDContext context, String firstArgAsString) { - if (firstArgAsString == null || result.getName().equals(firstArgAsString)) { - return result; - } else { - return null; - } + super(containerIdentifier, pointcutName, MethodNode.class, ClassNode::getMethods, MethodNode::getName); } } diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindPropertyPointcut.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindPropertyPointcut.java index efb1f35ed7..a03fa62677 100644 --- a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindPropertyPointcut.java +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/pointcuts/impl/FindPropertyPointcut.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2017 the original author or authors. + * Copyright 2009-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,57 +15,17 @@ */ package org.codehaus.groovy.eclipse.dsl.pointcuts.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.PropertyNode; -import org.codehaus.groovy.eclipse.dsl.pointcuts.GroovyDSLDContext; import org.eclipse.core.resources.IStorage; /** - * the match returns true if the pattern passed in has a field with the - * supplied characteristics (either a name, or another pointcut such as hasAnnotation). + * Matches if the input has a property with the supplied characteristics -- either + * a name or another pointcut like {@code hasAnnotation}. */ -public class FindPropertyPointcut extends FilteringPointcut { +public class FindPropertyPointcut extends FindASTPointcut { public FindPropertyPointcut(IStorage containerIdentifier, String pointcutName) { - super(containerIdentifier, pointcutName, PropertyNode.class); - } - - /** - * Converts toMatch to a collection of property nodes. Might be null or empty list - * In either of these cases, this is considered a non-match - * @param toMatch the object to explode - */ - @Override - protected Collection explodeObject(Object toMatch) { - if (toMatch instanceof Collection) { - Collection properties = new ArrayList<>(); - for (Object obj : (Collection) toMatch) { - if (obj instanceof PropertyNode) { - properties.add((PropertyNode) obj); - } else if (obj instanceof ClassNode) { - properties.addAll(((ClassNode) obj).getProperties()); - } - } - return properties; - } else if (toMatch instanceof PropertyNode) { - return Collections.singleton((PropertyNode) toMatch); - } else if (toMatch instanceof ClassNode) { - return new ArrayList<>(((ClassNode) toMatch).getProperties()); - } - return null; - } - - - @Override - protected PropertyNode filterObject(PropertyNode result, GroovyDSLDContext context, String firstArgAsString) { - if (firstArgAsString == null || result.getName().equals(firstArgAsString)) { - return result; - } else { - return null; - } + super(containerIdentifier, pointcutName, PropertyNode.class, ClassNode::getProperties, PropertyNode::getName); } } diff --git a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/script/PointcutFactory.java b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/script/PointcutFactory.java index f41d0d44cb..b239c0fec4 100644 --- a/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/script/PointcutFactory.java +++ b/ide/org.codehaus.groovy.eclipse.dsl/src/org/codehaus/groovy/eclipse/dsl/script/PointcutFactory.java @@ -48,6 +48,7 @@ import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FileExtensionPointcut; import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FileNamePointcut; import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FindAnnotationPointcut; +import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FindCtorPointcut; import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FindFieldPointcut; import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FindMethodPointcut; import org.codehaus.groovy.eclipse.dsl.pointcuts.impl.FindPropertyPointcut; @@ -130,6 +131,8 @@ public class PointcutFactory { registerGlobalPointcut("properties", FindPropertyPointcut.class, createFind("property", "properties")); + registerGlobalPointcut("hasConstructor", FindCtorPointcut.class, createFind("constructor", "constructors")); + registerGlobalPointcut("name", NamePointcut.class, createDoc(// "Matches when the items passed in equal the name specified as the argument. " + "Often, this pointcut is superfluous as the properties, methods, and fields pointcuts already take a String as a name. " + @@ -308,10 +311,10 @@ private static String createDoc(String description, String expects, String retur private static String createFind(String kind, String kinds) { return createDoc( - "Matches when the containing pointcut passes in a type or a list of " + kind + " that has at least one " + kind + " specified by the argument of this pointcut.", - "A String corresponding to a " + kind + " name. Alternatively, a pointcut, such as annotatedBy, which " + "would match all " + kinds + " annotated by the inner pointcut.", - "the " + kind + " or " + kinds + " matched by the argument. " + "Eg- If the surrounding pointcut passes in a type, then the value returned will be a set of all " + kinds + " in that " + - "type that match the contained pointcut, or that have the specified name." + " If the surrounding pointcut passes in a set of " + kinds + ", thne the result will be a subset of those " + kinds + " containing only nodes with the correct annotation."); + "Matches when the containing pointcut passes in a type or a list of " + kinds + " that has at least one " + kind + " specified by the argument of this pointcut.", + "A String corresponding to a " + kind + ". Alternatively, a pointcut, such as annotatedBy, which " + "would match all " + kinds + " annotated by the inner pointcut.", + "The " + kind + " or " + kinds + " matched by the argument. " + "Eg- If the surrounding pointcut passes in a type, then the value returned will be the set of all " + kinds + " in that " + + "type that match the contained pointcut or have the specified signature." + " If the surrounding pointcut passes in a set of " + kinds + ", then the result will be a subset of those " + kinds + " that satisfy the contained pointcut or have the specified signature."); } private static String createModifier(String modifier) {