| /* |
| * 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; |
| |
| import com.intellij.codeInsight.javadoc.JavaDocUtil; |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| 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.filters.element.ModifierFilter; |
| import com.intellij.psi.impl.CheckUtil; |
| import com.intellij.psi.impl.DebugUtil; |
| import com.intellij.psi.impl.PsiImplUtil; |
| import com.intellij.psi.impl.PsiManagerEx; |
| import com.intellij.psi.impl.source.resolve.ClassResolverProcessor; |
| import com.intellij.psi.impl.source.resolve.JavaResolveUtil; |
| import com.intellij.psi.impl.source.resolve.ResolveCache; |
| import com.intellij.psi.impl.source.resolve.VariableResolverProcessor; |
| import com.intellij.psi.impl.source.tree.*; |
| import com.intellij.psi.infos.CandidateInfo; |
| import com.intellij.psi.javadoc.PsiDocComment; |
| import com.intellij.psi.scope.ElementClassFilter; |
| import com.intellij.psi.scope.PsiScopeProcessor; |
| import com.intellij.psi.scope.processor.FilterScopeProcessor; |
| 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.util.IncorrectOperationException; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| public class PsiJavaCodeReferenceElementImpl extends CompositePsiElement implements PsiJavaCodeReferenceElement, SourceJavaCodeReference { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl"); |
| |
| private volatile String myCachedQName = null; |
| private volatile String myCachedNormalizedText; |
| private volatile int myKindWhenDummy = CLASS_NAME_KIND; |
| |
| public static final int CLASS_NAME_KIND = 1; |
| public static final int PACKAGE_NAME_KIND = 2; |
| public static final int CLASS_OR_PACKAGE_NAME_KIND = 3; |
| public static final int CLASS_FQ_NAME_KIND = 4; |
| public static final int CLASS_FQ_OR_PACKAGE_NAME_KIND = 5; |
| public static final int CLASS_IN_QUALIFIED_NEW_KIND = 6; |
| |
| @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod") private final int myHC = ourHC++; |
| |
| public PsiJavaCodeReferenceElementImpl() { |
| super(JavaElementType.JAVA_CODE_REFERENCE); |
| } |
| |
| @Override |
| public final int hashCode() { |
| return myHC; |
| } |
| |
| @Override |
| public int getTextOffset() { |
| final ASTNode refName = getReferenceNameNode(); |
| return refName != null ? refName.getStartOffset() : super.getTextOffset(); |
| } |
| |
| public void setKindWhenDummy(final int kind) { |
| LOG.assertTrue(isDummy(getTreeParent().getElementType())); |
| myKindWhenDummy = kind; |
| } |
| |
| private static boolean isDummy(final IElementType type) { |
| return type == TokenType.DUMMY_HOLDER || type == JavaElementType.DUMMY_ELEMENT; |
| } |
| |
| public int getKind(@NotNull PsiFile containingFile) { |
| PsiUtilCore.ensureValid(containingFile); |
| CompositeElement treeParent = getTreeParent(); |
| IElementType i = treeParent.getElementType(); |
| if (isDummy(i)) { |
| return myKindWhenDummy; |
| } |
| if (i == JavaElementType.TYPE) { |
| return treeParent.getTreeParent().getPsi() instanceof PsiTypeCodeFragment ? CLASS_OR_PACKAGE_NAME_KIND : CLASS_NAME_KIND; |
| } |
| if (i == JavaElementType.EXTENDS_LIST || |
| i == JavaElementType.IMPLEMENTS_LIST || |
| i == JavaElementType.EXTENDS_BOUND_LIST || |
| i == JavaElementType.THROWS_LIST || |
| i == JavaElementType.THIS_EXPRESSION || |
| i == JavaElementType.SUPER_EXPRESSION || |
| i == JavaDocElementType.DOC_METHOD_OR_FIELD_REF || |
| i == JavaDocElementType.DOC_TAG_VALUE_ELEMENT || |
| i == JavaElementType.REFERENCE_PARAMETER_LIST || |
| i == JavaElementType.ANNOTATION) { |
| if (isQualified()) { |
| return CLASS_OR_PACKAGE_NAME_KIND; |
| } |
| return CLASS_NAME_KIND; |
| } |
| if (i == JavaElementType.NEW_EXPRESSION) { |
| final ASTNode qualifier = treeParent.findChildByRole(ChildRole.QUALIFIER); |
| return qualifier != null ? CLASS_IN_QUALIFIED_NEW_KIND : CLASS_NAME_KIND; |
| } |
| if (i == JavaElementType.ANONYMOUS_CLASS) { |
| if (treeParent.getChildRole(this) == ChildRole.BASE_CLASS_REFERENCE) { |
| LOG.assertTrue(treeParent.getTreeParent().getElementType() == JavaElementType.NEW_EXPRESSION); |
| final ASTNode qualifier = treeParent.getTreeParent().findChildByRole(ChildRole.QUALIFIER); |
| return qualifier != null ? CLASS_IN_QUALIFIED_NEW_KIND : CLASS_NAME_KIND; |
| } |
| else { |
| return CLASS_OR_PACKAGE_NAME_KIND; // incomplete code |
| } |
| } |
| if (i == JavaElementType.PACKAGE_STATEMENT) { |
| return PACKAGE_NAME_KIND; |
| } |
| if (i == JavaElementType.IMPORT_STATEMENT) { |
| boolean isOnDemand = SourceTreeToPsiMap.<PsiImportStatement>treeToPsiNotNull(treeParent).isOnDemand(); |
| return isOnDemand ? CLASS_FQ_OR_PACKAGE_NAME_KIND : CLASS_FQ_NAME_KIND; |
| } |
| if (i == JavaElementType.IMPORT_STATIC_STATEMENT) { |
| return CLASS_FQ_OR_PACKAGE_NAME_KIND; |
| } |
| if (i == JavaElementType.JAVA_CODE_REFERENCE) { |
| int parentKind = ((PsiJavaCodeReferenceElementImpl)treeParent).getKind(containingFile); |
| if (parentKind == CLASS_NAME_KIND) { |
| return CLASS_OR_PACKAGE_NAME_KIND; |
| } |
| if (parentKind == CLASS_FQ_NAME_KIND) { |
| return CLASS_FQ_OR_PACKAGE_NAME_KIND; |
| } |
| return parentKind; |
| } |
| if (i == JavaElementType.CLASS || i == JavaElementType.PARAMETER_LIST || i == TokenType.ERROR_ELEMENT) { |
| return CLASS_OR_PACKAGE_NAME_KIND; |
| } |
| if (i == JavaElementType.IMPORT_STATIC_REFERENCE) { |
| return CLASS_FQ_OR_PACKAGE_NAME_KIND; |
| } |
| if (i == JavaDocElementType.DOC_TAG || |
| i == JavaDocElementType.DOC_INLINE_TAG || |
| i == JavaDocElementType.DOC_REFERENCE_HOLDER || |
| i == JavaDocElementType.DOC_TYPE_HOLDER) { |
| PsiDocComment docComment = PsiTreeUtil.getParentOfType(this, PsiDocComment.class); |
| if (JavaDocUtil.isInsidePackageInfo(docComment)) { |
| return CLASS_FQ_OR_PACKAGE_NAME_KIND; |
| } |
| |
| return CLASS_OR_PACKAGE_NAME_KIND; |
| } |
| if (isCodeFragmentType(i)) { |
| PsiJavaCodeReferenceCodeFragment fragment = (PsiJavaCodeReferenceCodeFragment)treeParent.getPsi(); |
| return fragment.isClassesAccepted() ? CLASS_FQ_OR_PACKAGE_NAME_KIND : PACKAGE_NAME_KIND; |
| } |
| |
| diagnoseUnknownParent(); |
| return CLASS_NAME_KIND; |
| } |
| |
| private void diagnoseUnknownParent() { |
| CompositeElement parent = getTreeParent(); |
| IElementType i = parent.getElementType(); |
| String message = "Unknown parent for java code reference: '" + parent + "'; Type: " + i + ";\n"; |
| while (parent != null && parent.getPsi() instanceof PsiExpression) { |
| parent = parent.getTreeParent(); |
| message += " Parent: '" + parent+"'; \n"; |
| } |
| if (parent != null) { |
| message += DebugUtil.treeToString(parent, false); |
| } |
| LOG.error(message); |
| } |
| |
| private static boolean isCodeFragmentType(IElementType type) { |
| return type == TokenType.CODE_FRAGMENT || type instanceof ICodeFragmentElementType; |
| } |
| |
| @Override |
| public void deleteChildInternal(@NotNull ASTNode child) { |
| if (getChildRole(child) == ChildRole.QUALIFIER) { |
| ASTNode dot = findChildByType(JavaTokenType.DOT, child); |
| assert dot != null : this; |
| deleteChildRange(child.getPsi(), dot.getPsi()); |
| |
| List<PsiAnnotation> annotations = PsiTreeUtil.getChildrenOfTypeAsList(this, PsiAnnotation.class); |
| setAnnotations(annotations); |
| |
| return; |
| } |
| |
| super.deleteChildInternal(child); |
| } |
| |
| @Override |
| public final ASTNode findChildByRole(final int role) { |
| LOG.assertTrue(ChildRole.isUnique(role)); |
| |
| switch (role) { |
| case ChildRole.REFERENCE_NAME: |
| return TreeUtil.findChildBackward(this, JavaTokenType.IDENTIFIER); |
| |
| case ChildRole.REFERENCE_PARAMETER_LIST: { |
| TreeElement lastChild = getLastChildNode(); |
| return lastChild.getElementType() == JavaElementType.REFERENCE_PARAMETER_LIST ? lastChild : null; |
| } |
| |
| case ChildRole.QUALIFIER: |
| return findChildByType(JavaElementType.JAVA_CODE_REFERENCE); |
| |
| case ChildRole.DOT: |
| return findChildByType(JavaTokenType.DOT); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public final int getChildRole(final ASTNode child) { |
| LOG.assertTrue(child.getTreeParent() == this); |
| final IElementType i = child.getElementType(); |
| if (i == JavaElementType.REFERENCE_PARAMETER_LIST) { |
| return ChildRole.REFERENCE_PARAMETER_LIST; |
| } |
| if (i == JavaElementType.JAVA_CODE_REFERENCE) { |
| return ChildRole.QUALIFIER; |
| } |
| if (i == JavaTokenType.DOT) { |
| return ChildRole.DOT; |
| } |
| if (i == JavaTokenType.IDENTIFIER) { |
| return ChildRole.REFERENCE_NAME; |
| } |
| return ChildRoleBase.NONE; |
| } |
| |
| @Override |
| @NotNull |
| public String getCanonicalText() { |
| return getCanonicalText(false, null, getContainingFile()); |
| } |
| |
| @NotNull |
| public String getCanonicalText(boolean annotated, @Nullable PsiAnnotation[] annotations, @NotNull PsiFile containingFile) { |
| switch (getKind(containingFile)) { |
| case CLASS_NAME_KIND: |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| case CLASS_IN_QUALIFIED_NEW_KIND: |
| JavaResolveResult[] results = multiResolve(false, containingFile, containingFile.getProject()); |
| final PsiElement target = results.length == 1 ? results[0].getElement() : null; |
| if (target instanceof PsiClass) { |
| PsiClass aClass = (PsiClass)target; |
| StringBuilder buffer = new StringBuilder(); |
| |
| PsiElement qualifier = getQualifier(); |
| String prefix = null; |
| if (qualifier instanceof PsiJavaCodeReferenceElementImpl) { |
| prefix = ((PsiJavaCodeReferenceElementImpl)qualifier).getCanonicalText(annotated, null, containingFile); |
| } |
| else { |
| String fqn = aClass.getQualifiedName(); |
| if (fqn != null) { |
| prefix = StringUtil.getPackageName(fqn); |
| } |
| } |
| |
| if (!StringUtil.isEmpty(prefix)) { |
| buffer.append(prefix); |
| buffer.append('.'); |
| } |
| |
| if (annotated) { |
| List<PsiAnnotation> list = annotations != null ? Arrays.asList(annotations) : getAnnotations(); |
| PsiNameHelper.appendAnnotations(buffer, list, true); |
| } |
| |
| buffer.append(aClass.getName()); |
| |
| PsiNameHelper.appendTypeArgs(buffer, getTypeParameters(), true, annotated); |
| |
| return buffer.toString(); |
| } |
| else if (target instanceof PsiPackage) { |
| return ((PsiPackage)target).getQualifiedName(); |
| } |
| else { |
| LOG.assertTrue(target == null, target); |
| return getNormalizedText(); |
| } |
| |
| case PACKAGE_NAME_KIND: |
| case CLASS_FQ_NAME_KIND: |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| return getNormalizedText(); |
| |
| default: |
| LOG.assertTrue(false); |
| return null; |
| } |
| } |
| |
| @Override |
| public PsiReference getReference() { |
| return this; |
| } |
| |
| @Override |
| public final PsiElement resolve() { |
| return advancedResolve(false).getElement(); |
| } |
| |
| private static final class OurGenericsResolver implements ResolveCache.PolyVariantContextResolver<PsiJavaReference> { |
| private static final OurGenericsResolver INSTANCE = new OurGenericsResolver(); |
| |
| @NotNull |
| @Override |
| public ResolveResult[] resolve(@NotNull PsiJavaReference ref, @NotNull PsiFile containingFile, boolean incompleteCode) { |
| PsiJavaCodeReferenceElementImpl referenceElement = (PsiJavaCodeReferenceElementImpl)ref; |
| int kind = referenceElement.getKind(containingFile); |
| JavaResolveResult[] result = referenceElement.resolve(kind, containingFile); |
| |
| if (incompleteCode && result.length == 0 && kind != CLASS_FQ_NAME_KIND && kind != CLASS_FQ_OR_PACKAGE_NAME_KIND) { |
| VariableResolverProcessor processor = new VariableResolverProcessor(referenceElement, containingFile); |
| PsiScopesUtil.resolveAndWalk(processor, referenceElement, null, true); |
| result = processor.getResult(); |
| if (result.length == 0 && kind == CLASS_NAME_KIND) { |
| result = referenceElement.resolve(PACKAGE_NAME_KIND, containingFile); |
| } |
| } |
| |
| JavaResolveUtil.substituteResults(referenceElement, result); |
| |
| return result; |
| } |
| } |
| |
| @Override |
| @NotNull |
| public JavaResolveResult advancedResolve(final boolean incompleteCode) { |
| final JavaResolveResult[] results = multiResolve(incompleteCode); |
| return results.length == 1 ? results[0] : JavaResolveResult.EMPTY; |
| } |
| |
| @Override |
| @NotNull |
| public JavaResolveResult[] multiResolve(final 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 containingFile = SharedImplUtil.getContainingFile(fileElement); |
| boolean valid = containingFile != null && containingFile.isValid(); |
| if (!valid) { |
| PsiUtilCore.ensureValid(this); |
| LOG.error("invalid!"); |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| Project project = manager.getProject(); |
| |
| return multiResolve(incompleteCode, containingFile, project); |
| } |
| |
| @NotNull |
| private JavaResolveResult[] multiResolve(boolean incompleteCode, @NotNull PsiFile containingFile, @NotNull Project project) { |
| final ResolveCache resolveCache = ResolveCache.getInstance(project); |
| final ResolveResult[] results = resolveCache.resolveWithCaching(this, OurGenericsResolver.INSTANCE, true, incompleteCode, containingFile); |
| return results.length == 0 ? JavaResolveResult.EMPTY_ARRAY : (JavaResolveResult[])results; |
| } |
| |
| private PsiSubstitutor updateSubstitutor(PsiSubstitutor subst, final PsiClass psiClass) { |
| final PsiType[] parameters = getTypeParameters(); |
| if (psiClass != null) { |
| subst = subst.putAll(psiClass, parameters); |
| } |
| return subst; |
| } |
| |
| @NotNull |
| private JavaResolveResult[] resolve(final int kind, @NotNull PsiFile containingFile) { |
| switch (kind) { |
| case CLASS_FQ_NAME_KIND: { |
| // TODO: support type parameters in FQ names |
| String text = getNormalizedText(); |
| if (StringUtil.isEmptyOrSpaces(text)) return JavaResolveResult.EMPTY_ARRAY; |
| |
| PsiClass aClass = JavaPsiFacade.getInstance(containingFile.getProject()).findClass(text, getResolveScope()); |
| if (aClass == null) return JavaResolveResult.EMPTY_ARRAY; |
| |
| if (!isQualified() && text.equals(aClass.getQualifiedName())) { |
| if (containingFile instanceof PsiJavaFile && !((PsiJavaFile)containingFile).getPackageName().isEmpty()) { |
| // classes in default (unnamed) package cannot be referenced from other packages |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| } |
| |
| return new JavaResolveResult[]{new CandidateInfo(aClass, updateSubstitutor(PsiSubstitutor.EMPTY, aClass), this, false)}; |
| } |
| case CLASS_IN_QUALIFIED_NEW_KIND: { |
| PsiElement parent = getParent(); |
| if (parent instanceof JavaDummyHolder) { |
| parent = parent.getContext(); |
| } |
| |
| if (parent instanceof PsiAnonymousClass) { |
| parent = parent.getParent(); |
| } |
| final PsiExpression qualifier; |
| if (parent instanceof PsiNewExpression) { |
| qualifier = ((PsiNewExpression)parent).getQualifier(); |
| LOG.assertTrue(qualifier != null); |
| } |
| else if (parent instanceof PsiJavaCodeReferenceElement) { |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| else { |
| LOG.error("Invalid java reference!"); |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| |
| final PsiType qualifierType = qualifier.getType(); |
| if (qualifierType == null) return JavaResolveResult.EMPTY_ARRAY; |
| if (!(qualifierType instanceof PsiClassType)) return JavaResolveResult.EMPTY_ARRAY; |
| final JavaResolveResult result = PsiUtil.resolveGenericsClassInType(qualifierType); |
| final PsiElement resultElement = result.getElement(); |
| if (resultElement == null) return JavaResolveResult.EMPTY_ARRAY; |
| final PsiElement classNameElement = getReferenceNameElement(); |
| if (!(classNameElement instanceof PsiIdentifier)) return JavaResolveResult.EMPTY_ARRAY; |
| final String className = classNameElement.getText(); |
| |
| final ClassResolverProcessor processor = new ClassResolverProcessor(className, this, containingFile); |
| resultElement.processDeclarations(processor, ResolveState.initial().put(PsiSubstitutor.KEY, result.getSubstitutor()), this, this); |
| return processor.getResult(); |
| } |
| case CLASS_NAME_KIND: { |
| final PsiElement classNameElement = getReferenceNameElement(); |
| if (!(classNameElement instanceof PsiIdentifier)) return JavaResolveResult.EMPTY_ARRAY; |
| final String className = classNameElement.getText(); |
| final ClassResolverProcessor processor = new ClassResolverProcessor(className, this, containingFile); |
| PsiScopesUtil.resolveAndWalk(processor, this, null); |
| return processor.getResult(); |
| } |
| case PACKAGE_NAME_KIND: { |
| String packageName = getNormalizedText(); |
| Project project = getManager().getProject(); |
| PsiPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(packageName); |
| if (aPackage == null || !aPackage.isValid()) { |
| return JavaPsiFacade.getInstance(project).isPartOfPackagePrefix(packageName) ? |
| CandidateInfo.RESOLVE_RESULT_FOR_PACKAGE_PREFIX_PACKAGE : JavaResolveResult.EMPTY_ARRAY; |
| } |
| return new JavaResolveResult[]{new CandidateInfo(aPackage, PsiSubstitutor.EMPTY)}; |
| } |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| case CLASS_OR_PACKAGE_NAME_KIND: { |
| int classKind = kind == CLASS_OR_PACKAGE_NAME_KIND ? CLASS_NAME_KIND : CLASS_FQ_NAME_KIND; |
| JavaResolveResult[] result = resolve(classKind, containingFile); |
| |
| if (result.length == 1 && !result[0].isAccessible()) { |
| JavaResolveResult[] packageResult = resolve(PACKAGE_NAME_KIND, containingFile); |
| if (packageResult.length != 0) { |
| result = packageResult; |
| } |
| } |
| else if (result.length == 0) { |
| result = resolve(PACKAGE_NAME_KIND, containingFile); |
| } |
| |
| return result; |
| } |
| } |
| |
| LOG.error(this); |
| return JavaResolveResult.EMPTY_ARRAY; |
| } |
| |
| @Override |
| public final PsiElement handleElementRename(final String newElementName) throws IncorrectOperationException { |
| final PsiElement oldIdentifier = getReferenceNameElement(); |
| if (oldIdentifier == null) { |
| throw new IncorrectOperationException(); |
| } |
| final PsiElement identifier = JavaPsiFacade.getInstance(getProject()).getElementFactory().createIdentifier(newElementName); |
| oldIdentifier.replace(identifier); |
| return this; |
| } |
| |
| @Override |
| public PsiElement bindToElement(@NotNull final PsiElement element) throws IncorrectOperationException { |
| PsiFile containingFile = getContainingFile(); |
| CheckUtil.checkWritable(containingFile); |
| if (isReferenceTo(element)) return this; |
| |
| switch (getKind(containingFile)) { |
| case CLASS_NAME_KIND: |
| case CLASS_FQ_NAME_KIND: |
| if (!(element instanceof PsiClass)) { |
| throw cannotBindError(element); |
| } |
| return bindToClass((PsiClass)element, containingFile); |
| |
| case PACKAGE_NAME_KIND: |
| if (!(element instanceof PsiPackage)) { |
| throw cannotBindError(element); |
| } |
| return bindToPackage((PsiPackage)element); |
| |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| if (element instanceof PsiClass) { |
| return bindToClass((PsiClass)element, containingFile); |
| } |
| else if (element instanceof PsiPackage) { |
| return bindToPackage((PsiPackage)element); |
| } |
| else { |
| throw cannotBindError(element); |
| } |
| case CLASS_IN_QUALIFIED_NEW_KIND: |
| if (element instanceof PsiClass) { |
| final PsiClass aClass = (PsiClass)element; |
| final String name = aClass.getName(); |
| if (name == null) { |
| throw new IncorrectOperationException(aClass.toString()); |
| } |
| final PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(containingFile.getProject()).getParserFacade(); |
| final PsiJavaCodeReferenceElement ref = parserFacade.createReferenceFromText(name, getParent()); |
| getTreeParent().replaceChildInternal(this, (TreeElement)ref.getNode()); |
| return ref; |
| } |
| else { |
| throw cannotBindError(element); |
| } |
| |
| default: |
| LOG.assertTrue(false); |
| return null; |
| } |
| } |
| |
| private static IncorrectOperationException cannotBindError(PsiElement element) { |
| return new IncorrectOperationException("Cannot bind to " + element); |
| } |
| |
| private PsiElement bindToClass(@NotNull PsiClass aClass, @NotNull PsiFile containingFile) throws IncorrectOperationException { |
| String qName = aClass.getQualifiedName(); |
| Project project = containingFile.getProject(); |
| boolean preserveQualification = JavaCodeStyleSettingsFacade.getInstance(project).useFQClassNames() && isFullyQualified(containingFile); |
| JavaPsiFacade facade = JavaPsiFacade.getInstance(project); |
| if (qName == null) { |
| qName = aClass.getName(); |
| PsiClass psiClass = facade.getResolveHelper().resolveReferencedClass(qName, this); |
| if (!getManager().areElementsEquivalent(psiClass, aClass)) { |
| throw cannotBindError(aClass); |
| } |
| } |
| else if (facade.findClass(qName, getResolveScope()) == null && !preserveQualification) { |
| return this; |
| } |
| |
| List<PsiAnnotation> annotations = getAnnotations(); |
| String text = qName; |
| PsiReferenceParameterList parameterList = getParameterList(); |
| if (parameterList != null) { |
| text += parameterList.getText(); |
| } |
| |
| PsiJavaCodeReferenceElement ref; |
| try { |
| ref = facade.getParserFacade().createReferenceFromText(text, getParent()); |
| } |
| catch (IncorrectOperationException e) { |
| throw new IncorrectOperationException(e.getMessage() + " [qname=" + qName + " class=" + aClass + ";" + aClass.getClass().getName() + "]"); |
| } |
| |
| ((PsiJavaCodeReferenceElementImpl)ref).setAnnotations(annotations); |
| getTreeParent().replaceChildInternal(this, (TreeElement)ref.getNode()); |
| |
| if (!preserveQualification) { |
| JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); |
| ref = (PsiJavaCodeReferenceElement)codeStyleManager.shortenClassReferences(ref, JavaCodeStyleManager.UNCOMPLETE_CODE); |
| } |
| |
| return ref; |
| } |
| |
| private List<PsiAnnotation> getAnnotations() { |
| List<PsiAnnotation> annotations = PsiTreeUtil.getChildrenOfTypeAsList(this, PsiAnnotation.class); |
| |
| if (!isQualified()) { |
| PsiModifierList modifierList = PsiImplUtil.findNeighbourModifierList(this); |
| if (modifierList != null) { |
| List<PsiAnnotation> typeAnnotations = PsiImplUtil.getTypeUseAnnotations(modifierList); |
| if (typeAnnotations != null && !typeAnnotations.isEmpty()) { |
| annotations.addAll(typeAnnotations); |
| } |
| } |
| } |
| |
| return annotations; |
| } |
| |
| private void setAnnotations(List<PsiAnnotation> annotations) { |
| if (annotations.isEmpty()) return; |
| |
| PsiElement newParent = this; |
| PsiElement anchor = SourceTreeToPsiMap.treeElementToPsi(findChildByType(JavaTokenType.DOT)); |
| if (anchor == null) { |
| PsiModifierList modifierList = PsiImplUtil.findNeighbourModifierList(this); |
| if (modifierList != null) { |
| newParent = modifierList; |
| } |
| } |
| |
| for (PsiAnnotation annotation : annotations) { |
| if (annotation.getParent() != newParent) { |
| if (anchor != null) { |
| newParent.addAfter(annotation, anchor); |
| } |
| else { |
| newParent.add(annotation); |
| } |
| annotation.delete(); |
| } |
| } |
| } |
| |
| private boolean isFullyQualified(@NotNull PsiFile containingFile) { |
| switch (getKind(containingFile)) { |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| if (resolve() instanceof PsiPackage) return true; |
| //noinspection fallthrough |
| case CLASS_NAME_KIND: |
| break; |
| |
| case PACKAGE_NAME_KIND: |
| case CLASS_FQ_NAME_KIND: |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| return true; |
| |
| default: |
| LOG.assertTrue(false); |
| return true; |
| } |
| |
| final ASTNode qualifier = findChildByRole(ChildRole.QUALIFIER); |
| if (qualifier == null) return false; |
| |
| LOG.assertTrue(qualifier.getElementType() == JavaElementType.JAVA_CODE_REFERENCE); |
| final PsiElement refElement = SourceTreeToPsiMap.<PsiJavaCodeReferenceElement>treeToPsiNotNull(qualifier).resolve(); |
| if (refElement instanceof PsiPackage) return true; |
| |
| return SourceTreeToPsiMap.<PsiJavaCodeReferenceElementImpl>treeToPsiNotNull(qualifier).isFullyQualified(containingFile); |
| } |
| |
| private PsiElement bindToPackage(@NotNull PsiPackage aPackage) throws IncorrectOperationException { |
| final String qName = aPackage.getQualifiedName(); |
| if (qName.isEmpty()) { |
| throw new IncorrectOperationException("Cannot bind to default package: "+aPackage); |
| } |
| final PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(getProject()).getParserFacade(); |
| final PsiJavaCodeReferenceElement ref = parserFacade.createReferenceFromText(qName, getParent()); |
| getTreeParent().replaceChildInternal(this, (TreeElement)ref.getNode()); |
| return ref; |
| } |
| |
| @Override |
| public boolean isReferenceTo(final PsiElement element) { |
| PsiFile containingFile = getContainingFile(); |
| return isReferenceTo(element, containingFile); |
| } |
| |
| private boolean isReferenceTo(PsiElement element, @NotNull PsiFile containingFile) { |
| switch (getKind(containingFile)) { |
| case CLASS_NAME_KIND: |
| case CLASS_IN_QUALIFIED_NEW_KIND: |
| if (!(element instanceof PsiClass)) return false; |
| break; |
| |
| case CLASS_FQ_NAME_KIND: { |
| if (!(element instanceof PsiClass)) return false; |
| final String qName = ((PsiClass)element).getQualifiedName(); |
| return qName != null && qName.equals(getCanonicalText(false, null, containingFile)); |
| } |
| |
| case PACKAGE_NAME_KIND: { |
| if (!(element instanceof PsiPackage)) return false; |
| final String qName = ((PsiPackage)element).getQualifiedName(); |
| return qName.equals(getCanonicalText(false, null, containingFile)); |
| } |
| |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| // if (lastChild.type != IDENTIFIER) return false; |
| if (element instanceof PsiPackage) { |
| final String qName = ((PsiPackage)element).getQualifiedName(); |
| return qName.equals(getCanonicalText(false, null, containingFile)); |
| } |
| if (element instanceof PsiClass) { |
| final PsiIdentifier nameIdentifier = ((PsiClass)element).getNameIdentifier(); |
| if (nameIdentifier == null) return false; |
| PsiElement nameElement = getReferenceNameElement(); |
| return nameElement != null && nameElement.textMatches(nameIdentifier) && |
| containingFile.getManager().areElementsEquivalent(resolve(), element); |
| } |
| return false; |
| |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| if (element instanceof PsiClass) { |
| final String qName = ((PsiClass)element).getQualifiedName(); |
| return qName != null && qName.equals(getCanonicalText(false, null, containingFile)); |
| } |
| if (element instanceof PsiPackage) { |
| final String qName = ((PsiPackage)element).getQualifiedName(); |
| return qName.equals(getCanonicalText(false, null, containingFile)); |
| } |
| return false; |
| default: |
| LOG.assertTrue(false); |
| return true; |
| } |
| |
| final ASTNode referenceNameElement = getReferenceNameNode(); |
| if (referenceNameElement == null || referenceNameElement.getElementType() != JavaTokenType.IDENTIFIER) return false; |
| final String name = ((PsiClass)element).getName(); |
| return name != null && referenceNameElement.getText().equals(name) && containingFile.getManager().areElementsEquivalent(resolve(), element); |
| } |
| |
| private String getNormalizedText() { |
| String whiteSpaceAndComments = myCachedNormalizedText; |
| if (whiteSpaceAndComments == null) { |
| myCachedNormalizedText = whiteSpaceAndComments = JavaSourceUtil.getReferenceText(this); |
| } |
| return whiteSpaceAndComments; |
| } |
| |
| @Override |
| public String getClassNameText() { |
| String cachedQName = myCachedQName; |
| if (cachedQName == null) { |
| myCachedQName = cachedQName = PsiNameHelper.getQualifiedClassName(getNormalizedText(), false); |
| } |
| return cachedQName; |
| } |
| |
| @Override |
| public void fullyQualify(@NotNull final PsiClass targetClass) { |
| final int kind = getKind(getContainingFile()); |
| if (kind != CLASS_NAME_KIND && kind != CLASS_OR_PACKAGE_NAME_KIND && kind != CLASS_IN_QUALIFIED_NEW_KIND) { |
| LOG.error("Wrong kind " + kind); |
| return; |
| } |
| JavaSourceUtil.fullyQualifyReference(this, targetClass); |
| } |
| |
| @Override |
| public boolean isQualified() { |
| return getQualifier() != null; |
| } |
| |
| @Override |
| public PsiElement getQualifier() { |
| return SourceTreeToPsiMap.treeElementToPsi(findChildByRole(ChildRole.QUALIFIER)); |
| } |
| |
| @Override |
| public void clearCaches() { |
| super.clearCaches(); |
| myCachedQName = null; |
| myCachedNormalizedText = null; |
| } |
| |
| @Override |
| @NotNull |
| public Object[] getVariants() { |
| final ElementFilter filter; |
| switch (getKind(getContainingFile())) { |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| filter = new OrFilter(); |
| ((OrFilter)filter).addFilter(ElementClassFilter.CLASS); |
| ((OrFilter)filter).addFilter(ElementClassFilter.PACKAGE_FILTER); |
| break; |
| case CLASS_NAME_KIND: |
| filter = ElementClassFilter.CLASS; |
| break; |
| case PACKAGE_NAME_KIND: |
| filter = ElementClassFilter.PACKAGE_FILTER; |
| break; |
| case CLASS_FQ_NAME_KIND: |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| filter = new OrFilter(); |
| ((OrFilter)filter).addFilter(ElementClassFilter.PACKAGE_FILTER); |
| if (isQualified()) { |
| ((OrFilter)filter).addFilter(ElementClassFilter.CLASS); |
| } |
| break; |
| case CLASS_IN_QUALIFIED_NEW_KIND: |
| filter = ElementClassFilter.CLASS; |
| break; |
| default: |
| throw new RuntimeException("Unknown reference type"); |
| } |
| |
| return PsiImplUtil.getReferenceVariantsByFilter(this, filter); |
| } |
| |
| @Override |
| public boolean isSoft() { |
| return false; |
| } |
| |
| @Override |
| public void processVariants(@NotNull final PsiScopeProcessor processor) { |
| final OrFilter filter = new OrFilter(); |
| if (isInCode() && !(getParent() instanceof PsiImportStatement) && !(getParent() instanceof PsiReferenceList)) { |
| filter.addFilter(new AndFilter(ElementClassFilter.METHOD, new NotFilter(new ConstructorFilter()))); |
| filter.addFilter(ElementClassFilter.VARIABLE); |
| } |
| switch (getKind(getContainingFile())) { |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| filter.addFilter(ElementClassFilter.CLASS); |
| filter.addFilter(ElementClassFilter.PACKAGE_FILTER); |
| break; |
| case CLASS_NAME_KIND: |
| filter.addFilter(ElementClassFilter.CLASS); |
| if (isQualified()) { |
| filter.addFilter(ElementClassFilter.PACKAGE_FILTER); |
| } |
| break; |
| case PACKAGE_NAME_KIND: |
| filter.addFilter(ElementClassFilter.PACKAGE_FILTER); |
| break; |
| case CLASS_FQ_NAME_KIND: |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| filter.addFilter(ElementClassFilter.PACKAGE_FILTER); |
| if (isQualified()) { |
| filter.addFilter(ElementClassFilter.CLASS); |
| } |
| break; |
| case CLASS_IN_QUALIFIED_NEW_KIND: |
| final PsiElement parent = getParent(); |
| if (parent instanceof PsiNewExpression) { |
| PsiExpression qualifier = ((PsiNewExpression)parent).getQualifier(); |
| assert qualifier != null : parent; |
| PsiType type = qualifier.getType(); |
| PsiClass aClass = PsiUtil.resolveClassInType(type); |
| if (aClass != null) { |
| aClass.processDeclarations( |
| new FilterScopeProcessor(new AndFilter(ElementClassFilter.CLASS, new ModifierFilter(PsiModifier.STATIC, false)), |
| processor), ResolveState.initial(), null, this); |
| } |
| } |
| return; |
| default: |
| throw new RuntimeException("Unknown reference type"); |
| } |
| final FilterScopeProcessor proc = new FilterScopeProcessor(filter, processor); |
| PsiScopesUtil.resolveAndWalk(proc, this, null, true); |
| } |
| |
| private boolean isInCode() { |
| if (isCodeFragmentType(getTreeParent().getElementType()) || getParent() instanceof PsiAnnotation) { |
| return false; |
| } |
| |
| PsiElement superParent = getParent(); |
| while (superParent != null) { |
| if (superParent instanceof PsiCodeBlock || superParent instanceof PsiLocalVariable) { |
| return true; |
| } |
| if (superParent instanceof PsiClass || superParent instanceof PsiCatchSection) { |
| return false; |
| } |
| superParent = superParent.getParent(); |
| } |
| return false; |
| } |
| |
| @Override |
| public PsiElement getReferenceNameElement() { |
| return SourceTreeToPsiMap.treeElementToPsi(getReferenceNameNode()); |
| } |
| |
| @Nullable |
| private ASTNode getReferenceNameNode() { |
| return findChildByRole(ChildRole.REFERENCE_NAME); |
| } |
| |
| |
| @Override |
| public PsiReferenceParameterList getParameterList() { |
| return (PsiReferenceParameterList)findChildByRoleAsPsiElement(ChildRole.REFERENCE_PARAMETER_LIST); |
| } |
| |
| @Override |
| public String getQualifiedName() { |
| switch (getKind(getContainingFile())) { |
| case CLASS_NAME_KIND: |
| case CLASS_OR_PACKAGE_NAME_KIND: |
| case CLASS_IN_QUALIFIED_NEW_KIND: |
| final PsiElement target = resolve(); |
| if (target instanceof PsiClass) { |
| final PsiClass aClass = (PsiClass)target; |
| String name = aClass.getQualifiedName(); |
| if (name == null) { |
| name = aClass.getName(); //? |
| } |
| return name; |
| } |
| else if (target instanceof PsiPackage) { |
| return ((PsiPackage)target).getQualifiedName(); |
| } |
| else { |
| LOG.assertTrue(target == null); |
| return getClassNameText(); |
| } |
| |
| case PACKAGE_NAME_KIND: |
| case CLASS_FQ_NAME_KIND: |
| case CLASS_FQ_OR_PACKAGE_NAME_KIND: |
| return getNormalizedText(); // there cannot be any <...> |
| |
| default: |
| LOG.assertTrue(false); |
| return null; |
| } |
| |
| } |
| |
| @Override |
| public String getReferenceName() { |
| final ASTNode childByRole = getReferenceNameNode(); |
| if (childByRole == null) return null; |
| return childByRole.getText(); |
| } |
| |
| @Override |
| public final TextRange getRangeInElement() { |
| final TreeElement nameChild = (TreeElement)getReferenceNameNode(); |
| if (nameChild == null) return new TextRange(0, getTextLength()); |
| final int startOffset = nameChild.getStartOffsetInParent(); |
| return new TextRange(startOffset, startOffset + nameChild.getTextLength()); |
| } |
| |
| @Override |
| @NotNull |
| public PsiType[] getTypeParameters() { |
| final PsiReferenceParameterList parameterList = getParameterList(); |
| if (parameterList == null) return PsiType.EMPTY_ARRAY; |
| return parameterList.getTypeArguments(); |
| } |
| |
| @Override |
| public final PsiElement getElement() { |
| return this; |
| } |
| |
| @Override |
| public final void accept(@NotNull final PsiElementVisitor visitor) { |
| if (visitor instanceof JavaElementVisitor) { |
| ((JavaElementVisitor)visitor).visitReferenceElement(this); |
| } |
| else { |
| visitor.visitElement(this); |
| } |
| } |
| |
| public final String toString() { |
| return "PsiJavaCodeReferenceElement:" + getText(); |
| } |
| } |