EA-91810 - assert: NoSwingUnderWriteAction.lambda$watchForEvents$
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / move / moveFilesOrDirectories / MoveFilesOrDirectoriesUtil.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.refactoring.move.moveFilesOrDirectories;
18
19 import com.intellij.ide.util.DirectoryChooserUtil;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.command.CommandProcessor;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.ui.DialogWrapper;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.*;
27 import com.intellij.refactoring.RefactoringBundle;
28 import com.intellij.refactoring.RefactoringSettings;
29 import com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler;
30 import com.intellij.refactoring.move.MoveCallback;
31 import com.intellij.refactoring.move.MoveHandler;
32 import com.intellij.refactoring.util.CommonRefactoringUtil;
33 import com.intellij.util.Function;
34 import com.intellij.util.IncorrectOperationException;
35 import com.intellij.util.containers.ContainerUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.HashMap;
43 import java.util.List;
44
45 public class MoveFilesOrDirectoriesUtil {
46   private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil");
47
48   private MoveFilesOrDirectoriesUtil() {
49   }
50
51   /**
52    * Moves the specified directory to the specified parent directory. Does not process non-code usages!
53    *
54    * @param dir          the directory to move.
55    * @param newParentDir the directory to move <code>dir</code> into.
56    * @throws IncorrectOperationException if the modification is not supported or not possible for some reason.
57    */
58   public static void doMoveDirectory(final PsiDirectory aDirectory, final PsiDirectory destDirectory) throws IncorrectOperationException {
59     PsiManager manager = aDirectory.getManager();
60     // do actual move
61     checkMove(aDirectory, destDirectory);
62
63     try {
64       aDirectory.getVirtualFile().move(manager, destDirectory.getVirtualFile());
65     }
66     catch (IOException e) {
67       throw new IncorrectOperationException(e);
68     }
69   }
70
71   /**
72    * Moves the specified file to the specified directory. Does not process non-code usages! file may be invalidated, need to be refreshed before use, like {@code newDirectory.findFile(file.getName())}
73    *
74    * @param file         the file to move.
75    * @param newDirectory the directory to move the file into.
76    * @throws IncorrectOperationException if the modification is not supported or not possible for some reason.
77    */
78   public static void doMoveFile(final PsiFile file, final PsiDirectory newDirectory) throws IncorrectOperationException {
79     PsiManager manager = file.getManager();
80     // the class is already there, this is true when multiple classes are defined in the same file
81     if (!newDirectory.equals(file.getContainingDirectory())) {
82       // do actual move
83       checkMove(file, newDirectory);
84
85       try {
86         final VirtualFile virtualFile = file.getVirtualFile();
87         LOG.assertTrue(virtualFile != null, file);
88         virtualFile.move(manager, newDirectory.getVirtualFile());
89       }
90       catch (IOException e) {
91         throw new IncorrectOperationException(e);
92       }
93     }
94   }
95
96   /**
97    * @param elements should contain PsiDirectories or PsiFiles only
98    */
99   public static void doMove(final Project project,
100                             final PsiElement[] elements,
101                             final PsiElement[] targetElement,
102                             final MoveCallback moveCallback) {
103     doMove(project, elements, targetElement, moveCallback, null);
104   }
105
106   /**
107    * @param elements should contain PsiDirectories or PsiFiles only if adjustElements == null
108    */
109   public static void doMove(final Project project,
110                             final PsiElement[] elements,
111                             final PsiElement[] targetElement,
112                             final MoveCallback moveCallback,
113                             final Function<PsiElement[], PsiElement[]> adjustElements) {
114     if (adjustElements == null) {
115       for (PsiElement element : elements) {
116         if (!(element instanceof PsiFile) && !(element instanceof PsiDirectory)) {
117           throw new IllegalArgumentException("unexpected element type: " + element);
118         }
119       }
120     }
121
122     final PsiDirectory targetDirectory = resolveToDirectory(project, targetElement[0]);
123     if (targetElement[0] != null && targetDirectory == null) return;
124
125     final PsiElement[] newElements = adjustElements != null ? adjustElements.fun(elements) : elements;
126
127     final PsiDirectory initialTargetDirectory = getInitialTargetDirectory(targetDirectory, elements);
128
129     final MoveFilesOrDirectoriesDialog.Callback doRun = new MoveFilesOrDirectoriesDialog.Callback() {
130       @Override
131       public void run(final MoveFilesOrDirectoriesDialog moveDialog) {
132         CommandProcessor.getInstance().executeCommand(project, () -> {
133           final PsiDirectory targetDirectory1 = moveDialog != null ? moveDialog.getTargetDirectory() : initialTargetDirectory;
134           if (targetDirectory1 == null) {
135             LOG.error("It is null! The target directory, it is null!");
136             return;
137           }
138
139           Collection<PsiElement> toCheck = ContainerUtil.newArrayList((PsiElement)targetDirectory1);
140           for (PsiElement e : newElements) {
141             toCheck.add(e instanceof PsiFileSystemItem && e.getParent() != null ? e.getParent() : e);
142           }
143           if (!CommonRefactoringUtil.checkReadOnlyStatus(project, toCheck, false)) {
144             return;
145           }
146
147           targetElement[0] = targetDirectory1;
148
149           try {
150             final int[] choice = elements.length > 1 || elements[0] instanceof PsiDirectory ? new int[]{-1} : null;
151             final List<PsiElement> els = new ArrayList<>();
152             for (final PsiElement psiElement : newElements) {
153               if (psiElement instanceof PsiFile) {
154                 final PsiFile file = (PsiFile)psiElement;
155                 if (CopyFilesOrDirectoriesHandler.checkFileExist(targetDirectory1, choice, file, file.getName(), "Move")) continue;
156               }
157               checkMove(psiElement, targetDirectory1);
158               els.add(psiElement);
159             }
160             final Runnable callback = () -> {
161               if (moveDialog != null) moveDialog.close(DialogWrapper.CANCEL_EXIT_CODE);
162             };
163             if (els.isEmpty()) {
164               callback.run();
165               return;
166             }
167             new MoveFilesOrDirectoriesProcessor(project, els.toArray(new PsiElement[els.size()]), targetDirectory1,
168                                                 RefactoringSettings.getInstance().MOVE_SEARCH_FOR_REFERENCES_FOR_FILE,
169                                                 false, false, moveCallback, callback).run();
170           }
171           catch (IncorrectOperationException e) {
172             CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(),
173                                                    "refactoring.moveFile", project);
174           }
175         }, MoveHandler.REFACTORING_NAME, null);
176       }
177     };
178
179     if (ApplicationManager.getApplication().isUnitTestMode()) {
180       doRun.run(null);
181     }
182     else {
183       final MoveFilesOrDirectoriesDialog moveDialog = new MoveFilesOrDirectoriesDialog(project, doRun);
184       moveDialog.setData(newElements, initialTargetDirectory, "refactoring.moveFile");
185       moveDialog.show();
186     }
187   }
188
189   @Nullable
190   public static PsiDirectory resolveToDirectory(final Project project, final PsiElement element) {
191     if (!(element instanceof PsiDirectoryContainer)) {
192       return (PsiDirectory)element;
193     }
194
195     PsiDirectory[] directories = ((PsiDirectoryContainer)element).getDirectories();
196     switch (directories.length) {
197       case 0:
198         return null;
199       case 1:
200         return directories[0];
201       default:
202         return DirectoryChooserUtil.chooseDirectory(directories, directories[0], project, new HashMap<>());
203     }
204
205   }
206
207   @Nullable
208   private static PsiDirectory getCommonDirectory(PsiElement[] movedElements) {
209     PsiDirectory commonDirectory = null;
210
211     for (PsiElement movedElement : movedElements) {
212       final PsiDirectory containingDirectory;
213       if (movedElement instanceof PsiDirectory) {
214         containingDirectory = ((PsiDirectory)movedElement).getParentDirectory();
215       }
216       else {
217         final PsiFile containingFile = movedElement.getContainingFile();
218         containingDirectory = containingFile == null ? null : containingFile.getContainingDirectory();
219       }
220
221       if (containingDirectory != null) {
222         if (commonDirectory == null) {
223           commonDirectory = containingDirectory;
224         }
225         else {
226           if (commonDirectory != containingDirectory) {
227             return null;
228           }
229         }
230       }
231     }
232     return commonDirectory;
233   }
234
235   @Nullable
236   public static PsiDirectory getInitialTargetDirectory(PsiDirectory initialTargetElement, final PsiElement[] movedElements) {
237     PsiDirectory initialTargetDirectory = initialTargetElement;
238     if (initialTargetDirectory == null) {
239       if (movedElements != null) {
240         final PsiDirectory commonDirectory = getCommonDirectory(movedElements);
241         if (commonDirectory != null) {
242           initialTargetDirectory = commonDirectory;
243         }
244         else {
245           initialTargetDirectory = getContainerDirectory(movedElements[0]);
246         }
247       }
248     }
249     return initialTargetDirectory;
250   }
251
252   @Nullable
253   private static PsiDirectory getContainerDirectory(final PsiElement psiElement) {
254     if (psiElement instanceof PsiDirectory) {
255       return (PsiDirectory)psiElement;
256     }
257     else if (psiElement != null) {
258       return psiElement.getContainingFile().getContainingDirectory();
259     }
260     else {
261       return null;
262     }
263   }
264
265   /**
266    * Checks if it is possible to move the specified PSI element under the specified container,
267    * and throws an exception if the move is not possible. Does not actually modify anything.
268    *
269    * @param element      the element to check the move possibility.
270    * @param newContainer the target container element to move into.
271    * @throws IncorrectOperationException if the modification is not supported or not possible for some reason.
272    */
273   public static void checkMove(@NotNull PsiElement element, @NotNull PsiElement newContainer) throws IncorrectOperationException {
274     if (element instanceof PsiDirectoryContainer) {
275       PsiDirectory[] dirs = ((PsiDirectoryContainer)element).getDirectories();
276       if (dirs.length == 0) {
277         throw new IncorrectOperationException();
278       }
279       else if (dirs.length > 1) {
280         throw new IncorrectOperationException(
281           "Moving of packages represented by more than one physical directory is not supported.");
282       }
283       checkMove(dirs[0], newContainer);
284       return;
285     }
286
287     //element.checkDelete(); //move != delete + add
288     newContainer.checkAdd(element);
289     checkIfMoveIntoSelf(element, newContainer);
290   }
291
292   public static void checkIfMoveIntoSelf(PsiElement element, PsiElement newContainer) throws IncorrectOperationException {
293     PsiElement container = newContainer;
294     while (container != null) {
295       if (container == element) {
296         if (element instanceof PsiDirectory) {
297           if (element == newContainer) {
298             throw new IncorrectOperationException("Cannot place directory into itself.");
299           }
300           else {
301             throw new IncorrectOperationException("Cannot place directory into its subdirectory.");
302           }
303         }
304         else {
305           throw new IncorrectOperationException();
306         }
307       }
308       container = container.getParent();
309     }
310   }
311 }