blob: d5e04ce3f95ab7058aee48da14dddee3efc5df90 [file] [log] [blame]
/*
* Copyright 2000-2014 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.*;
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
import com.intellij.codeInsight.lookup.*;
import com.intellij.openapi.util.Key;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.PsiElementPattern;
import com.intellij.patterns.PsiJavaPatterns;
import com.intellij.psi.*;
import com.intellij.psi.filters.ElementExtractorFilter;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.filters.GeneratorFilter;
import com.intellij.psi.filters.OrFilter;
import com.intellij.psi.filters.getters.*;
import com.intellij.psi.filters.types.AssignableFromFilter;
import com.intellij.psi.filters.types.AssignableGroupFilter;
import com.intellij.psi.filters.types.AssignableToFilter;
import com.intellij.psi.impl.source.PsiLabelReference;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Consumer;
import com.intellij.util.ProcessingContext;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
import static com.intellij.patterns.StandardPatterns.*;
/**
* @author peter
*/
public class JavaSmartCompletionContributor extends CompletionContributor {
private static final TObjectHashingStrategy<ExpectedTypeInfo> EXPECTED_TYPE_INFO_STRATEGY = new TObjectHashingStrategy<ExpectedTypeInfo>() {
@Override
public int computeHashCode(final ExpectedTypeInfo object) {
return object.getType().hashCode();
}
@Override
public boolean equals(final ExpectedTypeInfo o1, final ExpectedTypeInfo o2) {
return o1.getType().equals(o2.getType());
}
};
private static final ElementExtractorFilter THROWABLES_FILTER = new ElementExtractorFilter(new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE));
@NonNls private static final String EXCEPTION_TAG = "exception";
public static final ElementPattern<PsiElement> AFTER_NEW =
psiElement().afterLeaf(
psiElement().withText(PsiKeyword.NEW).andNot(
psiElement().afterLeaf(
psiElement().withText(PsiKeyword.THROW))));
static final ElementPattern<PsiElement> AFTER_THROW_NEW = psiElement().afterLeaf(psiElement().withText(PsiKeyword.NEW).afterLeaf(PsiKeyword.THROW));
private static final OrFilter THROWABLE_TYPE_FILTER = new OrFilter(
new GeneratorFilter(AssignableGroupFilter.class, new ThrowsListGetter()),
new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE));
public static final ElementPattern<PsiElement> INSIDE_EXPRESSION = or(
psiElement().withParent(PsiExpression.class).andNot(psiElement().withParent(PsiLiteralExpression.class)).andNot(psiElement().withParent(PsiMethodReferenceExpression.class)),
psiElement().inside(PsiClassObjectAccessExpression.class),
psiElement().inside(PsiThisExpression.class),
psiElement().inside(PsiSuperExpression.class)
);
static final ElementPattern<PsiElement> INSIDE_TYPECAST_EXPRESSION = psiElement().withParent(
psiElement(PsiReferenceExpression.class).afterLeaf(
psiElement().withText(")").withParent(PsiTypeCastExpression.class)));
static final PsiElementPattern.Capture<PsiElement> IN_TYPE_ARGS =
psiElement().inside(psiElement(PsiReferenceParameterList.class));
static final PsiElementPattern.Capture<PsiElement> LAMBDA = psiElement().with(new PatternCondition<PsiElement>("LAMBDA_CONTEXT") {
@Override
public boolean accepts(@NotNull PsiElement element, ProcessingContext context) {
final PsiElement rulezzRef = element.getParent();
return rulezzRef != null &&
rulezzRef instanceof PsiReferenceExpression &&
((PsiReferenceExpression)rulezzRef).getQualifier() == null &&
LambdaUtil.isValidLambdaContext(rulezzRef.getParent());
}});
static final PsiElementPattern.Capture<PsiElement> METHOD_REFERENCE = psiElement().with(new PatternCondition<PsiElement>("METHOD_REFERENCE_CONTEXT") {
@Override
public boolean accepts(@NotNull PsiElement element, ProcessingContext context) {
final PsiElement rulezzRef = element.getParent();
return rulezzRef != null &&
LambdaUtil.isValidLambdaContext(rulezzRef.getParent());
}});
@Nullable
private static ElementFilter getReferenceFilter(PsiElement element) {
//throw new foo
if (AFTER_THROW_NEW.accepts(element)) {
return new ElementExtractorFilter(THROWABLE_TYPE_FILTER);
}
//new xxx.yyy
if (psiElement().afterLeaf(psiElement().withText(".")).withSuperParent(2, psiElement(PsiNewExpression.class)).accepts(element)) {
if (((PsiNewExpression)element.getParent().getParent()).getClassReference() == element.getParent()) {
return new GeneratorFilter(AssignableGroupFilter.class, new ExpectedTypesGetter());
}
}
return null;
}
public JavaSmartCompletionContributor() {
extend(CompletionType.SMART, SmartCastProvider.INSIDE_TYPECAST_TYPE, new SmartCastProvider());
extend(CompletionType.SMART, SameSignatureCallParametersProvider.IN_CALL_ARGUMENT, new SameSignatureCallParametersProvider());
extend(CompletionType.SMART, psiElement().afterLeaf(PsiKeyword.INSTANCEOF), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
final PsiElement position = parameters.getPosition();
final PsiType[] leftTypes = InstanceOfLeftPartTypeGetter.getLeftTypes(position);
final Set<PsiClassType> expectedClassTypes = new LinkedHashSet<PsiClassType>();
final Set<PsiClass> parameterizedTypes = new THashSet<PsiClass>();
for (final PsiType type : leftTypes) {
if (type instanceof PsiClassType) {
final PsiClassType classType = (PsiClassType)type;
if (!classType.isRaw()) {
ContainerUtil.addIfNotNull(classType.resolve(), parameterizedTypes);
}
expectedClassTypes.add(classType.rawType());
}
}
JavaInheritorsGetter
.processInheritors(parameters, expectedClassTypes, result.getPrefixMatcher(), new Consumer<PsiType>() {
@Override
public void consume(PsiType type) {
final PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (psiClass == null || psiClass instanceof PsiTypeParameter) return;
//noinspection SuspiciousMethodCalls
if (expectedClassTypes.contains(type)) return;
result.addElement(createInstanceofLookupElement(psiClass, parameterizedTypes));
}
});
}
});
extend(CompletionType.SMART, psiElement(), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
final PsiElement element = parameters.getPosition();
final PsiReference reference = element.getContainingFile().findReferenceAt(parameters.getOffset());
if (reference != null) {
final ElementFilter filter = getReferenceFilter(element);
if (filter != null) {
final List<ExpectedTypeInfo> infos = Arrays.asList(getExpectedTypes(parameters));
for (final LookupElement item : completeReference(element, reference, filter, true, false, parameters, result.getPrefixMatcher())) {
if (item.getObject() instanceof PsiClass) {
result.addElement(decorate(LookupElementDecorator.withInsertHandler((LookupItem)item, ConstructorInsertHandler.SMART_INSTANCE), infos));
}
}
}
else if (INSIDE_TYPECAST_EXPRESSION.accepts(element)) {
for (final LookupElement item : completeReference(element, reference, new GeneratorFilter(AssignableToFilter.class, new CastTypeGetter()), false, true, parameters,
result.getPrefixMatcher())) {
result.addElement(item);
}
}
}
}
});
//method throws clause
extend(CompletionType.SMART, psiElement().inside(
psiElement(PsiReferenceList.class).save("refList").withParent(
psiMethod().withThrowsList(get("refList")))), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
final PsiElement element = parameters.getPosition();
final PsiReference reference = element.getContainingFile().findReferenceAt(parameters.getOffset());
assert reference != null;
for (final LookupElement item : completeReference(element, reference, THROWABLES_FILTER, true, false, parameters, result.getPrefixMatcher())) {
result.addElement(item);
}
}
});
extend(CompletionType.SMART, INSIDE_EXPRESSION, new ExpectedTypeBasedCompletionProvider() {
@Override
protected void addCompletions(final CompletionParameters params, final CompletionResultSet result, final Collection<ExpectedTypeInfo> _infos) {
Consumer<LookupElement> noTypeCheck = new Consumer<LookupElement>() {
@Override
public void consume(final LookupElement lookupElement) {
result.addElement(decorate(lookupElement, _infos));
}
};
THashSet<ExpectedTypeInfo> mergedInfos = new THashSet<ExpectedTypeInfo>(_infos, EXPECTED_TYPE_INFO_STRATEGY);
List<Runnable> chainedEtc = new ArrayList<Runnable>();
for (final ExpectedTypeInfo info : mergedInfos) {
Runnable slowContinuation =
ReferenceExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), noTypeCheck);
ContainerUtil.addIfNotNull(chainedEtc, slowContinuation);
}
addExpectedTypeMembers(params, mergedInfos, true, noTypeCheck);
for (final ExpectedTypeInfo info : mergedInfos) {
BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), new Consumer<LookupElement>() {
@Override
public void consume(LookupElement lookupElement) {
final TypedLookupItem typed = lookupElement.as(TypedLookupItem.CLASS_CONDITION_KEY);
if (typed != null) {
final PsiType psiType = typed.getType();
if (psiType != null && info.getType().isAssignableFrom(psiType)) {
result.addElement(decorate(lookupElement, _infos));
}
}
}
}, result.getPrefixMatcher());
}
for (Runnable runnable : chainedEtc) {
runnable.run();
}
final boolean searchInheritors = params.getInvocationCount() > 1;
if (searchInheritors) {
addExpectedTypeMembers(params, mergedInfos, false, noTypeCheck);
}
}
});
extend(CompletionType.SMART, or(
PsiJavaPatterns.psiElement().withParent(PsiNameValuePair.class),
PsiJavaPatterns.psiElement().withSuperParent(2, PsiNameValuePair.class)), new CompletionProvider<CompletionParameters>() {
@Override
public void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
final PsiElement element = parameters.getPosition();
final ElementPattern<? extends PsiElement> leftNeighbor = JavaCompletionData.AFTER_DOT;
final boolean needQualify = leftNeighbor.accepts(element);
for (final PsiType type : ExpectedTypesGetter.getExpectedTypes(element, false)) {
final PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (psiClass != null && psiClass.isAnnotationType()) {
final LookupItem item = AllClassesGetter.createLookupItem(psiClass, AnnotationInsertHandler.INSTANCE);
if (needQualify) JavaCompletionUtil.qualify(item);
result.addElement(item);
}
}
}
});
extend(CompletionType.SMART, psiElement().inside(
psiElement(PsiDocTag.class).withName(
string().oneOf(PsiKeyword.THROWS, EXCEPTION_TAG))), new CompletionProvider<CompletionParameters>() {
@Override
public void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
final PsiElement element = parameters.getPosition();
final Set<PsiClass> throwsSet = new HashSet<PsiClass>();
final PsiMethod method = PsiTreeUtil.getContextOfType(element, PsiMethod.class, true);
if(method != null){
for (PsiClassType ref : method.getThrowsList().getReferencedTypes()) {
final PsiClass exception = ref.resolve();
if (exception != null && throwsSet.add(exception)) {
result.addElement(TailTypeDecorator.withTail(new JavaPsiClassReferenceElement(exception), TailType.HUMBLE_SPACE_BEFORE_WORD));
}
}
}
}
});
final Key<PsiTryStatement> tryKey = Key.create("try");
extend(CompletionType.SMART, psiElement().insideStarting(
psiElement(PsiTypeElement.class).withParent(
psiElement(PsiCatchSection.class).withParent(
psiElement(PsiTryStatement.class).save(tryKey)))), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
final PsiCodeBlock tryBlock = context.get(tryKey).getTryBlock();
if (tryBlock == null) return;
for (final PsiClassType type : ExceptionUtil.getThrownExceptions(tryBlock.getStatements())) {
result.addElement(TailTypeDecorator.withTail(PsiTypeLookupItem.createLookupItem(type, tryBlock).setInsertHandler(new DefaultInsertHandler()), TailType.HUMBLE_SPACE_BEFORE_WORD));
}
}
});
extend(CompletionType.SMART, IN_TYPE_ARGS, new TypeArgumentCompletionProvider(true, null));
extend(CompletionType.SMART, AFTER_NEW, new JavaInheritorsGetter(ConstructorInsertHandler.SMART_INSTANCE));
extend(CompletionType.SMART, psiElement().afterLeaf(PsiKeyword.BREAK, PsiKeyword.CONTINUE), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
PsiReference ref = parameters.getPosition().getContainingFile().findReferenceAt(parameters.getOffset());
if (ref instanceof PsiLabelReference) {
JavaCompletionContributor.processLabelReference(result, (PsiLabelReference)ref);
}
}
});
extend(CompletionType.SMART, LAMBDA, new LambdaCompletionProvider());
extend(CompletionType.SMART, METHOD_REFERENCE, new MethodReferenceCompletionProvider());
}
private static void addExpectedTypeMembers(CompletionParameters params,
THashSet<ExpectedTypeInfo> mergedInfos,
boolean quick,
Consumer<LookupElement> consumer) {
PsiElement position = params.getPosition();
if (!JavaCompletionData.AFTER_DOT.accepts(position)) {
for (ExpectedTypeInfo info : mergedInfos) {
new JavaMembersGetter(info.getType(), params).addMembers(!quick, consumer);
if (!info.getDefaultType().equals(info.getType())) {
new JavaMembersGetter(info.getDefaultType(), params).addMembers(!quick, consumer);
}
}
}
}
@Override
public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
super.fillCompletionVariants(parameters, JavaCompletionSorting.addJavaSorting(parameters, result));
}
public static SmartCompletionDecorator decorate(LookupElement lookupElement, Collection<ExpectedTypeInfo> infos) {
LookupItem item = lookupElement.as(LookupItem.CLASS_CONDITION_KEY);
if (item != null && item.getInsertHandler() == null) {
item.setInsertHandler(DefaultInsertHandler.NO_TAIL_HANDLER);
}
return new SmartCompletionDecorator(lookupElement, infos);
}
private static LookupElement createInstanceofLookupElement(PsiClass psiClass, Set<PsiClass> toWildcardInheritors) {
final PsiTypeParameter[] typeParameters = psiClass.getTypeParameters();
if (typeParameters.length > 0) {
for (final PsiClass parameterizedType : toWildcardInheritors) {
if (psiClass.isInheritor(parameterizedType, true)) {
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
final PsiWildcardType wildcard = PsiWildcardType.createUnbounded(psiClass.getManager());
for (final PsiTypeParameter typeParameter : typeParameters) {
substitutor = substitutor.put(typeParameter, wildcard);
}
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(psiClass.getProject());
return PsiTypeLookupItem.createLookupItem(factory.createType(psiClass, substitutor), psiClass);
}
}
}
return new JavaPsiClassReferenceElement(psiClass);
}
@NotNull
public static ExpectedTypeInfo[] getExpectedTypes(final CompletionParameters parameters) {
return getExpectedTypes(parameters, parameters.getCompletionType() == CompletionType.SMART);
}
@NotNull
public static ExpectedTypeInfo[] getExpectedTypes(final CompletionParameters parameters, boolean voidable) {
final PsiElement position = parameters.getPosition();
if (psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(PsiThrowStatement.class)).accepts(position)) {
final PsiElementFactory factory = JavaPsiFacade.getInstance(position.getProject()).getElementFactory();
final PsiClassType classType = factory
.createTypeByFQClassName(CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, position.getResolveScope());
final List<ExpectedTypeInfo> result = new SmartList<ExpectedTypeInfo>();
result.add(new ExpectedTypeInfoImpl(classType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, classType, TailType.SEMICOLON, null, ExpectedTypeInfoImpl.NULL));
final PsiMethod method = PsiTreeUtil.getContextOfType(position, PsiMethod.class, true);
if (method != null) {
for (final PsiClassType type : method.getThrowsList().getReferencedTypes()) {
result.add(new ExpectedTypeInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.SEMICOLON, null, ExpectedTypeInfoImpl.NULL));
}
}
return result.toArray(new ExpectedTypeInfo[result.size()]);
}
PsiExpression expression = PsiTreeUtil.getContextOfType(position, PsiExpression.class, true);
if (expression == null) return ExpectedTypeInfo.EMPTY_ARRAY;
return ExpectedTypesProvider.getExpectedTypes(expression, true, voidable, false);
}
static Set<LookupElement> completeReference(final PsiElement element,
PsiReference reference,
final ElementFilter filter,
final boolean acceptClasses,
final boolean acceptMembers,
CompletionParameters parameters, final PrefixMatcher matcher) {
if (reference instanceof PsiMultiReference) {
reference = ContainerUtil.findInstance(((PsiMultiReference) reference).getReferences(), PsiJavaReference.class);
}
if (reference instanceof PsiJavaReference) {
final PsiJavaReference javaReference = (PsiJavaReference)reference;
ElementFilter checkClass = new ElementFilter() {
@Override
public boolean isAcceptable(Object element, PsiElement context) {
return filter.isAcceptable(element, context);
}
@Override
public boolean isClassAcceptable(Class hintClass) {
if (ReflectionUtil.isAssignable(PsiClass.class, hintClass)) {
return acceptClasses;
}
if (ReflectionUtil.isAssignable(PsiVariable.class, hintClass) ||
ReflectionUtil.isAssignable(PsiMethod.class, hintClass) ||
ReflectionUtil.isAssignable(CandidateInfo.class, hintClass)) {
return acceptMembers;
}
return false;
}
};
JavaCompletionProcessor.Options options =
JavaCompletionProcessor.Options.DEFAULT_OPTIONS.withFilterStaticAfterInstance(parameters.getInvocationCount() <= 1);
return JavaCompletionUtil.processJavaReference(element, javaReference, checkClass, options, matcher, parameters);
}
return Collections.emptySet();
}
@Override
public void beforeCompletion(@NotNull CompletionInitializationContext context) {
if (context.getCompletionType() != CompletionType.SMART) {
return;
}
if (!context.getEditor().getSelectionModel().hasSelection()) {
final PsiFile file = context.getFile();
PsiElement element = file.findElementAt(context.getStartOffset());
if (element instanceof PsiIdentifier) {
element = element.getParent();
while (element instanceof PsiJavaCodeReferenceElement || element instanceof PsiCall ||
element instanceof PsiThisExpression || element instanceof PsiSuperExpression ||
element instanceof PsiTypeElement ||
element instanceof PsiClassObjectAccessExpression) {
int newEnd = element.getTextRange().getEndOffset();
if (element instanceof PsiMethodCallExpression) {
newEnd = ((PsiMethodCallExpression)element).getMethodExpression().getTextRange().getEndOffset();
}
else if (element instanceof PsiNewExpression) {
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)element).getClassReference();
if (classReference != null) {
newEnd = classReference.getTextRange().getEndOffset();
}
}
context.setReplacementOffset(newEnd);
element = element.getParent();
}
}
}
PsiElement lastElement = context.getFile().findElementAt(context.getStartOffset() - 1);
if (lastElement != null && lastElement.getText().equals("(")) {
final PsiElement parent = lastElement.getParent();
if (parent instanceof PsiTypeCastExpression) {
context.setDummyIdentifier("");
return;
}
if (parent instanceof PsiParenthesizedExpression) {
context.setDummyIdentifier(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED + ")" + CompletionUtil.DUMMY_IDENTIFIER_TRIMMED + " "); // to handle type cast
return;
}
}
context.setDummyIdentifier(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED);
}
}