/* * Copyright 2000-2013 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.PolyVariantResolver<PsiJavaReference> { public static final OurGenericsResolver INSTANCE = new OurGenericsResolver(); @Override @NotNull public JavaResolveResult[] resolve(@NotNull PsiJavaReference ref, boolean incompleteCode) { PsiReferenceExpressionImpl expression = (PsiReferenceExpressionImpl)ref; CompositeElement treeParent = expression.getTreeParent(); IElementType parentType = treeParent == null ? null : treeParent.getElementType(); PsiFile file = expression.getContainingFile(); List<PsiElement> qualifiers = resolveAllQualifiers(expression, file); try { JavaResolveResult[] result = expression.resolve(parentType, file); if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) { result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, file); } 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(); } } } 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(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, PsiFile containingFile) { if (parentType == JavaElementType.REFERENCE_EXPRESSION) { JavaResolveResult[] result = resolveToVariable(); 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(); } @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(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, 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() { final VariableResolverProcessor processor = new VariableResolverProcessor(this, getContainingFile()); 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, 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, 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); } private static boolean seemsScrambled(PsiClass element) { if (!(element instanceof PsiCompiledElement)) { return false; } final String qualifiedName = element.getQualifiedName(); return qualifiedName != null && qualifiedName.length() <= 2 && !qualifiedName.isEmpty() && Character.isLowerCase(qualifiedName.charAt(0)); } @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(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 = SourceUtil.getReferenceText(this); } return whiteSpaceAndComments; } }