/* * Copyright 2000-2016 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(@NotNull 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(); } }