blob: 2523a5233e344c388654fe43d4d476970f1f197e [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.scope;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.codeInspection.SuppressManager;
import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstance;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.filters.ElementFilter;
import com.intellij.psi.impl.light.LightMethodBuilder;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.BaseScopeProcessor;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.JavaScopeProcessorEvent;
import com.intellij.psi.util.*;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: ik
* Date: 20.01.2003
* Time: 16:13:27
* To change this template use Options | File Templates.
*/
public class JavaCompletionProcessor extends BaseScopeProcessor implements ElementClassHint {
private boolean myStatic = false;
private PsiElement myDeclarationHolder = null;
private final Set<Object> myResultNames = new THashSet<Object>(new TObjectHashingStrategy<Object>() {
@Override
public int computeHashCode(Object object) {
if (object instanceof MethodSignature) {
return MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY.computeHashCode((MethodSignature)object);
}
return object != null ? object.hashCode() : 0;
}
@Override
public boolean equals(Object o1, Object o2) {
if (o1 instanceof MethodSignature && o2 instanceof MethodSignature) {
return MethodSignatureUtil.METHOD_PARAMETERS_ERASURE_EQUALITY.equals((MethodSignature)o1, (MethodSignature)o2);
}
return o1 != null ? o1.equals(o2) : o2 == null;
}
});
private final List<CompletionElement> myResults = new ArrayList<CompletionElement>();
private final List<CompletionElement> myFilteredResults = new ArrayList<CompletionElement>();
private final PsiElement myElement;
private final PsiElement myScope;
private final ElementFilter myFilter;
private boolean myMembersFlag = false;
private boolean myQualified = false;
private PsiType myQualifierType = null;
private PsiClass myQualifierClass = null;
private final Condition<String> myMatcher;
private final Options myOptions;
private final Set<PsiField> myNonInitializedFields = new HashSet<PsiField>();
private final boolean myAllowStaticWithInstanceQualifier;
public JavaCompletionProcessor(@NotNull PsiElement element, ElementFilter filter, Options options, @NotNull Condition<String> nameCondition) {
myOptions = options;
myElement = element;
myMatcher = nameCondition;
myFilter = filter;
PsiElement scope = element;
if (JavaResolveUtil.isInJavaDoc(myElement)) myMembersFlag = true;
while(scope != null && !(scope instanceof PsiFile) && !(scope instanceof PsiClass)){
scope = scope.getContext();
}
myScope = scope;
if (!(element.getContainingFile() instanceof PsiJavaFile)) {
myMembersFlag = true;
}
PsiElement elementParent = element.getContext();
if (elementParent instanceof PsiReferenceExpression) {
PsiExpression qualifier = ((PsiReferenceExpression)elementParent).getQualifierExpression();
if (qualifier instanceof PsiSuperExpression) {
final PsiJavaCodeReferenceElement qSuper = ((PsiSuperExpression)qualifier).getQualifier();
if (qSuper == null) {
myQualifierClass = JavaResolveUtil.getContextClass(myElement);
} else {
final PsiElement target = qSuper.resolve();
myQualifierClass = target instanceof PsiClass ? (PsiClass)target : null;
}
}
else if (qualifier != null) {
myQualified = true;
setQualifierType(qualifier.getType());
if (myQualifierType == null && qualifier instanceof PsiJavaCodeReferenceElement) {
final PsiElement target = ((PsiJavaCodeReferenceElement)qualifier).resolve();
if (target instanceof PsiClass) {
myQualifierClass = (PsiClass)target;
}
}
} else {
myQualifierClass = JavaResolveUtil.getContextClass(myElement);
}
}
if (myQualifierClass != null && myQualifierType == null) {
myQualifierType = JavaPsiFacade.getElementFactory(element.getProject()).createType(myQualifierClass);
}
if (myOptions.checkInitialized) {
myNonInitializedFields.addAll(getNonInitializedFields(element));
}
myAllowStaticWithInstanceQualifier = !options.filterStaticAfterInstance || CodeInsightSettings.getInstance().SHOW_STATIC_AFTER_INSTANCE ||
SuppressManager.getInstance()
.isSuppressedFor(element, AccessStaticViaInstance.ACCESS_STATIC_VIA_INSTANCE);
}
private static boolean isInitializedImplicitly(PsiField field) {
field = CompletionUtil.getOriginalOrSelf(field);
for(ImplicitUsageProvider provider: ImplicitUsageProvider.EP_NAME.getExtensions()) {
if (provider.isImplicitWrite(field)) {
return true;
}
}
return false;
}
public static Set<PsiField> getNonInitializedFields(PsiElement element) {
final PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class);
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiClass.class);
if (statement == null || method == null || !method.isConstructor()) {
return Collections.emptySet();
}
PsiElement parent = element.getParent();
while (parent != statement) {
PsiElement next = parent.getParent();
if (next instanceof PsiAssignmentExpression && parent == ((PsiAssignmentExpression)next).getLExpression()) {
return Collections.emptySet();
}
if (parent instanceof PsiReferenceExpression && next instanceof PsiExpressionStatement) {
return Collections.emptySet();
}
parent = next;
}
final Set<PsiField> fields = new HashSet<PsiField>();
final PsiClass containingClass = method.getContainingClass();
assert containingClass != null;
for (PsiField field : containingClass.getFields()) {
if (!field.hasModifierProperty(PsiModifier.STATIC) && field.getInitializer() == null && !isInitializedImplicitly(field)) {
fields.add(field);
}
}
method.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
if (expression.getTextRange().getStartOffset() < statement.getTextRange().getStartOffset()) {
final PsiExpression lExpression = expression.getLExpression();
if (lExpression instanceof PsiReferenceExpression) {
//noinspection SuspiciousMethodCalls
fields.remove(((PsiReferenceExpression)lExpression).resolve());
}
}
super.visitAssignmentExpression(expression);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
if (expression.getTextRange().getStartOffset() < statement.getTextRange().getStartOffset()) {
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
if (methodExpression.textMatches("this")) {
fields.clear();
}
}
super.visitMethodCallExpression(expression);
}
});
return fields;
}
@Override
public void handleEvent(@NotNull Event event, Object associated){
if(event == JavaScopeProcessorEvent.START_STATIC){
myStatic = true;
}
if(event == JavaScopeProcessorEvent.CHANGE_LEVEL){
myMembersFlag = true;
}
if (event == JavaScopeProcessorEvent.SET_CURRENT_FILE_CONTEXT) {
myDeclarationHolder = (PsiElement)associated;
}
}
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
//noinspection SuspiciousMethodCalls
if (myNonInitializedFields.contains(element)) {
return true;
}
if (element instanceof PsiPackage && !isQualifiedContext()) {
if (myScope instanceof PsiClass) {
return true;
}
if (((PsiPackage)element).getQualifiedName().contains(".") &&
PsiTreeUtil.getParentOfType(myElement, PsiImportStatementBase.class) != null) {
return true;
}
}
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
if (PsiTypesUtil.isGetClass(method) && PsiUtil.isLanguageLevel5OrHigher(myElement)) {
PsiType patchedType = PsiTypesUtil.createJavaLangClassType(myElement, myQualifierType, false);
if (patchedType != null) {
element = new LightMethodBuilder(element.getManager(), method.getName()).
addModifier(PsiModifier.PUBLIC).
setMethodReturnType(patchedType).
setContainingClass(method.getContainingClass());
}
}
}
if (satisfies(element, state) && isAccessible(element)) {
CompletionElement element1 = new CompletionElement(element, state.get(PsiSubstitutor.KEY));
if (myResultNames.add(element1.getUniqueId())) {
StaticProblem sp = myElement.getParent() instanceof PsiMethodReferenceExpression ? StaticProblem.none : getStaticProblem(element);
if (sp != StaticProblem.instanceAfterStatic) {
(sp == StaticProblem.staticAfterInstance ? myFilteredResults : myResults).add(element1);
}
}
} else if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
myResultNames.add(CompletionElement.getVariableUniqueId((PsiVariable)element));
}
return true;
}
private boolean isQualifiedContext() {
final PsiElement elementParent = myElement.getParent();
return elementParent instanceof PsiQualifiedReference && ((PsiQualifiedReference)elementParent).getQualifier() != null;
}
private StaticProblem getStaticProblem(PsiElement element) {
if (myOptions.showInstanceInStaticContext && !isQualifiedContext()) {
return StaticProblem.none;
}
if (element instanceof PsiModifierListOwner) {
PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)element;
if (myStatic) {
if (!(element instanceof PsiClass) && !modifierListOwner.hasModifierProperty(PsiModifier.STATIC)) {
// we don't need non static method in static context.
return StaticProblem.instanceAfterStatic;
}
}
else {
if (!myAllowStaticWithInstanceQualifier
&& modifierListOwner.hasModifierProperty(PsiModifier.STATIC)
&& !myMembersFlag) {
// according settings we don't need to process such fields/methods
return StaticProblem.staticAfterInstance;
}
}
}
return StaticProblem.none;
}
public boolean satisfies(@NotNull PsiElement element, @NotNull ResolveState state) {
final String name = PsiUtilCore.getName(element);
if (name != null && StringUtil.isNotEmpty(name) && myMatcher.value(name)) {
if (myFilter.isClassAcceptable(element.getClass()) && myFilter.isAcceptable(new CandidateInfo(element, state.get(PsiSubstitutor.KEY)), myElement)) {
return true;
}
}
return false;
}
public void setQualifierType(@Nullable PsiType qualifierType) {
myQualifierType = qualifierType;
myQualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierType);
}
@Nullable
public PsiType getQualifierType() {
return myQualifierType;
}
public boolean isAccessible(@Nullable final PsiElement element) {
if (!myOptions.checkAccess) return true;
if (!(element instanceof PsiMember)) return true;
PsiMember member = (PsiMember)element;
PsiClass accessObjectClass = myQualified ? myQualifierClass : null;
return JavaPsiFacade.getInstance(element.getProject()).getResolveHelper().isAccessible(member, member.getModifierList(), myElement,
accessObjectClass, myDeclarationHolder);
}
public void setCompletionElements(@NotNull Object[] elements) {
for (Object element: elements) {
myResults.add(new CompletionElement(element, PsiSubstitutor.EMPTY));
}
}
public Iterable<CompletionElement> getResults() {
if (myResults.isEmpty()) {
return myFilteredResults;
}
return myResults;
}
public void clear() {
myResults.clear();
myFilteredResults.clear();
}
@Override
public boolean shouldProcess(DeclarationKind kind) {
switch (kind) {
case CLASS:
return myFilter.isClassAcceptable(PsiClass.class);
case FIELD:
return myFilter.isClassAcceptable(PsiField.class);
case METHOD:
return myFilter.isClassAcceptable(PsiMethod.class);
case PACKAGE:
return myFilter.isClassAcceptable(PsiPackage.class);
case VARIABLE:
return myFilter.isClassAcceptable(PsiVariable.class);
case ENUM_CONST:
return myFilter.isClassAcceptable(PsiEnumConstant.class);
}
return false;
}
@Override
public <T> T getHint(@NotNull Key<T> hintKey) {
if (hintKey == ElementClassHint.KEY) {
//noinspection unchecked
return (T)this;
}
if (hintKey == JavaCompletionHints.NAME_FILTER) {
//noinspection unchecked
return (T)myMatcher;
}
return super.getHint(hintKey);
}
public static class Options {
public static final Options DEFAULT_OPTIONS = new Options(true, false, true, false);
public static final Options CHECK_NOTHING = new Options(false, false, false, false);
final boolean checkAccess;
final boolean checkInitialized;
final boolean filterStaticAfterInstance;
final boolean showInstanceInStaticContext;
private Options(boolean checkAccess, boolean checkInitialized, boolean filterStaticAfterInstance, boolean showInstanceInStaticContext) {
this.checkAccess = checkAccess;
this.checkInitialized = checkInitialized;
this.filterStaticAfterInstance = filterStaticAfterInstance;
this.showInstanceInStaticContext = showInstanceInStaticContext;
}
public Options withInitialized(boolean checkInitialized) {
return new Options(checkAccess, checkInitialized, filterStaticAfterInstance, showInstanceInStaticContext);
}
public Options withCheckAccess(boolean checkAccess) {
return new Options(checkAccess, checkInitialized, filterStaticAfterInstance, showInstanceInStaticContext);
}
public Options withFilterStaticAfterInstance(boolean filterStaticAfterInstance) {
return new Options(checkAccess, checkInitialized, filterStaticAfterInstance, showInstanceInStaticContext);
}
public Options withShowInstanceInStaticContext(boolean showInstanceInStaticContext) {
return new Options(checkAccess, checkInitialized, filterStaticAfterInstance, showInstanceInStaticContext);
}
}
private enum StaticProblem { none, staticAfterInstance, instanceAfterStatic }
}