blob: 7d078351f9c881605062b99d14570b11b7ee213d [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 org.jetbrains.plugins.groovy.codeStyle;
import com.intellij.lang.ASTNode;
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.ReferenceAdjuster;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment;
import org.jetbrains.plugins.groovy.lang.psi.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrReferenceElementImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyCodeStyleSettingsFacade;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrBindingVariable;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
/**
* @author Max Medvedev
*/
public class GrReferenceAdjuster implements ReferenceAdjuster {
public GrReferenceAdjuster() {
@SuppressWarnings("UnusedDeclaration") int i = 0;
}
public static void shortenAllReferencesIn(@Nullable GroovyPsiElement newTypeElement) {
if (newTypeElement != null) {
newTypeElement.accept(new GroovyRecursiveElementVisitor() {
@Override
public void visitCodeReferenceElement(GrCodeReferenceElement refElement) {
super.visitCodeReferenceElement(refElement);
shortenReference(refElement);
}
});
}
}
@Override
public ASTNode process(@NotNull ASTNode element, boolean addImports, boolean incompleteCode, boolean useFqInJavadoc, boolean useFqInCode) {
final TextRange range = element.getTextRange();
process(element.getPsi(), range.getStartOffset(), range.getEndOffset(), addImports, incompleteCode, useFqInJavadoc, useFqInCode);
return element;
}
@Override
public ASTNode process(@NotNull ASTNode element, boolean addImports, boolean incompleteCode, Project project) {
GroovyCodeStyleSettingsFacade facade = GroovyCodeStyleSettingsFacade.getInstance(project);
return process(element, addImports, incompleteCode, facade.useFqClassNamesInJavadoc(), facade.useFqClassNames());
}
@Override
public void processRange(@NotNull ASTNode element, int startOffset, int endOffset, boolean useFqInJavadoc, boolean useFqInCode) {
process(element.getPsi(), startOffset, endOffset, true, true, useFqInJavadoc, useFqInCode);
}
@Override
public void processRange(@NotNull ASTNode element, int startOffset, int endOffset, Project project) {
GroovyCodeStyleSettingsFacade facade = GroovyCodeStyleSettingsFacade.getInstance(project);
processRange(element, startOffset, endOffset, facade.useFqClassNamesInJavadoc(), facade.useFqClassNames());
}
private static boolean process(@NotNull PsiElement element,
int start,
int end,
boolean addImports,
boolean incomplete,
boolean useFqInJavadoc,
boolean useFqInCode) {
boolean result = false;
if (element instanceof GrQualifiedReference<?> && ((GrQualifiedReference)element).resolve() instanceof PsiClass) {
result = shortenReferenceInner((GrQualifiedReference<?>)element, addImports, incomplete, useFqInJavadoc, useFqInCode);
}
else if (element instanceof GrReferenceExpression && PsiUtil.isSuperReference(((GrReferenceExpression)element).getQualifier())) {
result = shortenReferenceInner((GrReferenceExpression)element, addImports, incomplete, useFqInJavadoc, useFqInCode);
}
PsiElement child = element.getFirstChild();
while (child != null) {
final TextRange range = child.getTextRange();
if (start < range.getEndOffset() && range.getStartOffset() < end) {
result |= process(child, start, end, addImports, incomplete, useFqInJavadoc, useFqInCode);
}
child = child.getNextSibling();
}
return result;
}
public static <T extends PsiElement> boolean shortenReference(@NotNull GrQualifiedReference<T> ref) {
GroovyCodeStyleSettingsFacade facade = GroovyCodeStyleSettingsFacade.getInstance(ref.getProject());
boolean result = shortenReferenceInner(ref, true, false, facade.useFqClassNamesInJavadoc(), facade.useFqClassNames());
final TextRange range = ref.getTextRange();
result |= process(ref, range.getStartOffset(), range.getEndOffset(), true, false, facade.useFqClassNamesInJavadoc(), facade.useFqClassNames());
return result;
}
private static <Qualifier extends PsiElement> boolean shortenReferenceInner(@NotNull GrQualifiedReference<Qualifier> ref,
boolean addImports,
boolean incomplete,
boolean useFqInJavadoc,
boolean useFqInCode) {
final Qualifier qualifier = ref.getQualifier();
if (qualifier == null || PsiUtil.isSuperReference(qualifier) || cannotShortenInContext(ref)) {
return false;
}
if (ref instanceof GrReferenceExpression) {
final GrTypeArgumentList typeArgs = ((GrReferenceExpression)ref).getTypeArgumentList();
if (typeArgs != null && typeArgs.getTypeArgumentElements().length > 0) {
return false;
}
}
if (!shorteningIsMeaningfully(ref, useFqInJavadoc, useFqInCode)) return false;
final PsiElement resolved = resolveRef(ref, incomplete);
if (resolved == null) return false;
if (!checkCopyWithoutQualifier(ref, addImports, resolved)) return false;
ref.setQualifier(null);
return true;
}
private static <Qualifier extends PsiElement> boolean checkCopyWithoutQualifier(@NotNull GrQualifiedReference<Qualifier> ref,
boolean addImports,
@NotNull PsiElement resolved) {
final GrQualifiedReference<Qualifier> copy = getCopy(ref);
if (copy == null) return false;
copy.setQualifier(null);
final PsiElement resolvedCopy = copy.resolve();
if (ref.getManager().areElementsEquivalent(resolved, resolvedCopy)) {
return true;
}
else if (resolvedCopy != null && !(resolvedCopy instanceof GrBindingVariable) && !isFromDefaultPackage(resolvedCopy)) {
return false;
}
if (resolved instanceof PsiClass) {
final PsiClass clazz = (PsiClass)resolved;
final String qName = clazz.getQualifiedName();
if (qName != null && addImports && checkIsInnerClass(clazz, ref) && mayInsertImport(ref)) {
final GroovyFileBase file = (GroovyFileBase)ref.getContainingFile();
final GrImportStatement added = file.addImportForClass(clazz);
if (added != null) {
if (copy.isReferenceTo(resolved)) return true;
file.removeImport(added);
}
}
}
return false;
}
private static boolean isFromDefaultPackage(@Nullable PsiElement element) {
if (element instanceof PsiClass) {
String qname = ((PsiClass)element).getQualifiedName();
if (qname != null) {
String packageName = StringUtil.getPackageName(qname);
if (ArrayUtil.contains(packageName, GroovyFileBase.IMPLICITLY_IMPORTED_PACKAGES)) {
return true;
}
if (ArrayUtil.contains(qname, GroovyFileBase.IMPLICITLY_IMPORTED_CLASSES)) {
return true;
}
}
}
return false;
}
private static <Qualifier extends PsiElement> boolean checkIsInnerClass(@NotNull PsiClass resolved, GrQualifiedReference<Qualifier> ref) {
final PsiClass containingClass = resolved.getContainingClass();
return containingClass == null ||
PsiTreeUtil.isAncestor(containingClass, ref, true) ||
GroovyCodeStyleSettingsFacade.getInstance(containingClass.getProject()).insertInnerClassImports();
}
@Nullable
private static <Qualifier extends PsiElement> PsiElement resolveRef(@NotNull GrQualifiedReference<Qualifier> ref, boolean incomplete) {
if (!incomplete) return ref.resolve();
PsiResolveHelper helper = JavaPsiFacade.getInstance(ref.getProject()).getResolveHelper();
if (ref instanceof GrReferenceElement) {
final String classNameText = ((GrReferenceElement)ref).getClassNameText();
return helper.resolveReferencedClass(classNameText, ref);
}
return null;
}
@SuppressWarnings("unchecked")
@Nullable
private static <Qualifier extends PsiElement> GrQualifiedReference<Qualifier> getCopy(@NotNull GrQualifiedReference<Qualifier> ref) {
if (ref.getParent() instanceof GrMethodCall) {
final GrMethodCall copy = ((GrMethodCall)ref.getParent().copy());
return (GrQualifiedReference<Qualifier>)copy.getInvokedExpression();
}
return (GrQualifiedReference<Qualifier>)ref.copy();
}
private static <Qualifier extends PsiElement> boolean shorteningIsMeaningfully(@NotNull GrQualifiedReference<Qualifier> ref,
boolean useFqInJavadoc, boolean useFqInCode) {
if (ref instanceof GrReferenceElementImpl && ((GrReferenceElementImpl)ref).isFullyQualified()) {
final GrDocComment doc = PsiTreeUtil.getParentOfType(ref, GrDocComment.class);
if (doc != null) {
if (useFqInJavadoc) return false;
}
else {
if (useFqInCode) return false;
}
}
final Qualifier qualifier = ref.getQualifier();
if (qualifier instanceof GrCodeReferenceElement) {
return true;
}
if (qualifier instanceof GrExpression) {
if (qualifier instanceof GrReferenceExpression && PsiUtil.isThisReference(qualifier)) return true;
if (qualifier instanceof GrReferenceExpression &&
PsiImplUtil.seemsToBeQualifiedClassName((GrExpression)qualifier)) {
final PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
if (resolved instanceof PsiClass || resolved instanceof PsiPackage) return true;
}
}
return false;
}
private static <Qualifier extends PsiElement> boolean cannotShortenInContext(@NotNull GrQualifiedReference<Qualifier> ref) {
return PsiTreeUtil.getParentOfType(ref, GrImportStatement.class) != null ||
PsiTreeUtil.getParentOfType(ref, GroovyCodeFragment.class) != null;
}
private static <Qualifier extends PsiElement> boolean mayInsertImport(@NotNull GrQualifiedReference<Qualifier> ref) {
return !(ref.getContainingFile() instanceof GroovyCodeFragment) &&
PsiTreeUtil.getParentOfType(ref, GrImportStatement.class) == null &&
ref.getContainingFile() instanceof GroovyFileBase;
}
public static GrReferenceAdjuster getInstance() {
return new GrReferenceAdjuster();
}
}