blob: e2cc1330f88e666d37858a568bee271fe9205146 [file] [log] [blame]
/*
* Copyright 2000-2009 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.AutoPopupController;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.TailType;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupItem;
import com.intellij.codeInsight.lookup.TailTypeDecorator;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @author peter
*/
public class SmartCompletionDecorator extends TailTypeDecorator<LookupElement> {
@NotNull private final Collection<ExpectedTypeInfo> myExpectedTypeInfos;
private PsiElement myPosition;
public SmartCompletionDecorator(LookupElement item, Collection<ExpectedTypeInfo> expectedTypeInfos) {
super(item);
myExpectedTypeInfos = expectedTypeInfos;
}
@Override
protected TailType computeTailType(InsertionContext context) {
if (context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR) {
return TailType.NONE;
}
if (LookupItem.getDefaultTailType(context.getCompletionChar()) != null) {
return null;
}
LookupElement delegate = getDelegate();
LookupItem item = as(LookupItem.CLASS_CONDITION_KEY);
Object object = delegate.getObject();
if (!CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET && (object instanceof PsiMethod || object instanceof PsiClass)) {
return TailType.NONE;
}
final PsiExpression enclosing = PsiTreeUtil.getContextOfType(myPosition, PsiExpression.class, true);
if (enclosing != null && object instanceof PsiElement) {
final PsiType type = JavaCompletionUtil.getLookupElementType(delegate);
final TailType itemType = item != null ? item.getTailType() : TailType.NONE;
if (type != null && type.isValid()) {
Set<TailType> voidTyped = new HashSet<TailType>();
Set<TailType> sameTyped = new HashSet<TailType>();
Set<TailType> assignableTyped = new HashSet<TailType>();
for (ExpectedTypeInfo info : myExpectedTypeInfos) {
final PsiType infoType = info.getType();
final PsiType originalInfoType = JavaCompletionUtil.originalize(infoType);
if (PsiType.VOID.equals(infoType)) {
voidTyped.add(info.getTailType());
} else if (infoType.equals(type) || originalInfoType.equals(type)) {
sameTyped.add(info.getTailType());
} else if ((info.getKind() == ExpectedTypeInfo.TYPE_OR_SUBTYPE &&
(infoType.isAssignableFrom(type) || originalInfoType.isAssignableFrom(type))) ||
(info.getKind() == ExpectedTypeInfo.TYPE_OR_SUPERTYPE &&
(type.isAssignableFrom(infoType) || type.isAssignableFrom(originalInfoType)))) {
assignableTyped.add(info.getTailType());
}
}
if (!sameTyped.isEmpty()) {
return sameTyped.size() == 1 ? sameTyped.iterator().next() : itemType;
}
if (!assignableTyped.isEmpty()) {
return assignableTyped.size() == 1 ? assignableTyped.iterator().next() : itemType;
}
if (!voidTyped.isEmpty()) {
return voidTyped.size() == 1 ? voidTyped.iterator().next() : itemType;
}
}
else {
if (myExpectedTypeInfos.size() == 1) {
return myExpectedTypeInfos.iterator().next().getTailType();
}
}
return itemType;
}
return null;
}
@Override
public void handleInsert(InsertionContext context) {
if (getObject() instanceof PsiVariable && context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
context.commitDocument();
DefaultInsertHandler.removeEndOfIdentifier(context);
context.commitDocument();
}
myPosition = getPosition(context, this);
TailType tailType = computeTailType(context);
super.handleInsert(context);
if (tailType == TailType.COMMA) {
AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(context.getEditor(), null);
}
}
public static boolean hasUnboundTypeParams(final PsiMethod method, PsiType expectedType) {
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
if (typeParameters.length == 0) return false;
final Set<PsiTypeParameter> set = new THashSet<PsiTypeParameter>(Arrays.asList(typeParameters));
final PsiTypeVisitor<Boolean> typeParamSearcher = new PsiTypeVisitor<Boolean>() {
@Override
public Boolean visitType(final PsiType type) {
return true;
}
@Override
public Boolean visitArrayType(final PsiArrayType arrayType) {
return arrayType.getComponentType().accept(this);
}
@Override
public Boolean visitClassType(final PsiClassType classType) {
final PsiClass aClass = classType.resolve();
if (aClass instanceof PsiTypeParameter && set.contains(aClass)) return false;
final PsiType[] types = classType.getParameters();
for (final PsiType psiType : types) {
if (!psiType.accept(this).booleanValue()) return false;
}
return true;
}
@Override
public Boolean visitWildcardType(final PsiWildcardType wildcardType) {
final PsiType bound = wildcardType.getBound();
return bound == null || bound.accept(this).booleanValue();
}
};
for (final PsiParameter parameter : method.getParameterList().getParameters()) {
if (!parameter.getType().accept(typeParamSearcher).booleanValue()) return false;
}
PsiSubstitutor substitutor = calculateMethodReturnTypeSubstitutor(method, expectedType);
for (PsiTypeParameter parameter : typeParameters) {
if (!TypeConversionUtil.typeParameterErasure(parameter).equals(substitutor.substitute(parameter))) {
return true;
}
}
return false;
}
public static PsiSubstitutor calculateMethodReturnTypeSubstitutor(PsiMethod method, final PsiType expected) {
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
PsiResolveHelper helper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper();
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
for (PsiTypeParameter typeParameter : typeParameters) {
PsiType substitution = helper.getSubstitutionForTypeParameter(typeParameter, method.getReturnType(), expected,
false, PsiUtil.getLanguageLevel(method));
if (PsiType.NULL.equals(substitution)) {
substitution = TypeConversionUtil.typeParameterErasure(typeParameter);
}
substitutor = substitutor.put(typeParameter, substitution);
}
return substitutor;
}
@Nullable
public static PsiElement getPosition(InsertionContext context, LookupElement element) {
PsiElement position = context.getFile().findElementAt(context.getStartOffset() + element.getLookupString().length() - 1);
if (position instanceof PsiJavaToken && ">".equals(position.getText())) {
// In case of generics class
return position.getParent().getParent();
}
return position;
}
}