/* * Copyright 2000-2012 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.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 { 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 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 typeItems = new ArrayList(); 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)); } if (typeItems.size() == 1 && myInheritors != null) { PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(typeItems.get(0).getPsiType()); if (aClass != null) { JavaCompletionUtil.setShowFQN(typeItems.get(0)); myInheritors.registerClass(aClass); } } resultSet.addElement(new TypeArgsLookupElement(typeItems, globalTail, ConstructorInsertHandler.hasConstructorParameters(actualClass, context))); } @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 typeList = Collections.singletonList((PsiClassType)TypeConversionUtil.typeParameterErasure( referencedClass.getTypeParameters()[parameterIndex])); JavaInheritorsGetter.processInheritors(parameters, typeList, resultSet.getPrefixMatcher(), new Consumer() { @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 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); } private static class TypeArgsLookupElement extends LookupElement { private String myLookupString; private final List myTypeItems; private final TailType myGlobalTail; private final boolean myHasParameters; public TypeArgsLookupElement(List typeItems, TailType globalTail, boolean hasParameters) { myTypeItems = typeItems; myGlobalTail = globalTail; myHasParameters = hasParameters; myLookupString = StringUtil.join(myTypeItems, new Function() { @Override public String fun(PsiTypeLookupItem item) { return item.getLookupString(); } }, ", "); } @NotNull @Override public Object getObject() { return myTypeItems.get(0).getObject(); } @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++) { CompletionUtil.emulateInsertion(context, context.getTailOffset(), myTypeItems.get(i)); 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(); } } }