| /* |
| * Copyright 2000-2013 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.lang.Language; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.event.DocumentAdapter; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.help.HelpManager; |
| import com.intellij.openapi.options.ConfigurationException; |
| 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.ui.Messages; |
| import com.intellij.openapi.util.Pass; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.ProjectScope; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.refactoring.*; |
| import com.intellij.refactoring.move.MoveCallback; |
| import com.intellij.refactoring.move.MoveClassesOrPackagesCallback; |
| import com.intellij.refactoring.move.MoveHandler; |
| import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil; |
| import com.intellij.refactoring.ui.ClassNameReferenceEditor; |
| import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo; |
| import com.intellij.refactoring.ui.RefactoringDialog; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import com.intellij.ui.ComboboxWithBrowseButton; |
| import com.intellij.ui.RecentsManager; |
| import com.intellij.ui.ReferenceEditorComboWithBrowseButton; |
| import com.intellij.ui.ReferenceEditorWithBrowseButton; |
| import com.intellij.usageView.UsageViewUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.hash.HashSet; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.List; |
| import java.util.Set; |
| |
| public class MoveClassesOrPackagesDialog extends RefactoringDialog { |
| @NonNls private static final String RECENTS_KEY = "MoveClassesOrPackagesDialog.RECENTS_KEY"; |
| private final PsiElement[] myElementsToMove; |
| private final MoveCallback myMoveCallback; |
| |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog"); |
| |
| |
| private JLabel myNameLabel; |
| private ReferenceEditorComboWithBrowseButton myWithBrowseButtonReference; |
| private JCheckBox myCbSearchInComments; |
| private JCheckBox myCbSearchTextOccurences; |
| private String myHelpID; |
| private final boolean mySearchTextOccurencesEnabled; |
| private final PsiManager myManager; |
| private JPanel myMainPanel; |
| private JRadioButton myToPackageRadioButton; |
| private JRadioButton myMakeInnerClassOfRadioButton; |
| private ReferenceEditorComboWithBrowseButton myClassPackageChooser; |
| private JPanel myCardPanel; |
| private ReferenceEditorWithBrowseButton myInnerClassChooser; |
| private JPanel myMoveClassPanel; |
| private JPanel myMovePackagePanel; |
| private ComboboxWithBrowseButton myDestinationFolderCB; |
| private JPanel myTargetPanel; |
| private JLabel myTargetDestinationLabel; |
| private boolean myHavePackages; |
| private boolean myTargetDirectoryFixed; |
| private boolean mySuggestToMoveToAnotherRoot; |
| |
| public MoveClassesOrPackagesDialog(Project project, |
| boolean searchTextOccurences, |
| PsiElement[] elementsToMove, |
| final PsiElement initialTargetElement, |
| MoveCallback moveCallback) { |
| super(project, true); |
| myElementsToMove = elementsToMove; |
| myMoveCallback = moveCallback; |
| myManager = PsiManager.getInstance(myProject); |
| setTitle(MoveHandler.REFACTORING_NAME); |
| mySearchTextOccurencesEnabled = searchTextOccurences; |
| |
| selectInitialCard(); |
| |
| init(); |
| |
| if (initialTargetElement instanceof PsiClass) { |
| myMakeInnerClassOfRadioButton.setSelected(true); |
| |
| myInnerClassChooser.setText(((PsiClass)initialTargetElement).getQualifiedName()); |
| |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| public void run() { |
| myInnerClassChooser.requestFocus(); |
| } |
| }, ModalityState.stateForComponent(myMainPanel)); |
| } |
| else if (initialTargetElement instanceof PsiPackage) { |
| myClassPackageChooser.setText(((PsiPackage)initialTargetElement).getQualifiedName()); |
| } |
| |
| updateControlsEnabled(); |
| myToPackageRadioButton.addActionListener(new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| updateControlsEnabled(); |
| myClassPackageChooser.requestFocus(); |
| } |
| }); |
| myMakeInnerClassOfRadioButton.addActionListener(new ActionListener() { |
| public void actionPerformed(ActionEvent e) { |
| updateControlsEnabled(); |
| myInnerClassChooser.requestFocus(); |
| } |
| }); |
| } |
| |
| private void updateControlsEnabled() { |
| myClassPackageChooser.setEnabled(myToPackageRadioButton.isSelected()); |
| myInnerClassChooser.setEnabled(myMakeInnerClassOfRadioButton.isSelected()); |
| UIUtil.setEnabled(myTargetPanel, isMoveToPackage() && getSourceRoots().size() > 1 && !myTargetDirectoryFixed, true); |
| validateButtons(); |
| } |
| |
| private void selectInitialCard() { |
| myHavePackages = false; |
| for (PsiElement psiElement : myElementsToMove) { |
| if (!(psiElement instanceof PsiClass)) { |
| myHavePackages = true; |
| break; |
| } |
| } |
| CardLayout cardLayout = (CardLayout)myCardPanel.getLayout(); |
| cardLayout.show(myCardPanel, myHavePackages ? "Package" : "Class"); |
| } |
| |
| public JComponent getPreferredFocusedComponent() { |
| return myHavePackages ? myWithBrowseButtonReference.getChildComponent() : myClassPackageChooser.getChildComponent(); |
| } |
| |
| protected JComponent createCenterPanel() { |
| boolean isDestinationVisible = getSourceRoots().size() > 1; |
| myDestinationFolderCB.setVisible(isDestinationVisible); |
| myTargetDestinationLabel.setVisible(isDestinationVisible); |
| return null; |
| } |
| |
| private void createUIComponents() { |
| myMainPanel = new JPanel(); |
| |
| myWithBrowseButtonReference = createPackageChooser(); |
| myClassPackageChooser = createPackageChooser(); |
| |
| GlobalSearchScope scope = JavaProjectRootsUtil.getScopeWithoutGeneratedSources(ProjectScope.getProjectScope(myProject), myProject); |
| myInnerClassChooser = new ClassNameReferenceEditor(myProject, null, scope); |
| myInnerClassChooser.addDocumentListener(new DocumentAdapter() { |
| public void documentChanged(DocumentEvent e) { |
| validateButtons(); |
| } |
| }); |
| |
| // override CardLayout sizing behavior |
| myCardPanel = new JPanel() { |
| public Dimension getMinimumSize() { |
| return myHavePackages ? myMovePackagePanel.getMinimumSize() : myMoveClassPanel.getMinimumSize(); |
| } |
| |
| public Dimension getPreferredSize() { |
| return myHavePackages ? myMovePackagePanel.getPreferredSize() : myMoveClassPanel.getPreferredSize(); |
| } |
| }; |
| |
| myDestinationFolderCB = new DestinationFolderComboBox() { |
| @Override |
| public String getTargetPackage() { |
| return MoveClassesOrPackagesDialog.this.getTargetPackage(); |
| } |
| }; |
| } |
| |
| private ReferenceEditorComboWithBrowseButton createPackageChooser() { |
| final ReferenceEditorComboWithBrowseButton packageChooser = |
| new PackageNameReferenceEditorCombo("", myProject, RECENTS_KEY, RefactoringBundle.message("choose.destination.package")); |
| final Document document = packageChooser.getChildComponent().getDocument(); |
| document.addDocumentListener(new DocumentAdapter() { |
| public void documentChanged(DocumentEvent e) { |
| validateButtons(); |
| } |
| }); |
| |
| return packageChooser; |
| } |
| |
| protected JComponent createNorthPanel() { |
| if (!mySearchTextOccurencesEnabled) { |
| myCbSearchTextOccurences.setEnabled(false); |
| myCbSearchTextOccurences.setVisible(false); |
| myCbSearchTextOccurences.setSelected(false); |
| } |
| |
| return myMainPanel; |
| } |
| |
| protected String getDimensionServiceKey() { |
| return myHavePackages |
| ? "#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog.packages" |
| : "#com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesDialog.classes"; |
| } |
| |
| public void setData(PsiElement[] psiElements, |
| String targetPackageName, |
| PsiDirectory initialTargetDirectory, |
| boolean isTargetDirectoryFixed, |
| boolean suggestToMoveToAnotherRoot, |
| boolean searchInComments, |
| boolean searchForTextOccurences, |
| String helpID) { |
| myTargetDirectoryFixed = isTargetDirectoryFixed; |
| mySuggestToMoveToAnotherRoot = suggestToMoveToAnotherRoot; |
| if (targetPackageName.length() != 0) { |
| myWithBrowseButtonReference.prependItem(targetPackageName); |
| myClassPackageChooser.prependItem(targetPackageName); |
| } |
| |
| String nameFromCallback = myMoveCallback instanceof MoveClassesOrPackagesCallback |
| ? ((MoveClassesOrPackagesCallback)myMoveCallback).getElementsToMoveName() |
| : null; |
| if (nameFromCallback != null) { |
| myNameLabel.setText(nameFromCallback); |
| } |
| else if (psiElements.length == 1) { |
| PsiElement firstElement = psiElements[0]; |
| if (firstElement instanceof PsiClass) { |
| LOG.assertTrue(!MoveClassesOrPackagesImpl.isClassInnerOrLocal((PsiClass)firstElement)); |
| } |
| else { |
| PsiElement parent = firstElement.getParent(); |
| LOG.assertTrue(parent != null); |
| } |
| myNameLabel.setText(RefactoringBundle.message("move.single.class.or.package.name.label", UsageViewUtil.getType(firstElement), |
| UsageViewUtil.getLongName(firstElement))); |
| } |
| else if (psiElements.length > 1) { |
| myNameLabel.setText(psiElements[0] instanceof PsiClass |
| ? RefactoringBundle.message("move.specified.classes") |
| : RefactoringBundle.message("move.specified.packages")); |
| } |
| selectInitialCard(); |
| |
| myCbSearchInComments.setSelected(searchInComments); |
| myCbSearchTextOccurences.setSelected(searchForTextOccurences); |
| |
| if (initialTargetDirectory != null && |
| JavaMoveClassesOrPackagesHandler.packageHasMultipleDirectoriesInModule(myProject, initialTargetDirectory)) { |
| final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); |
| final Set<VirtualFile> initialRoots = new HashSet<VirtualFile>(); |
| collectSourceRoots(psiElements, fileIndex, initialRoots); |
| if (initialRoots.size() > 1) { |
| initialTargetDirectory = null; |
| } |
| } |
| ((DestinationFolderComboBox)myDestinationFolderCB).setData(myProject, initialTargetDirectory, |
| new Pass<String>() { |
| @Override |
| public void pass(String s) { |
| setErrorText(s); |
| } |
| }, myHavePackages ? myWithBrowseButtonReference.getChildComponent() : myClassPackageChooser.getChildComponent()); |
| UIUtil.setEnabled(myTargetPanel, !getSourceRoots().isEmpty() && isMoveToPackage() && !isTargetDirectoryFixed, true); |
| validateButtons(); |
| myHelpID = helpID; |
| } |
| |
| private static void collectSourceRoots(PsiElement[] psiElements, ProjectFileIndex fileIndex, Set<VirtualFile> initialRoots) { |
| for (PsiElement element : psiElements) { |
| final VirtualFile file = PsiUtilCore.getVirtualFile(element); |
| if (file != null) { |
| final VirtualFile sourceRootForFile = fileIndex.getSourceRootForFile(file); |
| if (sourceRootForFile != null) { |
| initialRoots.add(sourceRootForFile); |
| } |
| } else if (element instanceof PsiDirectoryContainer) { |
| collectSourceRoots(((PsiDirectoryContainer)element).getDirectories(), fileIndex, initialRoots); |
| } |
| } |
| } |
| |
| protected void doHelpAction() { |
| HelpManager.getInstance().invokeHelp(myHelpID); |
| } |
| |
| protected final boolean isSearchInComments() { |
| return myCbSearchInComments.isSelected(); |
| } |
| |
| @Override |
| protected void canRun() throws ConfigurationException { |
| if (isMoveToPackage()) { |
| String name = getTargetPackage().trim(); |
| if (name.length() != 0 && !PsiNameHelper.getInstance(myManager.getProject()).isQualifiedName(name)) { |
| throw new ConfigurationException("\'" + name + "\' is invalid destination package name"); |
| } |
| } |
| else { |
| if (findTargetClass() == null) throw new ConfigurationException("Destination class not found"); |
| final String validationError = getValidationError(); |
| if (validationError != null) throw new ConfigurationException(validationError); |
| } |
| } |
| |
| protected void validateButtons() { |
| super.validateButtons(); |
| setErrorText(getValidationError()); |
| } |
| |
| @Nullable |
| private String getValidationError() { |
| if (!isMoveToPackage()) { |
| return verifyInnerClassDestination(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private PsiClass findTargetClass() { |
| String name = myInnerClassChooser.getText().trim(); |
| return JavaPsiFacade.getInstance(myManager.getProject()).findClass(name, ProjectScope.getProjectScope(myProject)); |
| } |
| |
| protected boolean isMoveToPackage() { |
| return myHavePackages || myToPackageRadioButton.isSelected(); |
| } |
| |
| protected String getTargetPackage() { |
| return myHavePackages ? myWithBrowseButtonReference.getText() : myClassPackageChooser.getText(); |
| } |
| |
| @Nullable |
| private static String verifyDestinationForElement(final PsiElement element, final MoveDestination moveDestination) { |
| final String message; |
| if (element instanceof PsiDirectory) { |
| message = moveDestination.verify((PsiDirectory)element); |
| } |
| else if (element instanceof PsiPackage) { |
| message = moveDestination.verify((PsiPackage)element); |
| } |
| else { |
| message = moveDestination.verify(element.getContainingFile()); |
| } |
| return message; |
| } |
| |
| protected void doAction() { |
| if (isMoveToPackage()) { |
| invokeMoveToPackage(); |
| } |
| else { |
| invokeMoveToInner(); |
| } |
| } |
| |
| private void invokeMoveToPackage() { |
| final MoveDestination destination = selectDestination(); |
| if (destination == null) return; |
| |
| saveRefactoringSettings(); |
| for (final PsiElement element : myElementsToMove) { |
| String message = verifyDestinationForElement(element, destination); |
| if (message != null) { |
| String helpId = HelpID.getMoveHelpID(myElementsToMove[0]); |
| CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), message, helpId, getProject()); |
| return; |
| } |
| } |
| try { |
| for (PsiElement element : myElementsToMove) { |
| if (element instanceof PsiClass) { |
| final PsiClass aClass = (PsiClass)element; |
| LOG.assertTrue(aClass.isPhysical(), aClass); |
| /*PsiElement toAdd; |
| if (aClass.getContainingFile() instanceof PsiJavaFile && ((PsiJavaFile)aClass.getContainingFile()).getClasses().length > 1) { |
| toAdd = aClass; |
| } |
| else { |
| toAdd = aClass.getContainingFile(); |
| }*/ |
| |
| final PsiDirectory targetDirectory = destination.getTargetIfExists(element.getContainingFile()); |
| if (targetDirectory != null) { |
| MoveFilesOrDirectoriesUtil.checkMove(aClass, targetDirectory); |
| } |
| } |
| } |
| |
| MoveClassesOrPackagesProcessor processor = createMoveToPackageProcessor(destination, myElementsToMove, myMoveCallback); |
| if (processor.verifyValidPackageName()) { |
| invokeRefactoring(processor); |
| } |
| } |
| catch (IncorrectOperationException e) { |
| String helpId = HelpID.getMoveHelpID(myElementsToMove[0]); |
| CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), helpId, getProject()); |
| } |
| } |
| |
| //for scala plugin |
| protected MoveClassesOrPackagesProcessor createMoveToPackageProcessor(MoveDestination destination, final PsiElement[] elementsToMove, |
| final MoveCallback callback) { |
| return new MoveClassesOrPackagesProcessor(getProject(), elementsToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), callback); |
| } |
| |
| private void saveRefactoringSettings() { |
| final JavaRefactoringSettings refactoringSettings = JavaRefactoringSettings.getInstance(); |
| final boolean searchInComments = isSearchInComments(); |
| final boolean searchForTextOccurences = isSearchInNonJavaFiles(); |
| refactoringSettings.MOVE_SEARCH_IN_COMMENTS = searchInComments; |
| refactoringSettings.MOVE_SEARCH_FOR_TEXT = searchForTextOccurences; |
| refactoringSettings.MOVE_PREVIEW_USAGES = isPreviewUsages(); |
| } |
| |
| @Nullable |
| private String verifyInnerClassDestination() { |
| PsiClass targetClass = findTargetClass(); |
| if (targetClass == null) return null; |
| |
| for (PsiElement element : myElementsToMove) { |
| if (PsiTreeUtil.isAncestor(element, targetClass, false)) { |
| return RefactoringBundle.message("move.class.to.inner.move.to.self.error"); |
| } |
| final Language targetClassLanguage = targetClass.getLanguage(); |
| if (!element.getLanguage().equals(targetClassLanguage)) { |
| return RefactoringBundle.message("move.to.different.language", UsageViewUtil.getType(element), |
| ((PsiClass)element).getQualifiedName(), targetClass.getQualifiedName()); |
| } |
| } |
| |
| while (targetClass != null) { |
| if (targetClass.getContainingClass() != null && !targetClass.hasModifierProperty(PsiModifier.STATIC)) { |
| return RefactoringBundle.message("move.class.to.inner.nonstatic.error"); |
| } |
| targetClass = targetClass.getContainingClass(); |
| } |
| |
| return null; |
| } |
| |
| private void invokeMoveToInner() { |
| saveRefactoringSettings(); |
| final PsiClass targetClass = findTargetClass(); |
| final PsiClass[] classesToMove = new PsiClass[myElementsToMove.length]; |
| for (int i = 0; i < myElementsToMove.length; i++) { |
| classesToMove[i] = (PsiClass)myElementsToMove[i]; |
| } |
| invokeRefactoring(createMoveToInnerProcessor(targetClass, classesToMove, myMoveCallback)); |
| } |
| |
| //for scala plugin |
| protected MoveClassToInnerProcessor createMoveToInnerProcessor(PsiClass destination, @NotNull PsiClass[] classesToMove, @Nullable final MoveCallback callback) { |
| return new MoveClassToInnerProcessor(getProject(), classesToMove, destination, isSearchInComments(), isSearchInNonJavaFiles(), callback); |
| } |
| |
| protected final boolean isSearchInNonJavaFiles() { |
| return myCbSearchTextOccurences.isSelected(); |
| } |
| |
| @Nullable |
| private MoveDestination selectDestination() { |
| final String packageName = getTargetPackage().trim(); |
| if (packageName.length() > 0 && !PsiNameHelper.getInstance(myManager.getProject()).isQualifiedName(packageName)) { |
| Messages.showErrorDialog(myProject, RefactoringBundle.message("please.enter.a.valid.target.package.name"), |
| RefactoringBundle.message("move.title")); |
| return null; |
| } |
| RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, packageName); |
| PackageWrapper targetPackage = new PackageWrapper(myManager, packageName); |
| if (!targetPackage.exists()) { |
| final int ret = Messages.showYesNoDialog(myProject, RefactoringBundle.message("package.does.not.exist", packageName), |
| RefactoringBundle.message("move.title"), Messages.getQuestionIcon()); |
| if (ret != Messages.YES) return null; |
| } |
| |
| return ((DestinationFolderComboBox)myDestinationFolderCB).selectDirectory(targetPackage, mySuggestToMoveToAnotherRoot); |
| } |
| |
| private List<VirtualFile> getSourceRoots() { |
| return JavaProjectRootsUtil.getSuitableDestinationSourceRoots(myProject); |
| } |
| } |