blob: dcc9c310b99e59531a1bf0a71efd531aeb1894df [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.codeInsight.completion;
import com.intellij.codeInsight.CharTailType;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.PsiTypeLookupItem;
import com.intellij.codeInsight.lookup.TailTypeDecorator;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
/**
* @author peter
*/
class TypeArgumentCompletionProvider extends CompletionProvider<CompletionParameters> {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.TypeArgumentCompletionProvider");
private final boolean mySmart;
@Nullable private final InheritorsHolder myInheritors;
TypeArgumentCompletionProvider(boolean smart, @Nullable InheritorsHolder inheritors) {
mySmart = smart;
myInheritors = inheritors;
}
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext processingContext, @NotNull final CompletionResultSet resultSet) {
final PsiElement context = parameters.getPosition();
final Pair<PsiClass, Integer> pair = getTypeParameterInfo(context);
if (pair == null) return;
PsiExpression expression = PsiTreeUtil.getContextOfType(context, PsiExpression.class, true);
if (expression != null) {
ExpectedTypeInfo[] types = ExpectedTypesProvider.getExpectedTypes(expression, true, false, false);
if (types.length > 0) {
for (ExpectedTypeInfo info : types) {
PsiType type = info.getType();
if (type instanceof PsiClassType && !type.equals(expression.getType())) {
fillExpectedTypeArgs(resultSet, context, pair.first, pair.second, ((PsiClassType)type).resolveGenerics(), mySmart ? info.getTailType() : TailType.NONE);
}
}
return;
}
}
if (mySmart) {
addInheritors(parameters, resultSet, pair.first, pair.second);
}
}
private void fillExpectedTypeArgs(CompletionResultSet resultSet,
PsiElement context,
final PsiClass actualClass,
final int index,
PsiClassType.ClassResolveResult expectedType,
TailType globalTail) {
final PsiClass expectedClass = expectedType.getElement();
if (!InheritanceUtil.isInheritorOrSelf(actualClass, expectedClass, true)) return;
assert expectedClass != null;
final PsiSubstitutor currentSubstitutor = TypeConversionUtil.getClassSubstitutor(expectedClass, actualClass, PsiSubstitutor.EMPTY);
assert currentSubstitutor != null;
PsiTypeParameter[] params = actualClass.getTypeParameters();
final List<PsiTypeLookupItem> typeItems = new ArrayList<PsiTypeLookupItem>();
for (int i = index; i < params.length; i++) {
PsiType arg = getExpectedTypeArg(context, i, expectedType, currentSubstitutor, params);
if (arg == null) {
arg = getExpectedTypeArg(context, index, expectedType, currentSubstitutor, params);
if (arg != null) {
resultSet.addElement(TailTypeDecorator.withTail(PsiTypeLookupItem.createLookupItem(arg, context), getTail(index == params.length - 1)));
}
return;
}
typeItems.add(PsiTypeLookupItem.createLookupItem(arg, context));
}
boolean hasParameters = ConstructorInsertHandler.hasConstructorParameters(actualClass, context);
TypeArgsLookupElement element = new TypeArgsLookupElement(typeItems, globalTail, hasParameters);
element.registerSingleClass(myInheritors);
resultSet.addElement(element);
}
@Nullable
private static PsiType getExpectedTypeArg(PsiElement context,
int index,
PsiClassType.ClassResolveResult expectedType,
PsiSubstitutor currentSubstitutor, PsiTypeParameter[] params) {
PsiClass expectedClass = expectedType.getElement();
assert expectedClass != null;
for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(expectedClass)) {
final PsiType argSubstitution = expectedType.getSubstitutor().substitute(parameter);
final PsiType paramSubstitution = currentSubstitutor.substitute(parameter);
final PsiType substitution = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper()
.getSubstitutionForTypeParameter(params[index], paramSubstitution, argSubstitution, false, PsiUtil.getLanguageLevel(context));
if (substitution != null && substitution != PsiType.NULL) {
return substitution;
}
}
return null;
}
private static void addInheritors(CompletionParameters parameters,
final CompletionResultSet resultSet,
final PsiClass referencedClass,
final int parameterIndex) {
final List<PsiClassType> typeList = Collections.singletonList((PsiClassType)TypeConversionUtil.typeParameterErasure(
referencedClass.getTypeParameters()[parameterIndex]));
JavaInheritorsGetter.processInheritors(parameters, typeList, resultSet.getPrefixMatcher(), new Consumer<PsiType>() {
@Override
public void consume(final PsiType type) {
final PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (psiClass == null) return;
resultSet.addElement(TailTypeDecorator.withTail(new JavaPsiClassReferenceElement(psiClass),
getTail(parameterIndex == referencedClass.getTypeParameters().length - 1)));
}
});
}
private static TailType getTail(boolean last) {
return last ? new CharTailType('>') : TailType.COMMA;
}
@Nullable
static Pair<PsiClass, Integer> getTypeParameterInfo(PsiElement context) {
final PsiReferenceParameterList parameterList = PsiTreeUtil.getContextOfType(context, PsiReferenceParameterList.class, true);
if (parameterList == null) return null;
PsiElement parent = parameterList.getParent();
if (!(parent instanceof PsiJavaCodeReferenceElement)) return null;
final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)parent;
final int parameterIndex;
int index = 0;
final PsiTypeElement typeElement = PsiTreeUtil.getContextOfType(context, PsiTypeElement.class, true);
if(typeElement != null){
final PsiTypeElement[] elements = referenceElement.getParameterList().getTypeParameterElements();
while (index < elements.length) {
final PsiTypeElement element = elements[index++];
if(element == typeElement) break;
}
}
parameterIndex = index - 1;
if(parameterIndex < 0) return null;
final PsiElement target = referenceElement.resolve();
if(!(target instanceof PsiClass)) return null;
final PsiClass referencedClass = (PsiClass)target;
final PsiTypeParameter[] typeParameters = referencedClass.getTypeParameters();
if(typeParameters.length <= parameterIndex) return null;
return Pair.create(referencedClass, parameterIndex);
}
public static class TypeArgsLookupElement extends LookupElement {
private String myLookupString;
private final List<PsiTypeLookupItem> myTypeItems;
private final TailType myGlobalTail;
private final boolean myHasParameters;
public TypeArgsLookupElement(List<PsiTypeLookupItem> typeItems, TailType globalTail, boolean hasParameters) {
myTypeItems = typeItems;
myGlobalTail = globalTail;
myHasParameters = hasParameters;
myLookupString = StringUtil.join(myTypeItems, new Function<PsiTypeLookupItem, String>() {
@Override
public String fun(PsiTypeLookupItem item) {
return item.getLookupString();
}
}, ", ");
}
@NotNull
@Override
public Object getObject() {
return myTypeItems.get(0).getObject();
}
public void registerSingleClass(@Nullable InheritorsHolder inheritors) {
if (inheritors != null && myTypeItems.size() == 1) {
PsiType type = myTypeItems.get(0).getPsiType();
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (aClass != null && !aClass.hasTypeParameters()) {
JavaCompletionUtil.setShowFQN(myTypeItems.get(0));
inheritors.registerClass(aClass);
}
}
}
@NotNull
@Override
public String getLookupString() {
return myLookupString;
}
@Override
public void renderElement(LookupElementPresentation presentation) {
myTypeItems.get(0).renderElement(presentation);
presentation.setItemText(getLookupString());
if (myTypeItems.size() > 1) {
presentation.setTailText(null);
presentation.setTypeText(null);
}
}
@Override
public void handleInsert(InsertionContext context) {
context.getDocument().deleteString(context.getStartOffset(), context.getTailOffset());
for (int i = 0; i < myTypeItems.size(); i++) {
PsiTypeLookupItem typeItem = myTypeItems.get(i);
CompletionUtil.emulateInsertion(context, context.getTailOffset(), typeItem);
if (context.getTailOffset() < 0) {
LOG.error("tail offset spoiled by " + typeItem);
return;
}
context.setTailOffset(getTail(i == myTypeItems.size() - 1).processTail(context.getEditor(), context.getTailOffset()));
}
context.setAddCompletionChar(false);
context.commitDocument();
PsiElement leaf = context.getFile().findElementAt(context.getTailOffset() - 1);
if (psiElement().withParents(PsiReferenceParameterList.class, PsiJavaCodeReferenceElement.class, PsiNewExpression.class)
.accepts(leaf)) {
ParenthesesInsertHandler.getInstance(myHasParameters).handleInsert(context, this);
myGlobalTail.processTail(context.getEditor(), context.getTailOffset());
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TypeArgsLookupElement element = (TypeArgsLookupElement)o;
if (!myTypeItems.equals(element.myTypeItems)) return false;
return true;
}
@Override
public int hashCode() {
return myTypeItems.hashCode();
}
}
}