blob: e7bd8f7bcb06ac589740c7b0776326c269d82c77 [file] [log] [blame]
/*
* 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;
}
}
}