Java tutorial
/* * Copyright 2000-2009 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.extractclass; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.Messages; import com.intellij.psi.*; import com.intellij.psi.presentation.java.SymbolPresentationUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.PackageWrapper; import com.intellij.refactoring.RefactorJBundle; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.DelegatingMemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoBase; import com.intellij.refactoring.classMembers.MemberInfoChange; import com.intellij.refactoring.classMembers.MemberInfoChangeListener; import com.intellij.refactoring.move.moveClassesOrPackages.DestinationFolderComboBox; import com.intellij.refactoring.ui.JavaVisibilityPanel; import com.intellij.refactoring.ui.MemberSelectionPanel; import com.intellij.refactoring.ui.MemberSelectionTable; import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo; import com.intellij.refactoring.ui.RefactoringDialog; import com.intellij.refactoring.util.classMembers.MemberInfo; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.ReferenceEditorComboWithBrowseButton; import com.intellij.ui.components.JBLabelDecorator; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.FormBuilder; import com.intellij.util.ui.UIUtil; @SuppressWarnings({ "OverridableMethodCallInConstructor" }) class ExtractClassDialog extends RefactoringDialog implements MemberInfoChangeListener<PsiMember, MemberInfo> { private final Map<MemberInfoBase<PsiMember>, PsiMember> myMember2CauseMap = new HashMap<MemberInfoBase<PsiMember>, PsiMember>(); private final PsiClass sourceClass; private final List<MemberInfo> memberInfo; private final JTextField classNameField; private final ReferenceEditorComboWithBrowseButton packageTextField; private final DestinationFolderComboBox myDestinationFolderComboBox; private final JTextField sourceClassTextField = null; private JCheckBox myGenerateAccessorsCb; private final JavaVisibilityPanel myVisibilityPanel; private final JCheckBox extractAsEnum; private final List<MemberInfo> enumConstants = new ArrayList<MemberInfo>(); ExtractClassDialog(PsiClass sourceClass, PsiMember selectedMember) { super(sourceClass.getProject(), true); setModal(true); setTitle(RefactorJBundle.message("extract.class.title")); myVisibilityPanel = new JavaVisibilityPanel(true, true); myVisibilityPanel.setVisibility(null); this.sourceClass = sourceClass; final DocumentListener docListener = new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { validateButtons(); } }; classNameField = new JTextField(); final PsiFile file = sourceClass.getContainingFile(); final String text = file instanceof PsiJavaFile ? ((PsiJavaFile) file).getPackageName() : ""; packageTextField = new PackageNameReferenceEditorCombo(text, myProject, "ExtractClass.RECENTS_KEY", RefactorJBundle.message("choose.destination.package.label")); packageTextField.getChildComponent().getDocument() .addDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() { @Override public void documentChanged(com.intellij.openapi.editor.event.DocumentEvent e) { validateButtons(); } }); myDestinationFolderComboBox = new DestinationFolderComboBox() { @Override public String getTargetPackage() { return getPackageName(); } }; myDestinationFolderComboBox.setData(myProject, sourceClass.getContainingFile().getContainingDirectory(), packageTextField.getChildComponent()); classNameField.getDocument().addDocumentListener(docListener); final MemberInfo.Filter<PsiMember> filter = new MemberInfo.Filter<PsiMember>() { public boolean includeMember(PsiMember element) { if (element instanceof PsiMethod) { return !((PsiMethod) element).isConstructor() && ((PsiMethod) element).getBody() != null; } else if (element instanceof PsiField) { return true; } else if (element instanceof PsiClass) { return PsiTreeUtil.isAncestor(ExtractClassDialog.this.sourceClass, element, true); } return false; } }; memberInfo = MemberInfo.extractClassMembers(this.sourceClass, filter, false); extractAsEnum = new JCheckBox("Extract as enum"); boolean hasConstants = false; for (MemberInfo info : memberInfo) { final PsiMember member = info.getMember(); if (member.equals(selectedMember)) { info.setChecked(true); } if (!hasConstants && member instanceof PsiField && member.hasModifierProperty(PsiModifier.FINAL) && member.hasModifierProperty(PsiModifier.STATIC)) { hasConstants = true; } } if (!hasConstants) { extractAsEnum.setVisible(false); } super.init(); validateButtons(); } protected void doAction() { final List<PsiField> fields = getFieldsToExtract(); final List<PsiMethod> methods = getMethodsToExtract(); final List<PsiClass> classes = getClassesToExtract(); final String newClassName = getClassName(); final String packageName = getPackageName(); Collections.sort(enumConstants, new Comparator<MemberInfo>() { public int compare(MemberInfo o1, MemberInfo o2) { return o1.getMember().getTextOffset() - o2.getMember().getTextOffset(); } }); final ExtractClassProcessor processor = new ExtractClassProcessor(sourceClass, fields, methods, classes, packageName, myDestinationFolderComboBox .selectDirectory(new PackageWrapper(PsiManager.getInstance(myProject), packageName), false), newClassName, myVisibilityPanel.getVisibility(), isGenerateAccessors(), isExtractAsEnum() ? enumConstants : Collections.<MemberInfo>emptyList()); if (processor.getCreatedClass() == null) { Messages.showErrorDialog(myVisibilityPanel, "Unable to create class with the given name"); classNameField.requestFocusInWindow(); return; } invokeRefactoring(processor); } @Override protected void canRun() throws ConfigurationException { final Project project = sourceClass.getProject(); final PsiNameHelper nameHelper = PsiNameHelper.getInstance(project); final List<PsiMethod> methods = getMethodsToExtract(); final List<PsiField> fields = getFieldsToExtract(); final List<PsiClass> innerClasses = getClassesToExtract(); if (methods.isEmpty() && fields.isEmpty() && innerClasses.isEmpty()) { throw new ConfigurationException("Nothing found to extract"); } final String className = getClassName(); if (className.length() == 0 || !nameHelper.isIdentifier(className)) { throw new ConfigurationException("\'" + className + "\' is invalid extracted class name"); } /*final String packageName = getPackageName(); if (packageName.length() == 0 || !nameHelper.isQualifiedName(packageName)) { throw new ConfigurationException("\'" + packageName + "\' is invalid extracted class package name"); }*/ for (PsiClass innerClass : innerClasses) { if (className.equals(innerClass.getName())) { throw new ConfigurationException("Extracted class should have unique name. Name " + "\'" + className + "\' is already in use by one of the inner classes"); } } } @NotNull public String getPackageName() { return packageTextField.getText().trim(); } @NotNull public String getClassName() { return classNameField.getText().trim(); } public List<PsiField> getFieldsToExtract() { return getMembersToExtract(true, PsiField.class); } public <T> List<T> getMembersToExtract(final boolean checked, Class<T> memberClass) { final List<T> out = new ArrayList<T>(); for (MemberInfo info : memberInfo) { if (checked && !info.isChecked()) continue; if (!checked && info.isChecked()) continue; final PsiMember member = info.getMember(); if (memberClass.isAssignableFrom(member.getClass())) { out.add((T) member); } } return out; } public List<PsiMethod> getMethodsToExtract() { return getMembersToExtract(true, PsiMethod.class); } public List<PsiClass> getClassesToExtract() { return getMembersToExtract(true, PsiClass.class); } public List<PsiClassInitializer> getClassInitializersToExtract() { return getMembersToExtract(true, PsiClassInitializer.class); } public boolean isGenerateAccessors() { return myGenerateAccessorsCb.isSelected(); } public boolean isExtractAsEnum() { return extractAsEnum.isVisible() && extractAsEnum.isEnabled() && extractAsEnum.isSelected(); } protected String getDimensionServiceKey() { return "RefactorJ.ExtractClass"; } protected JComponent createNorthPanel() { 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(RefactorJBundle.message("package.for.new.class.label"), packageTextField); if (ProjectRootManager.getInstance(myProject).getContentSourceRoots().length > 1) { builder.addLabeledComponent(RefactoringBundle.message("target.destination.folder"), myDestinationFolderComboBox); } return builder.addVerticalGap(5).getPanel(); } protected JComponent createCenterPanel() { final JPanel panel = new JPanel(new BorderLayout()); final MemberSelectionPanel memberSelectionPanel = new MemberSelectionPanel( RefactorJBundle.message("members.to.extract.label"), memberInfo, "As enum") { @Override protected MemberSelectionTable createMemberSelectionTable(final List<MemberInfo> memberInfo, String abstractColumnHeader) { return new MemberSelectionTable(memberInfo, abstractColumnHeader) { @Nullable @Override protected Object getAbstractColumnValue(MemberInfo memberInfo) { if (isExtractAsEnum()) { final PsiMember member = memberInfo.getMember(); if (isConstantField(member)) { return Boolean.valueOf(enumConstants.contains(memberInfo)); } } return null; } @Override protected boolean isAbstractColumnEditable(int rowIndex) { final MemberInfo info = memberInfo.get(rowIndex); if (info.isChecked()) { final PsiMember member = info.getMember(); if (isConstantField(member)) { if (enumConstants.isEmpty()) return true; final MemberInfo currentEnumConstant = enumConstants.get(0); if (((PsiField) currentEnumConstant.getMember()).getType() .equals(((PsiField) member).getType())) return true; } } return false; } }; } }; final MemberSelectionTable table = memberSelectionPanel.getTable(); table.setMemberInfoModel(new DelegatingMemberInfoModel<PsiMember, MemberInfo>(table.getMemberInfoModel()) { @Override public int checkForProblems(@NotNull final MemberInfo member) { final PsiMember cause = getCause(member); if (member.isChecked() && cause != null) return ERROR; if (!member.isChecked() && cause != null) return WARNING; return OK; } @Override public String getTooltipText(final MemberInfo member) { final PsiMember cause = getCause(member); if (cause != null) { final String presentation = SymbolPresentationUtil.getSymbolPresentableText(cause); if (member.isChecked()) { return "Depends on " + presentation + " from " + sourceClass.getName(); } else { final String className = getClassName(); return "Depends on " + presentation + " from new class" + (className.length() > 0 ? ": " + className : ""); } } return null; } private PsiMember getCause(final MemberInfo member) { PsiMember cause = myMember2CauseMap.get(member); if (cause != null) return cause; final BackpointerUsageVisitor visitor; if (member.isChecked()) { visitor = new BackpointerUsageVisitor(getFieldsToExtract(), getClassesToExtract(), getMethodsToExtract(), sourceClass); } else { visitor = new BackpointerUsageVisitor(getMembersToExtract(false, PsiField.class), getMembersToExtract(false, PsiClass.class), getMembersToExtract(false, PsiMethod.class), sourceClass, false); } member.getMember().accept(visitor); cause = visitor.getCause(); myMember2CauseMap.put(member, cause); return cause; } }); panel.add(memberSelectionPanel, BorderLayout.CENTER); table.addMemberInfoChangeListener(this); extractAsEnum.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (extractAsEnum.isSelected()) { preselectOneTypeEnumConstants(); } table.repaint(); } }); myGenerateAccessorsCb = new JCheckBox("Generate accessors"); myGenerateAccessorsCb.setMnemonic('G'); panel.add(myGenerateAccessorsCb, BorderLayout.SOUTH); panel.add(myVisibilityPanel, BorderLayout.EAST); return panel; } private void preselectOneTypeEnumConstants() { if (enumConstants.isEmpty()) { MemberInfo selected = null; for (MemberInfo info : memberInfo) { if (info.isChecked()) { selected = info; break; } } if (selected != null && isConstantField(selected.getMember())) { enumConstants.add(selected); selected.setToAbstract(true); } } for (MemberInfo info : memberInfo) { final PsiMember member = info.getMember(); if (isConstantField(member)) { if (enumConstants.isEmpty() || ((PsiField) enumConstants.get(0).getMember()).getType() .equals(((PsiField) member).getType())) { if (!enumConstants.contains(info)) enumConstants.add(info); info.setToAbstract(true); } } } } private static boolean isConstantField(PsiMember member) { return member instanceof PsiField && member.hasModifierProperty(PsiModifier.STATIC) && // member.hasModifierProperty(PsiModifier.FINAL) && ((PsiField) member).hasInitializer(); } public JComponent getPreferredFocusedComponent() { return classNameField; } protected void doHelpAction() { final HelpManager helpManager = HelpManager.getInstance(); helpManager.invokeHelp(HelpID.ExtractClass); } public void memberInfoChanged(MemberInfoChange memberInfoChange) { validateButtons(); myMember2CauseMap.clear(); if (extractAsEnum.isVisible()) { for (Object info : memberInfoChange.getChangedMembers()) { if (((MemberInfo) info).isToAbstract()) { if (!enumConstants.contains(info)) { enumConstants.add((MemberInfo) info); } } else { enumConstants.remove((MemberInfo) info); } } extractAsEnum.setEnabled(canExtractEnum()); } } private boolean canExtractEnum() { final List<PsiField> fields = new ArrayList<PsiField>(); final List<PsiClass> innerClasses = new ArrayList<PsiClass>(); final List<PsiMethod> methods = new ArrayList<PsiMethod>(); for (MemberInfo info : memberInfo) { if (info.isChecked()) { final PsiMember member = info.getMember(); if (member instanceof PsiField) { fields.add((PsiField) member); } else if (member instanceof PsiMethod) { methods.add((PsiMethod) member); } else if (member instanceof PsiClass) { innerClasses.add((PsiClass) member); } } } return !new BackpointerUsageVisitor(fields, innerClasses, methods, sourceClass).backpointerRequired(); } }