blob: d3b9cec9481df4ef0226e365ba5a0d7804ab84d0 [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.findUsages;
import com.intellij.openapi.application.QueryExecutorBase;
import com.intellij.openapi.application.ReadActionProcessor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightMemberReference;
import com.intellij.psi.search.*;
import com.intellij.psi.search.searches.AnnotatedElementsSearch;
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
import com.intellij.psi.search.searches.MethodReferencesSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.PairProcessor;
import com.intellij.util.Processor;
import com.intellij.util.containers.ConcurrentHashSet;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.gpp.GppTypeConverter;
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* @author Maxim.Medvedev
*/
public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiReference, MethodReferencesSearch.SearchParameters> {
public GroovyConstructorUsagesSearcher() {
super(true);
}
@Override
public void processQuery(@NotNull MethodReferencesSearch.SearchParameters p, @NotNull Processor<PsiReference> consumer) {
processConstructorUsages(p.getMethod(), p.getScope(), consumer, p.getOptimizer(), true, !p.isStrictSignatureSearch());
}
public static final Key<Set<PsiClass>> LITERALLY_CONSTRUCTED_CLASSES = Key.create("LITERALLY_CONSTRUCTED_CLASSES");
static void processConstructorUsages(final PsiMethod constructor, final SearchScope searchScope, final Processor<PsiReference> consumer, final SearchRequestCollector collector, final boolean searchGppCalls, final boolean includeOverloads) {
if (!constructor.isConstructor()) return;
final PsiClass clazz = constructor.getContainingClass();
if (clazz == null) return;
SearchScope onlyGroovy = GroovyScopeUtil.restrictScopeToGroovyFiles(searchScope, GroovyScopeUtil.getEffectiveScope(constructor));
Set<PsiClass> processed = collector.getSearchSession().getUserData(LITERALLY_CONSTRUCTED_CLASSES);
if (processed == null) {
collector.getSearchSession().putUserData(LITERALLY_CONSTRUCTED_CLASSES, processed = new ConcurrentHashSet<PsiClass>());
}
if (!processed.add(clazz)) return;
if (clazz.isEnum() && clazz instanceof GroovyPsiElement) {
for (PsiField field : clazz.getFields()) {
if (field instanceof GrEnumConstant) {
final PsiReference ref = field.getReference();
if (ref != null && ref.isReferenceTo(constructor)) {
if (!consumer.process(ref)) return;
}
}
}
}
final LiteralConstructorSearcher literalProcessor = new LiteralConstructorSearcher(constructor, consumer, includeOverloads);
final Processor<GrNewExpression> newExpressionProcessor = new Processor<GrNewExpression>() {
@Override
public boolean process(GrNewExpression grNewExpression) {
final PsiMethod resolvedConstructor = grNewExpression.resolveMethod();
if (includeOverloads || constructor.getManager().areElementsEquivalent(resolvedConstructor, constructor)) {
return consumer.process(grNewExpression.getReferenceElement());
}
return true;
}
};
processGroovyClassUsages(clazz, searchScope, collector, searchGppCalls, newExpressionProcessor, literalProcessor);
//this()
if (clazz instanceof GrTypeDefinition) {
if (!processConstructors(constructor, consumer, clazz, true)) {
return;
}
}
//super()
DirectClassInheritorsSearch.search(clazz, onlyGroovy).forEach(new ReadActionProcessor<PsiClass>() {
@Override
public boolean processInReadAction(PsiClass inheritor) {
if (inheritor instanceof GrTypeDefinition) {
if (!processConstructors(constructor, consumer, inheritor, false)) return false;
}
return true;
}
});
}
public static void processGroovyClassUsages(final PsiClass clazz,
final SearchScope scope,
SearchRequestCollector collector,
final boolean searchGppCalls,
final Processor<GrNewExpression> newExpressionProcessor,
final LiteralConstructorSearcher literalProcessor) {
final Set<PsiAnchor> processedMethods = new ConcurrentHashSet<PsiAnchor>();
ReferencesSearch.searchOptimized(clazz, scope, true, collector, true, new PairProcessor<PsiReference, SearchRequestCollector>() {
@Override
public boolean process(PsiReference ref, SearchRequestCollector collector) {
final PsiElement element = ref.getElement();
if (element instanceof GrCodeReferenceElement) {
if (!processGroovyConstructorUsages((GrCodeReferenceElement)element, !searchGppCalls, newExpressionProcessor, literalProcessor)) {
return false;
}
}
if (searchGppCalls) {
final PsiMethod method = getMethodToSearchForCallsWithLiteralArguments(element, clazz);
if (method != null && processedMethods.add(PsiAnchor.create(method))) {
processGppMethodCalls(clazz, scope, collector, method, literalProcessor);
}
}
return true;
}
});
}
@Nullable
private static PsiMethod getMethodToSearchForCallsWithLiteralArguments(PsiElement element, PsiClass targetClass) {
final PsiParameter parameter = PsiTreeUtil.getParentOfType(element, PsiParameter.class);
if (parameter != null) {
final PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class);
if (method != null) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
final int idx = Arrays.asList(parameters).indexOf(parameter);
if (idx >= 0) {
PsiType parameterType = parameter.getType();
if (parameterType instanceof PsiArrayType && idx == parameters.length - 1) {
parameterType = ((PsiArrayType)parameterType).getComponentType();
}
if (parameterType instanceof PsiClassType) {
if (method.getManager().areElementsEquivalent(targetClass, ((PsiClassType)parameterType).resolve())) {
return method;
}
}
}
}
}
return null;
}
private static void processGppMethodCalls(final PsiClass targetClass,
SearchScope scope,
SearchRequestCollector originalCollector, @NotNull PsiMethod currentTarget,
final LiteralConstructorSearcher literalProcessor) {
final SearchScope gppScope = getGppScope(targetClass.getProject()).intersectWith(scope);
if (gppScope instanceof GlobalSearchScope) {
String name = currentTarget.getName();
if (PsiSearchHelper.SERVICE.getInstance(currentTarget.getProject()).isCheapEnoughToSearch(name, (GlobalSearchScope)gppScope, null,
null) ==
PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
return;
}
}
final ReadActionProcessor<PsiReference> gppCallProcessor = new ReadActionProcessor<PsiReference>() {
@Nullable
private GrExpression[] getCallArguments(PsiReference psiReference) {
if (psiReference instanceof GrReferenceElement) {
final PsiElement parent = ((GrReferenceElement)psiReference).getParent();
if (parent instanceof GrCall) {
final GrArgumentList argList = ((GrCall)parent).getArgumentList();
if (argList != null) {
return argList.getExpressionArguments();
}
}
}
else if (psiReference instanceof LiteralConstructorReference) {
return ((LiteralConstructorReference)psiReference).getCallArguments();
}
return null;
}
@Override
public boolean processInReadAction(PsiReference psiReference) {
final GrExpression[] arguments = getCallArguments(psiReference);
if (arguments == null) {
return true;
}
boolean checkedTypedContext = false;
for (GrExpression argument : arguments) {
if (argument instanceof GrListOrMap) {
if (!checkedTypedContext) {
if (!GppTypeConverter.hasTypedContext(psiReference.getElement())) {
return true;
}
checkedTypedContext = true;
}
if (!literalProcessor.processLiteral((GrListOrMap)argument, true)) {
return false;
}
}
}
return true;
}
};
if (currentTarget.isConstructor()) {
processConstructorUsages(currentTarget, gppScope, gppCallProcessor, originalCollector, true, false);
}
else {
MethodReferencesSearch.searchOptimized(currentTarget, gppScope, true, originalCollector, gppCallProcessor);
}
}
private static GlobalSearchScope getGppScope(final Project project) {
return CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider<GlobalSearchScope>() {
@Override
public Result<GlobalSearchScope> compute() {
return Result.create(calcGppScope(project), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, ProjectRootManager.getInstance(project));
}
});
}
private static boolean processGroovyConstructorUsages(GrCodeReferenceElement element,
boolean usualCallsOnly,
final Processor<GrNewExpression> newExpressionProcessor,
final LiteralConstructorSearcher literalProcessor) {
PsiElement parent = element.getParent();
if (parent instanceof GrAnonymousClassDefinition) {
parent = parent.getParent();
}
if (parent instanceof GrNewExpression) {
return newExpressionProcessor.process((GrNewExpression)parent);
}
if (usualCallsOnly) {
return true;
}
if (parent instanceof GrTypeElement) {
final GrTypeElement typeElement = (GrTypeElement)parent;
final PsiElement grandpa = typeElement.getParent();
if (grandpa instanceof GrVariableDeclaration) {
final GrVariable[] vars = ((GrVariableDeclaration)grandpa).getVariables();
if (vars.length == 1) {
final GrVariable variable = vars[0];
if (!checkLiteralInstantiation(variable.getInitializerGroovy(), literalProcessor)) {
return false;
}
}
}
else if (grandpa instanceof GrMethod) {
final GrMethod method = (GrMethod)grandpa;
if (typeElement == method.getReturnTypeElementGroovy()) {
ControlFlowUtils.visitAllExitPoints(method.getBlock(), new ControlFlowUtils.ExitPointVisitor() {
@Override
public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) {
if (!checkLiteralInstantiation(returnValue, literalProcessor)) {
return false;
}
return true;
}
});
}
}
else if (grandpa instanceof GrTypeCastExpression) {
final GrTypeCastExpression cast = (GrTypeCastExpression)grandpa;
if (cast.getCastTypeElement() == typeElement &&
!checkLiteralInstantiation(cast.getOperand(), literalProcessor)) {
return false;
}
}
else if (grandpa instanceof GrSafeCastExpression) {
final GrSafeCastExpression cast = (GrSafeCastExpression)grandpa;
if (cast.getCastTypeElement() == typeElement &&
!checkLiteralInstantiation(cast.getOperand(), literalProcessor)) {
return false;
}
}
}
return true;
}
private static GlobalSearchScope calcGppScope(Project project) {
final GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
final GlobalSearchScope maximal = GlobalSearchScope.getScopeRestrictedByFileTypes(allScope, GroovyFileType.GROOVY_FILE_TYPE);
GlobalSearchScope gppExtensions = new DelegatingGlobalSearchScope(maximal, "groovy.gpp") {
@Override
public boolean contains(@NotNull VirtualFile file) {
return super.contains(file) && GppTypeConverter.isGppExtension(file.getExtension());
}
};
final PsiClass typed = JavaPsiFacade.getInstance(project).findClass(GppTypeConverter.GROOVY_LANG_TYPED, allScope);
if (typed != null) {
final Set<VirtualFile> files = new HashSet<VirtualFile>();
AnnotatedElementsSearch.searchElements(typed, maximal, PsiModifierListOwner.class).forEach(new Processor<PsiModifierListOwner>() {
@Override
public boolean process(PsiModifierListOwner occurrence) {
ContainerUtil.addIfNotNull(occurrence.getContainingFile().getVirtualFile(), files);
return true;
}
});
GlobalSearchScope withTypedAnno = GlobalSearchScope.filesScope(project, files);
return withTypedAnno.union(gppExtensions);
}
return gppExtensions;
}
private static boolean checkLiteralInstantiation(GrExpression expression,
final LiteralConstructorSearcher literalProcessor) {
if (expression instanceof GrListOrMap) {
return literalProcessor.processLiteral((GrListOrMap)expression, GppTypeConverter.hasTypedContext(expression));
}
return true;
}
private static boolean processConstructors(final PsiMethod searchedConstructor, final Processor<PsiReference> consumer, final PsiClass clazz,
final boolean processThisRefs) {
final PsiMethod[] constructors = clazz.getConstructors();
if (constructors.length == 0) {
processImplicitConstructorCall(clazz, consumer, searchedConstructor);
}
for (PsiMethod constructor : constructors) {
final GrOpenBlock block = ((GrMethod)constructor).getBlock();
if (block != null) {
final GrStatement[] statements = block.getStatements();
if (statements.length > 0 && statements[0] instanceof GrConstructorInvocation) {
final GrConstructorInvocation invocation = (GrConstructorInvocation)statements[0];
if (invocation.isThisCall() == processThisRefs &&
invocation.getManager().areElementsEquivalent(invocation.resolveMethod(), searchedConstructor) &&
!consumer.process(invocation.getInvokedExpression())) {
return false;
}
}
else {
processImplicitConstructorCall(constructor, consumer, searchedConstructor);
}
}
}
return true;
}
private static void processImplicitConstructorCall(@NotNull final PsiMember usage,
final Processor<PsiReference> processor,
final PsiMethod constructor) {
if (constructor instanceof GrMethod) {
GrParameter[] grParameters = (GrParameter[])constructor.getParameterList().getParameters();
if (grParameters.length > 0 && !grParameters[0].isOptional()) return;
}
else if (constructor.getParameterList().getParameters().length > 0) return;
PsiManager manager = constructor.getManager();
if (manager.areElementsEquivalent(usage, constructor) || manager.areElementsEquivalent(constructor.getContainingClass(), usage.getContainingClass())) return;
processor.process(new LightMemberReference(manager, usage, PsiSubstitutor.EMPTY) {
@Override
public PsiElement getElement() {
return usage;
}
@Override
public TextRange getRangeInElement() {
if (usage instanceof PsiClass) {
PsiIdentifier identifier = ((PsiClass)usage).getNameIdentifier();
if (identifier != null) return TextRange.from(identifier.getStartOffsetInParent(), identifier.getTextLength());
}
else if (usage instanceof PsiMethod) {
PsiIdentifier identifier = ((PsiMethod)usage).getNameIdentifier();
if (identifier != null) return TextRange.from(identifier.getStartOffsetInParent(), identifier.getTextLength());
}
return super.getRangeInElement();
}
});
}
}