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
;
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
) {
91 myElementsToMove
= elementsToMove
;
92 myMoveCallback
= moveCallback
;
93 myManager
= PsiManager
.getInstance(myProject
);
94 setTitle(MoveHandler
.REFACTORING_NAME
);
95 mySearchTextOccurencesEnabled
= searchTextOccurences
;
101 if (initialTargetElement
instanceof PsiClass
) {
102 myMakeInnerClassOfRadioButton
.setSelected(true);
104 myInnerClassChooser
.setText(((PsiClass
)initialTargetElement
).getQualifiedName());
106 ApplicationManager
.getApplication().invokeLater(new Runnable() {
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
);
138 private void selectInitialCard() {
139 myHavePackages
= false;
140 for (PsiElement psiElement
: myElementsToMove
) {
141 if (!(psiElement
instanceof PsiClass
)) {
142 myHavePackages
= true;
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() {
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
) {
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
) {
191 return packageChooser
;
194 protected JComponent
createNorthPanel() {
195 if (!mySearchTextOccurencesEnabled
) {
196 myCbSearchTextOccurences
.setEnabled(false);
197 myCbSearchTextOccurences
.setVisible(false);
198 myCbSearchTextOccurences
.setSelected(false);
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
,
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()
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"));
244 myCbSearchInComments
.setSelected(searchInComments
);
245 myCbSearchTextOccurences
.setSelected(searchForTextOccurences
);
247 if (getSourceRoots().length
== 1) {
248 myCbMoveToAnotherSourceFolder
.setSelected(false);
249 myCbMoveToAnotherSourceFolder
.setEnabled(false);
252 myCbMoveToAnotherSourceFolder
.setSelected(!isTargetDirectoryFixed
);
259 protected void doHelpAction() {
260 HelpManager
.getInstance().invokeHelp(myHelpID
);
263 protected final boolean isSearchInComments() {
264 return myCbSearchInComments
.isSelected();
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");
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());
288 private String
getValidationError() {
289 if (!isMoveToPackage()) {
290 return verifyInnerClassDestination();
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();
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
);
319 message
= moveDestination
.verify(element
.getContainingFile());
324 protected void doAction() {
325 if (isMoveToPackage()) {
326 invokeMoveToPackage();
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());
348 for (PsiElement element
: myElementsToMove
) {
349 if (element
instanceof PsiClass
) {
350 final PsiClass aClass
= (PsiClass
)element
;
352 if (aClass.getContainingFile() instanceof PsiJavaFile && ((PsiJavaFile)aClass.getContainingFile()).getClasses().length > 1) {
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());
378 protected MoveClassesOrPackagesProcessor
createMoveToPackageProcessor(MoveDestination destination
, final PsiElement
[] elementsToMove
,
379 final MoveCallback callback
) {
380 return new MoveClassesOrPackagesProcessor(getProject(), elementsToMove
, destination
,
381 isSearchInComments(), isSearchInNonJavaFiles(),
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();
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();
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
));
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"));
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();