diff --git a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassDialog.java b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassDialog.java index 9089af1e0f93..e2a6657c34c4 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassDialog.java +++ b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassDialog.java @@ -63,6 +63,7 @@ class ExtractClassDialog extends RefactoringDialog implements MemberInfoChangeLi private JCheckBox myGenerateAccessorsCb; private final JavaVisibilityPanel myVisibilityPanel; private final JCheckBox extractAsEnum; + private final JCheckBox createInner; private final List enumConstants = new ArrayList(); ExtractClassDialog(PsiClass sourceClass, PsiMember selectedMember) { @@ -129,6 +130,7 @@ class ExtractClassDialog extends RefactoringDialog implements MemberInfoChangeLi if (!hasConstants) { extractAsEnum.setVisible(false); } + createInner = new JCheckBox("Create nested class"); super.init(); validateButtons(); } @@ -152,9 +154,8 @@ class ExtractClassDialog extends RefactoringDialog implements MemberInfoChangeLi false), newClassName, myVisibilityPanel.getVisibility(), isGenerateAccessors(), - isExtractAsEnum() - ? enumConstants - : Collections.emptyList()); + isExtractAsEnum() ? enumConstants : Collections.emptyList()); + processor.setExtractInnerClass(createInner.isSelected()); if (processor.getCreatedClass() == null) { Messages.showErrorDialog(myVisibilityPanel, "Unable to create class with the given name"); classNameField.requestFocusInWindow(); @@ -243,12 +244,15 @@ class ExtractClassDialog extends RefactoringDialog implements MemberInfoChangeLi } protected JComponent createNorthPanel() { + final JPanel checkboxPanel = new JPanel(new BorderLayout()); + checkboxPanel.add(createInner, BorderLayout.WEST); + checkboxPanel.add(extractAsEnum, BorderLayout.EAST); FormBuilder builder = FormBuilder.createFormBuilder() .addComponent( JBLabelDecorator.createJBLabelDecorator(RefactorJBundle.message("extract.class.from.label", sourceClass.getQualifiedName())) .setBold(true)) .addLabeledComponent(RefactorJBundle.message("name.for.new.class.label"), classNameField, UIUtil.LARGE_VGAP) - .addLabeledComponent(new JLabel(), extractAsEnum) + .addLabeledComponent(new JLabel(), checkboxPanel) .addLabeledComponent(RefactorJBundle.message("package.for.new.class.label"), packageTextField); if (JavaProjectRootsUtil.getSuitableDestinationSourceRoots(myProject).size() > 1) { @@ -349,6 +353,14 @@ class ExtractClassDialog extends RefactoringDialog implements MemberInfoChangeLi table.repaint(); } }); + createInner.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final boolean isCreateInner = createInner.isSelected(); + packageTextField.setEnabled(!isCreateInner); + myDestinationFolderComboBox.setEnabled(!isCreateInner); + } + }); myGenerateAccessorsCb = new JCheckBox("Generate accessors"); myGenerateAccessorsCb.setMnemonic('G'); panel.add(myGenerateAccessorsCb, BorderLayout.SOUTH); diff --git a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassProcessor.java b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassProcessor.java index 03d4f641bf73..37fdab9adee5 100644 --- a/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/extractclass/ExtractClassProcessor.java @@ -84,6 +84,7 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { private boolean delegationRequired = false; private final ExtractEnumProcessor myExtractEnumProcessor; private final PsiClass myClass; + private boolean extractInnerClass; public ExtractClassProcessor(PsiClass sourceClass, List fields, @@ -150,6 +151,10 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { return myClass; } + public void setExtractInnerClass(boolean extractInnerClass) { + this.extractInnerClass = extractInnerClass; + } + @Override protected boolean preprocessUsages(@NotNull final Ref refUsages) { final MultiMap conflicts = new MultiMap(); @@ -166,7 +171,7 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { final Project project = sourceClass.getProject(); final GlobalSearchScope scope = GlobalSearchScope.allScope(project); final PsiClass existingClass = - JavaPsiFacade.getInstance(project).findClass(StringUtil.getQualifiedName(newPackageName, newClassName), scope); + JavaPsiFacade.getInstance(project).findClass(getQualifiedName(), scope); if (existingClass != null) { conflicts.putValue(existingClass, RefactorJBundle.message("cannot.perform.the.refactoring") + RefactorJBundle.message("there.already.exists.a.class.with.the.chosen.name")); @@ -193,6 +198,10 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { return showConflicts(conflicts, refUsages.get()); } + private String getQualifiedName() { + return extractInnerClass ? newClassName : StringUtil.getQualifiedName(newPackageName, newClassName); + } + private void calculateInitializersConflicts(MultiMap conflicts) { final PsiClassInitializer[] initializers = sourceClass.getInitializers(); @@ -412,7 +421,7 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { final String delegateVisibility = calculateDelegateVisibility(); if (delegateVisibility.length() > 0) fieldBuffer.append(delegateVisibility).append(' '); fieldBuffer.append("final "); - final String fullyQualifiedName = StringUtil.getQualifiedName(newPackageName, newClassName); + final String fullyQualifiedName = getQualifiedName(); fieldBuffer.append(fullyQualifiedName); if (!typeParams.isEmpty()) { fieldBuffer.append('<'); @@ -497,7 +506,7 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { assert innerName != null; final String sourceClassQualifiedName = sourceClass.getQualifiedName(); assert sourceClassQualifiedName != null; - final String newInnerClassName = StringUtil.getQualifiedName(newPackageName, newClassName) + innerName.substring(sourceClassQualifiedName.length()); + final String newInnerClassName = getQualifiedName() + innerName.substring(sourceClassQualifiedName.length()); boolean hasExternalReference = false; for (PsiReference reference : calls) { final PsiElement referenceElement = reference.getElement(); @@ -552,7 +561,7 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { final Project project = psiManager.getProject(); final GlobalSearchScope scope = GlobalSearchScope.allScope(project); final Iterable calls = ReferencesSearch.search(method, scope); - final String fullyQualifiedName = StringUtil.getQualifiedName(newPackageName, newClassName); + final String fullyQualifiedName = getQualifiedName(); for (PsiReference reference : calls) { final PsiElement referenceElement = reference.getElement(); @@ -594,7 +603,7 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { final Project project = psiManager.getProject(); final GlobalSearchScope scope = GlobalSearchScope.allScope(project); - final String qualifiedName = StringUtil.getQualifiedName(newPackageName, newClassName); + final String qualifiedName = getQualifiedName(); @NonNls String getter = null; if (myGenerateAccessors) { getter = GenerateMembersUtil.suggestGetterName(field); @@ -683,6 +692,19 @@ public class ExtractClassProcessor extends FixableUsagesRefactoringProcessor { } final String classString = extractedClassBuilder.buildBeanClass(); + if (extractInnerClass) { + final PsiFileFactory factory = PsiFileFactory.getInstance(project); + final PsiJavaFile newFile = (PsiJavaFile)factory.createFileFromText(newClassName + ".java", JavaFileType.INSTANCE, classString); + final PsiClass psiClass = newFile.getClasses()[0]; + if (!psiClass.isEnum()) { + final PsiModifierList modifierList = psiClass.getModifierList(); + assert modifierList != null; + modifierList.setModifierProperty(PsiModifier.STATIC, true); + } + final PsiElement addedClass = sourceClass.add(psiClass); + return (PsiClass)CodeStyleManager.getInstance(manager) + .reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(addedClass)); + } try { final PsiFile containingFile = sourceClass.getContainingFile(); diff --git a/java/java-tests/testData/refactoring/extractClass/innerClass/after/Test.java b/java/java-tests/testData/refactoring/extractClass/innerClass/after/Test.java new file mode 100644 index 000000000000..4ce6c8fff1af --- /dev/null +++ b/java/java-tests/testData/refactoring/extractClass/innerClass/after/Test.java @@ -0,0 +1,29 @@ +class Test { + final Extracted extracted = new Extracted(this); + + void bar(){ + System.out.println(extracted.getMyT()); + } + + String foo() { + return ""; + } + + void bazz() { + bar(); + } + + public static class Extracted { + private final Test test; + String myT; + + public String getMyT() { + return myT; + } + + public Extracted(Test test) { + this.test = test; + this.myT = test.foo(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/extractClass/innerClass/before/Test.java b/java/java-tests/testData/refactoring/extractClass/innerClass/before/Test.java new file mode 100644 index 000000000000..3311280a1ee7 --- /dev/null +++ b/java/java-tests/testData/refactoring/extractClass/innerClass/before/Test.java @@ -0,0 +1,15 @@ +class Test { + String myT = foo(); + + void bar(){ + System.out.println(myT); + } + + String foo() { + return ""; + } + + void bazz() { + bar(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java b/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java index a3a87316f1cc..504f85df5aa3 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/ExtractClassTest.java @@ -192,11 +192,37 @@ public class ExtractClassTest extends MultiFileTestCase{ }); } - private static void doTest(final PsiClass aClass, final ArrayList methods, final ArrayList fields, final String conflicts, + public void testInnerClass() throws Exception { + doTest((rootDir, rootAfter) -> { + PsiClass aClass = myJavaFacade.findClass("Test", GlobalSearchScope.projectScope(myProject)); + + assertNotNull("Class Test not found", aClass); + + final ArrayList fields = new ArrayList<>(); + fields.add(aClass.findFieldByName("myT", false)); + + doTest(aClass, new ArrayList<>(), fields, null, true, true); + }); + } + + private static void doTest(final PsiClass aClass, + final ArrayList methods, + final ArrayList fields, + final String conflicts, boolean generateGettersSetters) { + doTest(aClass, methods, fields, conflicts, generateGettersSetters, false); + } + + private static void doTest(final PsiClass aClass, + final ArrayList methods, + final ArrayList fields, + final String conflicts, + boolean generateGettersSetters, + boolean inner) { try { ExtractClassProcessor processor = new ExtractClassProcessor(aClass, fields, methods, new ArrayList<>(), StringUtil.getPackageName(aClass.getQualifiedName()), null, "Extracted", null, generateGettersSetters, Collections.emptyList()); + processor.setExtractInnerClass(inner); processor.run(); LocalFileSystem.getInstance().refresh(false); FileDocumentManager.getInstance().saveAllDocuments();