/* * 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. */ /* * Extended by Julien Cohen (Ascola team, Univ. Nantes), Feb/March 2012. * Copyright 2012 Université de Nantes for those contributions. */ /* Up-to-date w.r.t. commit on 10 Apr 2012. */ /* Up-to-date w.r.t. commit on 14 Feb 2013. */ /* Up-to-date w.r.t. commit on 30 Aug 2013. */ /* Up-to-date w.r.t. commit on 14 Jul 2013. (not completely) : createNorthPanel and createCenterPanel not moved */ /* Up-to-date w.r.t. commit on 24 Sep 2013. */ /* Up-to-date w.r.t. commit on 1 Oct 2013. */ /* Up-to-date w.r.t. commit on 23 Oct 2013. */ /* Up-to-date w.r.t. commit on 7 Feb 2014. */ /* Up-to-date w.r.t. commit on 18 Jul 2014. */ /* Up-to-date w.r.t. commit on 5 Dec 2014. */ package com.intellij.refactoring.memberPullUp; import com.intellij.openapi.help.HelpManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.statistics.StatisticsInfo; import com.intellij.psi.statistics.StatisticsManager; import com.intellij.psi.util.MethodSignature; import com.intellij.psi.util.MethodSignatureUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.JavaRefactoringSettings; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.classMembers.MemberInfoModel; import com.intellij.refactoring.classMembers.MemberInfoChange; import com.intellij.refactoring.genUtils.SisterClassesUtil; import com.intellij.refactoring.ui.ClassCellRenderer; import com.intellij.refactoring.ui.DocCommentPanel; import com.intellij.refactoring.util.DocCommentPolicy; import com.intellij.refactoring.util.RefactoringHierarchyUtil; import com.intellij.refactoring.util.classMembers.InterfaceContainmentVerifier; import com.intellij.refactoring.util.classMembers.MemberInfo; import com.intellij.refactoring.util.classMembers.MemberInfoStorage; import com.intellij.refactoring.util.classMembers.UsesAndInterfacesDependencyMemberInfoModel; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.List; import com.intellij.ui.SeparatorFactory; import com.intellij.ui.components.JBList; import com.intellij.usageView.UsageViewUtil; import java.util.Collection; import com.intellij.refactoring.ui.CustomMemberSelectionPanel; import com.intellij.refactoring.ui.CustomMemberSelectionTable; import com.intellij.refactoring.ui.ShortClassCellRenderer; /** * @author dsl * Date: 18.06.2002 */ public class PullUpGenDialog extends PullUpDialogBase<MemberInfoStorage, MemberInfo, PsiMember, PsiClass> { private final Callback myCallback; private DocCommentPanel myJavaDocPanel; CustomMemberSelectionPanel myMemberSelectionPanel; // rem : initialized by createCenterPanel() // TODO : check that for the type (J) // WARNING : hides the inherited myMemberSelectionPanel JComboBox mySecondClassCombo; // FIXME : temporary, just to hide the private combo private final InterfaceContainmentVerifier myInterfaceContainmentVerifier = new InterfaceContainmentVerifier() { public boolean checkedInterfacesContain(PsiMethod psiMethod) { return PullUpGenProcessor.checkedInterfacesContain(myMemberInfos, psiMethod); // TODO : check that (J) } }; private static final String PULL_UP_STATISTICS_KEY = "pull.up##"; //Julien : to display the list of sister classes. JList mySisterClassList; public interface Callback { boolean checkConflicts(PullUpGenDialog dialog); } public PullUpGenDialog(Project project, PsiClass aClass, List<PsiClass> superClasses, MemberInfoStorage memberInfoStorage, Callback callback) { super(project, aClass, superClasses, memberInfoStorage, JavaPullUpGenHandler.REFACTORING_NAME); // TODO : check that (J) myCallback = callback; init(); // Julien fillAllAnalyses(); } // removed in community by commit on 14 Jul 2013 but I keep it to branch on the second class combo. @Override @NotNull public PsiClass getSuperClass() { if (mySecondClassCombo != null) { return (PsiClass) mySecondClassCombo.getSelectedItem(); // J } else { return null; // FIXME } } public int getJavaDocPolicy() { return myJavaDocPanel.getPolicy(); } protected String getDimensionServiceKey() { return "#com.intellij.refactoring.memberPullUp.PullUpGenDialog"; } InterfaceContainmentVerifier getContainmentVerifier() { return myInterfaceContainmentVerifier; } // The north panel contains the selector for the target super class (but not the member selection panel). // I (Julien) add the sister classes panel. // Removed in community commit 14 Jul 2013 (moved to PullUpDialogBase). @Override protected JComponent createNorthPanel() { JPanel panel = new JPanel(); panel.setLayout(new GridBagLayout()); GridBagConstraints gbConstraints = new GridBagConstraints(); gbConstraints.insets = new Insets(4, 0, 4, 8); // external paddings gbConstraints.weighty = 1; gbConstraints.weightx = 1; gbConstraints.gridy = 0; // index of te first element to be displayed gbConstraints.gridwidth = 1 ;//GridBagConstraints.REMAINDER; (CHANGED JULIEN) gbConstraints.fill = GridBagConstraints.NONE ;// GridBagConstraints.BOTH;(CHANGED JULIEN) gbConstraints.anchor = GridBagConstraints.WEST; final JLabel classComboLabel = new JLabel(); panel.add(classComboLabel, gbConstraints); mySecondClassCombo = new JComboBox(mySuperClasses.toArray()); // cop (1) mySecondClassCombo.setRenderer(new ClassCellRenderer(mySecondClassCombo.getRenderer())); //cop (2) classComboLabel.setText(RefactoringBundle.message("pull.up.members.to", UsageViewUtil.getLongName(myClass))); // cop (3) classComboLabel.setLabelFor(mySecondClassCombo); // cop (4) final PsiClass superClassPreselection = getPreselection(); int indexToSelect = 0; if (superClassPreselection != null) { indexToSelect = mySuperClasses.indexOf(superClassPreselection); } mySecondClassCombo.setSelectedIndex(indexToSelect); mySecondClassCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { updateMemberInfo(); if (myMemberSelectionPanel != null) { ((MyMemberInfoModel) myMemberInfoModel).setSuperClass(getSuperClass()); getCustomTable().setMemberInfos(myMemberInfos); getCustomTable().fireExternalDataChange(); } // Julien: Update sister classes and analyses setSisterClassDisplay(); // Julien fillAllAnalyses(); // Julien myMemberSelectionPanel.repaint(); } } }); gbConstraints.gridy++; panel.add(mySecondClassCombo, gbConstraints); // new (Julien) createSisterPanel(panel, gbConstraints); return panel; } // TODO : replace the sister panel creation @Override protected void initClassCombo(JComboBox classCombo) { classCombo.setRenderer(new ClassCellRenderer(classCombo.getRenderer())); classCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { if (myMemberSelectionPanel != null) { ((MyMemberInfoModel)myMemberInfoModel).setSuperClass(getSuperClass()); getCustomTable().setMemberInfos(myMemberInfos); getCustomTable().fireExternalDataChange(); } // Julien: Update sister classes and analyses setSisterClassDisplay(); // Julien TODO : check that (already in createNorthPanel) fillAllAnalyses(); // Julien TODO : check that (already in createNorthPanel) } } }); } // new (julien) (based on the handling of myClassCombo) protected void createSisterPanel(JPanel panel, GridBagConstraints gbConstraints) { GridBagConstraints sisgbConstraints = (GridBagConstraints) gbConstraints.clone(); sisgbConstraints.gridy=0; sisgbConstraints.gridx=1; final String text = "Classes to be modified:"; panel.add(SeparatorFactory.createSeparator(text, mySisterClassList), sisgbConstraints); mySisterClassList = new JBList(); mySisterClassList.setCellRenderer(new ShortClassCellRenderer(mySisterClassList.getCellRenderer())); // from (2) mySisterClassList.setFixedCellWidth((int)(1.1 * panel.getFontMetrics(panel.getFont()).charsWidth(text.toCharArray(), 0, text.length()))); sisgbConstraints.gridy++; // increment the target position before adding panel.add(mySisterClassList, sisgbConstraints); setSisterClassDisplay(); // cannot be done much earlier because myClassCombo must be initialized. } private void setSisterClassDisplay() { Collection<PsiClass> mySisterClasses = SisterClassesUtil.findDirectSubClassesInDirectory(getSuperClass()); // getSuperClass can be invoked only after myClassCombo has been initialized. mySisterClassList.setListData(mySisterClasses.toArray()); } protected PsiClass getPreselection() { PsiClass preselection = RefactoringHierarchyUtil.getNearestBaseClass(myClass, false); final String statKey = PULL_UP_STATISTICS_KEY + myClass.getQualifiedName(); for (StatisticsInfo info : StatisticsManager.getInstance().getAllValues(statKey)) { final String superClassName = info.getValue(); PsiClass superClass = null; for (PsiClass aClass : mySuperClasses) { if (Comparing.strEqual(superClassName, aClass.getQualifiedName())) { superClass = aClass; break; } } if (superClass != null && StatisticsManager.getInstance().getUseCount(info) > 0) { preselection = superClass; break; } } return preselection; } protected void doHelpAction() { HelpManager.getInstance().invokeHelp(HelpID.MEMBERS_PULL_UP); } protected void doAction() { if (!myCallback.checkConflicts(this)) return; JavaRefactoringSettings.getInstance().PULL_UP_MEMBERS_JAVADOC = myJavaDocPanel.getPolicy(); final PsiClass superClass = getSuperClass(); String name = superClass.getQualifiedName(); if (name != null) { StatisticsManager .getInstance().incUseCount(new StatisticsInfo(PULL_UP_STATISTICS_KEY + myClass.getQualifiedName(), name)); } List<MemberInfo> infos = getSelectedMemberInfos(); invokeRefactoring(new PullUpGenProcessor(myClass, superClass, infos.toArray(new MemberInfo[infos.size()]), new DocCommentPolicy(getJavaDocPolicy()))); close(OK_EXIT_CODE); } // FIXME : doublon // moved in PullUpDialogBase in community commit on 14 Jul 2013 // The center panel contains the member selection panel and the javadoc panel @Override protected JComponent createCenterPanel() { JPanel panel = new JPanel(new BorderLayout()); final CustomMemberSelectionTable customTable = createMemberSelectionTable(myMemberInfos); final CustomMemberSelectionPanel customMemberSelectionPanel = new CustomMemberSelectionPanel(RefactoringBundle.message("members.to.be.pulled.up"), customTable /*, RefactoringBundle.message("make.abstract") */); myMemberSelectionPanel = customMemberSelectionPanel; // Julien : use custom panel for abstract column myMemberInfoModel = new MyMemberInfoModel(); myMemberInfoModel.memberInfoChanged(new MemberInfoChange<PsiMember, MemberInfo>(myMemberInfos)); myMemberSelectionPanel.getTable().setMemberInfoModel(myMemberInfoModel); myMemberSelectionPanel.getTable().addMemberInfoChangeListener(myMemberInfoModel); panel.add(myMemberSelectionPanel, BorderLayout.CENTER); addCustomElementsToCentralPanel(panel); return panel; } protected CustomMemberSelectionTable getCustomTable(){ // FIXME return myMemberSelectionPanel.getTable(); } @Override protected void addCustomElementsToCentralPanel(JPanel panel) { // TODO (J) : use that kind of method to add the sister panel myJavaDocPanel = new DocCommentPanel(RefactoringBundle.message("javadoc.for.abstracts")); myJavaDocPanel.setPolicy(JavaRefactoringSettings.getInstance().PULL_UP_MEMBERS_JAVADOC); boolean hasJavadoc = false; for (MemberInfo info : myMemberInfos) { final PsiMember member = info.getMember(); if (myMemberInfoModel.isAbstractEnabled(info)) { info.setToAbstract(myMemberInfoModel.isAbstractWhenDisabled(info)); if (!hasJavadoc && member instanceof PsiDocCommentOwner && ((PsiDocCommentOwner)member).getDocComment() != null) { hasJavadoc = true; } } } UIUtil.setEnabled(myJavaDocPanel, hasJavadoc, true); panel.add(myJavaDocPanel, BorderLayout.EAST); } @Override protected CustomMemberSelectionTable createMemberSelectionTable(List<MemberInfo> infos) { //J return new CustomMemberSelectionTable(infos, RefactoringBundle.message("make.abstract")); } @Override protected MemberInfoModel<PsiMember, MemberInfo> createMemberInfoModel() { return new MyMemberInfoModel(); } private class MyMemberInfoModel extends UsesAndInterfacesDependencyMemberInfoModel<PsiMember, MemberInfo> { public MyMemberInfoModel() { super(myClass, getSuperClass(), false, myInterfaceContainmentVerifier); } // what is it supposed to indicate? The first box is checkable? the method is pullupable? is it redundant? // TODO: Replace with a read on the "can make abstract" checkbox? // TODO : Do we compute several times the same result (fill "can make abstract" ...) @Override public boolean isMemberEnabled(MemberInfo member) { /* rem Julien : indicates if a member can be pulled up to the selected superclass (not clear) */ final PsiClass currentSuperClass = getSuperClass(); if(currentSuperClass == null) return true; if (myMemberInfoStorage.getDuplicatedMemberInfos(currentSuperClass).contains(member)) return false; if (myMemberInfoStorage.getExtending(currentSuperClass).contains(member.getMember())) return false; /* rem Julien cannot do a pull up if the method is already in the selected superclass */ final boolean isInterface = currentSuperClass.isInterface(); if (!isInterface) return true; /* rem Julien : if the selected superclass is a real class (not an interface, the above tests are sufficients, else (interface), continue with some tests */ PsiElement element = member.getMember(); if (element instanceof PsiClass && ((PsiClass) element).isInterface()) return true; // rem Julien : can pull up an interface in an interface if (element instanceof PsiField) { /* rem Julien : interfaces can contain static fields (must be final) */ return ((PsiModifierListOwner) element).hasModifierProperty(PsiModifier.STATIC); } if (element instanceof PsiMethod) { /* rem Julien : don't pull up static methods in interfaces */ final PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(currentSuperClass, myClass, PsiSubstitutor.EMPTY); final MethodSignature signature = ((PsiMethod) element).getSignature(superSubstitutor); final PsiMethod superClassMethod = MethodSignatureUtil.findMethodBySignature(currentSuperClass, signature, false); if (superClassMethod != null && !PsiUtil.isLanguageLevel8OrHigher(currentSuperClass)) return false; return !((PsiModifierListOwner) element).hasModifierProperty(PsiModifier.STATIC) || PsiUtil.isLanguageLevel8OrHigher(currentSuperClass); } return true; /* Rem Julien : can pull up instance method in interface */ } @Override public boolean isAbstractEnabled(MemberInfo member) { PsiClass currentSuperClass = getSuperClass(); if (currentSuperClass == null || !currentSuperClass.isInterface()) return true; if (PsiUtil.isLanguageLevel8OrHigher(currentSuperClass)) { return true; } return false; } @Override public boolean isAbstractWhenDisabled(MemberInfo member) { // rem Julien: if a method is pulled-up to an interface, it becomes abstract in the interface even if the 'abstract' chekbox is not checked (the checkbox is disabled). PsiClass currentSuperClass = getSuperClass(); if(currentSuperClass == null) return false; if (currentSuperClass.isInterface()) { final PsiMember psiMember = member.getMember(); if (psiMember instanceof PsiMethod) { return !psiMember.hasModifierProperty(PsiModifier.STATIC); } } return false; } @Override public int checkForProblems(@NotNull MemberInfo member) { if (member.isChecked()) return OK; PsiClass currentSuperClass = getSuperClass(); if (currentSuperClass != null && currentSuperClass.isInterface()) { PsiMember element = member.getMember(); if (element.hasModifierProperty(PsiModifier.STATIC)) { return super.checkForProblems(member); } return OK; } else { return super.checkForProblems(member); } } @Override public Boolean isFixedAbstract(MemberInfo member) { return Boolean.TRUE; /* rem Julien : according to MemberSelectionTable.isAbstractColumnEditable(...) (is it the correct reference?), to be non-editable the member must have the abstract modifier AND isFixedAbstract(...) must return TRUE or FALSE (not null). This expresses the fact that an abstract method cannot override a non abstract method. */ } } void fillAllAnalyses() { CustomMemberSelectionTable table = getCustomTable(); table.fillAllCanGenMembers(getSuperClass()); table.fillAllDirectAbstractPullupFields(getSuperClass()); table.fillAllCanMakeAbstractFields(); // J (to be done in that order because of data dependancy) } }