blob: b8d1c4e8f2cb66210ae38c3754aa79e3b283d432 [file] [log] [blame]
/*
* Copyright 2000-2009 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.
*/
/*
* User: anna
* Date: 28-Dec-2009
*/
package com.intellij.refactoring.move.moveClassesOrPackages;
import com.intellij.CommonBundle;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
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.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.Function;
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<PsiDirectory>(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<PsiFile, TargetDirectoryWrapper>();
myNestedDirsToMove = new HashMap<PsiDirectory, TargetDirectoryWrapper>();
for (PsiDirectory dir : directories) {
collectFiles2Move(myFilesToMove, myNestedDirsToMove, dir, includeSelf ? dir.getParentDirectory() : dir, getTargetDirectory(dir));
}
}
@NotNull
@Override
protected UsageViewDescriptor createUsageViewDescriptor(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<UsageInfo>();
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(Ref<UsageInfo[]> refUsages) {
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
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(PsiElement[] elements) {}
@Override
public void performRefactoring(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();
}
}
catch (IncorrectOperationException e) {
Messages.showErrorDialog(myProject, e.getMessage(), CommonBundle.getErrorTitle());
return;
}
try {
final List<PsiFile> movedFiles = new ArrayList<PsiFile>();
final Map<PsiElement, PsiElement> oldToNewElementsMapping = new HashMap<PsiElement, PsiElement>();
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, new Function<PsiDirectory, PsiDirectory>() {
@Override
public PsiDirectory fun(PsiDirectory dir) {
return getResultDirectory(dir).getTargetDirectory();
}
});
}
for (PsiDirectory directory : myDirectories) {
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(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) {
final PsiDirectory root = myParentDirectory.findOrCreateTargetDirectory();
myTargetDirectory = root.findSubdirectory(myRelativePath);
if (myTargetDirectory == null) {
myTargetDirectory = root.createSubdirectory(myRelativePath);
}
}
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);
}
}
}
}