blob: 3d1e1c01c586f84e73f81e1e3aa094ab409bd350 [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.codeStyle;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.ReferenceAdjuster;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
import com.intellij.psi.impl.source.SourceJavaCodeReference;
import com.intellij.psi.impl.source.jsp.jspJava.JspClass;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.jsp.JspFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class JavaReferenceAdjuster implements ReferenceAdjuster {
@Override
public ASTNode process(@NotNull ASTNode element, boolean addImports, boolean incompleteCode, boolean useFqInJavadoc, boolean useFqInCode) {
IElementType elementType = element.getElementType();
if ((elementType == JavaElementType.JAVA_CODE_REFERENCE || elementType == JavaElementType.REFERENCE_EXPRESSION) && !isAnnotated(element)) {
IElementType parentType = element.getTreeParent().getElementType();
if (elementType == JavaElementType.JAVA_CODE_REFERENCE || incompleteCode ||
parentType == JavaElementType.REFERENCE_EXPRESSION || parentType == JavaElementType.METHOD_REF_EXPRESSION) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)element.getPsi();
PsiReferenceParameterList parameterList = ref.getParameterList();
if (parameterList != null) {
PsiTypeElement[] typeParameters = parameterList.getTypeParameterElements();
for (PsiTypeElement typeParameter : typeParameters) {
process(typeParameter.getNode(), addImports, incompleteCode, useFqInJavadoc, useFqInCode);
}
}
boolean rightKind = true;
if (elementType == JavaElementType.JAVA_CODE_REFERENCE) {
PsiJavaCodeReferenceElementImpl impl = (PsiJavaCodeReferenceElementImpl)element;
int kind = impl.getKind(impl.getContainingFile());
rightKind = kind == PsiJavaCodeReferenceElementImpl.CLASS_NAME_KIND || kind == PsiJavaCodeReferenceElementImpl.CLASS_OR_PACKAGE_NAME_KIND;
}
if (rightKind) {
// annotations may jump out of reference (see PsiJavaCodeReferenceImpl#setAnnotations()) so they should be processed first
List<PsiAnnotation> annotations = PsiTreeUtil.getChildrenOfTypeAsList(ref, PsiAnnotation.class);
for (PsiAnnotation annotation : annotations) {
process(annotation.getNode(), addImports, incompleteCode, useFqInJavadoc, useFqInCode);
}
boolean isInsideDocComment = TreeUtil.findParent(element, JavaDocElementType.DOC_COMMENT) != null;
boolean isShort = !ref.isQualified();
if (isInsideDocComment ? !useFqInJavadoc : !useFqInCode) {
if (isShort) return element; // short name already, no need to change
}
PsiElement refElement;
if (!incompleteCode) {
refElement = ref.resolve();
}
else {
PsiResolveHelper helper = JavaPsiFacade.getInstance(ref.getManager().getProject()).getResolveHelper();
final SourceJavaCodeReference reference = (SourceJavaCodeReference)element;
refElement = helper.resolveReferencedClass(reference.getClassNameText(), ref);
}
if (refElement instanceof PsiClass) {
PsiClass psiClass = (PsiClass)refElement;
if (isInsideDocComment ? useFqInJavadoc : useFqInCode) {
String qName = psiClass.getQualifiedName();
if (qName == null) return element;
PsiFile file = ref.getContainingFile();
if (file instanceof PsiJavaFile) {
if (ImportHelper.isImplicitlyImported(qName, (PsiJavaFile)file)) {
if (isShort) return element;
return makeShortReference((CompositeElement)element, psiClass, addImports);
}
String thisPackageName = ((PsiJavaFile)file).getPackageName();
if (ImportHelper.hasPackage(qName, thisPackageName)) {
if (!isShort) {
return makeShortReference((CompositeElement)element, psiClass, addImports);
}
}
}
return replaceReferenceWithFQ(element, psiClass);
}
else {
int oldLength = element.getTextLength();
ASTNode treeElement = makeShortReference((CompositeElement)element, psiClass, addImports);
if (treeElement.getTextLength() == oldLength && psiClass.getContainingClass() != null) {
PsiElement qualifier = ref.getQualifier();
if (qualifier instanceof PsiJavaCodeReferenceElement && ((PsiJavaCodeReferenceElement)qualifier).resolve() instanceof PsiClass) {
process(qualifier.getNode(), addImports, incompleteCode, useFqInJavadoc, useFqInCode);
}
}
return treeElement;
}
}
}
}
}
for (ASTNode child = element.getFirstChildNode(); child != null; child = child.getTreeNext()) {
//noinspection AssignmentToForLoopParameter
child = process(child, addImports, incompleteCode, useFqInJavadoc, useFqInCode);
}
return element;
}
@Override
public ASTNode process(@NotNull ASTNode element, boolean addImports, boolean incompleteCode, Project project) {
final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
return process(element, addImports, incompleteCode, settings.USE_FQ_CLASS_NAMES_IN_JAVADOC, settings.USE_FQ_CLASS_NAMES);
}
private static boolean isAnnotated(ASTNode element) {
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)element.getPsi();
PsiElement qualifier = ref.getQualifier();
if (qualifier instanceof PsiJavaCodeReferenceElement) {
if (((PsiJavaCodeReferenceElement)qualifier).resolve() instanceof PsiPackage) {
return false;
}
if (PsiTreeUtil.getChildOfType(qualifier, PsiAnnotation.class) != null) {
return true;
}
}
PsiModifierList modifierList = PsiImplUtil.findNeighbourModifierList(ref);
if (modifierList != null) {
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
if (PsiImplUtil.findApplicableTarget(annotation, PsiAnnotation.TargetType.TYPE_USE) != null) {
return true;
}
}
}
return false;
}
@Override
public void processRange(@NotNull ASTNode element, int startOffset, int endOffset, boolean useFqInJavadoc, boolean useFqInCode) {
List<ASTNode> array = new ArrayList<ASTNode>();
addReferencesInRange(array, element, startOffset, endOffset);
for (ASTNode ref : array) {
if (ref.getPsi().isValid()) {
process(ref, true, true, useFqInJavadoc, useFqInCode);
}
}
}
@Override
public void processRange(@NotNull ASTNode element, int startOffset, int endOffset, Project project) {
final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
processRange(element, startOffset, endOffset, settings.USE_FQ_CLASS_NAMES_IN_JAVADOC, settings.USE_FQ_CLASS_NAMES);
}
private static void addReferencesInRange(List<ASTNode> array, ASTNode parent, int startOffset, int endOffset) {
if (parent.getElementType() == JavaElementType.JAVA_CODE_REFERENCE || parent.getElementType() == JavaElementType.REFERENCE_EXPRESSION) {
array.add(parent);
return;
}
if (parent.getPsi() instanceof PsiFile) {
JspFile jspFile = JspPsiUtil.getJspFile(parent.getPsi());
if (jspFile != null) {
JspClass jspClass = (JspClass)jspFile.getJavaClass();
addReferencesInRange(array, jspClass.getNode(), startOffset, endOffset);
return;
}
}
addReferencesInRangeForComposite(array, parent, startOffset, endOffset);
}
private static void addReferencesInRangeForComposite(List<ASTNode> array, ASTNode parent, int startOffset, int endOffset) {
int offset = 0;
for (ASTNode child = parent.getFirstChildNode(); child != null; child = child.getTreeNext()) {
int length = child.getTextLength();
if (startOffset <= offset + length && offset <= endOffset) {
IElementType type = child.getElementType();
if (type == JavaElementType.JAVA_CODE_REFERENCE || type == JavaElementType.REFERENCE_EXPRESSION) {
array.add(child);
}
else {
addReferencesInRangeForComposite(array, child, startOffset - offset, endOffset - offset);
}
}
offset += length;
}
}
@NotNull
private static ASTNode makeShortReference(@NotNull CompositeElement reference, @NotNull PsiClass refClass, boolean addImports) {
@NotNull final PsiJavaCodeReferenceElement psiReference = (PsiJavaCodeReferenceElement)reference.getPsi();
final PsiQualifiedReferenceElement reference1 = getClassReferenceToShorten(refClass, addImports, psiReference);
if (reference1 != null) replaceReferenceWithShort(reference1);
return reference;
}
@Nullable
public static PsiQualifiedReferenceElement getClassReferenceToShorten(@NotNull final PsiClass refClass,
final boolean addImports,
@NotNull final PsiQualifiedReferenceElement reference) {
PsiClass parentClass = refClass.getContainingClass();
if (parentClass != null) {
JavaPsiFacade facade = JavaPsiFacade.getInstance(parentClass.getProject());
final PsiResolveHelper resolveHelper = facade.getResolveHelper();
if (resolveHelper.isAccessible(refClass, reference, null) && isSafeToShortenReference(reference.getReferenceName(), reference, refClass)) {
return reference;
}
if (!CodeStyleSettingsManager.getSettings(reference.getProject()).INSERT_INNER_CLASS_IMPORTS) {
final PsiElement qualifier = reference.getQualifier();
if (qualifier instanceof PsiQualifiedReferenceElement) {
return getClassReferenceToShorten(parentClass, addImports, (PsiQualifiedReferenceElement)qualifier);
}
return null;
}
}
if (addImports && !((PsiImportHolder)reference.getContainingFile()).importClass(refClass)) return null;
if (!isSafeToShortenReference(reference, refClass)) return null;
return reference;
}
private static boolean isSafeToShortenReference(@NotNull PsiElement psiReference, @NotNull PsiClass refClass) {
return isSafeToShortenReference(refClass.getName(), psiReference, refClass);
}
private static boolean isSafeToShortenReference(final String referenceText, final PsiElement psiReference, final PsiClass refClass) {
final PsiManager manager = refClass.getManager();
final JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
final PsiResolveHelper helper = facade.getResolveHelper();
if (manager.areElementsEquivalent(refClass, helper.resolveReferencedClass(referenceText, psiReference))) {
PsiElement parent = psiReference.getParent();
if (parent instanceof PsiJavaCodeReferenceElement && parent.getParent() instanceof PsiNewExpression) return true;
return helper.resolveReferencedVariable(referenceText, psiReference) == null;
}
return false;
}
@NotNull
private static ASTNode replaceReferenceWithShort(PsiQualifiedReferenceElement reference) {
ASTNode node = reference.getNode();
assert node != null;
deQualifyImpl((CompositeElement)node);
return node;
}
private static void deQualifyImpl(@NotNull CompositeElement reference) {
ASTNode qualifier = reference.findChildByRole(ChildRole.QUALIFIER);
if (qualifier != null) {
ASTNode firstChildNode = qualifier.getFirstChildNode();
boolean markToReformatBefore = firstChildNode instanceof TreeElement && CodeEditUtil.isMarkedToReformatBefore((TreeElement)firstChildNode);
reference.deleteChildInternal(qualifier);
if (markToReformatBefore) {
firstChildNode = reference.getFirstChildNode();
if (firstChildNode != null) {
CodeEditUtil.markToReformatBefore(firstChildNode, true);
}
}
}
}
private static ASTNode replaceReferenceWithFQ(ASTNode reference, PsiClass refClass) {
((SourceJavaCodeReference)reference).fullyQualify(refClass);
return reference;
}
}