blob: fe5af0ca06eeaa525eba1ebe0fe1713e5ec14293 [file] [log] [blame]
/*
* Copyright 2000-2013 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 com.intellij.compiler.classFilesIndex.chainsSearch;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.completion.JavaChainLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ChainCompletionContext;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ContextRelevantStaticMethod;
import com.intellij.compiler.classFilesIndex.chainsSearch.context.ContextRelevantVariableGetter;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.ChainCompletionNewVariableLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.WeightableChainLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.GetterLookupSubLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.SubLookupElement;
import com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.sub.VariableSubLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.VariableLookupItem;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.intellij.compiler.classFilesIndex.chainsSearch.completion.lookup.ChainCompletionLookupElementUtil.createLookupElement;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
/**
* @author Dmitry Batkovich <dmitry.batkovich@jetbrains.com>
*/
public class MethodsChainLookupRangingHelper {
public static List<LookupElement> chainsToWeightableLookupElements(final List<MethodsChain> chains,
final ChainCompletionContext context) {
final CachedRelevantStaticMethodSearcher staticMethodSearcher = new CachedRelevantStaticMethodSearcher(context);
final List<LookupElement> lookupElements = new ArrayList<LookupElement>(chains.size());
for (final MethodsChain chain : chains) {
final LookupElement lookupElement = chainToWeightableLookupElement(chain, context, staticMethodSearcher);
if (lookupElement != null) {
lookupElements.add(lookupElement);
}
}
return lookupElements;
}
@SuppressWarnings("ConstantConditions")
@Nullable
private static LookupElement chainToWeightableLookupElement(final MethodsChain chain,
final ChainCompletionContext context,
final CachedRelevantStaticMethodSearcher staticMethodSearcher) {
final int chainSize = chain.size();
assert chainSize != 0;
final int lastMethodWeight = chain.getChainWeight();
int unreachableParametersCount = 0;
int notMatchedStringVars = 0;
int matchedParametersInContext = 0;
Boolean isFirstMethodStatic = null;
Boolean hasCallingVariableInContext = null;
LookupElement chainLookupElement = null;
PsiClass newVariableClass = null;
final NullableNotNullManager nullableNotNullManager = NullableNotNullManager.getInstance(context.getProject());
for (final PsiMethod[] psiMethods : chain.getPath()) {
final PsiMethod method =
MethodChainsSearchUtil.getMethodWithMinNotPrimitiveParameters(psiMethods, Collections.singleton(context.getTarget().getClassQName()));
if (method == null) {
return null;
}
if (isFirstMethodStatic == null) {
isFirstMethodStatic = psiMethods[0].hasModifierProperty(PsiModifier.STATIC);
}
final PsiClass qualifierClass;
final boolean isHead = chainLookupElement == null;
if (isHead) {
final String qualifierClassName = chain.getQualifierClassName();
qualifierClass = JavaPsiFacade.getInstance(context.getProject()).
findClass(qualifierClassName, context.getResolveScope());
}
else {
qualifierClass = null;
}
final MethodProcResult procResult = processMethod(method, qualifierClass, isHead, lastMethodWeight, context, staticMethodSearcher,
nullableNotNullManager);
if (procResult == null) {
return null;
}
if (hasCallingVariableInContext == null) {
hasCallingVariableInContext = procResult.hasCallingVariableInContext();
}
if (isHead && procResult.isIntroduceNewVariable()) {
newVariableClass = qualifierClass;
}
matchedParametersInContext += procResult.getMatchedParametersInContext();
unreachableParametersCount += procResult.getUnreachableParametersCount();
notMatchedStringVars += procResult.getNotMatchedStringVars();
chainLookupElement =
isHead ? procResult.getLookupElement() : new JavaChainLookupElement(chainLookupElement, procResult.getLookupElement());
}
if (newVariableClass != null) {
chainLookupElement = ChainCompletionNewVariableLookupElement.create(newVariableClass, chainLookupElement);
}
final ChainRelevance relevance =
new ChainRelevance(chainSize, lastMethodWeight, unreachableParametersCount, notMatchedStringVars, hasCallingVariableInContext,
isFirstMethodStatic, matchedParametersInContext);
return new WeightableChainLookupElement(chainLookupElement, relevance);
}
@Nullable
private static MethodProcResult processMethod(@NotNull final PsiMethod method,
@Nullable final PsiClass qualifierClass,
final boolean isHeadMethod,
final int weight,
final ChainCompletionContext context,
final CachedRelevantStaticMethodSearcher staticMethodSearcher,
final NullableNotNullManager nullableNotNullManager) {
int unreachableParametersCount = 0;
int notMatchedStringVars = 0;
int matchedParametersInContext = 0;
boolean hasCallingVariableInContext = false;
boolean introduceNewVariable = false;
final PsiParameterList parameterList = method.getParameterList();
final TIntObjectHashMap<SubLookupElement> parametersMap = new TIntObjectHashMap<SubLookupElement>(parameterList.getParametersCount());
final PsiParameter[] parameters = parameterList.getParameters();
for (int i = 0; i < parameters.length; i++) {
final PsiParameter parameter = parameters[i];
final String typeQName = parameter.getType().getCanonicalText();
if (JAVA_LANG_STRING.equals(typeQName)) {
final PsiVariable relevantStringVar = context.findRelevantStringInContext(parameter.getName());
if (relevantStringVar == null) {
notMatchedStringVars++;
}
else {
parametersMap.put(i, new VariableSubLookupElement(relevantStringVar));
}
}
else if (!ChainCompletionStringUtil.isPrimitiveOrArrayOfPrimitives(typeQName)) {
final Collection<PsiVariable> contextVariables = context.getVariables(typeQName);
final PsiVariable contextVariable = ContainerUtil.getFirstItem(contextVariables, null);
if (contextVariable != null) {
if (contextVariables.size() == 1) parametersMap.put(i, new VariableSubLookupElement(contextVariable));
matchedParametersInContext++;
continue;
}
final Collection<ContextRelevantVariableGetter> relevantVariablesGetters = context.getRelevantVariablesGetters(typeQName);
final ContextRelevantVariableGetter contextVariableGetter = ContainerUtil.getFirstItem(relevantVariablesGetters, null);
if (contextVariableGetter != null) {
if (relevantVariablesGetters.size() == 1) parametersMap.put(i, contextVariableGetter.createSubLookupElement());
matchedParametersInContext++;
continue;
}
final Collection<PsiMethod> containingClassMethods = context.getContainingClassMethods(typeQName);
final PsiMethod contextRelevantGetter = ContainerUtil.getFirstItem(containingClassMethods, null);
if (contextRelevantGetter != null) {
if (containingClassMethods.size() == 1) parametersMap.put(i, new GetterLookupSubLookupElement(method.getName()));
matchedParametersInContext++;
continue;
}
final ContextRelevantStaticMethod contextRelevantStaticMethod =
ContainerUtil.getFirstItem(staticMethodSearcher.getRelevantStaticMethods(typeQName, weight), null);
if (contextRelevantStaticMethod != null) {
//
// In most cases it is not really relevant
//
//parametersMap.put(i, contextRelevantStaticMethod.createLookupElement());
matchedParametersInContext++;
continue;
}
if (!nullableNotNullManager.isNullable(parameter, true)) {
unreachableParametersCount++;
}
}
}
final LookupElement lookupElement;
if (isHeadMethod) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
hasCallingVariableInContext = true;
lookupElement = createLookupElement(method, parametersMap);
}
else if (method.isConstructor()) {
return null;
}
else {
@SuppressWarnings("ConstantConditions")
final String classQName = qualifierClass.getQualifiedName();
if (classQName == null) return null;
final Object e = ContainerUtil.getFirstItem(context.getContextRefElements(classQName), null);
if (e != null) {
final LookupElement firstChainElement;
if (e instanceof PsiVariable) {
firstChainElement = new VariableLookupItem((PsiVariable)e);
}
else if (e instanceof PsiMethod) {
firstChainElement = createLookupElement((PsiMethod)e, null);
}
else if (e instanceof LookupElement) {
firstChainElement = (LookupElement)e;
}
else {
throw new AssertionError();
}
hasCallingVariableInContext = true;
lookupElement = new JavaChainLookupElement(firstChainElement, createLookupElement(method, parametersMap));
}
else {
lookupElement = createLookupElement(method, parametersMap);
if (!context.getContainingClassQNames().contains(classQName)) {
introduceNewVariable = true;
}
}
}
}
else {
lookupElement = createLookupElement(method, parametersMap);
}
return new MethodProcResult(lookupElement,
unreachableParametersCount,
notMatchedStringVars,
hasCallingVariableInContext,
introduceNewVariable,
matchedParametersInContext);
}
private static class MethodProcResult {
private final LookupElement myMethodLookup;
private final int myUnreachableParametersCount;
private final int myNotMatchedStringVars;
private final boolean myHasCallingVariableInContext;
private final boolean myIntroduceNewVariable;
private final int myMatchedParametersInContext;
private MethodProcResult(final LookupElement methodLookup,
final int unreachableParametersCount,
final int notMatchedStringVars,
final boolean hasCallingVariableInContext,
final boolean introduceNewVariable,
final int matchedParametersInContext) {
myMethodLookup = methodLookup;
myUnreachableParametersCount = unreachableParametersCount;
myNotMatchedStringVars = notMatchedStringVars;
myHasCallingVariableInContext = hasCallingVariableInContext;
myIntroduceNewVariable = introduceNewVariable;
myMatchedParametersInContext = matchedParametersInContext;
}
private boolean isIntroduceNewVariable() {
return myIntroduceNewVariable;
}
private boolean hasCallingVariableInContext() {
return myHasCallingVariableInContext;
}
private LookupElement getLookupElement() {
return myMethodLookup;
}
private int getUnreachableParametersCount() {
return myUnreachableParametersCount;
}
private int getNotMatchedStringVars() {
return myNotMatchedStringVars;
}
public int getMatchedParametersInContext() {
return myMatchedParametersInContext;
}
}
}