blob: 1a14010ec8345a9b61266374ab0041bb056ac903 [file] [log] [blame]
/*
* Copyright 2000-2010 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.codeInsight.completion;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightClassUtil;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.PsiTypeLookupItem;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.filters.getters.ExpectedTypesGetter;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.statistics.JavaStatisticsManager;
import com.intellij.psi.statistics.StatisticsInfo;
import com.intellij.psi.statistics.StatisticsManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
/**
* @author peter
*/
public class JavaInheritorsGetter extends CompletionProvider<CompletionParameters> {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaInheritorsGetter");
private final ConstructorInsertHandler myConstructorInsertHandler;
public JavaInheritorsGetter(final ConstructorInsertHandler constructorInsertHandler) {
myConstructorInsertHandler = constructorInsertHandler;
}
private static boolean shouldAddArrayInitializer(PsiElement position) {
if (!JavaCompletionContributor.isInJavaContext(position) || !JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) {
return false;
}
PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(position, PsiNewExpression.class);
return newExpression != null && newExpression.getParent() instanceof PsiExpressionList;
}
@Override
public void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext matchingContext, @NotNull final CompletionResultSet result) {
final ExpectedTypeInfo[] infos = JavaSmartCompletionContributor.getExpectedTypes(parameters);
final List<ExpectedTypeInfo> infoCollection = Arrays.asList(infos);
generateVariants(parameters, result.getPrefixMatcher(), infos, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement lookupElement) {
result.addElement(JavaSmartCompletionContributor.decorate(lookupElement, infoCollection));
}
});
}
public void generateVariants(final CompletionParameters parameters, final PrefixMatcher prefixMatcher, final Consumer<LookupElement> consumer) {
generateVariants(parameters, prefixMatcher, JavaSmartCompletionContributor.getExpectedTypes(parameters), consumer);
}
private void generateVariants(final CompletionParameters parameters, final PrefixMatcher prefixMatcher,
final ExpectedTypeInfo[] infos, final Consumer<LookupElement> consumer) {
addArrayTypes(parameters.getPosition(), infos, consumer);
processInheritors(parameters, extractClassTypes(infos), prefixMatcher, new Consumer<PsiType>() {
@Override
public void consume(final PsiType type) {
final LookupElement element = addExpectedType(type, parameters);
if (element != null) {
consumer.consume(element);
}
}
});
}
private static void addArrayTypes(PsiElement identifierCopy,
ExpectedTypeInfo[] infos, final Consumer<LookupElement> consumer) {
for (final PsiType type : ExpectedTypesGetter.extractTypes(infos, true)) {
if (type instanceof PsiArrayType) {
consumer.consume(createNewArrayItem(identifierCopy, type));
if (shouldAddArrayInitializer(identifierCopy)) {
PsiTypeLookupItem item = createNewArrayItem(identifierCopy, type);
item.setAddArrayInitializer();
consumer.consume(item);
}
}
}
}
private static PsiTypeLookupItem createNewArrayItem(PsiElement identifierCopy, PsiType type) {
PsiTypeLookupItem item = PsiTypeLookupItem.createLookupItem(TypeConversionUtil.erasure(type), identifierCopy);
if (item.getObject() instanceof PsiClass) {
JavaCompletionUtil.setShowFQN(item);
}
item.setInsertHandler(new DefaultInsertHandler()); //braces & shortening
return item;
}
private static List<PsiClassType> extractClassTypes(ExpectedTypeInfo[] infos) {
final List<PsiClassType> expectedClassTypes = new SmartList<PsiClassType>();
for (PsiType type : ExpectedTypesGetter.extractTypes(infos, true)) {
if (type instanceof PsiClassType) {
final PsiClassType classType = (PsiClassType)type;
if (classType.resolve() != null) {
expectedClassTypes.add(classType);
}
}
}
return expectedClassTypes;
}
@Nullable
private LookupElement addExpectedType(final PsiType type,
final CompletionParameters parameters) {
if (!JavaCompletionUtil.hasAccessibleConstructor(type)) return null;
final PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (psiClass == null || psiClass.getName() == null) return null;
PsiElement position = parameters.getPosition();
if ((parameters.getInvocationCount() < 2 || psiClass instanceof PsiCompiledElement) &&
HighlightClassUtil.checkCreateInnerClassFromStaticContext(position, null, psiClass) != null &&
!psiElement().afterLeaf(psiElement().withText(PsiKeyword.NEW).afterLeaf(".")).accepts(position)) {
return null;
}
PsiType psiType = GenericsUtil.eliminateWildcards(type);
if (JavaSmartCompletionContributor.AFTER_NEW.accepts(parameters.getOriginalPosition()) &&
PsiUtil.getLanguageLevel(parameters.getOriginalFile()).isAtLeast(LanguageLevel.JDK_1_7)) {
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(psiClass.getProject());
if (psiClass.hasTypeParameters() && !((PsiClassType)type).isRaw()) {
final String erasedText = TypeConversionUtil.erasure(psiType).getCanonicalText();
String canonicalText = psiType.getCanonicalText();
if (canonicalText.contains("?extends") || canonicalText.contains("?super")) {
LOG.error("Malformed canonical text: " + psiType + " of " + psiType.getClass() + "; " +
(psiType instanceof PsiClassReferenceType ? ((PsiClassReferenceType)psiType).getReference().getClass() : ""));
return null;
}
final PsiStatement statement = elementFactory
.createStatementFromText(canonicalText + " v = new " + erasedText + "<>()", parameters.getOriginalFile());
final PsiVariable declaredVar = (PsiVariable)((PsiDeclarationStatement)statement).getDeclaredElements()[0];
final PsiNewExpression initializer = (PsiNewExpression)declaredVar.getInitializer();
final boolean hasDefaultConstructorOrNoGenericsOne = PsiDiamondTypeImpl.hasDefaultConstructor(psiClass) ||
!PsiDiamondTypeImpl.haveConstructorsGenericsParameters(psiClass);
if (hasDefaultConstructorOrNoGenericsOne) {
final PsiDiamondTypeImpl.DiamondInferenceResult inferenceResult = PsiDiamondTypeImpl.resolveInferredTypes(initializer);
if (inferenceResult.getErrorMessage() == null &&
!psiClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
areInferredTypesApplicable(inferenceResult.getTypes(), parameters.getPosition())) {
psiType = initializer.getType();
}
}
}
}
final PsiTypeLookupItem item = PsiTypeLookupItem.createLookupItem(psiType, position);
JavaCompletionUtil.setShowFQN(item);
if (psiClass.isInterface() || psiClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
item.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
item.setIndicateAnonymous(true);
}
return LookupElementDecorator.withInsertHandler(item, myConstructorInsertHandler);
}
private static boolean areInferredTypesApplicable(@NotNull PsiType[] types, PsiElement position) {
final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(position, PsiMethodCallExpression.class);
if (methodCallExpression != null) {
if (PsiUtil.isLanguageLevel8OrHigher(methodCallExpression)) {
final PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(position, PsiNewExpression.class, false);
if (newExpression != null) {
PsiElement parent = newExpression;
while (parent.getParent() instanceof PsiParenthesizedExpression) {
parent = parent.getParent();
}
final int idx = ArrayUtil.find(methodCallExpression.getArgumentList().getExpressions(), parent);
if (idx > -1) {
final JavaResolveResult resolveResult = methodCallExpression.resolveMethodGenerics();
final PsiMethod method = (PsiMethod)resolveResult.getElement();
if (method != null) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (idx < parameters.length) {
final PsiType expectedType = resolveResult.getSubstitutor().substitute(parameters[idx].getType());
final PsiClass aClass = PsiUtil.resolveClassInType(expectedType);
if (aClass != null) {
final PsiClassType inferredArg = JavaPsiFacade.getElementFactory(method.getProject()).createType(aClass, types);
LOG.assertTrue(expectedType != null);
return TypeConversionUtil.isAssignable(expectedType, inferredArg);
}
}
}
}
}
}
return false;
}
return true;
}
public static void processInheritors(final CompletionParameters parameters,
final Collection<PsiClassType> expectedClassTypes,
final PrefixMatcher matcher, final Consumer<PsiType> consumer) {
//quick
if (!processMostProbableInheritors(parameters, expectedClassTypes, consumer)) return;
//long
for (final PsiClassType type : expectedClassTypes) {
final PsiClass psiClass = type.resolve();
if (psiClass != null && !psiClass.hasModifierProperty(PsiModifier.FINAL)) {
CodeInsightUtil.processSubTypes(type, parameters.getPosition(), false, matcher, consumer);
}
}
}
private static boolean processMostProbableInheritors(CompletionParameters parameters,
Collection<PsiClassType> expectedClassTypes,
Consumer<PsiType> consumer) {
PsiFile file = parameters.getOriginalFile();
for (final PsiClassType type : expectedClassTypes) {
consumer.consume(type);
final PsiClassType.ClassResolveResult baseResult = JavaCompletionUtil.originalize(type).resolveGenerics();
final PsiClass baseClass = baseResult.getElement();
if (baseClass == null) return false;
final PsiSubstitutor baseSubstitutor = baseResult.getSubstitutor();
final Processor<PsiClass> processor = CodeInsightUtil.createInheritorsProcessor(parameters.getPosition(), type, 0, false,
consumer, baseClass, baseSubstitutor);
final StatisticsInfo[] stats = StatisticsManager.getInstance().getAllValues(JavaStatisticsManager.getAfterNewKey(type));
for (final StatisticsInfo statisticsInfo : stats) {
final String value = statisticsInfo.getValue();
if (value.startsWith(JavaStatisticsManager.CLASS_PREFIX)) {
final String qname = value.substring(JavaStatisticsManager.CLASS_PREFIX.length());
final PsiClass psiClass = JavaPsiFacade.getInstance(file.getProject()).findClass(qname, file.getResolveScope());
if (psiClass != null && !PsiTreeUtil.isAncestor(file, psiClass, true) && !processor.process(psiClass)) break;
}
}
}
return true;
}
}