From 2c77401fc59c6e60b0b8013c0cac14c8ad83cdbf Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 25 May 2011 17:36:15 +0200 Subject: [PATCH] bind pointcut --- .../groovy/dsl/CustomMembersGenerator.java | 2 +- .../plugins/groovy/dsl/DslPointcut.java | 58 +++++++++++++++---- .../plugins/groovy/dsl/GdslType.java | 41 +++++++++++++ .../groovy/dsl/GroovyDslExecutor.groovy | 28 ++++++++- .../groovy/dsl/PointcutContextFilter.java | 6 +- .../dsl/toplevel/ClassContextFilter.java | 12 ++-- .../plugins/groovy/dsl/DsldTest.groovy | 5 ++ 7 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GdslType.java diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/CustomMembersGenerator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/CustomMembersGenerator.java index 770acc8638a9..2c939970d405 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/CustomMembersGenerator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/CustomMembersGenerator.java @@ -90,7 +90,7 @@ public class CustomMembersGenerator implements GdslMembersHolderConsumer { myDepot.addHolder(holder); } - protected Object[] constructNewArgs(Object[] args) { + public Object[] constructNewArgs(Object[] args) { final Object[] newArgs = new Object[args.length + 1]; //noinspection ManualArrayCopy for (int i = 0; i < args.length; i++) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/DslPointcut.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/DslPointcut.java index d2d24cb4d4a9..f4375d2004b5 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/DslPointcut.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/DslPointcut.java @@ -1,22 +1,32 @@ package org.jetbrains.plugins.groovy.dsl; +import com.intellij.openapi.util.Key; import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.dsl.toplevel.ClassContextFilter; +import java.util.*; + /** * @author peter */ -public abstract class DslPointcut { +public abstract class DslPointcut { + public static Key> BOUND = Key.create("gdsl.bound"); - abstract boolean matches(T t, ProcessingContext context); + @Nullable + abstract List matches(T src, ProcessingContext context); abstract boolean operatesOn(Class c); - public static DslPointcut currentType(final Object arg) { - return new DslPointcut() { + public static DslPointcut currentType(final Object arg) { + return new DslPointcut() { + @Override - boolean matches(GroovyClassDescriptor groovyClassDescriptor, ProcessingContext context) { - return ClassContextFilter.subtypeOf((String)arg).isApplicable(groovyClassDescriptor, context); + List matches(GroovyClassDescriptor src, ProcessingContext context) { + if (ClassContextFilter.subtypeOf((String)arg).isApplicable(src, context)) { + return Arrays.asList(new GdslType(ClassContextFilter.findPsiType(src, context))); + } + return null; } @Override @@ -26,11 +36,39 @@ public abstract class DslPointcut { }; } - public static DslPointcut unknownPointcut() { - return new DslPointcut() { + public static DslPointcut bind(final Object arg) { + assert arg instanceof Map; + assert ((Map)arg).size() == 1; + final String name = (String)((Map)arg).keySet().iterator().next(); + final DslPointcut pct = (DslPointcut)((Map)arg).values().iterator().next(); + + return new DslPointcut() { @Override - boolean matches(T t, ProcessingContext context) { - return true; + List matches(Object src, ProcessingContext context) { + final List result = pct.matches(src, context); + if (result != null) { + Map map = context.get(BOUND); + if (map == null) { + context.put(BOUND, map = new HashMap()); + } + map.put(name, result); + } + return result; + } + + @Override + boolean operatesOn(Class c) { + return pct.operatesOn(c); + } + }; + } + + public static DslPointcut unknownPointcut() { + return new DslPointcut() { + + @Override + List matches(Object src, ProcessingContext context) { + return Collections.emptyList(); } @Override diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GdslType.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GdslType.java new file mode 100644 index 000000000000..da5e8dc244b1 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GdslType.java @@ -0,0 +1,41 @@ +package org.jetbrains.plugins.groovy.dsl; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiClassType; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiWildcardType; + +/** + * @author peter + */ +public class GdslType { + private final PsiType myPsiType; + + public GdslType(PsiType psiType) { + myPsiType = psiType; + } + + public String getName() { + PsiType type = myPsiType; + if (type instanceof PsiWildcardType) { + type = ((PsiWildcardType)type).getBound(); + } + if (type instanceof PsiClassType) { + final PsiClass resolve = ((PsiClassType)type).resolve(); + if (resolve != null) { + return resolve.getName(); + } + final String canonicalText = type.getCanonicalText(); + final int i = canonicalText.indexOf('<'); + if (i < 0) return canonicalText; + return canonicalText.substring(0, i); + } + + if (type == null) { + return ""; + } + + return type.getCanonicalText(); + } + +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy index ad50ef72a016..202f653a934d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/GroovyDslExecutor.groovy @@ -6,6 +6,7 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.patterns.ElementPattern import com.intellij.patterns.PsiJavaPatterns import com.intellij.util.ProcessingContext +import org.jetbrains.plugins.groovy.dsl.dsltop.GdslMembersProvider import org.jetbrains.plugins.groovy.dsl.psi.PsiEnhancerCategory import org.jetbrains.plugins.groovy.dsl.toplevel.CompositeContextFilter import org.jetbrains.plugins.groovy.dsl.toplevel.Context @@ -70,6 +71,7 @@ public class GroovyDslExecutor { mc.contribute = contribute mc.currentType = { arg -> DslPointcut.currentType(arg) } + mc.bind = { arg -> DslPointcut.bind(arg) } oldStylePrimitives(mc) @@ -112,11 +114,12 @@ public class GroovyDslExecutor { enhancers << Pair.create(CompositeContextFilter.compose(cts, false), toDo) } - def processVariants(GroovyClassDescriptor descriptor, consumer, ProcessingContext ctx) { + def processVariants(GroovyClassDescriptor descriptor, CustomMembersGenerator consumer, ProcessingContext ctx) { for (pair in enhancers) { if (pair.first.isApplicable(descriptor, ctx)) { Closure f = pair.second.clone() f.delegate = consumer + consumer.metaClass = contributionDelegateMetaClass(ctx, consumer) f.resolveStrategy = Closure.DELEGATE_FIRST use(cats) { @@ -126,6 +129,29 @@ public class GroovyDslExecutor { } } + static final def memberProviders = GdslMembersProvider.EP_NAME.getExtensions() + + private ExpandoMetaClass contributionDelegateMetaClass(ProcessingContext ctx, CustomMembersGenerator consumer) { + def mc = new ExpandoMetaClass(CustomMembersGenerator) + mc.methodMissing = { String name, Object args -> + final def newArgs = consumer.constructNewArgs(args) + + // Get other DSL methods from extensions + for (d in memberProviders) { + final def variants = d.metaClass.respondsTo(d, name, newArgs) + if (variants.size() == 1) { + return d.invokeMethod(name, newArgs) + } + } + return null + } + + def bound = ctx.get(DslPointcut.BOUND) + bound.each { name, value -> mc."$name" = value } + mc.initialize() + return mc + } + def String toString() { return "${super.toString()}; file = $myFileName"; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/PointcutContextFilter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/PointcutContextFilter.java index 7afccd92de0c..b33b997511ff 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/PointcutContextFilter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/PointcutContextFilter.java @@ -7,14 +7,14 @@ import org.jetbrains.plugins.groovy.dsl.toplevel.ContextFilter; * @author peter */ public class PointcutContextFilter implements ContextFilter { - private final DslPointcut myPointcut; + private final DslPointcut myPointcut; - public PointcutContextFilter(DslPointcut pointcut) { + public PointcutContextFilter(DslPointcut pointcut) { myPointcut = pointcut; } @Override public boolean isApplicable(GroovyClassDescriptor descriptor, ProcessingContext ctx) { - return myPointcut.matches(descriptor, ctx); + return myPointcut.matches(descriptor, ctx) != null; } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/toplevel/ClassContextFilter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/toplevel/ClassContextFilter.java index ef09533c3e11..ab494c6b04c7 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/toplevel/ClassContextFilter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/dsl/toplevel/ClassContextFilter.java @@ -1,6 +1,5 @@ package org.jetbrains.plugins.groovy.dsl.toplevel; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; @@ -11,7 +10,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiType; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ConcurrentHashMap; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.dsl.GroovyClassDescriptor; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; @@ -29,18 +28,19 @@ public class ClassContextFilter implements ContextFilter { public boolean isApplicable(GroovyClassDescriptor descriptor, ProcessingContext ctx) { final PsiFile place = descriptor.getPlaceFile(); - return myPattern.value(Pair.create(findPsiType(descriptor.getProject(), descriptor.getTypeText(), place, ctx), place)); + return myPattern.value(Pair.create(findPsiType(descriptor, ctx), place)); } - @Nullable - private static PsiType findPsiType(Project project, String typeText, PsiFile place, ProcessingContext ctx) { + @NotNull + public static PsiType findPsiType(GroovyClassDescriptor descriptor, ProcessingContext ctx) { + String typeText = descriptor.getTypeText(); final String key = getClassKey(typeText); final Object cached = ctx.get(key); if (cached instanceof PsiType) { return (PsiType)cached; } - final PsiType found = JavaPsiFacade.getElementFactory(project).createTypeFromText(typeText, place); + final PsiType found = JavaPsiFacade.getElementFactory(descriptor.getProject()).createTypeFromText(typeText, descriptor.getPlaceFile()); ctx.put(key, found); return found; } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy index 7a9d23f76b79..1f5e8b1bb859 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/dsl/DsldTest.groovy @@ -22,6 +22,11 @@ class DsldTest extends LightGroovyTestCase { 'println "".foo + [].foo' } + public void testBind() { + checkHighlighting 'contribute(bind(types:currentType("java.lang.CharSequence"))) { property name:types[0].name[-3..-1] }', + 'println "".ing + "".foo' + } + private def checkHighlighting(String dsl, String code) { def file = myFixture.addFileToProject('a.gdsl', dsl) GroovyDslFileIndex.activateUntilModification(file.virtualFile)