blob: 8b10b6326df990eca7d3bce937134f6f653cd401 [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.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.LogUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleSettingsFacade;
import com.intellij.psi.filters.*;
import com.intellij.psi.impl.CheckUtil;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.SourceJavaCodeReference;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.impl.source.resolve.*;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.scope.ElementClassFilter;
import com.intellij.psi.scope.MethodProcessorSetupFailedException;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.scope.processor.FilterScopeProcessor;
import com.intellij.psi.scope.processor.MethodResolverProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.*;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class PsiReferenceExpressionImpl extends PsiReferenceExpressionBase implements PsiReferenceExpression, SourceJavaCodeReference {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl");
private volatile String myCachedQName = null;
private volatile String myCachedNormalizedText = null;
public PsiReferenceExpressionImpl() {
super(JavaElementType.REFERENCE_EXPRESSION);
}
@Override
public PsiExpression getQualifierExpression() {
return (PsiExpression)findChildByRoleAsPsiElement(ChildRole.QUALIFIER);
}
@Override
public PsiElement bindToElementViaStaticImport(@NotNull PsiClass qualifierClass) throws IncorrectOperationException {
String qualifiedName = qualifierClass.getQualifiedName();
if (qualifiedName == null) throw new IncorrectOperationException();
if (getQualifierExpression() != null) {
throw new IncorrectOperationException("Reference is qualified: "+getText());
}
if (!isPhysical()) {
// don't qualify reference: the isReferenceTo() check fails anyway, whether we have a static import for this member or not
return this;
}
String staticName = getReferenceName();
PsiFile containingFile = getContainingFile();
PsiImportList importList = null;
boolean doImportStatic;
if (containingFile instanceof PsiJavaFile) {
importList = ((PsiJavaFile)containingFile).getImportList();
PsiImportStatementBase singleImportStatement = importList.findSingleImportStatement(staticName);
doImportStatic = singleImportStatement == null;
if (singleImportStatement instanceof PsiImportStaticStatement) {
String qName = qualifierClass.getQualifiedName() + "." + staticName;
if (qName.equals(singleImportStatement.getImportReference().getQualifiedName())) return this;
}
}
else {
doImportStatic = false;
}
if (doImportStatic) {
bindToElementViaStaticImport(qualifierClass, staticName, importList);
}
else {
PsiManagerEx manager = getManager();
PsiReferenceExpression classRef = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory().createReferenceExpression(
qualifierClass);
final CharTable treeCharTab = SharedImplUtil.findCharTableByTree(this);
LeafElement dot = Factory.createSingleLeafElement(JavaTokenType.DOT, ".", 0, 1, treeCharTab, manager);
addInternal(dot, dot, SourceTreeToPsiMap.psiElementToTree(getParameterList()), Boolean.TRUE);
addBefore(classRef, SourceTreeToPsiMap.treeElementToPsi(dot));
}
return this;
}
public static void bindToElementViaStaticImport(final PsiClass qualifierClass, final String staticName, final PsiImportList importList)
throws IncorrectOperationException {
final String qualifiedName = qualifierClass.getQualifiedName();
final List<PsiJavaCodeReferenceElement> refs = getImportsFromClass(importList, qualifiedName);
if (refs.size() < JavaCodeStyleSettingsFacade.getInstance(qualifierClass.getProject()).getNamesCountToUseImportOnDemand()) {
importList.add(JavaPsiFacade.getInstance(qualifierClass.getProject()).getElementFactory().createImportStaticStatement(qualifierClass,
staticName));
} else {
for (PsiJavaCodeReferenceElement ref : refs) {
final PsiImportStaticStatement importStatement = PsiTreeUtil.getParentOfType(ref, PsiImportStaticStatement.class);
if (importStatement != null) {
importStatement.delete();
}
}
importList.add(JavaPsiFacade.getInstance(qualifierClass.getProject()).getElementFactory().createImportStaticStatement(qualifierClass,
"*"));
}
}
private static List<PsiJavaCodeReferenceElement> getImportsFromClass(@NotNull PsiImportList importList, String className){
final List<PsiJavaCodeReferenceElement> array = new ArrayList<PsiJavaCodeReferenceElement>();
for (PsiImportStaticStatement staticStatement : importList.getImportStaticStatements()) {
final PsiClass psiClass = staticStatement.resolveTargetClass();
if (psiClass != null && Comparing.strEqual(psiClass.getQualifiedName(), className)) {
array.add(staticStatement.getImportReference());
}
}
return array;
}
@Override
public void setQualifierExpression(@Nullable PsiExpression newQualifier) throws IncorrectOperationException {
final PsiExpression oldQualifier = getQualifierExpression();
if (newQualifier == null) {
if (oldQualifier != null) {
deleteChildInternal(oldQualifier.getNode());
}
}
else {
if (oldQualifier != null) {
oldQualifier.replace(newQualifier);
}
else {
final CharTable treeCharTab = SharedImplUtil.findCharTableByTree(this);
TreeElement dot = (TreeElement)findChildByRole(ChildRole.DOT);
if (dot == null) {
dot = Factory.createSingleLeafElement(JavaTokenType.DOT, ".", 0, 1, treeCharTab, getManager());
dot = addInternal(dot, dot, getFirstChildNode(), Boolean.TRUE);
}
addBefore(newQualifier, dot.getPsi());
}
}
}
@Override
public PsiElement getQualifier() {
return getQualifierExpression();
}
@Override
public void clearCaches() {
myCachedQName = null;
myCachedNormalizedText = null;
super.clearCaches();
}
public static final class OurGenericsResolver implements ResolveCache.PolyVariantContextResolver<PsiJavaReference> {
public static final OurGenericsResolver INSTANCE = new OurGenericsResolver();
@NotNull
@Override
public ResolveResult[] resolve(@NotNull PsiJavaReference ref, @NotNull PsiFile containingFile, boolean incompleteCode) {
PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)ref;
CompositeElement treeParent = expression.getTreeParent();
IElementType parentType = treeParent == null ? null : treeParent.getElementType();
List<PsiElement> qualifiers = resolveAllQualifiers(expression, containingFile);
try {
JavaResolveResult[] result = expression.resolve(parentType, containingFile);
if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) {
result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
}
JavaResolveUtil.substituteResults(expression, result);
return result;
}
finally {
PsiElement item = qualifiers.isEmpty() ? PsiUtilCore.NULL_PSI_ELEMENT : qualifiers.get(qualifiers.size()-1);
qualifiers.clear(); // hold qualifiers list until this moment to avoid psi elements inside to GC
if (item == null) {
throw new IncorrectOperationException();
}
}
}
@NotNull
private static List<PsiElement> resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull final PsiFile containingFile) {
// to avoid SOE, resolve all qualifiers starting from the innermost
PsiElement qualifier = expression.getQualifier();
if (qualifier == null) return Collections.emptyList();
final List<PsiElement> qualifiers = new SmartList<PsiElement>();
final ResolveCache resolveCache = ResolveCache.getInstance(containingFile.getProject());
qualifier.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
if (!(expression instanceof PsiReferenceExpressionImpl)) {
return;
}
ResolveResult[] cachedResults = resolveCache.getCachedResults(expression, true, false, true);
if (cachedResults != null) {
return;
}
visitElement(expression);
}
@Override
protected void elementFinished(@NotNull PsiElement element) {
if (!(element instanceof PsiReferenceExpressionImpl)) return;
PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)element;
resolveCache.resolveWithCaching(expression, INSTANCE, false, false, containingFile);
qualifiers.add(expression);
}
});
return qualifiers;
}
}
@NotNull
private JavaResolveResult[] resolve(IElementType parentType, @NotNull PsiFile containingFile) {
if (parentType == JavaElementType.REFERENCE_EXPRESSION) {
JavaResolveResult[] result = resolveToVariable(containingFile);
if (result.length > 0) {
return result;
}
PsiElement classNameElement = getReferenceNameElement();
if (!(classNameElement instanceof PsiIdentifier)) {
return JavaResolveResult.EMPTY_ARRAY;
}
result = resolveToClass(classNameElement, containingFile);
if (result.length == 1 && !result[0].isAccessible()) {
JavaResolveResult[] packageResult = resolveToPackage(containingFile);
if (packageResult.length != 0) {
result = packageResult;
}
}
else if (result.length == 0) {
result = resolveToPackage(containingFile);
}
return result;
}
if (parentType == JavaElementType.METHOD_CALL_EXPRESSION) {
return resolveToMethod(containingFile);
}
if (parentType == JavaElementType.METHOD_REF_EXPRESSION) {
return resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
}
return resolveToVariable(containingFile);
}
@NotNull
private JavaResolveResult[] resolveToMethod(@NotNull PsiFile containingFile) {
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)getParent();
final MethodResolverProcessor processor = new MethodResolverProcessor(methodCall, containingFile);
try {
PsiScopesUtil.setupAndRunProcessor(processor, methodCall, false);
}
catch (MethodProcessorSetupFailedException e) {
return JavaResolveResult.EMPTY_ARRAY;
}
return processor.getResult();
}
@NotNull
private JavaResolveResult[] resolveToPackage(@NotNull PsiFile containingFile) {
final String packageName = getCachedNormalizedText();
Project project = containingFile.getProject();
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiPackage aPackage = psiFacade.findPackage(packageName);
if (aPackage == null) {
return psiFacade.isPartOfPackagePrefix(packageName)
? CandidateInfo.RESOLVE_RESULT_FOR_PACKAGE_PREFIX_PACKAGE
: JavaResolveResult.EMPTY_ARRAY;
}
// check that all qualifiers must resolve to package parts, to prevent local vars shadowing corresponding package case
PsiExpression qualifier = getQualifierExpression();
if (qualifier instanceof PsiReferenceExpression && !(((PsiReferenceExpression)qualifier).resolve() instanceof PsiPackage)) {
return JavaResolveResult.EMPTY_ARRAY;
}
return new JavaResolveResult[]{new CandidateInfo(aPackage, PsiSubstitutor.EMPTY)};
}
@NotNull
private JavaResolveResult[] resolveToClass(@NotNull PsiElement classNameElement, @NotNull PsiFile containingFile) {
final String className = classNameElement.getText();
final ClassResolverProcessor processor = new ClassResolverProcessor(className, this, containingFile);
PsiScopesUtil.resolveAndWalk(processor, this, null);
return processor.getResult();
}
@NotNull
private JavaResolveResult[] resolveToVariable(@NotNull PsiFile containingFile) {
final VariableResolverProcessor processor = new VariableResolverProcessor(this, containingFile);
PsiScopesUtil.resolveAndWalk(processor, this, null);
return processor.getResult();
}
@Override
@NotNull
public JavaResolveResult[] multiResolve(boolean incompleteCode) {
FileElement fileElement = SharedImplUtil.findFileElement(this);
if (fileElement == null) {
LOG.error("fileElement == null!");
return JavaResolveResult.EMPTY_ARRAY;
}
final PsiManagerEx manager = fileElement.getManager();
if (manager == null) {
LOG.error("getManager() == null!");
return JavaResolveResult.EMPTY_ARRAY;
}
PsiFile file = SharedImplUtil.getContainingFile(fileElement);
boolean valid = file != null && file.isValid();
if (!valid) {
LOG.error("invalid!");
return JavaResolveResult.EMPTY_ARRAY;
}
Project project = manager.getProject();
ResolveResult[] results = ResolveCache.getInstance(project).resolveWithCaching(this, OurGenericsResolver.INSTANCE, true, incompleteCode, file);
return results.length == 0 ? JavaResolveResult.EMPTY_ARRAY : (JavaResolveResult[])results;
}
@Override
@NotNull
public String getCanonicalText() {
final PsiElement element = resolve();
if (element instanceof PsiClass && !(element instanceof PsiTypeParameter)) {
final String fqn = ((PsiClass)element).getQualifiedName();
if (fqn != null) return fqn;
LOG.error("FQN is null. Reference:" + getElement().getText() +
" resolves to:" + LogUtil.objectAndClass(element) +
" parent:" + LogUtil.objectAndClass(element.getParent()));
}
return getCachedNormalizedText();
}
private static final Function<PsiReferenceExpressionImpl, PsiType> TYPE_EVALUATOR = new TypeEvaluator();
private static class TypeEvaluator implements NullableFunction<PsiReferenceExpressionImpl, PsiType> {
@Override
public PsiType fun(final PsiReferenceExpressionImpl expr) {
PsiFile file = expr.getContainingFile();
Project project = file.getProject();
ResolveResult[] results = ResolveCache.getInstance(project).resolveWithCaching(expr, OurGenericsResolver.INSTANCE, true, false, file);
JavaResolveResult result = results.length == 1 ? (JavaResolveResult)results[0] : null;
PsiElement resolve = result == null ? null : result.getElement();
if (resolve == null) {
ASTNode refName = expr.findChildByRole(ChildRole.REFERENCE_NAME);
if (refName != null && "length".equals(refName.getText())) {
ASTNode qualifier = expr.findChildByRole(ChildRole.QUALIFIER);
if (qualifier != null && ElementType.EXPRESSION_BIT_SET.contains(qualifier.getElementType())) {
PsiType type = SourceTreeToPsiMap.<PsiExpression>treeToPsiNotNull(qualifier).getType();
if (type instanceof PsiArrayType) {
return PsiType.INT;
}
}
}
return null;
}
PsiTypeParameterListOwner owner = null;
PsiType ret = null;
if (resolve instanceof PsiVariable) {
PsiType type = ((PsiVariable)resolve).getType();
ret = type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type;
if (ret != null && !ret.isValid()) {
LOG.error("invalid type of " + resolve + " of class " + resolve.getClass() + ", valid=" + resolve.isValid());
}
if (resolve instanceof PsiField && !((PsiField)resolve).hasModifierProperty(PsiModifier.STATIC)) {
owner = ((PsiField)resolve).getContainingClass();
}
}
else if (resolve instanceof PsiMethod) {
PsiMethod method = (PsiMethod)resolve;
ret = method.getReturnType();
if (ret != null) {
PsiUtil.ensureValidType(ret);
}
owner = method;
}
if (ret == null) return null;
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(file);
if (ret instanceof PsiClassType) {
ret = ((PsiClassType)ret).setLanguageLevel(languageLevel);
}
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) {
final PsiSubstitutor substitutor = result.getSubstitutor();
if (owner == null || !PsiUtil.isRawSubstitutor(owner, substitutor)) {
PsiType substitutedType = substitutor.substitute(ret);
PsiUtil.ensureValidType(substitutedType);
PsiType normalized = PsiImplUtil.normalizeWildcardTypeByPosition(substitutedType, expr);
PsiUtil.ensureValidType(normalized);
return normalized;
}
}
return TypeConversionUtil.erasure(ret);
}
}
@Override
public PsiType getType() {
return JavaResolveCache.getInstance(getProject()).getType(this, TYPE_EVALUATOR);
}
@Override
public boolean isReferenceTo(PsiElement element) {
IElementType i = getLastChildNode().getElementType();
boolean resolvingToMethod = element instanceof PsiMethod;
if (i == JavaTokenType.IDENTIFIER) {
if (!(element instanceof PsiPackage)) {
if (!(element instanceof PsiNamedElement)) return false;
String name = ((PsiNamedElement)element).getName();
if (name == null) return false;
if (!name.equals(getLastChildNode().getText())) return false;
}
}
else if (i == JavaTokenType.SUPER_KEYWORD || i == JavaTokenType.THIS_KEYWORD) {
if (!resolvingToMethod) return false;
if (!((PsiMethod)element).isConstructor()) return false;
}
PsiElement parent = getParent();
boolean parentIsMethodCall = parent instanceof PsiMethodCallExpression;
// optimization: methodCallExpression should resolve to a method
if (parentIsMethodCall != resolvingToMethod) return false;
return element.getManager().areElementsEquivalent(element, advancedResolve(true).getElement());
}
@Override
public void processVariants(@NotNull PsiScopeProcessor processor) {
OrFilter filter = new OrFilter();
filter.addFilter(ElementClassFilter.CLASS);
if (isQualified()) {
filter.addFilter(ElementClassFilter.PACKAGE_FILTER);
}
filter.addFilter(new AndFilter(ElementClassFilter.METHOD, new NotFilter(new ConstructorFilter()), new ElementFilter() {
@Override
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
return LambdaUtil.isValidQualifier4InterfaceStaticMethodCall((PsiMethod)element, PsiReferenceExpressionImpl.this,
null, PsiUtil.getLanguageLevel(PsiReferenceExpressionImpl.this));
}
@Override
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}));
filter.addFilter(ElementClassFilter.VARIABLE);
FilterScopeProcessor filterProcessor = new FilterScopeProcessor<CandidateInfo>(filter, processor) {
private final Set<String> myVarNames = new THashSet<String>();
@Override
public boolean execute(@NotNull final PsiElement element, @NotNull final ResolveState state) {
if (element instanceof PsiLocalVariable || element instanceof PsiParameter) {
myVarNames.add(((PsiVariable) element).getName());
}
else if (element instanceof PsiField && myVarNames.contains(((PsiVariable) element).getName())) {
return true;
}
else if (element instanceof PsiClass && seemsScrambled((PsiClass)element)) {
return true;
}
return super.execute(element, state);
}
};
PsiScopesUtil.resolveAndWalk(filterProcessor, this, null, true);
}
public static boolean seemsScrambled(PsiClass aClass) {
if (!(aClass instanceof PsiCompiledElement)) {
return false;
}
final String name = aClass.getName();
return name != null && !name.isEmpty() && name.length() <= 2;
}
@Override
public PsiElement getReferenceNameElement() {
return findChildByRoleAsPsiElement(ChildRole.REFERENCE_NAME);
}
@Override
public int getTextOffset() {
ASTNode refName = findChildByRole(ChildRole.REFERENCE_NAME);
return refName == null ? super.getTextOffset() : refName.getStartOffset();
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
if (getQualifierExpression() != null) {
return renameDirectly(newElementName);
}
final JavaResolveResult resolveResult = advancedResolve(false);
if (resolveResult.getElement() == null) {
return renameDirectly(newElementName);
}
PsiElement currentFileResolveScope = resolveResult.getCurrentFileResolveScope();
if (!(currentFileResolveScope instanceof PsiImportStaticStatement) ||
((PsiImportStaticStatement)currentFileResolveScope).isOnDemand()) {
return renameDirectly(newElementName);
}
final PsiImportStaticStatement importStaticStatement = (PsiImportStaticStatement)currentFileResolveScope;
final String referenceName = importStaticStatement.getReferenceName();
LOG.assertTrue(referenceName != null);
final PsiElement element = importStaticStatement.getImportReference().resolve();
if (getManager().areElementsEquivalent(element, resolveResult.getElement())) {
return renameDirectly(newElementName);
}
final PsiClass psiClass = importStaticStatement.resolveTargetClass();
if (psiClass == null) return renameDirectly(newElementName);
final PsiElementFactory factory = JavaPsiFacade.getInstance(getProject()).getElementFactory();
final PsiReferenceExpression expression = (PsiReferenceExpression)factory.createExpressionFromText("X." + newElementName, this);
final PsiReferenceExpression result = (PsiReferenceExpression)replace(expression);
((PsiReferenceExpression)result.getQualifierExpression()).bindToElement(psiClass);
return result;
}
private PsiElement renameDirectly(String newElementName) throws IncorrectOperationException {
PsiElement oldIdentifier = findChildByRoleAsPsiElement(ChildRole.REFERENCE_NAME);
if (oldIdentifier == null) {
throw new IncorrectOperationException();
}
final String oldRefName = oldIdentifier.getText();
if (PsiKeyword.THIS.equals(oldRefName) || PsiKeyword.SUPER.equals(oldRefName) || Comparing.strEqual(oldRefName, newElementName)) return this;
PsiIdentifier identifier = JavaPsiFacade.getInstance(getProject()).getElementFactory().createIdentifier(newElementName);
oldIdentifier.replace(identifier);
return this;
}
@Override
public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
if (isReferenceTo(element)) return this;
final PsiManager manager = getManager();
final PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(getProject()).getParserFacade();
if (element instanceof PsiClass) {
final boolean preserveQualification = JavaCodeStyleSettingsFacade.getInstance(getProject()).useFQClassNames() && isFullyQualified(this);
String qName = ((PsiClass)element).getQualifiedName();
if (qName == null) {
qName = ((PsiClass)element).getName();
}
else if (JavaPsiFacade.getInstance(manager.getProject()).findClass(qName, getResolveScope()) == null && !preserveQualification) {
return this;
}
PsiExpression ref = parserFacade.createExpressionFromText(qName, this);
getTreeParent().replaceChildInternal(this, (TreeElement)ref.getNode());
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(manager.getProject());
if (!preserveQualification) {
ref = (PsiExpression)codeStyleManager.shortenClassReferences(ref, JavaCodeStyleManager.UNCOMPLETE_CODE);
}
return ref;
}
else if (element instanceof PsiPackage) {
final String qName = ((PsiPackage)element).getQualifiedName();
if (qName.isEmpty()) {
throw new IncorrectOperationException();
}
final PsiExpression ref = parserFacade.createExpressionFromText(qName, this);
getTreeParent().replaceChildInternal(this, (TreeElement)ref.getNode());
return ref;
}
else if ((element instanceof PsiField || element instanceof PsiMethod) && ((PsiMember) element).hasModifierProperty(PsiModifier.STATIC)) {
if (!isPhysical()) {
// don't qualify reference: the isReferenceTo() check fails anyway, whether we have a static import for this member or not
return this;
}
final PsiMember member = (PsiMember) element;
final PsiClass psiClass = member.getContainingClass();
if (psiClass == null) throw new IncorrectOperationException();
final String qName = psiClass.getQualifiedName() + "." + member.getName();
final PsiExpression ref = parserFacade.createExpressionFromText(qName, this);
getTreeParent().replaceChildInternal(this, (TreeElement)ref.getNode());
return ref;
}
else {
throw new IncorrectOperationException(element.toString());
}
}
private static boolean isFullyQualified(CompositeElement classRef) {
ASTNode qualifier = classRef.findChildByRole(ChildRole.QUALIFIER);
if (qualifier == null) return false;
if (qualifier.getElementType() != JavaElementType.REFERENCE_EXPRESSION) return false;
PsiElement refElement = ((PsiReference)qualifier).resolve();
return refElement instanceof PsiPackage || isFullyQualified((CompositeElement)qualifier);
}
@Override
public void deleteChildInternal(@NotNull ASTNode child) {
if (getChildRole(child) == ChildRole.QUALIFIER) {
ASTNode dot = findChildByRole(ChildRole.DOT);
super.deleteChildInternal(child);
deleteChildInternal(dot);
}
else {
super.deleteChildInternal(child);
}
}
@Override
public ASTNode findChildByRole(int role) {
LOG.assertTrue(ChildRole.isUnique(role));
switch (role) {
default:
return null;
case ChildRole.REFERENCE_NAME:
if (getChildRole(getLastChildNode()) == role) {
return getLastChildNode();
}
return findChildByType(JavaTokenType.IDENTIFIER);
case ChildRole.QUALIFIER:
if (getChildRole(getFirstChildNode()) == ChildRole.QUALIFIER) {
return getFirstChildNode();
}
return null;
case ChildRole.REFERENCE_PARAMETER_LIST:
return findChildByType(JavaElementType.REFERENCE_PARAMETER_LIST);
case ChildRole.DOT:
return findChildByType(JavaTokenType.DOT);
}
}
@Override
public int getChildRole(ASTNode child) {
LOG.assertTrue(child.getTreeParent() == this);
IElementType i = child.getElementType();
if (i == JavaTokenType.DOT) {
return ChildRole.DOT;
}
else if (i == JavaElementType.REFERENCE_PARAMETER_LIST) {
return ChildRole.REFERENCE_PARAMETER_LIST;
}
else if (i == JavaTokenType.IDENTIFIER || i == JavaTokenType.THIS_KEYWORD || i == JavaTokenType.SUPER_KEYWORD) {
return ChildRole.REFERENCE_NAME;
}
else {
if (ElementType.EXPRESSION_BIT_SET.contains(child.getElementType())) {
return ChildRole.QUALIFIER;
}
return ChildRoleBase.NONE;
}
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitReferenceExpression(this);
}
else {
visitor.visitElement(this);
}
}
public String toString() {
return "PsiReferenceExpression:" + getText();
}
@Override
public TextRange getRangeInElement() {
TreeElement nameChild = (TreeElement)findChildByRole(ChildRole.REFERENCE_NAME);
if (nameChild == null) {
final TreeElement dot = (TreeElement)findChildByRole(ChildRole.DOT);
if (dot == null) {
LOG.error(toString());
}
return new TextRange(dot.getStartOffsetInParent() + dot.getTextLength(), getTextLength());
}
return new TextRange(nameChild.getStartOffsetInParent(), getTextLength());
}
@Override
public String getClassNameText() {
String cachedQName = myCachedQName;
if (cachedQName == null) {
myCachedQName = cachedQName = PsiNameHelper.getQualifiedClassName(getCachedNormalizedText(), false);
}
return cachedQName;
}
@Override
public void fullyQualify(@NotNull PsiClass targetClass) {
JavaSourceUtil.fullyQualifyReference(this, targetClass);
}
@Override
public boolean isQualified() {
return getChildRole(getFirstChildNode()) == ChildRole.QUALIFIER;
}
@Override
public void subtreeChanged() {
super.subtreeChanged();
// We want to reformat method call arguments on method name change because there is a possible situation that they are aligned
// and method change breaks the alignment.
// Example:
// test(1,
// 2);
// Suppose we're renaming the method to test123. We get the following if parameter list is not reformatted:
// test123(1,
// 2);
PsiElement methodCallCandidate = getParent();
if (methodCallCandidate instanceof PsiMethodCallExpression) {
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)methodCallCandidate;
CodeEditUtil.markToReformat(methodCallExpression.getArgumentList().getNode(), true);
}
}
private String getCachedNormalizedText() {
String whiteSpaceAndComments = myCachedNormalizedText;
if (whiteSpaceAndComments == null) {
myCachedNormalizedText = whiteSpaceAndComments = JavaSourceUtil.getReferenceText(this);
}
return whiteSpaceAndComments;
}
}