/* * Copyright 2000-2015 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.refactoring.move.moveClassesOrPackages; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.refactoring.BaseRefactoringProcessor; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.listeners.RefactoringElementListener; import com.intellij.refactoring.listeners.RefactoringEventData; import com.intellij.refactoring.move.FileReferenceContextUtil; import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveMultipleElementsViewDescriptor; import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFileHandler; import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil; import com.intellij.refactoring.rename.RenameUtil; import com.intellij.refactoring.util.NonCodeUsageInfo; import com.intellij.refactoring.util.RefactoringUIUtil; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewDescriptor; import com.intellij.usageView.UsageViewUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; public class MoveDirectoryWithClassesProcessor extends BaseRefactoringProcessor { private final PsiDirectory[] myDirectories; private final PsiDirectory myTargetDirectory; private final boolean mySearchInComments; private final boolean mySearchInNonJavaFiles; private final Map<PsiFile, TargetDirectoryWrapper> myFilesToMove; private final Map<PsiDirectory, TargetDirectoryWrapper> myNestedDirsToMove; private NonCodeUsageInfo[] myNonCodeUsages; private final MoveCallback myMoveCallback; public MoveDirectoryWithClassesProcessor(Project project, PsiDirectory[] directories, PsiDirectory targetDirectory, boolean searchInComments, boolean searchInNonJavaFiles, boolean includeSelf, MoveCallback moveCallback) { super(project); if (targetDirectory != null) { final List<PsiDirectory> dirs = new ArrayList<>(Arrays.asList(directories)); for (Iterator<PsiDirectory> iterator = dirs.iterator(); iterator.hasNext(); ) { final PsiDirectory directory = iterator.next(); if (targetDirectory.equals(directory.getParentDirectory()) || targetDirectory.equals(directory)) { iterator.remove(); } } directories = dirs.toArray(new PsiDirectory[dirs.size()]); } myDirectories = directories; myTargetDirectory = targetDirectory; mySearchInComments = searchInComments; mySearchInNonJavaFiles = searchInNonJavaFiles; myMoveCallback = moveCallback; myFilesToMove = new HashMap<>(); myNestedDirsToMove = new HashMap<>(); for (PsiDirectory dir : directories) { collectFiles2Move(myFilesToMove, myNestedDirsToMove, dir, includeSelf ? dir.getParentDirectory() : dir, getTargetDirectory(dir)); } } @NotNull @Override protected UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) { PsiElement[] elements = new PsiElement[myFilesToMove.size()]; final PsiFile[] classes = PsiUtilCore.toPsiFileArray(myFilesToMove.keySet()); System.arraycopy(classes, 0, elements, 0, classes.length); return new MoveMultipleElementsViewDescriptor(elements, getTargetName()); } protected String getTargetName() { return RefactoringUIUtil.getDescription(getTargetDirectory(null).getTargetDirectory(), false); } @NotNull @Override public UsageInfo[] findUsages() { final List<UsageInfo> usages = new ArrayList<>(); for (MoveDirectoryWithClassesHelper helper : MoveDirectoryWithClassesHelper.findAll()) { helper.findUsages(myFilesToMove.keySet(), myDirectories, usages, mySearchInComments, mySearchInNonJavaFiles, myProject); } return UsageViewUtil.removeDuplicatedUsages(usages.toArray(new UsageInfo[usages.size()])); } @Override protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) { final MultiMap<PsiElement, String> conflicts = new MultiMap<>(); for (PsiFile psiFile : myFilesToMove.keySet()) { try { myFilesToMove.get(psiFile).checkMove(psiFile); } catch (IncorrectOperationException e) { conflicts.putValue(psiFile, e.getMessage()); } } for (MoveDirectoryWithClassesHelper helper : MoveDirectoryWithClassesHelper.findAll()) { helper.preprocessUsages(myProject, myFilesToMove.keySet(), refUsages.get(), myTargetDirectory, conflicts); } return showConflicts(conflicts, refUsages.get()); } @Override protected void refreshElements(@NotNull PsiElement[] elements) {} @Override public void performRefactoring(@NotNull UsageInfo[] usages) { //try to create all directories beforehand try { //top level directories should be created even if they are empty for (PsiDirectory directory : myDirectories) { getResultDirectory(directory).findOrCreateTargetDirectory(); } for (PsiDirectory directory : myNestedDirsToMove.keySet()) { myNestedDirsToMove.get(directory).findOrCreateTargetDirectory(); } for (PsiFile psiFile : myFilesToMove.keySet()) { myFilesToMove.get(psiFile).findOrCreateTargetDirectory(); } DumbService.getInstance(myProject).completeJustSubmittedTasks(); final List<PsiFile> movedFiles = new ArrayList<>(); final Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<>(); for (PsiFile psiFile : myFilesToMove.keySet()) { for (MoveDirectoryWithClassesHelper helper : MoveDirectoryWithClassesHelper.findAll()) { helper.beforeMove(psiFile); } final RefactoringElementListener listener = getTransaction().getElementListener(psiFile); final PsiDirectory moveDestination = myFilesToMove.get(psiFile).getTargetDirectory(); for (MoveDirectoryWithClassesHelper helper : MoveDirectoryWithClassesHelper.findAll()) { boolean processed = helper.move(psiFile, moveDestination, oldToNewElementsMapping, movedFiles, listener); if (processed) { break; } } } for (PsiElement newElement : oldToNewElementsMapping.values()) { for (MoveDirectoryWithClassesHelper helper : MoveDirectoryWithClassesHelper.findAll()) { helper.afterMove(newElement); } } // fix references in moved files to outer files for (PsiFile movedFile : movedFiles) { MoveFileHandler.forElement(movedFile).updateMovedFile(movedFile); FileReferenceContextUtil.decodeFileReferences(movedFile); } myNonCodeUsages = CommonMoveUtil.retargetUsages(usages, oldToNewElementsMapping); for (MoveDirectoryWithClassesHelper helper : MoveDirectoryWithClassesHelper.findAll()) { helper.postProcessUsages(usages, dir -> getResultDirectory(dir).findOrCreateTargetDirectory()); } for (PsiDirectory directory : myDirectories) { final TargetDirectoryWrapper wrapper = myNestedDirsToMove.get(directory); final PsiDirectory targetDirectory = wrapper.getTargetDirectory(); if (targetDirectory == null || !PsiTreeUtil.isAncestor(directory, targetDirectory, false)) { directory.delete(); } } } catch (IncorrectOperationException e) { myNonCodeUsages = new NonCodeUsageInfo[0]; RefactoringUIUtil.processIncorrectOperation(myProject, e); } } @Nullable @Override protected String getRefactoringId() { return "refactoring.move"; } @Nullable @Override protected RefactoringEventData getBeforeData() { RefactoringEventData data = new RefactoringEventData(); data.addElements(myDirectories); return data; } @Nullable @Override protected RefactoringEventData getAfterData(@NotNull UsageInfo[] usages) { RefactoringEventData data = new RefactoringEventData(); data.addElement(myTargetDirectory); return data; } private TargetDirectoryWrapper getResultDirectory(PsiDirectory dir) { return myTargetDirectory != null ? new TargetDirectoryWrapper(myTargetDirectory, dir.getName()) : getTargetDirectory(dir); } @Override protected void performPsiSpoilingRefactoring() { if (myNonCodeUsages == null) return; //refactoring was aborted RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); if (myMoveCallback != null) { myMoveCallback.refactoringCompleted(); } } private static void collectFiles2Move(Map<PsiFile, TargetDirectoryWrapper> files2Move, Map<PsiDirectory, TargetDirectoryWrapper> nestedDirsToMove, PsiDirectory directory, PsiDirectory rootDirectory, @NotNull TargetDirectoryWrapper targetDirectory) { final PsiElement[] children = directory.getChildren(); final String relativePath = VfsUtilCore.getRelativePath(directory.getVirtualFile(), rootDirectory.getVirtualFile(), '/'); final TargetDirectoryWrapper newTargetDirectory = relativePath.length() == 0 ? targetDirectory : targetDirectory.findOrCreateChild(relativePath); nestedDirsToMove.put(directory, newTargetDirectory); for (PsiElement child : children) { if (child instanceof PsiFile) { files2Move.put((PsiFile)child, newTargetDirectory); } else if (child instanceof PsiDirectory){ collectFiles2Move(files2Move, nestedDirsToMove, (PsiDirectory)child, directory, newTargetDirectory); } } } @Override protected String getCommandName() { return RefactoringBundle.message("moving.directories.command"); } public TargetDirectoryWrapper getTargetDirectory(PsiDirectory dir) { return new TargetDirectoryWrapper(myTargetDirectory); } public static class TargetDirectoryWrapper { private TargetDirectoryWrapper myParentDirectory; private PsiDirectory myTargetDirectory; private String myRelativePath; public TargetDirectoryWrapper(PsiDirectory targetDirectory) { myTargetDirectory = targetDirectory; } public TargetDirectoryWrapper(TargetDirectoryWrapper parentDirectory, String relativePath) { myParentDirectory = parentDirectory; myRelativePath = relativePath; } public TargetDirectoryWrapper(PsiDirectory parentDirectory, String relativePath) { myTargetDirectory = parentDirectory.findSubdirectory(relativePath); //in case it was null myParentDirectory = new TargetDirectoryWrapper(parentDirectory); myRelativePath = relativePath; } public PsiDirectory findOrCreateTargetDirectory() throws IncorrectOperationException{ if (myTargetDirectory == null) { PsiDirectory root = myParentDirectory.findOrCreateTargetDirectory(); final String[] pathComponents = myRelativePath.split("/"); for (String component : pathComponents) { myTargetDirectory = root.findSubdirectory(component); if (myTargetDirectory == null) { myTargetDirectory = root.createSubdirectory(component); } root = myTargetDirectory; } } return myTargetDirectory; } @Nullable public PsiDirectory getTargetDirectory() { return myTargetDirectory; } public TargetDirectoryWrapper findOrCreateChild(String relativePath) { if (myTargetDirectory != null) { final PsiDirectory psiDirectory = myTargetDirectory.findSubdirectory(relativePath); if (psiDirectory != null) { return new TargetDirectoryWrapper(psiDirectory); } } return new TargetDirectoryWrapper(this, relativePath); } public void checkMove(PsiFile psiFile) throws IncorrectOperationException { if (myTargetDirectory != null) { MoveFilesOrDirectoriesUtil.checkMove(psiFile, myTargetDirectory); } } } }