/* * Copyright 2010-2016 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 org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.ide.util.ClassFilter; import com.intellij.ide.util.TreeClassChooser; import com.intellij.ide.util.TreeJavaClassChooserDialog; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.AbstractMemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoChange; import com.intellij.refactoring.move.MoveCallback; import com.intellij.refactoring.move.MoveHandler; import com.intellij.refactoring.ui.RefactoringDialog; import kotlin.collections.CollectionsKt; import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.asJava.LightClassUtilsKt; import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration; import org.jetbrains.kotlin.idea.completion.CompletionUtilsKt; import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject; import org.jetbrains.kotlin.idea.projectView.KtClassOrObjectTreeNode; import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel; import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable; import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*; import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton; import org.jetbrains.kotlin.psi.*; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; public class MoveKotlinNestedClassesDialog extends RefactoringDialog { private static final String RECENTS_KEY = MoveKotlinNestedClassesDialog.class.getName() + ".RECENTS_KEY"; private final KtClassOrObject originalClass; private final MoveCallback moveCallback; private JPanel mainPanel; private JTextField originalClassField; private JPanel membersInfoPanel; private KotlinTypeReferenceEditorComboWithBrowseButton targetClassChooser; private JCheckBox openInEditorCheckBox; private JPanel targetClassChooserPanel; private KotlinMemberSelectionTable memberTable; private PsiElement targetClass; public MoveKotlinNestedClassesDialog( @NotNull Project project, @NotNull List<KtClassOrObject> elementsToMove, @NotNull KtClassOrObject originalClass, @NotNull KtClassOrObject targetClass, @Nullable MoveCallback moveCallback ) { super(project, true); this.originalClass = originalClass; this.targetClass = targetClass; this.moveCallback = moveCallback; init(); setTitle(MoveHandler.REFACTORING_NAME); initClassChooser(targetClass); initMemberInfo(elementsToMove); validateButtons(); } private void initClassChooser(@NotNull KtClassOrObject initialTargetClass) { //noinspection ConstantConditions originalClassField.setText(originalClass.getFqName().asString()); //noinspection ConstantConditions targetClassChooser = new KotlinTypeReferenceEditorComboWithBrowseButton( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { TreeClassChooser chooser = new TreeJavaClassChooserDialog( RefactoringBundle.message("choose.destination.class"), myProject, GlobalSearchScope.projectScope(myProject), new ClassFilter() { @Override public boolean isAccepted(PsiClass aClass) { if (!(aClass instanceof KtLightClassForSourceDeclaration)) return false; KtClassOrObject classOrObject = ((KtLightClassForSourceDeclaration) aClass).getKotlinOrigin(); if (classOrObject instanceof KtObjectDeclaration) { return !((KtObjectDeclaration) classOrObject).isObjectLiteral(); } if (classOrObject instanceof KtClass) { KtClass ktClass = (KtClass) classOrObject; return !(ktClass.isInner() || ktClass.isAnnotation()); } return false; } }, null, null, true ) { @Nullable @Override protected PsiClass getSelectedFromTreeUserObject(DefaultMutableTreeNode node) { PsiClass psiClass = super.getSelectedFromTreeUserObject(node); if (psiClass != null) return psiClass; Object userObject = node.getUserObject(); if (!(userObject instanceof KtClassOrObjectTreeNode)) return null; return LightClassUtilsKt.toLightClass(((KtClassOrObjectTreeNode) userObject).getValue()); } }; chooser.selectDirectory((targetClass != null ? targetClass : originalClass).getContainingFile().getContainingDirectory()); chooser.showDialog(); PsiClass aClass = chooser.getSelected(); if (aClass instanceof KtLightClassForSourceDeclaration) { targetClass = ((KtLightClassForSourceDeclaration) aClass).getKotlinOrigin(); targetClassChooser.setText(aClass.getQualifiedName()); } else { targetClass = aClass; } } }, initialTargetClass.getFqName().asString(), originalClass, RECENTS_KEY); KtTypeCodeFragment codeFragment = targetClassChooser.getCodeFragment(); if (codeFragment != null) { CompletionUtilsKt.setExtraCompletionFilter( codeFragment, new Function1<LookupElement, Boolean>() { @Override public Boolean invoke(LookupElement lookupElement) { Object lookupObject = lookupElement.getObject(); if (!(lookupObject instanceof DeclarationLookupObject)) return false; PsiElement psiElement = ((DeclarationLookupObject) lookupObject).getPsiElement(); if (!(psiElement instanceof KtClassOrObject)) return false; return KotlinRefactoringUtilKt.canRefactor(psiElement); } } ); } targetClassChooser.getChildComponent().getDocument().addDocumentListener( new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { PsiClass aClass = JavaPsiFacade .getInstance(myProject) .findClass(targetClassChooser.getText(), GlobalSearchScope.projectScope(myProject)); targetClass = aClass instanceof KtLightClassForSourceDeclaration ? ((KtLightClassForSourceDeclaration) aClass).getKotlinOrigin() : aClass; validateButtons(); } } ); targetClassChooserPanel.add(targetClassChooser); } private void initMemberInfo(@NotNull final List<KtClassOrObject> elementsToMove) { List<KotlinMemberInfo> memberInfos = CollectionsKt.mapNotNull( originalClass.getDeclarations(), new Function1<KtDeclaration, KotlinMemberInfo>() { @Override public KotlinMemberInfo invoke(KtDeclaration declaration) { if (!(declaration instanceof KtClassOrObject)) return null; KtClassOrObject classOrObject = (KtClassOrObject) declaration; if (classOrObject instanceof KtClass && ((KtClass) classOrObject).isInner()) return null; if (classOrObject instanceof KtObjectDeclaration && ((KtObjectDeclaration) classOrObject).isCompanion()) return null; KotlinMemberInfo memberInfo = new KotlinMemberInfo(classOrObject, false); memberInfo.setChecked(elementsToMove.contains(declaration)); return memberInfo; } } ); KotlinMemberSelectionPanel selectionPanel = new KotlinMemberSelectionPanel(getTitle(), memberInfos, null); memberTable = selectionPanel.getTable(); MemberInfoModelImpl memberInfoModel = new MemberInfoModelImpl(); memberInfoModel.memberInfoChanged(new MemberInfoChange<KtNamedDeclaration, KotlinMemberInfo>(memberInfos)); selectionPanel.getTable().setMemberInfoModel(memberInfoModel); selectionPanel.getTable().addMemberInfoChangeListener(memberInfoModel); membersInfoPanel.add(selectionPanel, BorderLayout.CENTER); } private List<KtClassOrObject> getSelectedElementsToMove() { return CollectionsKt.map( memberTable.getSelectedMemberInfos(), new Function1<KotlinMemberInfo, KtClassOrObject>() { @Override public KtClassOrObject invoke(KotlinMemberInfo info) { return (KtClassOrObject) info.getMember(); } } ); } @Override protected JComponent createCenterPanel() { return mainPanel; } @Override protected String getDimensionServiceKey() { return "#" + getClass().getName(); } @Override protected void canRun() throws ConfigurationException { if (targetClass == null) throw new ConfigurationException("No destination class specified"); if (!(targetClass instanceof KtClassOrObject)) throw new ConfigurationException("Destination class must be a Kotlin class"); if (originalClass == targetClass) { throw new ConfigurationException(RefactoringBundle.message("source.and.destination.classes.should.be.different")); } for (KtClassOrObject classOrObject : getSelectedElementsToMove()) { if (PsiTreeUtil.isAncestor(classOrObject, targetClass, false)) { throw new ConfigurationException("Cannot move nested class " + classOrObject.getName() + " to itself"); } } } @Override protected void doAction() { List<KtClassOrObject> elementsToMove = getSelectedElementsToMove(); KotlinMoveTarget target = new KotlinMoveTargetForExistingElement((KtClassOrObject) targetClass); MoveDeclarationsDelegate.NestedClass delegate = new MoveDeclarationsDelegate.NestedClass(); MoveDeclarationsDescriptor descriptor = new MoveDeclarationsDescriptor( myProject, elementsToMove, target, delegate, false, false, false, false, moveCallback, openInEditorCheckBox.isSelected() ); invokeRefactoring(new MoveKotlinDeclarationsProcessor(descriptor, Mover.Default.INSTANCE)); } @Override public JComponent getPreferredFocusedComponent() { return targetClassChooser.getChildComponent(); } private static class MemberInfoModelImpl extends AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo> { } }