com.jetbrains.unchain.ui.UnchainPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.jetbrains.unchain.ui.UnchainPanel.java

Source

/*
 * Copyright (c) 2013 JetBrains
 *
 *    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.jetbrains.unchain.ui;

import com.intellij.icons.AllIcons;
import com.intellij.ide.actions.CloseTabToolbarAction;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.ui.*;
import com.intellij.ui.components.JBList;
import com.jetbrains.unchain.BadDependencyItem;
import com.jetbrains.unchain.PsiQNames;
import com.jetbrains.unchain.UnchainMover;
import com.jetbrains.unchain.Unchainer;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author yole
 */
public class UnchainPanel extends JPanel {
    private final Project myProject;
    private JPanel myMainPanel;
    private JPanel myClassNamePlaceholder;
    private JComboBox myTargetModuleComboBox;
    private JButton myGoButton;
    private JPanel myCardsPanel;
    private JBList myBadDepsList;
    private JBList myCallChainList;
    private JList myGoodDepsList;
    private JButton myMoveClassesButton;
    private final EditorTextField myClassNameField;
    private boolean myBadDepsVisible;
    private boolean myGoodDepsVisible;
    private final List<String> myUnwantedDeps = new ArrayList<String>();
    private final List<String> myForcedMerges = new ArrayList<String>();

    public UnchainPanel(final Project project, final PsiClass initialClass) {
        myProject = project;
        setLayout(new BorderLayout());
        add(myMainPanel, BorderLayout.CENTER);

        myBadDepsList.getEmptyText().setText("Select class to analyze and press Analyze Dependencies");

        createToolbar();

        myClassNameField = new EditorTextField("", project, StdFileTypes.JAVA);

        final JavaCodeFragmentFactory factory = JavaCodeFragmentFactory.getInstance(myProject);
        PsiPackage defaultPackage = JavaPsiFacade.getInstance(myProject).findPackage("");
        final PsiCodeFragment fragment = factory.createReferenceCodeFragment("", defaultPackage, true, true);
        myClassNameField.setDocument(PsiDocumentManager.getInstance(myProject).getDocument(fragment));
        if (initialClass != null) {
            myClassNameField.setText(initialClass.getQualifiedName());
        }

        ComponentWithBrowseButton<EditorTextField> classNameWithBrowseButton = new ComponentWithBrowseButton<EditorTextField>(
                myClassNameField, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent actionEvent) {
                        PsiClass initialClass = getSelectedClass();
                        TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project)
                                .createProjectScopeChooser("Choose Class to Move", initialClass);
                        chooser.showDialog();
                        PsiClass selected = chooser.getSelected();
                        if (selected != null) {
                            myClassNameField.setText(selected.getQualifiedName());
                        }
                    }
                });
        myClassNamePlaceholder.add(classNameWithBrowseButton, BorderLayout.CENTER);

        Module[] modules = ModuleManager.getInstance(project).getModules();
        myTargetModuleComboBox.setModel(new CollectionComboBoxModel(Arrays.asList(modules)));
        myTargetModuleComboBox.setRenderer(new ColoredListCellRenderer() {
            @Override
            protected void customizeCellRenderer(JList list, Object value, int index, boolean selected,
                    boolean hasFocus) {
                if (value != null) {
                    append(((Module) value).getName());
                }
            }
        });
        new ComboboxSpeedSearch(myTargetModuleComboBox) {
            @Override
            protected String getElementText(Object element) {
                return element instanceof Module ? ((Module) element).getName() : "";
            }
        };
        myGoButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                runUnchainer();
            }
        });

        setupBadDependenciesListeners();
        setupCallChainListeners();
        setupGoodDependenciesListeners();
        myMoveClassesButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                moveClasses();
            }
        });
    }

    private void createToolbar() {
        DefaultActionGroup group = new DefaultActionGroup();
        group.add(new CloseTabToolbarAction() {
            @Override
            public void actionPerformed(AnActionEvent e) {
                ToolWindowManager.getInstance(myProject).unregisterToolWindow(UnchainAction.UNCHAIN_TOOLWINDOW_ID);
            }
        });
        group.add(new MergeAction());
        group.add(new MarkUnwantedAction());
        ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
        add(toolbar.getComponent(), BorderLayout.NORTH);
    }

    private void setupBadDependenciesListeners() {
        myBadDepsList.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent mouseEvent) {
                if (mouseEvent.getClickCount() == 2 && !mouseEvent.isPopupTrigger()) {
                    BadDependencyItem selectedValue = (BadDependencyItem) myBadDepsList.getSelectedValue();
                    if (selectedValue != null) {
                        Navigatable navigatable = selectedValue.getNavigatable();
                        if (navigatable != null) {
                            navigatable.navigate(true);
                        }
                    }
                }
            }
        });
        myBadDepsList.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent listSelectionEvent) {
                BadDependencyItem selectedValue = (BadDependencyItem) myBadDepsList.getSelectedValue();
                if (selectedValue != null) {
                    myCallChainList.setModel(new CollectionListModel<String>(selectedValue.getCallChain()));
                }
            }
        });
    }

    private void setupCallChainListeners() {
        new DoubleClickListener() {
            @Override
            protected boolean onDoubleClick(MouseEvent event) {
                String qName = (String) myCallChainList.getSelectedValue();
                PsiElement target = PsiQNames.findElementByQName(myProject, qName);
                navigateToReference(target);
                return true;
            }
        }.installOn(myCallChainList);
    }

    private void setupGoodDependenciesListeners() {
        new DoubleClickListener() {
            @Override
            protected boolean onDoubleClick(MouseEvent event) {
                String qName = (String) myGoodDepsList.getSelectedValue();
                PsiElement target = PsiQNames.findElementByQName(myProject, qName);
                if (target instanceof Navigatable) {
                    ((Navigatable) target).navigate(true);
                }
                return true;
            }
        }.installOn(myGoodDepsList);
    }

    private void navigateToReference(PsiElement target) {
        if (target != null && myCallChainList.getSelectedIndex() == myCallChainList.getModel().getSize() - 1) {
            BadDependencyItem badDependency = (BadDependencyItem) myBadDepsList.getSelectedValue();
            PsiReference reference = ReferencesSearch
                    .search(badDependency.getPsiElement(), new LocalSearchScope(target)).findFirst();
            if (reference != null) {
                new OpenFileDescriptor(myProject, reference.getElement().getContainingFile().getVirtualFile(),
                        reference.getRangeInElement().getStartOffset()
                                + reference.getElement().getTextRange().getStartOffset()).navigate(true);
                return;
            }
        }
        if (target instanceof Navigatable) {
            ((Navigatable) target).navigate(true);
        }
    }

    private void runUnchainer() {
        PsiClass psiClass = getSelectedClass();
        Module module = (Module) myTargetModuleComboBox.getSelectedItem();
        if (psiClass != null && module != null) {
            runUnchainer(psiClass, module);
        }
    }

    private void runUnchainer(PsiClass psiClass, Module module) {
        final Unchainer unchainer = new Unchainer(psiClass, module);
        unchainer.setUnwantedDependencies(myUnwantedDeps);
        unchainer.setForcedMerges(myForcedMerges);
        unchainer.setBadDependencyFoundCallback(new Runnable() {
            @Override
            public void run() {
                ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
                indicator.setText2("Found " + unchainer.getBadDependencyCount() + " bad dependencies");
            }
        });
        ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
            @Override
            public void run() {
                ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
                indicator.setIndeterminate(true);
                indicator.setText2("Found no bad dependencies");
                unchainer.run();
            }
        }, "Analyzing Dependencies", true, myProject);

        boolean haveBadDeps = unchainer.getBadDependencies().size() > 0;
        showDepsCard(haveBadDeps);
        if (haveBadDeps) {
            fillBadDependenciesList(unchainer);
        } else {
            fillGoodDependenciesList(unchainer);
        }
    }

    private void showDepsCard(boolean bad) {
        myGoodDepsVisible = false;
        myBadDepsVisible = false;
        CardLayout cardLayout = (CardLayout) myCardsPanel.getLayout();
        if (bad) {
            cardLayout.show(myCardsPanel, "BadDeps");
            myBadDepsVisible = true;
        } else {
            cardLayout.show(myCardsPanel, "GoodDeps");
            myGoodDepsVisible = true;
        }
    }

    private void fillGoodDependenciesList(Unchainer unchainer) {
        myGoodDepsList.setModel(new CollectionListModel<String>(unchainer.getGoodDependencies()));
    }

    private void fillBadDependenciesList(Unchainer unchainer) {
        myBadDepsList.setModel(new CollectionListModel<BadDependencyItem>(unchainer.getBadDependencies()));
        if (myBadDepsList.getItemsCount() > 0) {
            myBadDepsList.setSelectedIndex(0);
        }
    }

    private PsiClass getSelectedClass() {
        return JavaPsiFacade.getInstance(myProject).findClass(myClassNameField.getText(),
                ProjectScope.getProjectScope(myProject));
    }

    private List<String> mergeMembers(List<String> qNames, String classToMergeQName) {
        List<String> result = new ArrayList<String>();
        for (String qName : qNames) {
            if (qName.startsWith(classToMergeQName)) {
                if (!result.contains(classToMergeQName)) {
                    result.add(classToMergeQName);
                }
            } else {
                result.add(qName);
            }
        }
        return result;
    }

    private void moveClasses() {
        Module selectedItem = (Module) myTargetModuleComboBox.getSelectedItem();
        CollectionListModel<String> model = (CollectionListModel<String>) myGoodDepsList.getModel();
        final UnchainMover mover = new UnchainMover(selectedItem, model.getItems());
        final Ref<Boolean> failed = Ref.create(false);
        new WriteCommandAction.Simple(myProject, "Moving classes to target module") {
            @Override
            protected void run() throws Throwable {
                try {
                    mover.run();
                } catch (UnsupportedOperationException e) {
                    Messages.showErrorDialog(myProject, e.getMessage(), "Move Failed");
                    failed.set(true);
                }
            }
        }.execute();

        if (!failed.get()) {
            myClassNameField.setText("");
            clearList(myBadDepsList);
            clearList(myCallChainList);
            showDepsCard(true);
        }
    }

    private void clearList(JBList list) {
        ListModel model = list.getModel();
        if (model instanceof CollectionListModel) {
            ((CollectionListModel) model).removeAll();
        }
    }

    private class MergeAction extends AnAction {
        public MergeAction() {
            super("Merge", "Move all methods of selected class to target module", AllIcons.Modules.Merge);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            CollectionListModel<String> model = (CollectionListModel<String>) myGoodDepsList.getModel();
            String selectedMemberQName = (String) myGoodDepsList.getSelectedValue();
            String selectedMemberClassName = PsiQNames.extractClassName(selectedMemberQName);
            myForcedMerges.add(selectedMemberClassName);
            model.replaceAll(mergeMembers(model.getItems(), selectedMemberClassName));
            runUnchainer();
        }

        @Override
        public void update(AnActionEvent e) {
            e.getPresentation().setEnabled(myGoodDepsVisible && myGoodDepsList.getSelectedValue() != null);
        }
    }

    private class MarkUnwantedAction extends AnAction {
        private MarkUnwantedAction() {
            super("Mark Unwanted", "Mark selected class as an unwanted dependency", AllIcons.Actions.Menu_cut);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            myUnwantedDeps.add(PsiQNames.extractClassName((String) myCallChainList.getSelectedValue()));
            int selIndex = myBadDepsList.getSelectedIndex();
            runUnchainer();
            int size = myBadDepsList.getModel().getSize();
            if (size > 0 && myBadDepsVisible) {
                myBadDepsList.setSelectedIndex(selIndex >= size ? size - 1 : selIndex);
            }
        }

        @Override
        public void update(AnActionEvent e) {
            e.getPresentation().setEnabled(myBadDepsVisible && myCallChainList.getSelectedValue() != null);
        }
    }
}