package org.jetbrains.research.intellijdeodorant.ide.ui; import com.intellij.analysis.AnalysisScope; import com.intellij.icons.AllIcons; import com.intellij.ide.util.EditorHelper; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.ui.JBColor; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.TableSpeedSearch; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.table.JBTable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.research.intellijdeodorant.IntelliJDeodorantBundle; import org.jetbrains.research.intellijdeodorant.JDeodorantFacade; import org.jetbrains.research.intellijdeodorant.core.distance.MoveMethodCandidateRefactoring; import org.jetbrains.research.intellijdeodorant.core.distance.ProjectInfo; import org.jetbrains.research.intellijdeodorant.ide.fus.collectors.IntelliJDeodorantCounterCollector; import org.jetbrains.research.intellijdeodorant.ide.refactoring.RefactoringsApplier; import org.jetbrains.research.intellijdeodorant.ide.refactoring.moveMethod.MoveMethodRefactoring; import org.jetbrains.research.intellijdeodorant.ide.ui.listeners.DoubleClickListener; import org.jetbrains.research.intellijdeodorant.utils.ExportResultsUtil; import org.jetbrains.research.intellijdeodorant.utils.PsiUtils; import javax.swing.*; import javax.swing.table.TableColumn; import java.awt.*; import java.awt.event.InputEvent; import java.util.List; import java.util.*; import java.util.stream.Collectors; import static javax.swing.ListSelectionModel.SINGLE_SELECTION; import static org.jetbrains.research.intellijdeodorant.ide.ui.MoveMethodTableModel.*; /** * Panel for Move Method refactoring. */ class MoveMethodPanel extends JPanel { @NotNull private final AnalysisScope scope; @NotNull private final MoveMethodTableModel model; private final JBTable table = new JBTable(); private final JButton selectAllButton = new JButton(); private final JButton deselectAllButton = new JButton(); private final JButton doRefactorButton = new JButton(AllIcons.Actions.RefactoringBulb); private final JButton refreshButton = new JButton(AllIcons.Actions.Refresh); private final List<MoveMethodRefactoring> refactorings = new ArrayList<>(); private JScrollPane scrollPane = new JBScrollPane(); private final JButton exportButton = new JButton(AllIcons.ToolbarDecorator.Export); private final JLabel refreshLabel = new JLabel( IntelliJDeodorantBundle.message("press.refresh.to.find.refactoring.opportunities"), SwingConstants.CENTER ); private final ScopeChooserCombo scopeChooserCombo; MoveMethodPanel(@NotNull AnalysisScope scope) { this.scope = scope; this.scopeChooserCombo = new ScopeChooserCombo(scope.getProject()); setLayout(new BorderLayout()); model = new MoveMethodTableModel(refactorings); setupGUI(); } private void setupGUI() { add(createTablePanel(), BorderLayout.CENTER); add(createButtonsPanel(), BorderLayout.NORTH); } private JScrollPane createTablePanel() { new TableSpeedSearch(table); table.setModel(model); model.setupRenderer(table); table.addMouseListener((DoubleClickListener) this::onDoubleClick); table.getSelectionModel().setSelectionMode(SINGLE_SELECTION); table.setAutoCreateRowSorter(true); setupTableLayout(); refreshLabel.setForeground(JBColor.GRAY); scrollPane = ScrollPaneFactory.createScrollPane(table); scrollPane.setViewportView(refreshLabel); return scrollPane; } private void setupTableLayout() { final TableColumn selectionColumn = table.getTableHeader().getColumnModel().getColumn(SELECTION_COLUMN_INDEX); selectionColumn.setMaxWidth(30); selectionColumn.setMinWidth(30); final TableColumn dependencies = table.getTableHeader().getColumnModel().getColumn(SELECTION_COLUMN_INDEX); dependencies.setMaxWidth(30); dependencies.setMinWidth(30); } private JComponent createButtonsPanel() { JPanel buttonsPanel = new JPanel(new BorderLayout()); buttonsPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); buttonsPanel.add(scopeChooserCombo); refreshButton.setToolTipText(IntelliJDeodorantBundle.message("refresh.button")); refreshButton.addActionListener(l -> refreshPanel()); buttonsPanel.add(refreshButton); doRefactorButton.setToolTipText(IntelliJDeodorantBundle.message("refactor.button")); doRefactorButton.addActionListener(e -> doRefactor()); doRefactorButton.setEnabled(false); buttonsPanel.add(doRefactorButton); exportButton.setToolTipText(IntelliJDeodorantBundle.message("export")); exportButton.addActionListener(e -> ExportResultsUtil.export(getValidRefactoringsSuggestions(), this)); exportButton.setEnabled(false); buttonsPanel.add(exportButton); model.addTableModelListener(l -> enableButtonsOnConditions()); return buttonsPanel; } private List<MoveMethodRefactoring> getValidRefactoringsSuggestions() { return refactorings.stream() .filter(refactoring -> refactoring.getOptionalMethod() .isPresent()) .collect(Collectors.toList()); } private void enableButtonsOnConditions() { doRefactorButton.setEnabled(model.isAnySelected()); selectAllButton.setEnabled(model.getRowCount() != 0); deselectAllButton.setEnabled(model.isAnySelected()); refreshButton.setEnabled(true); exportButton.setEnabled(refactorings.stream() .anyMatch(refactoring -> refactoring.getOptionalMethod().isPresent())); } private void disableAllButtons() { doRefactorButton.setEnabled(false); selectAllButton.setEnabled(false); deselectAllButton.setEnabled(false); refreshButton.setEnabled(false); exportButton.setEnabled(false); } private void doRefactor() { disableAllButtons(); table.setEnabled(false); final Set<MoveMethodRefactoring> selectedRefactorings = new HashSet<>(model.pullSelected()); RefactoringsApplier.moveRefactoring(new ArrayList<>(selectedRefactorings)); model.updateRows(); table.setEnabled(true); enableButtonsOnConditions(); } private void refreshPanel() { if (scopeChooserCombo.getScope() != null) { refactorings.clear(); model.clearTable(); disableAllButtons(); scrollPane.setVisible(false); calculateRefactorings(); } } private void calculateRefactorings() { Project project = scope.getProject(); ProjectInfo projectInfo = new ProjectInfo(scopeChooserCombo.getScope(), true); Set<String> classNamesToBeExamined = new HashSet<>(); PsiUtils.extractFiles(project).stream() .filter(file -> scopeChooserCombo.getScope().contains(file)) .forEach(list -> Arrays.stream(list.getClasses()).map(PsiClass::getQualifiedName).forEach(classNamesToBeExamined::add)); final Task.Backgroundable backgroundable = new Task.Backgroundable(project, IntelliJDeodorantBundle.message("feature.envy.detect.indicator.status"), true) { @Override public void run(@NotNull ProgressIndicator indicator) { ApplicationManager.getApplication().runReadAction(() -> { List<MoveMethodCandidateRefactoring> candidates = JDeodorantFacade.getMoveMethodRefactoringOpportunities(projectInfo, indicator, classNamesToBeExamined); final List<MoveMethodRefactoring> references = candidates.stream().filter(Objects::nonNull) .map(x -> new MoveMethodRefactoring(x.getSourceMethodDeclaration(), x.getTargetClass().getClassObject().getPsiClass(), x.getDistinctSourceDependencies(), x.getDistinctTargetDependencies())) .collect(Collectors.toList()); refactorings.clear(); refactorings.addAll(new ArrayList<>(references)); model.updateTable(refactorings); scrollPane.setVisible(true); scrollPane.setViewportView(table); enableButtonsOnConditions(); IntelliJDeodorantCounterCollector.getInstance().refactoringFound(project, "move.method", references.size()); }); } @Override public void onCancel() { showEmptyPanel(); } }; AbstractRefactoringPanel.runAfterCompilationCheck(backgroundable, project, projectInfo); } private void showEmptyPanel() { scrollPane.setVisible(true); scrollPane.setViewportView(refreshLabel); refreshButton.setEnabled(true); } private void onDoubleClick(InputEvent e) { final int selectedRow = table.getSelectedRow() == -1 ? -1 : table.convertRowIndexToModel(table.getSelectedRow()); final int selectedColumn = table.getSelectedColumn(); if (selectedRow != -1 && (selectedColumn == ENTITY_COLUMN_INDEX || selectedColumn == MOVE_TO_COLUMN_INDEX)) { openDefinition(model.getUnitAt(selectedRow, selectedColumn).orElse(null), scope); } } private static void openDefinition(@Nullable PsiMember unit, AnalysisScope scope) { new Task.Backgroundable(scope.getProject(), "Search Definition") { private PsiElement result; @Override public void run(@NotNull ProgressIndicator indicator) { indicator.setIndeterminate(true); result = unit; } @Override public void onSuccess() { if (result != null) { EditorHelper.openInEditor(result); } } }.queue(); } }