blob: f60256de839777a84b951f9b81012d49ba0dcbef [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.lang.psi.impl.statements.blocks;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import groovy.lang.Closure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
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.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrAnnotationUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
/**
* @author Max Medvedev
*/
public class GrDelegatesToUtil {
@Nullable
static DelegatesToInfo getDelegatesToInfo(@NotNull PsiElement place, @NotNull final GrClosableBlock closableBlock) {
GrCall call = getContainingCall(closableBlock);
if (call == null) return null;
GroovyResolveResult result = resolveCall(call);
if (GdkMethodUtil.isWithOrIdentity(result)) {
final GrExpression qualifier = inferCallQualifier((GrMethodCall)call);
if (qualifier == null) return null;
return new DelegatesToInfo(qualifier.getType(), Closure.DELEGATE_FIRST);
}
GrClosureSignature signature = inferSignature(result.getElement());
if (signature == null) return null;
final GrClosureSignatureUtil.ArgInfo<PsiElement>[] map = mapArgs(place, call, signature);
if (map == null) return null;
final PsiParameter parameter = findParameter(closableBlock, map, result);
if (parameter == null) return null;
final PsiModifierList modifierList = parameter.getModifierList();
if (modifierList == null) return null;
final PsiAnnotation delegatesTo = modifierList.findAnnotation(GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO);
if (delegatesTo == null) return null;
final PsiType type = inferDelegateType(delegatesTo, signature, map);
if (type == null) return null;
final int strategyValue = getStrategyValue(delegatesTo.findAttributeValue("strategy"));
return new DelegatesToInfo(type, strategyValue);
}
private static GrClosureSignatureUtil.ArgInfo<PsiElement>[] mapArgs(PsiElement place, GrCall call, GrClosureSignature signature) {
GrClosureSignature rawSignature = GrClosureSignatureUtil.rawSignature(signature);
return GrClosureSignatureUtil.mapParametersToArguments(
rawSignature, call.getNamedArguments(), call.getExpressionArguments(), call.getClosureArguments(), place, false, false
);
}
@Nullable
private static GrClosureSignature inferSignature(@Nullable PsiElement element) {
if (element instanceof PsiMethod) {
return GrClosureSignatureUtil.createSignature((PsiMethod)element, PsiSubstitutor.EMPTY);
}
else if (element instanceof GrVariable) {
final PsiType type = ((GrVariable)element).getTypeGroovy();
if (type instanceof GrClosureType) {
final GrSignature signature = ((GrClosureType)type).getSignature();
if (signature instanceof GrClosureSignature) {
return (GrClosureSignature)signature;
}
}
}
return null;
}
@Nullable
private static PsiParameter findParameter(@NotNull GrClosableBlock closableBlock,
@NotNull GrClosureSignatureUtil.ArgInfo<PsiElement>[] map,
@NotNull GroovyResolveResult result) {
final PsiElement element = result.getElement();
if (element instanceof PsiMethod) {
final PsiParameter[] parameters = ((PsiMethod)element).getParameterList().getParameters();
for (int i = 0; i < map.length; i++) {
if (map[i].args.contains(closableBlock)) return parameters[i];
}
}
return null;
}
@Nullable
private static PsiType inferDelegateType(@NotNull PsiAnnotation delegatesTo,
@NotNull GrClosureSignature signature,
@NotNull GrClosureSignatureUtil.ArgInfo<PsiElement>[] map) {
final PsiAnnotationMemberValue value = delegatesTo.findDeclaredAttributeValue("value");
if (value instanceof GrReferenceExpression) {
return extractTypeFromClassType(((GrReferenceExpression)value).getType());
}
else if (value instanceof PsiClassObjectAccessExpression) {
return extractTypeFromClassType(((PsiClassObjectAccessExpression)value).getType());
}
else if (value == null ||
value instanceof PsiLiteralExpression && ((PsiLiteralExpression)value).getType() == PsiType.NULL ||
value instanceof GrLiteral && ((GrLiteral)value).getType() == PsiType.NULL) {
String target = GrAnnotationUtil.inferStringAttribute(delegatesTo, "target");
if (target == null) return null;
final int parameter = findTargetParameter(delegatesTo, target);
if (parameter >= 0) {
final PsiType type = map[parameter].type;
final Integer index = GrAnnotationUtil.inferIntegerAttribute(delegatesTo, "genericTypeIndex");
if (index != null) {
return inferGenericArgType(signature, type, index, parameter);
}
else {
return type;
}
}
}
else if (value instanceof PsiExpression) {
return ((PsiExpression)value).getType();
}
return null;
}
@Nullable
private static PsiType inferGenericArgType(@NotNull GrClosureSignature signature,
@NotNull PsiType targetType,
int genericIndex,
int param) {
if (targetType instanceof PsiClassType) {
final PsiClassType.ClassResolveResult result = ((PsiClassType)targetType).resolveGenerics();
final PsiClass psiClass = result.getElement();
if (psiClass != null) {
final PsiSubstitutor substitutor = result.getSubstitutor();
final PsiType baseType = signature.getParameters()[param].getType();
final PsiClass baseClass = PsiUtil.resolveClassInClassTypeOnly(baseType);
if (baseClass != null && InheritanceUtil.isInheritorOrSelf(psiClass, baseClass, true)) {
final PsiTypeParameter[] typeParameters = baseClass.getTypeParameters();
if (genericIndex < typeParameters.length) {
final PsiSubstitutor superClassSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, psiClass, substitutor);
return superClassSubstitutor.substitute(typeParameters[genericIndex]);
}
}
}
}
return null;
}
private static int findTargetParameter(@NotNull PsiAnnotation delegatesTo, @NotNull String target) {
// ann mod.list parameter param.list
final PsiParameterList list = (PsiParameterList)delegatesTo.getParent().getParent().getParent();
PsiParameter[] parameters = list.getParameters();
for (int i = 0; i < parameters.length; i++) {
final PsiModifierList modifierList = parameters[i].getModifierList();
if (modifierList == null) continue;
final PsiAnnotation targetAnnotation = modifierList.findAnnotation(GroovyCommonClassNames.GROOVY_LANG_DELEGATES_TO_TARGET);
if (targetAnnotation == null) continue;
final String value = GrAnnotationUtil.inferStringAttribute(targetAnnotation, "value");
if (value == null) continue;
if (value.equals(target)) return i;
}
return -1;
}
@Nullable
private static GrExpression inferCallQualifier(@NotNull GrMethodCall call) {
final GrExpression expression = call.getInvokedExpression();
if (!(expression instanceof GrReferenceExpression)) return null;
return ((GrReferenceExpression)expression).getQualifier();
}
@NotNull
private static GroovyResolveResult resolveCall(@NotNull GrCall call) {
GroovyResolveResult result = GroovyResolveResult.EMPTY_RESULT;
if (call instanceof GrMethodCall) {
final GrExpression invoked = ((GrMethodCall)call).getInvokedExpression();
if (invoked instanceof GrReferenceExpression) {
final GroovyResolveResult[] results = ((GrReferenceExpression)invoked).resolveByShape();
if (results.length == 1) {
result = results[0];
}
}
}
else {
result = call.advancedResolve();
}
return result;
}
@Nullable
private static PsiType extractTypeFromClassType(@Nullable PsiType type) {
if (type instanceof PsiClassType) {
final PsiClass resolved = ((PsiClassType)type).resolve();
if (resolved != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolved.getQualifiedName())) {
final PsiType[] parameters = ((PsiClassType)type).getParameters();
if (parameters.length == 1) {
return parameters[0];
}
}
}
return null;
}
private static int getStrategyValue(@Nullable PsiAnnotationMemberValue strategy) {
if (strategy == null) return -1;
final String text = strategy.getText();
if ("0".equals(text)) return 0;
if ("1".equals(text)) return 1;
if ("2".equals(text)) return 2;
if ("3".equals(text)) return 3;
if ("4".equals(text)) return 4;
if (text.endsWith("OWNER_FIRST")) return Closure.OWNER_FIRST;
if (text.endsWith("DELEGATE_FIRST")) return Closure.DELEGATE_FIRST;
if (text.endsWith("OWNER_ONLY")) return Closure.OWNER_ONLY;
if (text.endsWith("DELEGATE_ONLY")) return Closure.DELEGATE_ONLY;
if (text.endsWith("TO_SELF")) return Closure.TO_SELF;
return -1;
}
@Nullable
static GrCall getContainingCall(@NotNull GrClosableBlock closableBlock) {
final PsiElement parent = closableBlock.getParent();
if (parent instanceof GrCall && ArrayUtil.contains(closableBlock, ((GrCall)parent).getClosureArguments())) {
return (GrCall)parent;
}
else if (parent instanceof GrArgumentList) {
final PsiElement parent1 = parent.getParent();
if (parent1 instanceof GrCall) {
return (GrCall)parent1;
}
}
return null;
}
public static class DelegatesToInfo {
final PsiType myClassToDelegate;
final int myStrategy;
private DelegatesToInfo(@Nullable PsiType classToDelegate, int strategy) {
myClassToDelegate = classToDelegate;
myStrategy = strategy;
}
public PsiType getTypeToDelegate() {
return myClassToDelegate;
}
public int getStrategy() {
return myStrategy;
}
}
}