ca.sqlpower.matchmaker.swingui.MergeColumnRuleEditor.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.matchmaker.swingui.MergeColumnRuleEditor.java

Source

/*
 * Copyright (c) 2008, SQL Power Group Inc.
 *
 * This file is part of DQguru
 *
 * DQguru is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * DQguru is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

package ca.sqlpower.matchmaker.swingui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableModel;
import javax.swing.tree.TreePath;

import org.apache.log4j.Logger;

import ca.sqlpower.matchmaker.ColumnMergeRules;
import ca.sqlpower.matchmaker.ColumnMergeRules.MergeActionType;
import ca.sqlpower.matchmaker.Project;
import ca.sqlpower.matchmaker.TableMergeRules;
import ca.sqlpower.matchmaker.TableMergeRules.ChildMergeActionType;
import ca.sqlpower.matchmaker.undo.AbstractUndoableEditorPane;
import ca.sqlpower.matchmaker.util.EditableJTable;
import ca.sqlpower.sqlobject.SQLColumn;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.swingui.SPSUtils;
import ca.sqlpower.swingui.table.TableUtils;
import ca.sqlpower.validation.Status;
import ca.sqlpower.validation.ValidateResult;
import ca.sqlpower.validation.Validator;
import ca.sqlpower.validation.swingui.FormValidationHandler;
import ca.sqlpower.validation.swingui.StatusComponent;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class MergeColumnRuleEditor extends AbstractUndoableEditorPane<TableMergeRules> {

    private class MergeColumnRuleJTableValidator implements Validator {

        public ValidateResult validate(Object contents) {
            TableModel model = (TableModel) contents;
            if (getMergeRule().isSourceMergeRule()) {
                MergeActionType mat;
                for (int i = 0; i < model.getRowCount(); i++) {
                    SQLColumn column = (SQLColumn) model.getValueAt(i, 0);
                    mat = (MergeActionType) model.getValueAt(i, 1);
                    if (mat == MergeActionType.CONCAT) {
                        if (column.getType() != Types.VARCHAR && column.getType() != Types.LONGVARCHAR) {
                            return ValidateResult.createValidateResult(Status.FAIL, "Invalid type for CONCAT");
                        }
                    } else if (mat == MergeActionType.SUM) {
                        if (column.getType() != Types.BIGINT && column.getType() != Types.DECIMAL
                                && column.getType() != Types.DOUBLE && column.getType() != Types.FLOAT
                                && column.getType() != Types.INTEGER && column.getType() != Types.NUMERIC
                                && column.getType() != Types.REAL && column.getType() != Types.SMALLINT
                                && column.getType() != Types.TINYINT) {

                            return ValidateResult.createValidateResult(Status.FAIL, "Invalid type for SUM");
                        }
                    }
                }
            } else {
                TableMergeRules tableMergeRule = getMergeRule();
                TableMergeRules parentMergeRule = tableMergeRule.getParentMergeRule();

                boolean uniqueKeyDefined = false;
                for (ColumnMergeRules cmr : tableMergeRule.getChildren(ColumnMergeRules.class)) {
                    uniqueKeyDefined |= cmr.isInPrimaryKey();
                }
                if (!uniqueKeyDefined) {
                    return ValidateResult.createValidateResult(Status.FAIL,
                            "No primary key index defined for this table");
                }

                // checks for invalid foreign keys types
                for (ColumnMergeRules cmr : tableMergeRule.getChildren(ColumnMergeRules.class)) {
                    if (cmr.getImportedKeyColumn() != null
                            && cmr.getImportedKeyColumn().getType() != cmr.getColumn().getType()) {
                        return ValidateResult.createValidateResult(Status.FAIL,
                                "Data type mismatch on imported key columns");
                    }
                }

                // checks for foreign keys that is not part of the parent's primary keys
                for (ColumnMergeRules cmr : tableMergeRule.getChildren(ColumnMergeRules.class)) {
                    if (cmr.getImportedKeyColumn() != null
                            && cmr.getImportedKeyColumn().getType() != cmr.getColumn().getType()) {
                        return ValidateResult.createValidateResult(Status.FAIL,
                                "Data type mismatch on imported key columns");
                    }
                }

                // checks for invalid foreign keys
                if (parentMergeRule != null) {
                    if (parentMergeRule.isSourceMergeRule()) {
                        int count = 0;
                        for (ColumnMergeRules cmr : tableMergeRule.getChildren(ColumnMergeRules.class)) {
                            if (cmr.getImportedKeyColumn() != null) {
                                boolean found = false;

                                // TODO: This check needs to be redone because it doesn't account
                                // for the possibility of a table importing keys from more than one table
                                try {
                                    for (SQLColumn column : parentMergeRule.getSourceTable().getColumns()) {
                                        if (column.equals(cmr.getImportedKeyColumn())) {
                                            count++;
                                            found = true;
                                            break;
                                        }
                                    }
                                } catch (SQLObjectException ex) {
                                    throw new RuntimeException(
                                            "An exception occured while listing table columns from the source table",
                                            ex);
                                }
                                if (!found) {
                                    return ValidateResult.createValidateResult(Status.FAIL,
                                            "Invalid foreign imported key columns");
                                }
                            }
                        }
                        // TODO: This spot used to have a count check, which ensured that the entire
                        // primary key got imported, and not part of it. However, it has to be done differently,
                        // as the previous version did not account for alternate keys, nor did it account for 
                        // the possibility that you can import a key more than once.
                        // For now, here's at least a half-assed check to make sure that there is at
                        // least an imported key column defined.
                        if (count < 1) {
                            return ValidateResult.createValidateResult(Status.FAIL,
                                    "No foreign key imported columns defined");
                        }
                    } else {
                        int primaryKeyCount = 0;
                        int foreignKeyCount = 0;
                        for (ColumnMergeRules parentColumn : parentMergeRule.getChildren(ColumnMergeRules.class)) {
                            if (parentColumn.isInPrimaryKey()) {
                                primaryKeyCount++;
                            }
                        }

                        // TODO: This check needs to be redone because it doesn't account
                        // for the possibility of a table importing keys from more than one table 
                        for (ColumnMergeRules childColumn : tableMergeRule.getChildren(ColumnMergeRules.class)) {
                            if (childColumn.getImportedKeyColumn() != null) {
                                boolean found = false;
                                for (ColumnMergeRules parentColumn : parentMergeRule
                                        .getChildren(ColumnMergeRules.class)) {
                                    if (parentColumn.getColumn().equals(childColumn.getImportedKeyColumn())) {
                                        foreignKeyCount++;
                                        found = true;
                                        break;
                                    }
                                }
                                if (!found) {
                                    return ValidateResult.createValidateResult(Status.FAIL,
                                            "Invalid foreign imported key columns");
                                }
                            }
                        }
                        // TODO: This spot used to have a count check, which ensured that the entire
                        // primary key got imported, and not part of it. However, it has to be done differently,
                        // as the previous version did not account for alternate keys, nor did it account for 
                        // the possibility that you can import a key more than once.
                        // For now, here's at least a half-assed check to make sure that there is at
                        // least an imported key column defined.
                        if (foreignKeyCount < 1) {
                            return ValidateResult.createValidateResult(Status.FAIL,
                                    "No foreign key imported columns defined");
                        }
                    }
                }
            }
            return ValidateResult.createValidateResult(Status.OK, "");
        }

    }

    /**
     * This Validator checks to ensure that the project contains no other TableMergeRule
     * that shares the same parent TableMergeRule.
     */
    private class ParentMergeRuleJComboBoxValidator implements Validator {

        public ValidateResult validate(Object contents) {
            TableMergeRules tableMergeRule = getMergeRule();
            for (TableMergeRules rule : project.getTableMergeRules()) {
                if (rule != tableMergeRule && !rule.isSourceMergeRule()
                        && rule.getSourceTable().equals(tableMergeRule.getSourceTable())
                        && rule.getParentMergeRule().equals(tableMergeRule.getParentMergeRule())) {
                    return ValidateResult.createValidateResult(Status.FAIL,
                            "There is more than one merge rule for this table with the same parent table. "
                                    + "Please select a different parent table.");
                }
            }
            return ValidateResult.createValidateResult(Status.OK, "");
        }
    }

    /**
     * EditableJTable implementation for related merge rules. It returns 
     * a combo box of {@link SQLColumn} in the parent table for 
     * column 2.
     */
    private class RelatedColumnMergeRulesTable extends EditableJTable {

        public RelatedColumnMergeRulesTable(AbstractMatchMakerTableModel ruleTableModel) {
            super(ruleTableModel);
        }

        @Override
        public TableCellEditor getCellEditor(int row, int column) {
            if (column == 2) {
                JComboBox importedKeyColumns = new JComboBox();
                if (getParentTableComboBox().getSelectedItem() != null) {
                    try {
                        List<SQLColumn> tableColumns = mergeRules.get(parentMergeRule.getSelectedIndex())
                                .getSourceTable().getColumns();//getParentTableUniqueKeyColumns();
                        importedKeyColumns.setModel(new DefaultComboBoxModel(tableColumns.toArray()));
                        importedKeyColumns.insertItemAt(null, 0);
                    } catch (SQLObjectException e) {
                        SPSUtils.showExceptionDialogNoReport(swingSession.getFrame(),
                                "Failed to load list of columns from parent table.", e);
                    }
                }
                return new DefaultCellEditor(importedKeyColumns);
            } else if (column == 3) {
                List<MergeActionType> comboList = new ArrayList<MergeActionType>();
                for (MergeActionType mat : ColumnMergeRules.MergeActionType.values()) {
                    if (mat != MergeActionType.NA) {
                        comboList.add(mat);
                    }
                }
                return new DefaultCellEditor(new JComboBox(new DefaultComboBoxModel(comboList.toArray())));
            } else {
                return super.getCellEditor(row, column);
            }
        }
    }

    /**
     * EditableJTable implementation for source merge rules. It returns 
     * a combo box of the {@link MergeActionType} in the third column.
     */
    private class SourceColumnMergeRulesTable extends EditableJTable {

        public SourceColumnMergeRulesTable(AbstractMatchMakerTableModel ruleTableModel) {
            super(ruleTableModel);
        }

        @Override
        public TableCellEditor getCellEditor(int row, int column) {
            if (column == 1) {
                List<MergeActionType> comboList = new ArrayList<MergeActionType>();
                for (MergeActionType mat : ColumnMergeRules.MergeActionType.values()) {
                    if (mat != MergeActionType.NA) {
                        comboList.add(mat);
                    }
                }
                return new DefaultCellEditor(new JComboBox(new DefaultComboBoxModel(comboList.toArray())));
            } else {
                return super.getCellEditor(row, column);
            }
        }
    }

    private static final Logger logger = Logger.getLogger(MergeColumnRuleEditor.class);
    private final Project project;

    private final StatusComponent status = new StatusComponent();
    private FormValidationHandler handler;

    /**
     * allows the user to set the parentMergeRule
     */
    private final JComboBox parentMergeRule = new JComboBox();

    private final List<TableMergeRules> mergeRules;

    /**
     * allows the user to set the childMergeAction
     */
    private final JComboBox childMergeAction = new JComboBox(TableMergeRules.ChildMergeActionType.values());;

    /**
     * The table that lists the column merge rules
     */
    private AbstractMatchMakerTableModel ruleTableModel;

    private EditableJTable ruleTable;

    private ListSelectionListener tablelistener = new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {
            int selectedRow = ruleTable.getSelectedRow();
            if (selectedRow >= 0) {
                ColumnMergeRules mergeColumn = mmo.getChildren(ColumnMergeRules.class).get(selectedRow);
                MatchMakerTreeModel treeModel = (MatchMakerTreeModel) swingSession.getTree().getModel();
                TreePath menuPath = treeModel.getPathForNode(mergeColumn);
                swingSession.getTree().setSelectionPath(menuPath);
            }
        }
    };

    private Action saveAction = new AbstractAction("Save") {
        public void actionPerformed(ActionEvent e) {
            if (!applyChanges()) {
                JOptionPane.showMessageDialog(swingSession.getFrame(), "Merge Column rules not saved.", "Save",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    };

    public MergeColumnRuleEditor(final MatchMakerSwingSession session, final Project project,
            final TableMergeRules mr) {
        super(session, mr);

        this.project = project;

        if (project == null) {
            throw new NullPointerException("You can't edit a null project");
        }
        if (mmo == null) {
            throw new NullPointerException("You can't edit a null merge rule");
        }

        if (mmo.isSourceMergeRule()) {
            ruleTableModel = new SourceMergeColumnRuleTableModel(mmo);
            ruleTable = new SourceColumnMergeRulesTable(ruleTableModel);
        } else {
            ruleTableModel = new RelatedMergeColumnRuleTableModel(mmo);
            ruleTable = new RelatedColumnMergeRulesTable(ruleTableModel);

        }
        ruleTable.getSelectionModel().addListSelectionListener(tablelistener);
        TableUtils.fitColumnWidths(ruleTable, 15);

        mergeRules = project.getTableMergeRules();
        for (TableMergeRules tmr : project.getTableMergeRules()) {
            if (!mmo.equals(tmr)) {
                parentMergeRule.addItem(tmr.getSourceTable());
            }
        }

        buildUI();
        addListenerToComponents();

        List<Action> actions = new ArrayList<Action>();
        actions.add(saveAction);
        handler = new FormValidationHandler(status, actions);
        handler.addValidateObject(ruleTable, new MergeColumnRuleJTableValidator());
        handler.addValidateObject(parentMergeRule, new ParentMergeRuleJComboBoxValidator());
        handler.resetHasValidated(); // avoid false hits when newly created
    }

    private void buildUI() {

        String comboMinSize = "fill:min(pref;" + (new JComboBox().getMinimumSize().width) + "px):grow";
        FormLayout layout = new FormLayout(
                "4dlu,pref,4dlu," + comboMinSize + ",4dlu,pref,4dlu," + comboMinSize + ",4dlu,pref,4dlu", // columns
                "10dlu,pref,4dlu,pref,4dlu,pref,4dlu,pref,4dlu,pref,4dlu,pref,4dlu,fill:40dlu:grow,4dlu,pref,4dlu"); // rows
        //    1     2    3    4               5    6    7     8         9    10   11      
        //    status    cat       schema    table     index     del dup   table      button bar

        PanelBuilder pb;
        JPanel p = logger.isDebugEnabled() ? new FormDebugPanel(layout) : new JPanel(layout);
        pb = new PanelBuilder(layout, p);
        CellConstraints cc = new CellConstraints();

        int row = 2;
        pb.add(status, cc.xy(4, row));
        row += 2;
        pb.add(new JLabel("Catalog:"), cc.xy(2, row, "r,c"));
        JTextField temp = new JTextField(mmo.getSourceTable().getCatalogName());
        temp.setEditable(false);
        pb.add(temp, cc.xyw(4, row, 5, "f,c"));
        row += 2;

        pb.add(new JLabel("Schema:"), cc.xy(2, row, "r,c"));
        temp = new JTextField(mmo.getSourceTable().getSchemaName());
        temp.setEditable(false);
        pb.add(temp, cc.xyw(4, row, 5, "f,c"));

        row += 2;
        pb.add(new JLabel("Table Name:"), cc.xy(2, row, "r,c"));
        temp = new JTextField(mmo.getTableName());
        temp.setEditable(false);
        pb.add(temp, cc.xyw(4, row, 5, "f,c"));

        row += 2;
        pb.add(new JLabel("Index Name:"), cc.xy(2, row, "r,c"));
        String indexName = "";

        if (mmo.getTableIndex() == null) {
            indexName = "";
        } else {
            indexName = mmo.getTableIndex().getName();
        }

        temp = new JTextField(indexName);
        temp.setEditable(false);
        pb.add(temp, cc.xyw(4, row, 5, "f,c"));

        row += 2;
        if (!mmo.isSourceMergeRule()) {
            pb.add(new JLabel("Parent Table:"), cc.xy(2, row, "l,c"));
            pb.add(parentMergeRule, cc.xy(4, row, "f,c"));
            if (mmo.getParentMergeRule() != null) {
                parentMergeRule.setSelectedItem(mmo.getParentMergeRule().getSourceTable());
            } else {
                parentMergeRule.setSelectedItem(null);
            }
            pb.add(new JLabel("Merge Action:"), cc.xy(6, row, "r,c"));
            pb.add(childMergeAction, cc.xy(8, row, "f,c"));
            childMergeAction.setSelectedItem(mmo.getChildMergeAction());
        }

        row += 2;
        pb.add(new JScrollPane(ruleTable), cc.xyw(4, row, 5, "f,f"));

        row += 2;
        pb.add(new JButton(saveAction), cc.xyw(4, row, 5, "c,c"));
        panel = pb.getPanel();
    }

    private void addListenerToComponents() {
        parentMergeRule.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                mmo.setParentMergeRuleAndImportedKeys(
                        (TableMergeRules) mergeRules.get(parentMergeRule.getSelectedIndex()));
            }
        });
        childMergeAction.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                mmo.setChildMergeAction((ChildMergeActionType) childMergeAction.getSelectedItem());
            }
        });
    }

    @Override
    public boolean applyChanges() {
        if (!handler.hasPerformedValidation()) {
            ruleTableModel.fireTableChanged(new TableModelEvent(ruleTableModel));
        }
        ValidateResult result = handler.getWorstValidationStatus();
        if (result.getStatus() == Status.FAIL) {
            JOptionPane.showMessageDialog(swingSession.getFrame(),
                    "You have to fix the error before you can save the merge rules", "Save",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }

        //adds the mergeRule to the project if it is new
        if (!project.getTableMergeRules().contains(mmo)) {
            project.addChild(mmo);
        }

        return super.applyChanges();
    }

    public TableMergeRules getMergeRule() {
        return (TableMergeRules) mmo;
    }

    protected JComboBox getParentTableComboBox() {
        return parentMergeRule;
    }

    private List<SQLColumn> getParentTableUniqueKeyColumns() throws SQLObjectException {
        List<SQLColumn> uniqueKeys = null;
        if (parentMergeRule.getSelectedItem() != null) {
            TableMergeRules tmr = mergeRules.get(parentMergeRule.getSelectedIndex());
            uniqueKeys = tmr.getUniqueKeyColumns();
        }
        return uniqueKeys;
    }

    @Override
    public boolean hasUnsavedChanges() {
        if (mmo.getParent() == null) {
            return true;
        }
        return super.hasUnsavedChanges();
    }

    public void setSelectedColumn(ColumnMergeRules selectedColumn) {
        if (selectedColumn != null) {
            int selected = mmo.getChildren().indexOf(selectedColumn);
            if (selected >= 0 && selected < ruleTable.getRowCount()) {
                ruleTable.setRowSelectionInterval(selected, selected);
            }
        }
    }

    @Override
    public void undoEventFired(PropertyChangeEvent evt) {
        if (!mmo.isSourceMergeRule()) {
            if (mmo.getParentMergeRule() != null) {
                parentMergeRule.setSelectedItem(mmo.getParentMergeRule().getSourceTable());
            } else {
                parentMergeRule.setSelectedItem(null);
            }
            childMergeAction.setSelectedItem(mmo.getChildMergeAction());
        }
        handler.performFormValidation();
    }

}