move selected classes to inner in one step (IDEA-39729 )
[fedora-idea.git] / java / java-impl / src / com / intellij / refactoring / move / moveClassesOrPackages / MoveClassesOrPackagesDialog.java
blob2c85428200b69bdcecdd0f54d77f41fa75f7d11f
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.refactoring.move.moveClassesOrPackages;
18 import com.intellij.lang.Language;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.editor.event.DocumentAdapter;
24 import com.intellij.openapi.editor.event.DocumentEvent;
25 import com.intellij.openapi.help.HelpManager;
26 import com.intellij.openapi.options.ConfigurationException;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.roots.ProjectRootManager;
29 import com.intellij.openapi.ui.Messages;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.*;
33 import com.intellij.psi.search.ProjectScope;
34 import com.intellij.psi.util.PsiTreeUtil;
35 import com.intellij.refactoring.*;
36 import com.intellij.refactoring.move.MoveCallback;
37 import com.intellij.refactoring.move.MoveClassesOrPackagesCallback;
38 import com.intellij.refactoring.move.MoveHandler;
39 import com.intellij.refactoring.ui.ClassNameReferenceEditor;
40 import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo;
41 import com.intellij.refactoring.ui.RefactoringDialog;
42 import com.intellij.refactoring.util.CommonRefactoringUtil;
43 import com.intellij.ui.RecentsManager;
44 import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
45 import com.intellij.ui.ReferenceEditorWithBrowseButton;
46 import com.intellij.usageView.UsageViewUtil;
47 import com.intellij.util.IncorrectOperationException;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import javax.swing.*;
53 import java.awt.*;
54 import java.awt.event.ActionEvent;
55 import java.awt.event.ActionListener;
57 public class MoveClassesOrPackagesDialog extends RefactoringDialog {
58 @NonNls private static final String RECENTS_KEY = "MoveClassesOrPackagesDialog.RECENTS_KEY";
59 private final PsiElement[] myElementsToMove;
60 private final MoveCallback myMoveCallback;
62 private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog");
65 private JLabel myNameLabel;
66 private ReferenceEditorComboWithBrowseButton myWithBrowseButtonReference;
67 private JCheckBox myCbSearchInComments;
68 private JCheckBox myCbSearchTextOccurences;
69 private JCheckBox myCbMoveToAnotherSourceFolder;
70 private String myHelpID;
71 private final boolean mySearchTextOccurencesEnabled;
72 private PsiDirectory myInitialTargetDirectory;
73 private final PsiManager myManager;
74 private JPanel myMainPanel;
75 private JRadioButton myToPackageRadioButton;
76 private JRadioButton myMakeInnerClassOfRadioButton;
77 private ReferenceEditorComboWithBrowseButton myClassPackageChooser;
78 private JPanel myCardPanel;
79 private ReferenceEditorWithBrowseButton myInnerClassChooser;
80 private JPanel myMoveClassPanel;
81 private JPanel myMovePackagePanel;
82 private boolean myHavePackages;
83 private boolean myTargetDirectoryFixed;
85 public MoveClassesOrPackagesDialog(Project project,
86 boolean searchTextOccurences,
87 PsiElement[] elementsToMove,
88 final PsiElement initialTargetElement,
89 MoveCallback moveCallback) {
90 super(project, true);
91 myElementsToMove = elementsToMove;
92 myMoveCallback = moveCallback;
93 myManager = PsiManager.getInstance(myProject);
94 setTitle(MoveHandler.REFACTORING_NAME);
95 mySearchTextOccurencesEnabled = searchTextOccurences;
97 selectInitialCard();
99 init();
101 if (initialTargetElement instanceof PsiClass) {
102 myMakeInnerClassOfRadioButton.setSelected(true);
104 myInnerClassChooser.setText(((PsiClass)initialTargetElement).getQualifiedName());
106 ApplicationManager.getApplication().invokeLater(new Runnable() {
107 public void run() {
108 myInnerClassChooser.requestFocus();
110 }, ModalityState.stateForComponent(myMainPanel));
112 else if (initialTargetElement instanceof PsiPackage) {
113 myClassPackageChooser.setText(((PsiPackage)initialTargetElement).getQualifiedName());
116 updateControlsEnabled();
117 myToPackageRadioButton.addActionListener(new ActionListener() {
118 public void actionPerformed(ActionEvent e) {
119 updateControlsEnabled();
120 myClassPackageChooser.requestFocus();
123 myMakeInnerClassOfRadioButton.addActionListener(new ActionListener() {
124 public void actionPerformed(ActionEvent e) {
125 updateControlsEnabled();
126 myInnerClassChooser.requestFocus();
131 private void updateControlsEnabled() {
132 myClassPackageChooser.setEnabled(myToPackageRadioButton.isSelected());
133 myInnerClassChooser.setEnabled(myMakeInnerClassOfRadioButton.isSelected());
134 myCbMoveToAnotherSourceFolder.setEnabled(isMoveToPackage() && getSourceRoots().length > 1 && !myTargetDirectoryFixed);
135 validateButtons();
138 private void selectInitialCard() {
139 myHavePackages = false;
140 for (PsiElement psiElement : myElementsToMove) {
141 if (!(psiElement instanceof PsiClass)) {
142 myHavePackages = true;
143 break;
146 CardLayout cardLayout = (CardLayout)myCardPanel.getLayout();
147 cardLayout.show(myCardPanel, myHavePackages ? "Package" : "Class");
150 public JComponent getPreferredFocusedComponent() {
151 return myHavePackages ? myWithBrowseButtonReference.getChildComponent() : myClassPackageChooser.getChildComponent();
154 protected JComponent createCenterPanel() {
155 return null;
158 private void createUIComponents() {
159 myWithBrowseButtonReference = createPackageChooser();
160 myClassPackageChooser = createPackageChooser();
162 myInnerClassChooser = new ClassNameReferenceEditor(PsiManager.getInstance(myProject), null, ProjectScope.getProjectScope(myProject));
163 myInnerClassChooser.addDocumentListener(new DocumentAdapter() {
164 public void documentChanged(DocumentEvent e) {
165 validateButtons();
169 // override CardLayout sizing behavior
170 myCardPanel = new JPanel() {
171 public Dimension getMinimumSize() {
172 return myHavePackages ? myMovePackagePanel.getMinimumSize() : myMoveClassPanel.getMinimumSize();
175 public Dimension getPreferredSize() {
176 return myHavePackages ? myMovePackagePanel.getPreferredSize() : myMoveClassPanel.getPreferredSize();
181 private ReferenceEditorComboWithBrowseButton createPackageChooser() {
182 final ReferenceEditorComboWithBrowseButton packageChooser =
183 new PackageNameReferenceEditorCombo("", myProject, RECENTS_KEY, RefactoringBundle.message("choose.destination.package"));
184 final Document document = packageChooser.getChildComponent().getDocument();
185 document.addDocumentListener(new DocumentAdapter() {
186 public void documentChanged(DocumentEvent e) {
187 validateButtons();
191 return packageChooser;
194 protected JComponent createNorthPanel() {
195 if (!mySearchTextOccurencesEnabled) {
196 myCbSearchTextOccurences.setEnabled(false);
197 myCbSearchTextOccurences.setVisible(false);
198 myCbSearchTextOccurences.setSelected(false);
201 return myMainPanel;
204 protected String getDimensionServiceKey() {
205 return myHavePackages
206 ? "#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog.packages"
207 : "#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog.classes";
210 public void setData(PsiElement[] psiElements,
211 String targetPackageName,
212 PsiDirectory initialTargetDirectory,
213 boolean isTargetDirectoryFixed,
214 boolean searchInComments,
215 boolean searchForTextOccurences,
216 String helpID) {
217 myInitialTargetDirectory = initialTargetDirectory;
218 myTargetDirectoryFixed = isTargetDirectoryFixed;
219 if (targetPackageName.length() != 0) {
220 myWithBrowseButtonReference.prependItem(targetPackageName);
221 myClassPackageChooser.prependItem(targetPackageName);
224 String nameFromCallback = myMoveCallback instanceof MoveClassesOrPackagesCallback
225 ? ((MoveClassesOrPackagesCallback)myMoveCallback).getElementsToMoveName()
226 : null;
227 if (nameFromCallback != null) {
228 myNameLabel.setText(nameFromCallback);
230 else if (psiElements.length == 1) {
231 PsiElement firstElement = psiElements[0];
232 PsiElement parent = firstElement.getParent();
233 LOG.assertTrue(parent != null);
234 myNameLabel.setText(RefactoringBundle.message("move.single.class.or.package.name.label", UsageViewUtil.getType(firstElement),
235 UsageViewUtil.getLongName(firstElement)));
237 else if (psiElements.length > 1) {
238 myNameLabel.setText(psiElements[0] instanceof PsiClass
239 ? RefactoringBundle.message("move.specified.classes")
240 : RefactoringBundle.message("move.specified.packages"));
242 selectInitialCard();
244 myCbSearchInComments.setSelected(searchInComments);
245 myCbSearchTextOccurences.setSelected(searchForTextOccurences);
247 if (getSourceRoots().length == 1) {
248 myCbMoveToAnotherSourceFolder.setSelected(false);
249 myCbMoveToAnotherSourceFolder.setEnabled(false);
251 else {
252 myCbMoveToAnotherSourceFolder.setSelected(!isTargetDirectoryFixed);
255 validateButtons();
256 myHelpID = helpID;
259 protected void doHelpAction() {
260 HelpManager.getInstance().invokeHelp(myHelpID);
263 protected final boolean isSearchInComments() {
264 return myCbSearchInComments.isSelected();
267 @Override
268 protected void canRun() throws ConfigurationException {
269 if (isMoveToPackage()) {
270 String name = getTargetPackage().trim();
271 if (name.length() == 0 || !JavaPsiFacade.getInstance(myManager.getProject()).getNameHelper().isQualifiedName(name)) {
272 throw new ConfigurationException("\'" + StringUtil.last(name, 10, true) + "\' is invalid destination package name");
275 else {
276 if (findTargetClass() == null) throw new ConfigurationException("Destination class not found");
277 final String validationError = getValidationError();
278 if (validationError != null) throw new ConfigurationException(validationError);
282 protected void validateButtons() {
283 super.validateButtons();
284 setErrorText(getValidationError());
287 @Nullable
288 private String getValidationError() {
289 if (!isMoveToPackage()) {
290 return verifyInnerClassDestination();
292 return null;
295 @Nullable
296 private PsiClass findTargetClass() {
297 String name = myInnerClassChooser.getText().trim();
298 return JavaPsiFacade.getInstance(myManager.getProject()).findClass(name, ProjectScope.getProjectScope(myProject));
301 protected boolean isMoveToPackage() {
302 return myHavePackages || myToPackageRadioButton.isSelected();
305 protected String getTargetPackage() {
306 return myHavePackages ? myWithBrowseButtonReference.getText() : myClassPackageChooser.getText();
309 @Nullable
310 private static String verifyDestinationForElement(final PsiElement element, final MoveDestination moveDestination) {
311 final String message;
312 if (element instanceof PsiDirectory) {
313 message = moveDestination.verify((PsiDirectory)element);
315 else if (element instanceof PsiPackage) {
316 message = moveDestination.verify((PsiPackage)element);
318 else {
319 message = moveDestination.verify(element.getContainingFile());
321 return message;
324 protected void doAction() {
325 if (isMoveToPackage()) {
326 invokeMoveToPackage();
328 else {
329 invokeMoveToInner();
333 private void invokeMoveToPackage() {
334 final MoveDestination destination = selectDestination();
335 if (destination == null) return;
337 saveRefactoringSettings();
338 PsiManager manager = PsiManager.getInstance(getProject());
339 for (final PsiElement element : myElementsToMove) {
340 String message = verifyDestinationForElement(element, destination);
341 if (message != null) {
342 String helpId = HelpID.getMoveHelpID(myElementsToMove[0]);
343 CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), message, helpId, getProject());
344 return;
347 try {
348 for (PsiElement element : myElementsToMove) {
349 if (element instanceof PsiClass) {
350 final PsiClass aClass = (PsiClass)element;
351 /*PsiElement toAdd;
352 if (aClass.getContainingFile() instanceof PsiJavaFile && ((PsiJavaFile)aClass.getContainingFile()).getClasses().length > 1) {
353 toAdd = aClass;
355 else {
356 toAdd = aClass.getContainingFile();
359 final PsiDirectory targetDirectory = destination.getTargetIfExists(element.getContainingFile());
360 if (targetDirectory != null) {
361 manager.checkMove(aClass, targetDirectory);
366 MoveClassesOrPackagesProcessor processor = createMoveToPackageProcessor(destination, myElementsToMove, myMoveCallback);
367 if (processor.verifyValidPackageName()) {
368 invokeRefactoring(processor);
371 catch (IncorrectOperationException e) {
372 String helpId = HelpID.getMoveHelpID(myElementsToMove[0]);
373 CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), helpId, getProject());
377 //for scala plugin
378 protected MoveClassesOrPackagesProcessor createMoveToPackageProcessor(MoveDestination destination, final PsiElement[] elementsToMove,
379 final MoveCallback callback) {
380 return new MoveClassesOrPackagesProcessor(getProject(), elementsToMove, destination,
381 isSearchInComments(), isSearchInNonJavaFiles(),
382 callback);
385 private void saveRefactoringSettings() {
386 final JavaRefactoringSettings refactoringSettings = JavaRefactoringSettings.getInstance();
387 final boolean searchInComments = isSearchInComments();
388 final boolean searchForTextOccurences = isSearchInNonJavaFiles();
389 refactoringSettings.MOVE_SEARCH_IN_COMMENTS = searchInComments;
390 refactoringSettings.MOVE_SEARCH_FOR_TEXT = searchForTextOccurences;
391 refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages();
394 @Nullable
395 private String verifyInnerClassDestination() {
396 PsiClass targetClass = findTargetClass();
397 if (targetClass == null) return null;
399 for (PsiElement element : myElementsToMove) {
400 if (PsiTreeUtil.isAncestor(element, targetClass, false)) {
401 return RefactoringBundle.message("move.class.to.inner.move.to.self.error");
403 final Language targetClassLanguage = targetClass.getLanguage();
404 if (!element.getLanguage().equals(targetClassLanguage)) {
405 return RefactoringBundle
406 .message("move.to.different.language", UsageViewUtil.getType(element), ((PsiClass)element).getQualifiedName(),
407 targetClass.getQualifiedName());
409 if (element.getLanguage().equals(Language.findLanguageByID("Groovy"))) {
410 return RefactoringBundle.message("dont.support.inner.classes", "Groovy");
414 while (targetClass != null) {
415 if (targetClass.getContainingClass() != null && !targetClass.hasModifierProperty(PsiModifier.STATIC)) {
416 return RefactoringBundle.message("move.class.to.inner.nonstatic.error");
418 targetClass = targetClass.getContainingClass();
421 return null;
424 private void invokeMoveToInner() {
425 saveRefactoringSettings();
426 final PsiClass targetClass = findTargetClass();
427 final PsiClass[] classesToMove = new PsiClass[myElementsToMove.length];
428 for (int i = 0; i < myElementsToMove.length; i++) {
429 classesToMove[i] = (PsiClass)myElementsToMove[i];
431 invokeRefactoring(createMoveToInnerProcessor(targetClass, classesToMove, myMoveCallback));
434 //for scala plugin
435 protected MoveClassToInnerProcessor createMoveToInnerProcessor(PsiClass destination, @NotNull PsiClass[] classesToMove, @Nullable final MoveCallback callback) {
436 return new MoveClassToInnerProcessor(getProject(), classesToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), callback);
439 protected final boolean isSearchInNonJavaFiles() {
440 return myCbSearchTextOccurences.isSelected();
443 private MoveDestination selectDestination() {
444 final String packageName = getTargetPackage().trim();
445 if (packageName.length() > 0 && !JavaPsiFacade.getInstance(myManager.getProject()).getNameHelper().isQualifiedName(packageName)) {
446 Messages.showErrorDialog(myProject, RefactoringBundle.message("please.enter.a.valid.target.package.name"),
447 RefactoringBundle.message("move.title"));
448 return null;
450 RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, packageName);
451 PackageWrapper targetPackage = new PackageWrapper(myManager, packageName);
452 if (!targetPackage.exists()) {
453 final int ret = Messages.showYesNoDialog(myProject, RefactoringBundle.message("package.does.not.exist", packageName),
454 RefactoringBundle.message("move.title"), Messages.getQuestionIcon());
455 if (ret != 0) return null;
458 if (!myCbMoveToAnotherSourceFolder.isSelected()) {
459 return new MultipleRootsMoveDestination(targetPackage);
462 final VirtualFile[] contentSourceRoots = getSourceRoots();
463 if (contentSourceRoots.length == 1) {
464 return new AutocreatingSingleSourceRootMoveDestination(targetPackage, contentSourceRoots[0]);
466 final VirtualFile sourceRootForFile =
467 MoveClassesOrPackagesUtil.chooseSourceRoot(targetPackage, contentSourceRoots, myInitialTargetDirectory);
468 if (sourceRootForFile == null) return null;
469 return new AutocreatingSingleSourceRootMoveDestination(targetPackage, sourceRootForFile);
472 private VirtualFile[] getSourceRoots() {
473 return ProjectRootManager.getInstance(myProject).getContentSourceRoots();