org.community.intellij.plugins.communitycase.rebase.RebaseEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.community.intellij.plugins.communitycase.rebase.RebaseEditor.java

Source

/*
 * 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 org.community.intellij.plugins.communitycase.rebase;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ListWithSelection;
import com.intellij.util.ui.ComboBoxTableCellEditor;
import com.intellij.util.ui.ComboBoxTableCellRenderer;
import org.community.intellij.plugins.communitycase.actions.ShowAllSubmittedFilesAction;
import org.community.intellij.plugins.communitycase.commands.StringScanner;
import org.community.intellij.plugins.communitycase.config.ConfigUtil;
import org.community.intellij.plugins.communitycase.i18n.Bundle;
import org.jetbrains.annotations.NonNls;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Editor for rebase entries. It allows reordering of
 * the entries and changing commit status.
 */
public class RebaseEditor extends DialogWrapper {
    /**
     * The table that lists all commits
     */
    private JTable myCommitsTable;
    /**
     * The move up button
     */
    private JButton myMoveUpButton;
    /**
     * The move down button
     */
    private JButton myMoveDownButton;
    /**
     * The view commit button
     */
    private JButton myViewButton;
    /**
     * The root panel
     */
    private JPanel myPanel;
    /**
     * Table model
     */
    private final MyTableModel myTableModel;
    /**
     * The file name
     */
    private final String myFile;
    /**
     * The project
     */
    private final Project myProject;
    /**
     * The root
     */
    private final VirtualFile myRoot;
    /**
     * The cygwin drive prefix
     */
    @NonNls
    private static final String CYGDRIVE_PREFIX = "/cygdrive/";

    /**
     * The constructor
     *
     * @param project the project
     * @param Root the root
     * @param file    the file to edit
     * @throws IOException if file could not be loaded
     */
    protected RebaseEditor(final Project project, final VirtualFile Root, String file) throws IOException {
        super(project, true);
        myProject = project;
        myRoot = Root;
        setTitle(Bundle.getString("rebase.editor.title"));
        setOKButtonText(Bundle.getString("rebase.editor.button"));
        if (SystemInfo.isWindows && file.startsWith(CYGDRIVE_PREFIX)) {
            final int prefixSize = CYGDRIVE_PREFIX.length();
            file = file.substring(prefixSize, prefixSize + 1) + ":" + file.substring(prefixSize + 1);
        }
        myFile = file;
        myTableModel = new MyTableModel();
        myTableModel.load(file);
        myCommitsTable.setModel(myTableModel);
        myCommitsTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        TableColumn actionColumn = myCommitsTable.getColumnModel().getColumn(MyTableModel.ACTION);
        actionColumn.setCellEditor(ComboBoxTableCellEditor.INSTANCE);
        actionColumn.setCellRenderer(ComboBoxTableCellRenderer.INSTANCE);
        myCommitsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(final ListSelectionEvent e) {
                myViewButton.setEnabled(myCommitsTable.getSelectedRowCount() == 1);
                final ListSelectionModel selectionModel = myCommitsTable.getSelectionModel();
                myMoveUpButton.setEnabled(selectionModel.getMinSelectionIndex() > 0);
                myMoveDownButton.setEnabled(selectionModel.getMaxSelectionIndex() != -1
                        && selectionModel.getMaxSelectionIndex() < myTableModel.myEntries.size() - 1);
            }
        });
        myViewButton.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent e) {
                int row = myCommitsTable.getSelectedRow();
                if (row < 0) {
                    return;
                }
                RebaseEntry entry = myTableModel.myEntries.get(row);
                ShowAllSubmittedFilesAction.showSubmittedFiles(project, entry.getCommit(), Root);
            }
        });

        myMoveUpButton.addActionListener(new MoveUpDownActionListener(MoveDirection.up));
        myMoveDownButton.addActionListener(new MoveUpDownActionListener(MoveDirection.down));

        myTableModel.addTableModelListener(new TableModelListener() {
            public void tableChanged(final TableModelEvent e) {
                validateFields();
            }
        });
        init();
    }

    /**
     * Validate fields
     */
    private void validateFields() {
        final List<RebaseEntry> entries = myTableModel.myEntries;
        if (entries.size() == 0) {
            setErrorText(Bundle.getString("rebase.editor.invalid.entryset"));
            setOKActionEnabled(false);
            return;
        }
        int i = 0;
        while (i < entries.size() && entries.get(i).getAction() == RebaseEntry.Action.skip) {
            i++;
        }
        if (i < entries.size() && entries.get(i).getAction() == RebaseEntry.Action.squash) {
            setErrorText(Bundle.getString("rebase.editor.invalid.squash"));
            setOKActionEnabled(false);
            return;
        }
        setErrorText(null);
        setOKActionEnabled(true);
    }

    /**
     * Save entries back to the file
     *
     * @throws IOException if there is IO problem with saving
     */
    public void save() throws IOException {
        myTableModel.save(myFile);
    }

    /**
     * {@inheritDoc}
     */
    protected JComponent createCenterPanel() {
        return myPanel;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String getDimensionServiceKey() {
        return getClass().getName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String getHelpId() {
        return "reference.VersionControl.RebaseCommits";
    }

    /**
     * Cancel rebase
     *
     * @throws IOException if file cannot be reset to empty one
     */
    public void cancel() throws IOException {
        myTableModel.cancel(myFile);
    }

    /**
     * The table model for the commits
     */
    private class MyTableModel extends AbstractTableModel {
        /**
         * The action column
         */
        private static final int ACTION = 0;
        /**
         * The commit hash column
         */
        private static final int COMMIT = 1;
        /**
         * The subject column
         */
        private static final int SUBJECT = 2;

        /**
         * The entries
         */
        final List<RebaseEntry> myEntries = new ArrayList<RebaseEntry>();
        private int[] myLastEditableSelectedRows = new int[] {};

        /**
         * {@inheritDoc}
         */
        @Override
        public Class<?> getColumnClass(final int columnIndex) {
            return columnIndex == ACTION ? ListWithSelection.class : String.class;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String getColumnName(final int column) {
            switch (column) {
            case ACTION:
                return Bundle.getString("rebase.editor.action.column");
            case COMMIT:
                return Bundle.getString("rebase.editor.commit.column");
            case SUBJECT:
                return Bundle.getString("rebase.editor.comment.column");
            default:
                throw new IllegalArgumentException("Unsupported column index: " + column);
            }
        }

        /**
         * {@inheritDoc}
         */
        public int getRowCount() {
            return myEntries.size();
        }

        /**
         * {@inheritDoc}
         */
        public int getColumnCount() {
            return SUBJECT + 1;
        }

        /**
         * {@inheritDoc}
         */
        public Object getValueAt(final int rowIndex, final int columnIndex) {
            RebaseEntry e = myEntries.get(rowIndex);
            switch (columnIndex) {
            case ACTION:
                return new ListWithSelection<RebaseEntry.Action>(Arrays.asList(RebaseEntry.Action.values()),
                        e.getAction());
            case COMMIT:
                return e.getCommit();
            case SUBJECT:
                return e.getSubject();
            default:
                throw new IllegalArgumentException("Unsupported column index: " + columnIndex);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        @SuppressWarnings({ "unchecked" })
        public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {
            assert columnIndex == ACTION;

            if (ArrayUtil.indexOf(myLastEditableSelectedRows, rowIndex) > -1) {
                final ContiguousIntIntervalTracker intervalBuilder = new ContiguousIntIntervalTracker();
                for (int lastEditableSelectedRow : myLastEditableSelectedRows) {
                    intervalBuilder.track(lastEditableSelectedRow);
                    setRowAction(aValue, lastEditableSelectedRow, columnIndex);
                }
                setSelection(intervalBuilder);
            } else {
                setRowAction(aValue, rowIndex, columnIndex);
            }
        }

        private void setSelection(ContiguousIntIntervalTracker intervalBuilder) {
            myCommitsTable.getSelectionModel().setSelectionInterval(intervalBuilder.getMin(),
                    intervalBuilder.getMax());
        }

        private void setRowAction(Object aValue, int rowIndex, int columnIndex) {
            RebaseEntry e = myEntries.get(rowIndex);
            e.setAction((RebaseEntry.Action) aValue);
            fireTableCellUpdated(rowIndex, columnIndex);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isCellEditable(final int rowIndex, final int columnIndex) {
            myLastEditableSelectedRows = myCommitsTable.getSelectedRows();
            return columnIndex == ACTION;
        }

        /**
         * Load data from the file
         *
         * @param file the file to load
         * @throws IOException if file could not be loaded
         */
        public void load(final String file) throws IOException {
            String encoding = ConfigUtil.getLogEncoding(myProject, myRoot);
            final StringScanner s = new StringScanner(new String(FileUtil.loadFileText(new File(file), encoding)));
            while (s.hasMoreData()) {
                if (s.isEol() || s.startsWith('#') || s.startsWith("noop")) {
                    s.nextLine();
                    continue;
                }
                String action = s.spaceToken();
                assert "pick".equals(action) : "Initial action should be pick: " + action;
                String hash = s.spaceToken();
                String comment = s.line();
                myEntries.add(new RebaseEntry(hash, comment));
            }
        }

        /**
         * Save text to the file
         *
         * @param file the file to save to
         * @throws IOException if there is IO problem
         */
        public void save(final String file) throws IOException {
            String encoding = ConfigUtil.getLogEncoding(myProject, myRoot);
            PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), encoding));
            try {
                for (RebaseEntry e : myEntries) {
                    if (e.getAction() != RebaseEntry.Action.skip) {
                        out.println(e.getAction().toString() + " " + e.getCommit() + " " + e.getSubject());
                    }
                }
            } finally {
                out.close();
            }
        }

        /**
         * Save text to the file
         *
         * @param file the file to save to
         * @throws IOException if there is IO problem
         */
        public void cancel(final String file) throws IOException {
            PrintWriter out = new PrintWriter(new FileWriter(file));
            try {
                //noinspection HardCodedStringLiteral
                out.println("# rebase is cancelled");
            } finally {
                out.close();
            }
        }

        public void moveRows(int[] rows, MoveDirection direction) {

            myCommitsTable.removeEditor();

            final ContiguousIntIntervalTracker selectionInterval = new ContiguousIntIntervalTracker();
            final ContiguousIntIntervalTracker rowsUpdatedInterval = new ContiguousIntIntervalTracker();

            for (int row : direction.preprocessRowIndexes(rows)) {
                final int targetIndex = row + direction.offset();
                assertIndexInRange(row, targetIndex);

                Collections.swap(myEntries, row, targetIndex);

                rowsUpdatedInterval.track(targetIndex, row);
                selectionInterval.track(targetIndex);
            }

            if (selectionInterval.hasValues()) {
                setSelection(selectionInterval);
                fireTableRowsUpdated(rowsUpdatedInterval.getMin(), rowsUpdatedInterval.getMax());
            }
        }

        private void assertIndexInRange(int... rowIndexes) {
            for (int rowIndex : rowIndexes) {
                assert rowIndex >= 0;
                assert rowIndex < myEntries.size();
            }
        }
    }

    private static class ContiguousIntIntervalTracker {
        private Integer myMin = null;
        private Integer myMax = null;
        private static final int UNSET_VALUE = -1;

        public Integer getMin() {
            return myMin == null ? UNSET_VALUE : myMin;
        }

        public Integer getMax() {
            return myMax == null ? UNSET_VALUE : myMax;
        }

        public void track(int... entries) {
            for (int entry : entries) {
                checkMax(entry);
                checkMin(entry);
            }
        }

        private void checkMax(int entry) {
            if (null == myMax || entry > myMax) {
                myMax = entry;
            }
        }

        private void checkMin(int entry) {
            if (null == myMin || entry < myMin) {
                myMin = entry;
            }
        }

        public boolean hasValues() {
            return (null != myMin && null != myMax);
        }
    }

    private enum MoveDirection {
        up, down;
        public int offset() {
            if (this == up) {
                return -1;
            } else {
                return +1;
            }
        }

        public int[] preprocessRowIndexes(int[] seletion) {
            int[] copy = seletion.clone();
            Arrays.sort(copy);
            if (this == up) {
                return copy;
            } else {
                return ArrayUtil.reverseArray(copy);
            }
        }
    }

    private class MoveUpDownActionListener implements ActionListener {
        private final MoveDirection direction;

        public MoveUpDownActionListener(MoveDirection direction) {
            this.direction = direction;
        }

        public void actionPerformed(final ActionEvent e) {
            myTableModel.moveRows(myCommitsTable.getSelectedRows(), direction);
        }
    }

}