blob: a24d855919f1df313ad27528bd3e156ab633cd79 [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.util;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
import com.intellij.psi.scope.DelegatingScopeProcessor;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
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.signatures.GrSignatureVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
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.GrAssignmentExpression;
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.typedef.members.GrGdkMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrGdkMethodImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightMethodBuilder;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import org.jetbrains.plugins.groovy.lang.resolve.noncode.MixinMemberContributor;
import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
import org.jetbrains.plugins.groovy.lang.resolve.processors.GrDelegatingScopeProcessorWithHints;
import java.util.List;
import java.util.Set;
/**
* @author Max Medvedev
*/
public class GdkMethodUtil {
private static final Logger LOG = Logger.getInstance(GdkMethodUtil.class);
public static final Set<String> COLLECTION_METHOD_NAMES = ContainerUtil.newHashSet(
"each", "eachWithIndex", "any", "every", "reverseEach", "collect", "collectAll", "find", "findAll", "retainAll", "removeAll", "split",
"groupBy", "groupEntriesBy", "findLastIndexOf", "findIndexValues", "findIndexOf"
);
@NonNls private static final String WITH = "with";
@NonNls private static final String IDENTITY = "identity";
@NonNls public static final String USE = "use";
@NonNls public static final String EACH_WITH_INDEX = "eachWithIndex";
@NonNls public static final String INJECT = "inject";
@NonNls public static final String EACH_PERMUTATION = "eachPermutation";
@NonNls public static final String WITH_DEFAULT = "withDefault";
@NonNls public static final String SORT = "sort";
@NonNls public static final String WITH_STREAM = "withStream";
@NonNls public static final String WITH_STREAMS = "withStreams";
@NonNls public static final String WITH_OBJECT_STREAMS = "withObjectStreams";
private GdkMethodUtil() {
}
public static boolean categoryIteration(GrClosableBlock place, final PsiScopeProcessor processor, ResolveState state) {
final ClassHint classHint = processor.getHint(ClassHint.KEY);
if (classHint != null && !classHint.shouldProcess(ClassHint.ResolveKind.METHOD)) return true;
final GrMethodCall call = checkMethodCall(place, USE);
if (call == null) return true;
final GrClosableBlock[] closures = call.getClosureArguments();
GrExpression[] args = call.getExpressionArguments();
if (!(placeEqualsSingleClosureArg(place, closures) || placeEqualsLastArg(place, args))) return true;
if (!(call.resolveMethod() instanceof GrGdkMethod)) return true;
state = state.put(ClassHint.RESOLVE_CONTEXT, call);
if ((args.length == 1 || args.length == 2 && placeEqualsLastArg(place, args))) {
PsiType type = args[0].getType();
if (type instanceof GrTupleType) {
return processTypesFromTuple((GrTupleType)type, processor, state, place);
}
}
return processTypesFomArgs(args, processor, state, place);
}
private static boolean processTypesFromTuple(@NotNull GrTupleType type,
@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull GrClosableBlock place) {
for (PsiType component : type.getComponentTypes()) {
PsiType clazz = PsiUtil.substituteTypeParameter(component, CommonClassNames.JAVA_LANG_CLASS, 0, false);
PsiClass aClass = PsiTypesUtil.getPsiClass(clazz);
if (aClass != null) {
if (!processCategoryMethods(place, processor, state, aClass)) return false;
}
}
return true;
}
private static boolean processTypesFomArgs(@NotNull GrExpression[] args,
@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
@NotNull GrClosableBlock place) {
for (GrExpression arg : args) {
if (arg instanceof GrReferenceExpression) {
final PsiElement resolved = ((GrReferenceExpression)arg).resolve();
if (resolved instanceof PsiClass) {
if (!processCategoryMethods(place, processor, state, (PsiClass)resolved)) return false;
}
}
}
return true;
}
private static boolean placeEqualsLastArg(GrClosableBlock place, GrExpression[] args) {
return args.length > 0 && place.equals(args[args.length - 1]);
}
private static boolean placeEqualsSingleClosureArg(GrClosableBlock place, GrClosableBlock[] closures) {
return closures.length == 1 && place.equals(closures[0]);
}
/**
*
* @param place - context of processing
* @param processor - processor to use
* @param categoryClass - category class to process
* @return
*/
public static boolean processCategoryMethods(final PsiElement place,
final PsiScopeProcessor processor,
@NotNull final ResolveState state,
@NotNull final PsiClass categoryClass) {
final PsiScopeProcessor delegate = new GrDelegatingScopeProcessorWithHints(processor, null, ClassHint.RESOLVE_KINDS_METHOD) {
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState delegateState) {
if (element instanceof PsiMethod && isCategoryMethod((PsiMethod)element, null, null, null)) {
PsiMethod method = (PsiMethod)element;
return processor.execute(GrGdkMethodImpl.createGdkMethod(method, false, generateOriginInfo(method)), delegateState);
}
return true;
}
};
return categoryClass.processDeclarations(delegate, state, null, place);
}
@Nullable
private static GrMethodCall checkMethodCall(GrClosableBlock place, String methodName) {
final PsiElement context = place.getContext();
GrMethodCall call = null;
if (context instanceof GrMethodCall) {
call = (GrMethodCall)context;
}
else if (context instanceof GrArgumentList) {
final PsiElement ccontext = context.getContext();
if (ccontext instanceof GrMethodCall) {
call = (GrMethodCall)ccontext;
}
}
if (call == null) return null;
final GrExpression invoked = call.getInvokedExpression();
if (!(invoked instanceof GrReferenceExpression) || !methodName.equals(((GrReferenceExpression)invoked).getReferenceName())) {
return null;
}
return call;
}
/**
* @param resolveContext is a qualifier of 'resolveContext.with {}'
*/
public static boolean isInWithContext(PsiElement resolveContext) {
if (resolveContext instanceof GrExpression) {
final PsiElement parent = resolveContext.getParent();
if (parent instanceof GrReferenceExpression && ((GrReferenceExpression)parent).getQualifier() == resolveContext) {
final PsiElement pparent = parent.getParent();
if (pparent instanceof GrMethodCall) {
final PsiMethod method = ((GrMethodCall)pparent).resolveMethod();
if (method instanceof GrGdkMethod && isWithName(method.getName())) {
return true;
}
}
}
}
return false;
}
public static boolean isWithName(String name) {
return WITH.equals(name) || IDENTITY.equals(name);
}
@Nullable
public static String generateOriginInfo(PsiMethod method) {
PsiClass cc = method.getContainingClass();
if (cc == null) return null;
//'\u2191'
return "via " + cc.getName();
}
public static boolean processMixinToMetaclass(GrStatementOwner run,
final PsiScopeProcessor processor,
ResolveState state,
PsiElement lastParent,
PsiElement place) {
GrStatement[] statements = run.getStatements();
for (GrStatement statement : statements) {
if (statement == lastParent) break;
final Trinity<PsiClassType, GrReferenceExpression, PsiClass> result = getMixinTypes(statement);
if (result != null) {
final PsiClassType subjectType = result.first;
final GrReferenceExpression qualifier = result.second;
final PsiClass mixin = result.third;
final DelegatingScopeProcessor delegate = new MixinMemberContributor.MixinProcessor(processor, subjectType, qualifier);
mixin.processDeclarations(delegate, state, null, place);
}
else {
Trinity<PsiClassType, GrReferenceExpression, List<GrMethod>> closureResult = getClosureMixins(statement);
if (closureResult != null) {
final PsiClassType subjectType = closureResult.first;
final GrReferenceExpression qualifier = closureResult.second;
final List<GrMethod> methods = closureResult.third;
final DelegatingScopeProcessor delegate = new MixinMemberContributor.MixinProcessor(processor, subjectType, qualifier);
for (GrMethod method : methods) {
ResolveUtil.processElement(delegate, method, state);
}
}
}
}
return true;
}
@NotNull
private static GrMethod createMethod(@NotNull GrClosureSignature signature,
@NotNull String name,
@NotNull GrAssignmentExpression statement,
@NotNull PsiClass closure) {
final GrLightMethodBuilder builder = new GrLightMethodBuilder(statement.getManager(), name);
GrClosureParameter[] parameters = signature.getParameters();
for (int i = 0; i < parameters.length; i++) {
GrClosureParameter parameter = parameters[i];
final String parameterName = parameter.getName() != null ? parameter.getName() : "p" + i;
final PsiType type = parameter.getType() != null ? parameter.getType() : TypesUtil.getJavaLangObject(statement);
builder.addParameter(parameterName, type, parameter.isOptional());
}
builder.setNavigationElement(statement.getLValue());
builder.setReturnType(signature.getReturnType());
builder.setContainingClass(closure);
return builder;
}
private static Trinity<PsiClassType, GrReferenceExpression, List<GrMethod>> getClosureMixins(final GrStatement statement) {
if (!(statement instanceof GrAssignmentExpression)) return null;
final GrAssignmentExpression assignment = (GrAssignmentExpression)statement;
return CachedValuesManager.getCachedValue(statement, new CachedValueProvider<Trinity<PsiClassType, GrReferenceExpression, List<GrMethod>>>() {
@Nullable
@Override
public Result<Trinity<PsiClassType, GrReferenceExpression, List<GrMethod>>> compute() {
Pair<PsiClassType, GrReferenceExpression> original = getTypeToMixIn(assignment);
if (original == null) return Result.create(null, PsiModificationTracker.MODIFICATION_COUNT);
final Pair<GrSignature, String> signatures = getTypeToMix(assignment);
if (signatures == null) return Result.create(null, PsiModificationTracker.MODIFICATION_COUNT);
final String name = signatures.second;
final List<GrMethod> methods = ContainerUtil.newArrayList();
final PsiClass closure = GroovyPsiManager.getInstance(statement.getProject()).findClassWithCache(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, statement.getResolveScope());
if (closure == null) return Result.create(null, PsiModificationTracker.MODIFICATION_COUNT);
signatures.first.accept(new GrSignatureVisitor() {
@Override
public void visitClosureSignature(GrClosureSignature signature) {
super.visitClosureSignature(signature);
GrMethod method = createMethod(signature, name, assignment, closure);
methods.add(method);
}
});
return Result.create(Trinity.create(original.first, original.second, methods), PsiModificationTracker.MODIFICATION_COUNT);
}
});
}
@Nullable
private static Pair<PsiClassType, GrReferenceExpression> getTypeToMixIn(GrAssignmentExpression assignment) {
final GrExpression lvalue = assignment.getLValue();
if (lvalue instanceof GrReferenceExpression) {
final GrExpression metaClassRef = ((GrReferenceExpression)lvalue).getQualifier();
if (metaClassRef instanceof GrReferenceExpression &&
(GrImportUtil.acceptName((GrReferenceElement)metaClassRef, "metaClass") ||
GrImportUtil.acceptName((GrReferenceElement)metaClassRef, "getMetaClass"))) {
final PsiElement resolved = ((GrReferenceElement)metaClassRef).resolve();
if (resolved instanceof PsiMethod && isMetaClassMethod((PsiMethod)resolved)) {
return getPsiClassFromReference(((GrReferenceExpression)metaClassRef).getQualifier());
}
}
}
return null;
}
@Nullable
private static Pair<GrSignature, String> getTypeToMix(GrAssignmentExpression assignment) {
GrExpression mixinRef = assignment.getRValue();
if (mixinRef == null) return null;
final PsiType type = mixinRef.getType();
if (type instanceof GrClosureType) {
final GrSignature signature = ((GrClosureType)type).getSignature();
final GrExpression lValue = assignment.getLValue();
assert lValue instanceof GrReferenceExpression;
final String name = ((GrReferenceExpression)lValue).getReferenceName();
return Pair.create(signature, name);
}
return null;
}
/**
* @return (type[1] in which methods mixed, reference to type[1], type[2] to mixin)
*/
@Nullable
private static Trinity<PsiClassType, GrReferenceExpression, PsiClass> getMixinTypes(final GrStatement statement) {
if (!(statement instanceof GrMethodCall)) return null;
return CachedValuesManager.getCachedValue(statement, new CachedValueProvider<Trinity<PsiClassType, GrReferenceExpression, PsiClass>>() {
@Nullable
@Override
public Result<Trinity<PsiClassType, GrReferenceExpression, PsiClass>> compute() {
GrMethodCall call = (GrMethodCall)statement;
Pair<PsiClassType, GrReferenceExpression> original = getTypeToMixIn(call);
if (original == null) return Result.create(null, PsiModificationTracker.MODIFICATION_COUNT);
PsiClass mix = getTypeToMix(call);
if (mix == null) return Result.create(null, PsiModificationTracker.MODIFICATION_COUNT);
return Result.create(new Trinity<PsiClassType, GrReferenceExpression, PsiClass>(original.first, original.second, mix),
PsiModificationTracker.MODIFICATION_COUNT);
}
});
}
@Nullable
private static PsiClass getTypeToMix(GrMethodCall call) {
if (!isSingleExpressionArg(call)) return null;
GrExpression mixinRef = call.getExpressionArguments()[0];
if (isClassRef(mixinRef)) {
mixinRef = ((GrReferenceExpression)mixinRef).getQualifier();
}
if (mixinRef instanceof GrReferenceExpression) {
PsiElement resolved = ((GrReferenceExpression)mixinRef).resolve();
if (resolved instanceof PsiClass) {
return (PsiClass)resolved;
}
}
return null;
}
private static boolean isSingleExpressionArg(GrMethodCall call) {
return call.getExpressionArguments().length == 1 &&
!PsiImplUtil.hasNamedArguments(call.getArgumentList()) &&
!PsiImplUtil.hasClosureArguments(call);
}
@Nullable
private static Pair<PsiClassType, GrReferenceExpression> getTypeToMixIn(GrMethodCall methodCall) {
GrExpression invoked = methodCall.getInvokedExpression();
if (invoked instanceof GrReferenceExpression && GrImportUtil.acceptName((GrReferenceExpression)invoked, "mixin")) {
PsiElement resolved = ((GrReferenceExpression)invoked).resolve();
if (resolved instanceof PsiMethod && isMixinMethod((PsiMethod)resolved)) {
GrExpression qualifier = ((GrReferenceExpression)invoked).getQualifier();
Pair<PsiClassType, GrReferenceExpression> type = getPsiClassFromReference(qualifier);
if (type != null) {
return type;
}
if (qualifier == null) {
qualifier = PsiImplUtil.getRuntimeQualifier((GrReferenceExpression)invoked);
}
if (qualifier != null && isMetaClass(qualifier.getType())) {
if (qualifier instanceof GrMethodCall) qualifier = ((GrMethodCall)qualifier).getInvokedExpression();
if (qualifier instanceof GrReferenceExpression) {
GrExpression qqualifier = ((GrReferenceExpression)qualifier).getQualifier();
if (qqualifier != null) {
Pair<PsiClassType, GrReferenceExpression> type1 = getPsiClassFromReference(qqualifier);
if (type1 != null) {
return type1;
}
}
else {
PsiType qtype = PsiImplUtil.getQualifierType((GrReferenceExpression)qualifier);
if (qtype instanceof PsiClassType && ((PsiClassType)qtype).resolve() != null) {
return Pair.create((PsiClassType)qtype, (GrReferenceExpression)qualifier);
}
}
}
}
}
}
return null;
}
private static boolean isMixinMethod(@NotNull PsiMethod method) {
if (method instanceof GrGdkMethod) method = ((GrGdkMethod)method).getStaticMethod();
PsiClass containingClass = method.getContainingClass();
String name = method.getName();
return "mixin".equals(name) && containingClass != null && GroovyCommonClassNames.DEFAULT_GROOVY_METHODS.equals(containingClass.getQualifiedName());
}
private static boolean isMetaClassMethod(@NotNull PsiMethod method) {
if (method instanceof GrGdkMethod) method = ((GrGdkMethod)method).getStaticMethod();
PsiClass containingClass = method.getContainingClass();
String name = method.getName();
return "getMetaClass".equals(name) &&
containingClass != null &&
(method.getParameterList().getParametersCount() == 0 ^
GroovyCommonClassNames.DEFAULT_GROOVY_METHODS.equals(containingClass.getQualifiedName()));
}
private static boolean isMetaClass(PsiType qualifierType) {
return qualifierType != null && qualifierType.equalsToText(GroovyCommonClassNames.GROOVY_LANG_META_CLASS);
}
private static boolean isClassRef(GrExpression mixinRef) {
return mixinRef instanceof GrReferenceExpression && "class".equals(((GrReferenceExpression)mixinRef).getReferenceName());
}
@Nullable
private static Pair<PsiClassType, GrReferenceExpression> getPsiClassFromReference(GrExpression ref) {
if (isClassRef(ref)) ref = ((GrReferenceExpression)ref).getQualifier();
if (ref instanceof GrReferenceExpression) {
PsiElement resolved = ((GrReferenceExpression)ref).resolve();
if (resolved instanceof PsiClass) {
PsiType type = ref.getType();
LOG.assertTrue(type instanceof PsiClassType, "reference resolved into PsiClass should have PsiClassType");
return Pair.create((PsiClassType)type, (GrReferenceExpression)ref);
}
}
return null;
}
public static boolean isCategoryMethod(@NotNull PsiMethod method, @Nullable PsiType qualifierType, @Nullable PsiElement place, @Nullable PsiSubstitutor substitutor) {
if (!method.hasModifierProperty(PsiModifier.STATIC)) return false;
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) return false;
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length == 0) return false;
if (qualifierType == null) return true;
PsiType selfType = parameters[0].getType();
if (selfType instanceof PsiPrimitiveType) return false;
if (substitutor != null) {
selfType = substitutor.substitute(selfType);
}
if (selfType instanceof PsiClassType &&
((PsiClassType)selfType).rawType().equalsToText(CommonClassNames.JAVA_LANG_CLASS) &&
place instanceof GrReferenceExpression &&
((GrReferenceExpression)place).resolve() instanceof PsiClass) { // ClassType.categoryMethod() where categoryMethod(Class<> cl, ...)
final GlobalSearchScope scope = method.getResolveScope();
final Project project = method.getProject();
return TypesUtil.isAssignableByMethodCallConversion(selfType, TypesUtil.createJavaLangClassType(qualifierType, project, scope),
method);
}
return TypesUtil.isAssignableByMethodCallConversion(selfType, qualifierType, method);
}
@Nullable
public static PsiClassType getCategoryType(@NotNull final PsiClass categoryAnnotationOwner) {
return CachedValuesManager.getCachedValue(categoryAnnotationOwner, new CachedValueProvider<PsiClassType>() {
@Override
public Result<PsiClassType> compute() {
return Result.create(inferCategoryType(categoryAnnotationOwner), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
}
@Nullable
private PsiClassType inferCategoryType(final PsiClass aClass) {
return RecursionManager.doPreventingRecursion(aClass, true, new NullableComputable<PsiClassType>() {
@Nullable
@Override
public PsiClassType compute() {
final PsiModifierList modifierList = aClass.getModifierList();
if (modifierList == null) return null;
final PsiAnnotation annotation = modifierList.findAnnotation(GroovyCommonClassNames.GROOVY_LANG_CATEGORY);
if (annotation == null) return null;
PsiAnnotationMemberValue value = annotation.findAttributeValue("value");
if (!(value instanceof GrReferenceExpression)) return null;
if ("class".equals(((GrReferenceExpression)value).getReferenceName())) value = ((GrReferenceExpression)value).getQualifier();
if (!(value instanceof GrReferenceExpression)) return null;
final PsiElement resolved = ((GrReferenceExpression)value).resolve();
if (!(resolved instanceof PsiClass)) return null;
String className = ((PsiClass)resolved).getQualifiedName();
if (className == null) className = ((PsiClass)resolved).getName();
if (className == null) return null;
return JavaPsiFacade.getElementFactory(aClass.getProject()).createTypeByFQClassName(className, resolved.getResolveScope());
}
});
}
});
}
public static boolean isWithOrIdentity(@NotNull GroovyResolveResult result) {
PsiElement element = result.getElement();
if (element instanceof PsiMethod && isWithName(((PsiMethod)element).getName())) {
if (element instanceof GrGdkMethod) {
element = ((GrGdkMethod)element).getStaticMethod();
}
final PsiClass containingClass = ((PsiMethod)element).getContainingClass();
if (containingClass != null) {
if (GroovyCommonClassNames.DEFAULT_GROOVY_METHODS.equals(containingClass.getQualifiedName())) {
return true;
}
}
}
return false;
}
}