| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.jetbrains.plugins.groovy.extensions; |
| |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.psi.*; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.PairFunction; |
| import com.intellij.util.SingletonInstancesCache; |
| 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; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder; |
| import org.jetbrains.plugins.groovy.lang.resolve.ClosureMissingMethodContributor; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyNamesUtil; |
| import org.jetbrains.plugins.groovy.util.FixedValuesReferenceProvider; |
| |
| import java.lang.reflect.Modifier; |
| import java.util.*; |
| |
| /** |
| * @author Sergey Evdokimov |
| */ |
| public class GroovyMethodInfo { |
| |
| private static volatile Map<String, Map<String, List<GroovyMethodInfo>>> METHOD_INFOS; |
| private static Map<String, Map<String, List<GroovyMethodInfo>>> LIGHT_METHOD_INFOS; |
| |
| private static final Set<String> myAllSupportedNamedArguments = new HashSet<String>(); |
| |
| private final List<String> myParams; |
| private final ClassLoader myClassLoader; |
| |
| private final String myReturnType; |
| private final String myReturnTypeCalculatorClassName; |
| private PairFunction<GrMethodCall, PsiMethod, PsiType> myReturnTypeCalculatorInstance; |
| |
| private final Map<String, NamedArgumentDescriptor> myNamedArguments; |
| private final String myNamedArgProviderClassName; |
| private GroovyNamedArgumentProvider myNamedArgProviderInstance; |
| |
| private final Map<String, NamedArgumentReference> myNamedArgReferenceProviders; |
| |
| private final GroovyMethodDescriptor myDescriptor; |
| |
| private static void ensureInit() { |
| if (METHOD_INFOS != null) return; |
| |
| synchronized (GroovyMethodInfo.class) { |
| Map<String, Map<String, List<GroovyMethodInfo>>> methodInfos = new HashMap<String, Map<String, List<GroovyMethodInfo>>>(); |
| Map<String, Map<String, List<GroovyMethodInfo>>> lightMethodInfos = new HashMap<String, Map<String, List<GroovyMethodInfo>>>(); |
| |
| for (GroovyClassDescriptor classDescriptor : GroovyClassDescriptor.EP_NAME.getExtensions()) { |
| ClassLoader classLoader = classDescriptor.getLoaderForClass(); |
| for (GroovyMethodDescriptor method : classDescriptor.methods) { |
| addMethodDescriptor(methodInfos, method, classLoader, classDescriptor.className); |
| } |
| } |
| |
| for (GroovyMethodDescriptorExtension methodDescriptor : GroovyMethodDescriptorExtension.EP_NAME.getExtensions()) { |
| if (methodDescriptor.className != null) { |
| assert methodDescriptor.lightMethodKey == null; |
| addMethodDescriptor(methodInfos, methodDescriptor, methodDescriptor.getLoaderForClass(), methodDescriptor.className); |
| } |
| else { |
| assert methodDescriptor.className == null; |
| addMethodDescriptor(lightMethodInfos, methodDescriptor, methodDescriptor.getLoaderForClass(), methodDescriptor.lightMethodKey); |
| } |
| } |
| |
| processUnnamedDescriptors(lightMethodInfos); |
| processUnnamedDescriptors(methodInfos); |
| |
| LIGHT_METHOD_INFOS = lightMethodInfos; |
| METHOD_INFOS = methodInfos; |
| } |
| } |
| |
| private static void processUnnamedDescriptors(Map<String, Map<String, List<GroovyMethodInfo>>> map) { |
| for (Map<String, List<GroovyMethodInfo>> methodMap : map.values()) { |
| List<GroovyMethodInfo> unnamedMethodDescriptors = methodMap.get(null); |
| if (unnamedMethodDescriptors != null) { |
| for (Map.Entry<String, List<GroovyMethodInfo>> entry : methodMap.entrySet()) { |
| if (entry.getKey() != null) { |
| entry.getValue().addAll(unnamedMethodDescriptors); |
| } |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private static List<GroovyMethodInfo> getInfos(Map<String, Map<String, List<GroovyMethodInfo>>> map, String key, PsiMethod method) { |
| Map<String, List<GroovyMethodInfo>> methodMap = map.get(key); |
| if (methodMap == null) return null; |
| |
| List<GroovyMethodInfo> res = methodMap.get(method.getName()); |
| if (res == null) { |
| res = methodMap.get(null); |
| } |
| |
| return res; |
| } |
| |
| public static List<GroovyMethodInfo> getInfos(PsiMethod method) { |
| ensureInit(); |
| |
| List<GroovyMethodInfo> lightMethodInfos = null; |
| |
| Object methodKind = GrLightMethodBuilder.getMethodKind(method); |
| if (methodKind instanceof String) { |
| lightMethodInfos = getInfos(LIGHT_METHOD_INFOS, (String)methodKind, method); |
| } |
| |
| List<GroovyMethodInfo> methodInfos = null; |
| |
| PsiClass containingClass = method.getContainingClass(); |
| if (containingClass != null) { |
| methodInfos = getInfos(METHOD_INFOS, containingClass.getQualifiedName(), method); |
| } |
| |
| if (methodInfos == null) { |
| return lightMethodInfos == null ? Collections.<GroovyMethodInfo>emptyList() : lightMethodInfos; |
| } |
| else { |
| if (lightMethodInfos == null) { |
| return methodInfos; |
| } |
| else { |
| return ContainerUtil.concat(lightMethodInfos, methodInfos); |
| } |
| } |
| } |
| |
| private GroovyMethodInfo(GroovyMethodDescriptor method, @NotNull ClassLoader classLoader) { |
| myClassLoader = classLoader; |
| myDescriptor = method; |
| |
| myParams = method.getParams(); |
| myReturnType = method.returnType; |
| myReturnTypeCalculatorClassName = method.returnTypeCalculator; |
| assert myReturnType == null || myReturnTypeCalculatorClassName == null; |
| |
| myNamedArguments = method.getArgumentsMap(); |
| myNamedArgProviderClassName = method.namedArgsProvider; |
| |
| myNamedArgReferenceProviders = getNamedArgumentsReferenceProviders(method); |
| |
| myAllSupportedNamedArguments.addAll(myNamedArgReferenceProviders.keySet()); |
| |
| if (ApplicationManager.getApplication().isInternal()) { |
| // Check classes to avoid typo. |
| |
| assertClassExists(myNamedArgProviderClassName, GroovyNamedArgumentProvider.class); |
| |
| assertClassExists(myReturnTypeCalculatorClassName, PairFunction.class); |
| |
| for (NamedArgumentReference r : myNamedArgReferenceProviders.values()) { |
| assertClassExists(r.myProviderClassName, PsiReferenceProvider.class, GroovyNamedArgumentReferenceProvider.class); |
| } |
| |
| if (method.myClosureArguments != null) { |
| for (GroovyMethodDescriptor.ClosureArgument argument : method.myClosureArguments) { |
| assertClassExists(argument.methodContributor, ClosureMissingMethodContributor.class); |
| } |
| } |
| } |
| } |
| |
| private void assertClassExists(@Nullable String className, Class<?> ... types) { |
| if (className == null) return; |
| |
| try { |
| Class<?> aClass = myClassLoader.loadClass(className); |
| for (Class<?> t : types) { |
| if (t.isAssignableFrom(aClass)) return; |
| } |
| |
| assert false : "Incorrect class type: " + aClass + " must be one of " + Arrays.asList(types); |
| |
| assert Modifier.isPublic(aClass.getConstructor(ArrayUtil.EMPTY_CLASS_ARRAY).getModifiers()); |
| } |
| catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public GroovyMethodDescriptor getDescriptor() { |
| return myDescriptor; |
| } |
| |
| private static Map<String, NamedArgumentReference> getNamedArgumentsReferenceProviders(GroovyMethodDescriptor methodDescriptor) { |
| if (methodDescriptor.myArguments == null) return Collections.emptyMap(); |
| |
| Map<String, NamedArgumentReference> res = new HashMap<String, NamedArgumentReference>(); |
| |
| for (GroovyMethodDescriptor.NamedArgument argument : methodDescriptor.myArguments) { |
| NamedArgumentReference r; |
| |
| if (argument.referenceProvider != null) { |
| assert argument.values == null; |
| r = new NamedArgumentReference(argument.referenceProvider); |
| } |
| else if (argument.values != null) { |
| List<String> values = new ArrayList<String>(); |
| for (StringTokenizer st = new StringTokenizer(argument.values, " ,;"); st.hasMoreTokens(); ) { |
| values.add(st.nextToken()); |
| } |
| |
| r = new NamedArgumentReference(values.toArray(new String[values.size()])); |
| } |
| else { |
| continue; |
| } |
| |
| for (String name : argument.getNames()) { |
| Object oldValue = res.put(name, r); |
| assert oldValue == null; |
| } |
| } |
| |
| return res; |
| } |
| |
| private static void addMethodDescriptor(Map<String, Map<String, List<GroovyMethodInfo>>> res, |
| GroovyMethodDescriptor method, |
| @NotNull ClassLoader classLoader, |
| @NotNull String key) { |
| if (method.methodName == null) { |
| addMethodDescriptor(res, method, classLoader, null, key); |
| } |
| else { |
| for (StringTokenizer st = new StringTokenizer(method.methodName, " \t,;"); st.hasMoreTokens(); ) { |
| String name = st.nextToken(); |
| assert GroovyNamesUtil.isIdentifier(name); |
| addMethodDescriptor(res, method, classLoader, name, key); |
| } |
| } |
| } |
| |
| private static void addMethodDescriptor(Map<String, Map<String, List<GroovyMethodInfo>>> res, |
| GroovyMethodDescriptor method, |
| @NotNull ClassLoader classLoader, |
| @Nullable String methodName, |
| @NotNull String key) { |
| Map<String, List<GroovyMethodInfo>> methodMap = res.get(key); |
| if (methodMap == null) { |
| methodMap = new HashMap<String, List<GroovyMethodInfo>>(); |
| res.put(key, methodMap); |
| } |
| |
| List<GroovyMethodInfo> methodsList = methodMap.get(methodName); |
| if (methodsList == null) { |
| methodsList = new ArrayList<GroovyMethodInfo>(); |
| methodMap.put(methodName, methodsList); |
| } |
| |
| methodsList.add(new GroovyMethodInfo(method, classLoader)); |
| } |
| |
| @Nullable |
| public String getReturnType() { |
| return myReturnType; |
| } |
| |
| public boolean isReturnTypeCalculatorDefined() { |
| return myReturnTypeCalculatorClassName != null; |
| } |
| |
| @NotNull |
| public PairFunction<GrMethodCall, PsiMethod, PsiType> getReturnTypeCalculator() { |
| if (myReturnTypeCalculatorInstance == null) { |
| myReturnTypeCalculatorInstance = SingletonInstancesCache.getInstance(myReturnTypeCalculatorClassName, myClassLoader); |
| } |
| return myReturnTypeCalculatorInstance; |
| } |
| |
| public static Set<String> getAllSupportedNamedArguments() { |
| ensureInit(); |
| |
| return myAllSupportedNamedArguments; |
| } |
| |
| /** |
| * @return instance of PsiReferenceProvider or GroovyNamedArgumentReferenceProvider or null. |
| */ |
| @Nullable |
| public Object getNamedArgReferenceProvider(String namedArgumentName) { |
| NamedArgumentReference r = myNamedArgReferenceProviders.get(namedArgumentName); |
| if (r == null) return null; |
| |
| return r.getProvider(myClassLoader); |
| } |
| |
| @Nullable |
| public Map<String, NamedArgumentDescriptor> getNamedArguments() { |
| return myNamedArguments; |
| } |
| |
| public boolean isNamedArgumentProviderDefined() { |
| return myNamedArgProviderClassName != null; |
| } |
| |
| public GroovyNamedArgumentProvider getNamedArgProvider() { |
| if (myNamedArgProviderInstance == null) { |
| myNamedArgProviderInstance = SingletonInstancesCache.getInstance(myNamedArgProviderClassName, myClassLoader); |
| } |
| return myNamedArgProviderInstance; |
| } |
| |
| public ClassLoader getPluginClassLoader() { |
| return myClassLoader; |
| } |
| |
| public boolean isApplicable(@NotNull PsiMethod method) { |
| if (myParams == null) { |
| return true; |
| } |
| |
| PsiParameterList parameterList = method.getParameterList(); |
| |
| if (parameterList.getParametersCount() != myParams.size()) return false; |
| |
| PsiParameter[] parameters = parameterList.getParameters(); |
| for (int i = 0; i < parameters.length; i++) { |
| if (!TypesUtil.isClassType(parameters[i].getType(), myParams.get(i))) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private static class NamedArgumentReference { |
| private final String myProviderClassName; |
| private final String[] myValues; |
| |
| private volatile Object myProvider; |
| |
| public NamedArgumentReference(String providerClassName) { |
| myProviderClassName = providerClassName; |
| myValues = null; |
| } |
| |
| public NamedArgumentReference(String[] values) { |
| myValues = values; |
| myProviderClassName = null; |
| } |
| |
| private Object doGetProvider(ClassLoader classLoader) { |
| if (myProviderClassName != null) { |
| return SingletonInstancesCache.getInstance(myProviderClassName, classLoader); |
| } |
| |
| return new FixedValuesReferenceProvider(myValues); |
| } |
| |
| // @return instance of PsiReferenceProvider or GroovyNamedArgumentReferenceProvider or null. |
| public Object getProvider(ClassLoader classLoader) { |
| Object res = myProvider; |
| if (res == null) { |
| res = doGetProvider(classLoader); |
| myProvider = res; |
| } |
| |
| return res; |
| } |
| } |
| } |