/* * Copyright 2000-2017 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.refactoring.move; import com.intellij.lang.FileASTNode; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.Factory; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassHandler; import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil; import com.intellij.usageView.UsageInfo; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.GroovyFileType; import org.jetbrains.plugins.groovy.GroovyLanguage; import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment; import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocCommentOwner; import org.jetbrains.plugins.groovy.lang.groovydoc.psi.impl.GrDocCommentUtil; import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.GrTopStatement; import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; import org.jetbrains.plugins.groovy.util.GroovyChangeContextUtil; import java.util.Collection; import java.util.Iterator; /** * @author Maxim.Medvedev */ public class MoveGroovyClassHandler implements MoveClassHandler { Logger LOG = Logger.getInstance(MoveGroovyClassHandler.class); @Override public PsiClass doMoveClass(@NotNull PsiClass aClass, @NotNull PsiDirectory moveDestination) throws IncorrectOperationException { if (!aClass.getLanguage().equals(GroovyLanguage.INSTANCE)) return null; PsiFile file = aClass.getContainingFile(); if (!(file instanceof GroovyFile)) return null; final PsiPackage newPackage = JavaDirectoryService.getInstance().getPackage(moveDestination); LOG.assertTrue(newPackage != null); PsiClass newClass = null; final String newPackageName = newPackage.getQualifiedName(); if (aClass instanceof GroovyScriptClass) { final PsiClass[] classes = ((GroovyFile)file).getClasses(); if (classes.length == 1) { if (!moveDestination.equals(file.getContainingDirectory())) { Project project = file.getProject(); MoveFilesOrDirectoriesUtil.doMoveFile(file, moveDestination); DumbService.getInstance(project).completeJustSubmittedTasks(); file = moveDestination.findFile(file.getName()); assert file != null; ((PsiClassOwner)file).setPackageName(newPackageName); } return ((GroovyFile)file).getScriptClass(); } //script class is moved the first from the file due to MoveClassOrPackageProcessor:88 (element sort) correctSelfReferences(aClass, newPackage); final GroovyFile newFile = generateNewScript((GroovyFile)file, newPackage); for (PsiElement child : file.getChildren()) { if (!(child instanceof GrTopStatement || child instanceof PsiComment)) continue; if (child instanceof PsiClass || child instanceof GrImportStatement || child instanceof GrPackageDefinition) continue; if (child instanceof GrDocComment) { final GrDocCommentOwner owner = GrDocCommentUtil.findDocOwner((GrDocComment)child); if (owner instanceof PsiClass) continue; } child.delete(); } if (!moveDestination.equals(file.getContainingDirectory())) { moveDestination.add(newFile); //aClass.getManager().moveFile(newFile, moveDestination); } newClass = newFile.getClasses()[0]; correctOldClassReferences(newClass, aClass); } else { if (!moveDestination.equals(file.getContainingDirectory()) && moveDestination.findFile(file.getName()) != null) { // moving second of two classes which were in the same file to a different directory (IDEADEV-3089) correctSelfReferences(aClass, newPackage); PsiFile newFile = moveDestination.findFile(file.getName()); final FileASTNode fileNode = newFile.getNode(); fileNode.addChild(Factory.createSingleLeafElement(GroovyTokenTypes.mNLS, "\n\n", 0, 2, null, aClass.getManager())); final PsiDocComment docComment = aClass.getDocComment(); if (docComment != null) { newFile.add(docComment); fileNode.addChild(Factory.createSingleLeafElement(GroovyTokenTypes.mNLS, "\n", 0, 1, null, aClass.getManager())); } newClass = (GrTypeDefinition)newFile.add(aClass); correctOldClassReferences(newClass, aClass); aClass.delete(); } else if (((GroovyFile)file).getClasses().length > 1) { correctSelfReferences(aClass, newPackage); Project project = aClass.getProject(); PsiFileFactory fileFactory = PsiFileFactory.getInstance(project); GroovyFile newFile = (GroovyFile)moveDestination.add(fileFactory.createFileFromText( aClass.getName() + "." + GroovyFileType.DEFAULT_EXTENSION, GroovyLanguage.INSTANCE, "class XXX {}" )); final PsiClass created = newFile.getClasses()[0]; PsiDocComment docComment = aClass.getDocComment(); if (docComment != null) { newFile.addBefore(docComment, created); docComment.delete(); } newClass = (PsiClass)created.replace(aClass); setPackageDefinition((GroovyFile)file, newFile, newPackageName); correctOldClassReferences(newClass, aClass); aClass.delete(); } } return newClass; } private static void setPackageDefinition(GroovyFile file, GroovyFile newFile, String newPackageName) { String modifiersText = null; final GrPackageDefinition packageDefinition = file.getPackageDefinition(); if (packageDefinition != null) { final PsiModifierList modifierList = packageDefinition.getModifierList(); if (modifierList != null) { modifiersText = modifierList.getText().trim(); } } if (modifiersText != null && !modifiersText.isEmpty()) { final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(file.getProject()); final GrPackageDefinition newPackageDefinition = (GrPackageDefinition)factory.createTopElementFromText(modifiersText + " package " + newPackageName); newFile.setPackage(newPackageDefinition); } else { newFile.setPackageName(newPackageName); } } private static GroovyFile generateNewScript(GroovyFile file, PsiPackage newPackage) { for (GrImportStatement importStatement : file.getImportStatements()) { importStatement.delete(); } final GroovyFile newFile = GroovyPsiElementFactory.getInstance(file.getProject()).createGroovyFile("", true, null); newFile.addRange(file.getFirstChild(), file.getLastChild()); final PsiClass[] newFileClasses = newFile.getClasses(); for (PsiClass psiClass : newFileClasses) { if (psiClass instanceof GroovyScriptClass) continue; final GrDocComment docComment = GrDocCommentUtil.findDocComment((GrDocCommentOwner)psiClass); if (docComment != null) docComment.delete(); psiClass.delete(); } final GrPackageDefinition packageDefinition = newFile.getPackageDefinition(); if (packageDefinition != null) packageDefinition.delete(); PsiElement cur = newFile.getFirstChild(); while (cur != null && PsiImplUtil.isWhiteSpaceOrNls(cur)) { cur = cur.getNextSibling(); } if (cur != null && cur != newFile.getFirstChild()) { cur = cur.getPrevSibling(); newFile.deleteChildRange(newFile.getFirstChild(), cur); } cur = newFile.getLastChild(); while (cur != null && PsiImplUtil.isWhiteSpaceOrNls(cur)) { cur = cur.getPrevSibling(); } if (cur != null && cur != newFile.getLastChild()) { cur = cur.getNextSibling(); newFile.deleteChildRange(cur, newFile.getLastChild()); } newFile.setName(file.getName()); setPackageDefinition(file, newFile, newPackage.getQualifiedName()); GroovyChangeContextUtil.decodeContextInfo(newFile, null, null); return newFile; } @Override @Nullable public String getName(PsiClass clazz) { final PsiFile file = clazz.getContainingFile(); if (!(file instanceof GroovyFile)) return null; return ((GroovyFile)file).getClasses().length > 1 ? clazz.getName() + "." + GroovyFileType.DEFAULT_EXTENSION : file.getName(); } @Override public void preprocessUsages(Collection<UsageInfo> results) { //remove all alias-imported usages from collection for (Iterator<UsageInfo> iterator = results.iterator(); iterator.hasNext(); ) { UsageInfo info = iterator.next(); if (info == null) { LOG.debug("info==null"); continue; } final PsiReference ref = info.getReference(); if (ref==null) continue; final PsiElement element = ref.getElement(); if (!(element instanceof GrReferenceElement)) continue; final GroovyResolveResult resolveResult = ((GrReferenceElement)element).advancedResolve(); final PsiElement context = resolveResult.getCurrentFileResolveContext(); if (!(context instanceof GrImportStatement)) continue; if (!((GrImportStatement)context).isAliasedImport()) continue; iterator.remove(); } } @Override public void prepareMove(@NotNull PsiClass aClass) { if (aClass.getContainingFile() instanceof GroovyFileBase) { GroovyChangeContextUtil.encodeContextInfo(getRealElement(aClass)); } } @Override public void finishMoveClass(@NotNull PsiClass aClass) { if (aClass.getContainingFile() instanceof GroovyFileBase) { GroovyChangeContextUtil.decodeContextInfo(getRealElement(aClass), null, null); } } private static PsiElement getRealElement(PsiClass aClass) { return aClass instanceof GroovyScriptClass ? aClass.getContainingFile() : aClass; } private static void correctOldClassReferences(final PsiClass newClass, final PsiClass oldClass) { final Collection<PsiReference> all = ReferencesSearch.search(oldClass, new LocalSearchScope(newClass.getContainingFile())).findAll(); for (PsiReference reference : all) { final PsiElement element = reference.getElement(); final PsiElement parent = element.getParent(); if (parent instanceof GrImportStatement && !((GrImportStatement)parent).isStatic()) { parent.delete(); } reference.bindToElement(newClass); } } private static void correctSelfReferences(final PsiClass aClass, final PsiPackage newContainingPackage) { final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(aClass.getContainingFile().getContainingDirectory()); if (aPackage == null) { return; } for (PsiReference reference : ReferencesSearch.search(aClass, new LocalSearchScope(aClass)).findAll()) { if (reference instanceof GrCodeReferenceElement) { final GrCodeReferenceElement qualifier = ((GrCodeReferenceElement)reference).getQualifier(); if (qualifier != null) { qualifier.bindToElement(newContainingPackage); } } } } }