blob: 03540a97e74963db53fcba9dc144c2b49920dbdc [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.
*/
package com.intellij.refactoring.move.moveClassesOrPackages;
import com.intellij.ide.util.DirectoryChooserUtil;
import com.intellij.lang.java.JavaFindUsagesProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.JavaProjectRootsUtil;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.refactoring.MoveDestination;
import com.intellij.refactoring.PackageWrapper;
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil;
import com.intellij.refactoring.util.MoveRenameUsageInfo;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.intellij.psi.util.FileTypeUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class MoveClassesOrPackagesUtil {
private static final Logger LOG = Logger.getInstance(
"#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil");
private MoveClassesOrPackagesUtil() {
}
public static UsageInfo[] findUsages(final PsiElement element,
boolean searchInStringsAndComments,
boolean searchInNonJavaFiles,
final String newQName) {
PsiManager manager = element.getManager();
ArrayList<UsageInfo> results = new ArrayList<UsageInfo>();
Set<PsiReference> foundReferences = new HashSet<PsiReference>();
GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject());
for (PsiReference reference : ReferencesSearch.search(element, projectScope, false)) {
TextRange range = reference.getRangeInElement();
if (foundReferences.contains(reference)) continue;
results.add(new MoveRenameUsageInfo(reference.getElement(), reference, range.getStartOffset(), range.getEndOffset(), element, false));
foundReferences.add(reference);
}
findNonCodeUsages(searchInStringsAndComments, searchInNonJavaFiles, element, newQName, results);
preprocessUsages(results);
return results.toArray(new UsageInfo[results.size()]);
}
private static void preprocessUsages(ArrayList<UsageInfo> results) {
for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensions()) {
handler.preprocessUsages(results);
}
}
public static void findNonCodeUsages(boolean searchInStringsAndComments,
boolean searchInNonJavaFiles,
final PsiElement element,
final String newQName,
ArrayList<UsageInfo> results) {
final String stringToSearch = getStringToSearch(element);
if (stringToSearch == null) return;
TextOccurrencesUtil.findNonCodeUsages(element, stringToSearch, searchInStringsAndComments, searchInNonJavaFiles, newQName, results);
}
private static String getStringToSearch(PsiElement element) {
if (element instanceof PsiPackage) {
return ((PsiPackage)element).getQualifiedName();
}
else if (element instanceof PsiClass) {
return ((PsiClass)element).getQualifiedName();
}
else if (element instanceof PsiDirectory) {
return getStringToSearch(JavaDirectoryService.getInstance().getPackage((PsiDirectory)element));
}
else {
LOG.error("Unknown element type");
return null;
}
}
// Does not process non-code usages!
public static PsiPackage doMovePackage(PsiPackage aPackage, MoveDestination moveDestination)
throws IncorrectOperationException {
final PackageWrapper targetPackage = moveDestination.getTargetPackage();
final String newPrefix;
if ("".equals(targetPackage.getQualifiedName())) {
newPrefix = "";
}
else {
newPrefix = targetPackage.getQualifiedName() + ".";
}
final String newPackageQualifiedName = newPrefix + aPackage.getName();
// do actual move
final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(aPackage.getProject());
PsiDirectory[] dirs = aPackage.getDirectories(projectScope);
for (PsiDirectory dir : dirs) {
final PsiDirectory targetDirectory = moveDestination.getTargetDirectory(dir);
if (targetDirectory != null) {
moveDirectoryRecursively(dir, targetDirectory);
}
}
aPackage.handleQualifiedNameChange(newPackageQualifiedName);
return JavaPsiFacade.getInstance(targetPackage.getManager().getProject()).findPackage(newPackageQualifiedName);
}
public static void moveDirectoryRecursively(PsiDirectory dir, PsiDirectory destination)
throws IncorrectOperationException {
if ( dir.getParentDirectory() == destination ) return;
moveDirectoryRecursively(dir, destination, new HashSet<VirtualFile>());
}
private static void moveDirectoryRecursively(PsiDirectory dir, PsiDirectory destination, HashSet<VirtualFile> movedPaths)
throws IncorrectOperationException {
final PsiManager manager = dir.getManager();
final VirtualFile destVFile = destination.getVirtualFile();
final VirtualFile sourceVFile = dir.getVirtualFile();
if (movedPaths.contains(sourceVFile)) return;
String targetName = dir.getName();
final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(dir);
if (aPackage != null) {
final String sourcePackageName = aPackage.getName();
if (!sourcePackageName.equals(targetName)) {
targetName = sourcePackageName;
}
}
final PsiDirectory subdirectoryInDest;
final boolean isSourceRoot = RefactoringUtil.isSourceRoot(dir);
if (VfsUtil.isAncestor(sourceVFile, destVFile, false) || isSourceRoot) {
PsiDirectory exitsingSubdir = destination.findSubdirectory(targetName);
if (exitsingSubdir == null) {
subdirectoryInDest = destination.createSubdirectory(targetName);
movedPaths.add(subdirectoryInDest.getVirtualFile());
} else {
subdirectoryInDest = exitsingSubdir;
}
} else {
subdirectoryInDest = destination.findSubdirectory(targetName);
}
if (subdirectoryInDest == null) {
VirtualFile virtualFile = dir.getVirtualFile();
MoveFilesOrDirectoriesUtil.doMoveDirectory(dir, destination);
movedPaths.add(virtualFile);
}
else {
final PsiFile[] files = dir.getFiles();
for (PsiFile file : files) {
try {
subdirectoryInDest.checkAdd(file);
}
catch (IncorrectOperationException e) {
continue;
}
MoveFilesOrDirectoriesUtil.doMoveFile(file, subdirectoryInDest);
}
final PsiDirectory[] subdirectories = dir.getSubdirectories();
for (PsiDirectory subdirectory : subdirectories) {
if (!subdirectory.equals(subdirectoryInDest)) {
moveDirectoryRecursively(subdirectory, subdirectoryInDest, movedPaths);
}
}
if (!isSourceRoot && dir.getFiles().length == 0 && dir.getSubdirectories().length == 0) {
dir.delete();
}
}
}
public static void prepareMoveClass(PsiClass aClass) {
for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensions()) {
handler.prepareMove(aClass);
}
}
public static void finishMoveClass(PsiClass aClass) {
for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensions()) {
handler.finishMoveClass(aClass);
}
}
// Does not process non-code usages!
public static PsiClass doMoveClass(PsiClass aClass, PsiDirectory moveDestination) throws IncorrectOperationException {
return doMoveClass(aClass, moveDestination, true);
}
// Does not process non-code usages!
public static PsiClass doMoveClass(PsiClass aClass, PsiDirectory moveDestination, boolean moveAllClassesInFile) throws IncorrectOperationException {
PsiClass newClass;
if (!moveAllClassesInFile) {
for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensions()) {
newClass = handler.doMoveClass(aClass, moveDestination);
if (newClass != null) return newClass;
}
}
PsiFile file = aClass.getContainingFile();
final PsiPackage newPackage = JavaDirectoryService.getInstance().getPackage(moveDestination);
newClass = aClass;
if (!moveDestination.equals(file.getContainingDirectory())) {
LOG.assertTrue(file.getVirtualFile() != null, aClass);
MoveFilesOrDirectoriesUtil.doMoveFile(file, moveDestination);
if (file instanceof PsiClassOwner && newPackage != null && !FileTypeUtils.isInServerPageFile(file)) {
// Do not rely on class instance identity retention after setPackageName (Scala)
String aClassName = aClass.getName();
((PsiClassOwner)file).setPackageName(newPackage.getQualifiedName());
newClass = findClassByName((PsiClassOwner)file, aClassName);
LOG.assertTrue(newClass != null);
}
}
return newClass;
}
@Nullable
private static PsiClass findClassByName(PsiClassOwner file, String name) {
PsiClass[] classes = file.getClasses();
for (PsiClass aClass : classes) {
if (name.equals(aClass.getName())) {
return aClass;
}
}
return null;
}
public static String getPackageName(PackageWrapper aPackage) {
if (aPackage == null) {
return null;
}
String name = aPackage.getQualifiedName();
if (name.length() > 0) {
return name;
}
else {
return JavaFindUsagesProvider.DEFAULT_PACKAGE_NAME;
}
}
@Nullable
public static PsiDirectory chooseDestinationPackage(Project project, String packageName, @Nullable PsiDirectory baseDir) {
final PsiManager psiManager = PsiManager.getInstance(project);
final PackageWrapper packageWrapper = new PackageWrapper(psiManager, packageName);
final PsiPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(packageName);
PsiDirectory directory;
PsiDirectory[] directories = aPackage != null ? aPackage.getDirectories() : null;
final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
final VirtualFile baseDirVirtualFile = baseDir != null ? baseDir.getVirtualFile() : null;
final boolean isBaseDirInTestSources = baseDirVirtualFile != null && fileIndex.isInTestSourceContent(baseDirVirtualFile);
if (directories != null && directories.length == 1 && (baseDirVirtualFile == null ||
fileIndex.isInTestSourceContent(directories[0].getVirtualFile()) == isBaseDirInTestSources)) {
directory = directories[0];
}
else {
final List<VirtualFile> contentSourceRoots = JavaProjectRootsUtil.getSuitableDestinationSourceRoots(project);
if (contentSourceRoots.size() == 1 && (baseDirVirtualFile == null || fileIndex.isInTestSourceContent(contentSourceRoots.get(0)) == isBaseDirInTestSources)) {
directory = ApplicationManager.getApplication().runWriteAction(new Computable<PsiDirectory>() {
@Override
public PsiDirectory compute() {
return RefactoringUtil.createPackageDirectoryInSourceRoot(packageWrapper, contentSourceRoots.get(0));
}
});
}
else {
final VirtualFile sourceRootForFile = chooseSourceRoot(packageWrapper, contentSourceRoots, baseDir);
if (sourceRootForFile == null) return null;
directory = ApplicationManager.getApplication().runWriteAction(new Computable<PsiDirectory>() {
@Override
public PsiDirectory compute() {
return new AutocreatingSingleSourceRootMoveDestination(packageWrapper, sourceRootForFile).getTargetDirectory((PsiDirectory)null);
}
});
}
}
return directory;
}
public static VirtualFile chooseSourceRoot(final PackageWrapper targetPackage,
final List<VirtualFile> contentSourceRoots,
final PsiDirectory initialDirectory) {
Project project = targetPackage.getManager().getProject();
//ensure that there would be no duplicates: e.g. when one content root is subfolder of another root (configured via excluded roots)
LinkedHashSet<PsiDirectory> targetDirectories = new LinkedHashSet<PsiDirectory>();
Map<PsiDirectory, String> relativePathsToCreate = new HashMap<PsiDirectory,String>();
buildDirectoryList(targetPackage, contentSourceRoots, targetDirectories, relativePathsToCreate);
final PsiDirectory selectedDirectory = DirectoryChooserUtil.chooseDirectory(
targetDirectories.toArray(new PsiDirectory[targetDirectories.size()]),
initialDirectory,
project,
relativePathsToCreate
);
if (selectedDirectory == null) return null;
final VirtualFile virt = selectedDirectory.getVirtualFile();
final VirtualFile sourceRootForFile = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(virt);
LOG.assertTrue(sourceRootForFile != null);
return sourceRootForFile;
}
public static void buildDirectoryList(PackageWrapper aPackage,
List<VirtualFile> contentSourceRoots,
LinkedHashSet<PsiDirectory> targetDirectories,
Map<PsiDirectory, String> relativePathsToCreate) {
sourceRoots:
for (VirtualFile root : contentSourceRoots) {
final PsiDirectory[] directories = aPackage.getDirectories();
for (PsiDirectory directory : directories) {
if (VfsUtil.isAncestor(root, directory.getVirtualFile(), false)) {
targetDirectories.add(directory);
continue sourceRoots;
}
}
String qNameToCreate;
try {
qNameToCreate = RefactoringUtil.qNameToCreateInSourceRoot(aPackage, root);
}
catch (IncorrectOperationException e) {
continue sourceRoots;
}
PsiDirectory currentDirectory = aPackage.getManager().findDirectory(root);
if (currentDirectory == null) continue;
final String[] shortNames = qNameToCreate.split("\\.");
for (int j = 0; j < shortNames.length; j++) {
String shortName = shortNames[j];
final PsiDirectory subdirectory = currentDirectory.findSubdirectory(shortName);
if (subdirectory == null) {
targetDirectories.add(currentDirectory);
final StringBuffer postfix = new StringBuffer();
for (int k = j; k < shortNames.length; k++) {
String name = shortNames[k];
postfix.append(File.separatorChar);
postfix.append(name);
}
relativePathsToCreate.put(currentDirectory, postfix.toString());
continue sourceRoots;
}
else {
currentDirectory = subdirectory;
}
}
}
LOG.assertTrue(targetDirectories.size() <= contentSourceRoots.size());
LOG.assertTrue(relativePathsToCreate.size() <= contentSourceRoots.size());
}
}