diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index 72d1233a2d96..44e1faf56d4a 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -371,6 +371,8 @@ + + getNames() { + assert !StringUtil.isEmptyOrSpaces(names); + return StringUtil.tokenize(names, ATTR_NAMES_DELIMITER); + } } @Nullable @@ -92,6 +100,23 @@ public class GroovyMethodDescriptor { } } + public Map getNamedArgumentsReferenceProviders() { + if (myArguments == null) return Collections.emptyMap(); + + Map res = new HashMap(); + + for (NamedArguments argument : myArguments) { + if (argument.referenceProvider != null) { + for (String name : argument.getNames()) { + Object oldValue = res.put(name, argument.referenceProvider); + assert oldValue == null; + } + } + } + + return res; + } + @Nullable public Map getArgumentsMap() { if (myArguments == null && namedArgs == null) { @@ -108,13 +133,7 @@ public class GroovyMethodDescriptor { for (NamedArguments arguments : myArguments) { NamedArgumentDescriptor descriptor = getDescriptor(isNamedArgsShowFirst, arguments.isFirst, arguments.type); - assert !StringUtil.isEmptyOrSpaces(arguments.names); - - String names = arguments.names; - - for (StringTokenizer st = new StringTokenizer(names, ATTR_NAMES_DELIMITER); st.hasMoreTokens(); ) { - String name = st.nextToken(); - + for (String name : arguments.getNames()) { Object oldValue = res.put(name, descriptor); assert oldValue == null; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java index 9b9a10f31202..c77a16419b2f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyMethodInfo.java @@ -1,7 +1,9 @@ package org.jetbrains.plugins.groovy.extensions; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.psi.*; import com.intellij.util.PairFunction; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; @@ -19,7 +21,9 @@ public class GroovyMethodInfo { private static volatile Map>> METHOD_INFOS; private static Map>> LIGHT_METHOD_INFOS; - + + private static final Set myAllSupportedNamedArguments = new HashSet(); + private final List myParams; private final ClassLoader myClassLoader; @@ -28,9 +32,11 @@ public class GroovyMethodInfo { private PairFunction myReturnTypeCalculatorInstance; private final Map myNamedArguments; - private final String myNamedArgProviderClassName; + private final String myNamedArgProviderClassName; private GroovyNamedArgumentProvider myNamedArgProviderInstance; + private Map myNamedArgReferenceProviderClassNames; + private static void ensureInit() { if (METHOD_INFOS != null) return; @@ -92,15 +98,14 @@ public class GroovyMethodInfo { ensureInit(); List lightMethodInfos = null; - if (method instanceof GrLightMethodBuilder) { - Object methodKind = ((GrLightMethodBuilder)method).getMethodKind(); - if (methodKind instanceof String) { - lightMethodInfos = getInfos(LIGHT_METHOD_INFOS, (String)methodKind, method); - } + + Object methodKind = GrLightMethodBuilder.getMethodKind(method); + if (methodKind instanceof String) { + lightMethodInfos = getInfos(LIGHT_METHOD_INFOS, (String)methodKind, method); } List methodInfos = null; - + PsiClass containingClass = method.getContainingClass(); if (containingClass != null) { methodInfos = getInfos(METHOD_INFOS, containingClass.getQualifiedName(), method); @@ -114,10 +119,7 @@ public class GroovyMethodInfo { return methodInfos; } else { - List res = new ArrayList(lightMethodInfos.size() + methodInfos.size()); - res.addAll(lightMethodInfos); - res.addAll(methodInfos); - return res; + return ContainerUtil.concat(lightMethodInfos, methodInfos); } } } @@ -132,6 +134,31 @@ public class GroovyMethodInfo { myNamedArguments = method.getArgumentsMap(); myNamedArgProviderClassName = method.namedArgsProvider; assert myNamedArguments == null || myNamedArgProviderClassName == null; + + myNamedArgReferenceProviderClassNames = method.getNamedArgumentsReferenceProviders(); + + myAllSupportedNamedArguments.addAll(myNamedArgReferenceProviderClassNames.keySet()); + + if (ApplicationManager.getApplication().isInternal()) { + // Check classes to avoid typo. + + try { + if (myReturnTypeCalculatorClassName != null) { + classLoader.loadClass(myReturnTypeCalculatorClassName); + } + if (myNamedArgProviderClassName != null) { + classLoader.loadClass(myNamedArgProviderClassName); + } + + for (String className : myNamedArgReferenceProviderClassNames.values()) { + Class aClass = classLoader.loadClass(className); + assert PsiReferenceProvider.class.isAssignableFrom(aClass) || GroovyNamedArgumentReferenceProvider.class.isAssignableFrom(aClass); + } + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } } private static void addMethodDescriptor(Map>> res, @@ -187,6 +214,21 @@ public class GroovyMethodInfo { return myReturnTypeCalculatorInstance; } + public static Set getAllSupportedNamedArguments() { + return myAllSupportedNamedArguments; + } + + /** + * @return instance of PsiReferenceProvider or GroovyNamedArgumentReferenceProvider or null. + */ + @Nullable + public Object getNamedArgReferenceProvider(String namedArgumentName) { + String className = myNamedArgReferenceProviderClassNames.get(namedArgumentName); + if (className == null) return null; + + return ClassInstanceCache.getInstance(className, myClassLoader); + } + @Nullable public Map getNamedArguments() { return myNamedArguments; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyNamedArgumentReferenceProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyNamedArgumentReferenceProvider.java new file mode 100644 index 000000000000..396f0b3ec7d3 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/extensions/GroovyNamedArgumentReferenceProvider.java @@ -0,0 +1,19 @@ +package org.jetbrains.plugins.groovy.extensions; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument; + +/** + * @author Sergey Evdokimov + */ +public interface GroovyNamedArgumentReferenceProvider { + + PsiReference[] createRef(@NotNull PsiElement element, + @NotNull GrNamedArgument namedArgument, + @NotNull GroovyResolveResult resolveResult, + @NotNull ProcessingContext context); +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java index bba17b964b12..91f765846994 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/synthetic/GrLightMethodBuilder.java @@ -17,6 +17,7 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.synthetic; import com.intellij.codeInsight.completion.originInfo.OriginInfoAwareElement; import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.util.Key; import com.intellij.psi.*; import com.intellij.psi.impl.ElementPresentationUtil; import com.intellij.psi.impl.PsiClassImplUtil; @@ -56,6 +57,9 @@ import java.util.Map; * @author Sergey Evdokimov */ public class GrLightMethodBuilder extends LightElement implements GrMethod, OriginInfoAwareElement { + + public static final Key KIND_KEY = Key.create("GrLightMethodBuilder.Key"); + protected String myName; private PsiType myReturnType = PsiType.VOID; private final GrLightModifierList myModifierList; @@ -366,16 +370,23 @@ public class GrLightMethodBuilder extends LightElement implements GrMethod, Ori public void acceptChildren(GroovyElementVisitor visitor) { } + public static Object getMethodKind(@Nullable PsiElement method) { + if (method == null) return null; + + if (method instanceof GrLightMethodBuilder) { + return ((GrLightMethodBuilder)method).getMethodKind(); + } + + return method.getUserData(KIND_KEY); + } + public static boolean checkKind(@Nullable PsiElement method, @NotNull Object kind) { - return method instanceof GrLightMethodBuilder && kind.equals(((GrLightMethodBuilder)method).myMethodKind); + return kind.equals(getMethodKind(method)); } public static boolean checkKind(@Nullable PsiElement method, @NotNull Object kind1, @NotNull Object kind2) { - if (method instanceof GrLightMethodBuilder) { - Object kind = ((GrLightMethodBuilder)method).getMethodKind(); - return kind1.equals(kind) || kind2.equals(kind); - } - return false; + Object kind = getMethodKind(method); + return kind1.equals(kind) || kind2.equals(kind); } @Override diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/patterns/GroovyPatterns.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/patterns/GroovyPatterns.java index 3902bc2d7872..293453a6327d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/patterns/GroovyPatterns.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/patterns/GroovyPatterns.java @@ -67,7 +67,7 @@ public class GroovyPatterns extends PsiJavaPatterns { return PsiJavaPatterns.psiMethod().with(new PatternCondition("GrLightMethodBuilder") { @Override public boolean accepts(@NotNull PsiMethod method, ProcessingContext context) { - return method instanceof GrLightMethodBuilder && key.equals(((GrLightMethodBuilder)method).getMethodKind()); + return GrLightMethodBuilder.checkKind(method, key); } }); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GroovyMethodArgumentReferenceContributor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GroovyMethodArgumentReferenceContributor.java new file mode 100644 index 000000000000..1869e5860055 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GroovyMethodArgumentReferenceContributor.java @@ -0,0 +1,159 @@ +package org.jetbrains.plugins.groovy.lang.resolve; + +import com.intellij.psi.*; +import com.intellij.util.ProcessingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.extensions.GroovyMethodInfo; +import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentReferenceProvider; +import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; +import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrConditionalExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; +import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; + +import static org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns.stringLiteral; + +/** + * @author Sergey Evdokimov + */ +public final class GroovyMethodArgumentReferenceContributor extends PsiReferenceContributor { + + @Override + public void registerReferenceProviders(PsiReferenceRegistrar registrar) { + registrar.registerReferenceProvider(stringLiteral(), new MyProvider()); + } + + private static class MyProvider extends PsiReferenceProvider { + @NotNull + @Override + public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { + GrExpression argument = (GrExpression)element; + PsiElement parent = element.getParent(); + + if (parent instanceof GrConditionalExpression) { + // support case: foo(a > b ? "aaa" : "bbb") + + if (((GrConditionalExpression)parent).getCondition() == parent) return PsiReference.EMPTY_ARRAY; + argument = (GrConditionalExpression)parent; + parent = parent.getParent(); + } + + if (parent instanceof GrListOrMap) { + // support case: foo(["aaa", "bbb"]) + if (!((GrListOrMap)parent).isMap()) { + argument = (GrListOrMap)parent; + parent = parent.getParent(); + } + } + + if (parent instanceof GrNamedArgument) { + return createReferencesForNamedArgument(element, (GrNamedArgument)parent, context); + } + + if (parent instanceof GrArgumentList) { + GrArgumentList argumentList = (GrArgumentList)parent; + int index = argumentList.getExpressionArgumentIndex(argument); + + PsiElement call = argumentList.getParent(); + if (!(call instanceof GrMethodCall)) return PsiReference.EMPTY_ARRAY; + + return PsiReference.EMPTY_ARRAY; + } + + return PsiReference.EMPTY_ARRAY; + } + + private static PsiReference[] createReferencesForNamedArgument(@NotNull PsiElement element, + GrNamedArgument namedArgument, + @NotNull ProcessingContext context) { + String labelName = namedArgument.getLabelName(); + if (labelName == null) return PsiReference.EMPTY_ARRAY; + + if (!GroovyMethodInfo.getAllSupportedNamedArguments().contains(labelName)) { + // Optimization: avoid unnecessary resolve. + return PsiReference.EMPTY_ARRAY; + } + + PsiElement call = PsiUtil.getCallByNamedParameter(namedArgument); + + if (!(call instanceof GrMethodCall)) return PsiReference.EMPTY_ARRAY; + + GrExpression invokedExpression = ((GrMethodCall)call).getInvokedExpression(); + if (!(invokedExpression instanceof GrReferenceExpression)) return PsiReference.EMPTY_ARRAY; + + for (GroovyResolveResult result : ((GrReferenceExpression)invokedExpression).multiResolve(false)) { + PsiElement eMethod = result.getElement(); + if (!(eMethod instanceof PsiMethod)) continue; + + PsiMethod method = (PsiMethod)eMethod; + + for (GroovyMethodInfo info : GroovyMethodInfo.getInfos(method)) { + Object referenceProvider = info.getNamedArgReferenceProvider(labelName); + if (referenceProvider != null) { + PsiReference[] refs; + + if (referenceProvider instanceof GroovyNamedArgumentReferenceProvider) { + refs = ((GroovyNamedArgumentReferenceProvider)referenceProvider).createRef(element, namedArgument, result, context); + } + else { + refs = ((PsiReferenceProvider)referenceProvider).getReferencesByElement(element, context); + } + + if (refs.length > 0) { + return refs; + } + } + } + } + + return PsiReference.EMPTY_ARRAY; + } + + //@NotNull + //private PsiReference[] createReferencesForArgument(@NotNull PsiElement element, + // int index, + // @NotNull GrMethodCall methodCall) { + // GrExpression invokedExpression = methodCall.getInvokedExpression(); + // if (!(invokedExpression instanceof GrReferenceExpression)) return PsiReference.EMPTY_ARRAY; + // + // String methodName = ((GrReferenceExpression)invokedExpression).getReferenceName(); + // if (methodName == null) return PsiReference.EMPTY_ARRAY; + // + // for (String key : new String[] {methodName, null}) { + // for (GroovyResolveResult result : ((GrReferenceExpression)invokedExpression).multiResolve(false)) { + // PsiElement eMethod = result.getElement(); + // if (eMethod instanceof PsiMethod) { + // PsiMethod method = (PsiMethod)eMethod; + // + // //noinspection ConstantConditions + // if (key != null && !key.equals(method.getName())) continue; + // + // for (Pair> pair : list) { + // if (pair.second.value(method)) { + // PsiReference[] res; + // if (attrNameOrParameterIndex instanceof Integer) { + // res = pair.first.createRef(element, methodCall, (Integer)attrNameOrParameterIndex, result); + // } + // else { + // assert namedArgument != null; + // res = pair.first.createRef(element, namedArgument, result); + // } + // + // if (res.length > 0) { + // return res; + // } + // } + // } + // } + // } + // } + // + // return PsiReference.EMPTY_ARRAY; + //} + } + +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/util/dynamicMembers/DynamicMemberUtils.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/util/dynamicMembers/DynamicMemberUtils.java index 9536bd0a2f3c..2f8cff1e22de 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/util/dynamicMembers/DynamicMemberUtils.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/util/dynamicMembers/DynamicMemberUtils.java @@ -35,6 +35,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefini import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrReflectedMethod; +import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder; import org.jetbrains.plugins.groovy.lang.psi.util.StaticChecker; import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint; @@ -198,6 +199,14 @@ public class DynamicMemberUtils { } dynamicMethod.setOriginalInfo(originalInfo); + String kind = commentMap.get("kind"); + if (kind == null) { + kind = classCommentMap.get("kind"); + } + if (kind != null) { + dynamicMethod.putUserData(GrLightMethodBuilder.KIND_KEY, kind); + } + multiMap.putValue(null, dynamicMethod); multiMap.putValue(method.getName(), dynamicMethod);