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

Java tutorial

Introduction

Here is the source code for ca.sqlpower.matchmaker.swingui.MungeProcessEditor.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.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
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.JToolBar;

import org.apache.log4j.Logger;

import ca.sqlpower.graph.DepthFirstSearch;
import ca.sqlpower.matchmaker.Project;
import ca.sqlpower.matchmaker.munge.AbstractMungeStep;
import ca.sqlpower.matchmaker.munge.MungeProcess;
import ca.sqlpower.matchmaker.munge.MungeProcessGraphModel;
import ca.sqlpower.matchmaker.munge.MungeStep;
import ca.sqlpower.matchmaker.munge.MungeStepOutput;
import ca.sqlpower.matchmaker.swingui.munge.MungePen;
import ca.sqlpower.matchmaker.swingui.munge.MungeStepLibrary;
import ca.sqlpower.matchmaker.undo.AbstractUndoableEditorPane;
import ca.sqlpower.sqlobject.SQLObjectException;
import ca.sqlpower.swingui.ColorCellRenderer;
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.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

/**
 * Implements the DataEntryPanel functionality for editing a munge process (MatchRuleSet).
 */
public class MungeProcessEditor extends AbstractUndoableEditorPane<MungeProcess> {

    private static final Logger logger = Logger.getLogger(MungeProcessEditor.class);

    /**
     * The project that is or will be the parent of the process we're editing.
     * If this editor was created for a new process, it will not belong to this
     * project until the doSave() method has been called.
     */
    private final Project parentProject;

    /**
     * The actual GUI component that provides the editing interface.
     */
    private final JTextField name = new JTextField();
    private final JTextField desc = new JTextField();
    private final JComboBox color = new JComboBox(ColorScheme.BREWER_SET19.toArray());

    private final MungePen mungePen;

    /**
     * Validator for handling errors within the munge steps
     */
    private final StatusComponent status = new StatusComponent();
    private final FormValidationHandler handler;

    /**
     * Creates a new editor for the given session's given munge process.
     * 
     * @param swingSession The session the given project and process belong to
     * @param project The project that is or will become the process's parent. If the
     * process is new, it will not currently have a parent, but this editor will
     * connect the process to this project when saving. 
     * @param process The process to edit
     */
    public MungeProcessEditor(MatchMakerSwingSession swingSession, Project project, MungeProcess process)
            throws SQLObjectException {

        super(swingSession, process);
        logger.debug("Creating a new munge process editor");

        this.parentProject = project;

        if (mmo.getParent() != null && mmo.getParent() != parentProject) {
            throw new IllegalStateException(
                    "The given process has a parent which is not the given parent match obejct!");
        }

        // the handler stuff
        ArrayList<Action> actions = new ArrayList<Action>();
        actions.add(saveAction);
        this.handler = new FormValidationHandler(status, actions);
        handler.addValidateObject(name, new MungeProcessNameValidator());

        //For some reason some process don't have sessions and this causes a null
        //pointer barrage when it tries to find the tree when it gets focus, then fails and 
        //gets focus again.
        if (process.getSession() == null) {
            process.setSession(swingSession);
        }

        this.mungePen = new MungePen(process, handler, parentProject);

        stepPrecheckResults = new ArrayList<ValidateResult>();

        for (MungeStep step : process.getChildren(MungeStep.class)) {
            if (step instanceof AbstractMungeStep) {
                ((AbstractMungeStep) step).setPreviewMode(true);
            }

            stepPrecheckResults.addAll(step.checkPreconditions());
        }

        buildUI();
        setDefaults();
        addListenerToComponents();

    }

    private void buildUI() throws SQLObjectException {
        panel = new JPanel(new BorderLayout());
        FormLayout layout = new FormLayout("4dlu,pref,4dlu,fill:pref:grow,4dlu,pref,4dlu,pref,4dlu", // columns
                "4dlu,pref,4dlu,pref,4dlu,pref,4dlu,pref,4dlu"); // rows
        CellConstraints cc = new CellConstraints();
        JPanel subPanel = new JPanel(layout);
        subPanel.add(status, cc.xyw(2, 2, 7));
        subPanel.add(new JLabel("Process Name: "), cc.xy(2, 4));
        subPanel.add(name, cc.xy(4, 4));

        subPanel.add(new JLabel("Description: "), cc.xy(2, 6));
        subPanel.add(desc, cc.xy(4, 6));

        subPanel.add(new JLabel("Colour: "), cc.xy(6, 6));
        ColorCellRenderer renderer = new ColorCellRenderer(85, 50);
        color.setRenderer(renderer);
        subPanel.add(color, cc.xy(8, 6));
        subPanel.add(new JButton(saveAction), cc.xy(2, 8));
        subPanel.add(mungePen.getEnablePreviewCheckBox(), cc.xy(4, 8));
        subPanel.add(new JButton(customColour), cc.xy(8, 8));
        panel.add(subPanel, BorderLayout.NORTH);

        JToolBar t = new JToolBar();

        MungeStepLibrary msl = new MungeStepLibrary(mungePen,
                ((SwingSessionContext) swingSession.getContext()).getStepMap());
        t.setBackground(Color.WHITE);
        t.setLayout(new BorderLayout());
        t.add(msl.getHideShowButton(), BorderLayout.NORTH);
        t.add(new JScrollPane(msl.getList()), BorderLayout.CENTER);
        t.setBorder(BorderFactory.createRaisedBevelBorder());
        t.setFloatable(false);
        panel.add(new JScrollPane(mungePen), BorderLayout.CENTER);
        panel.add(t, BorderLayout.EAST);
    }

    private void addListenerToComponents() {
        name.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
            }

            public void keyReleased(KeyEvent e) {
                mmo.setName(name.getText());
            }

            public void keyTyped(KeyEvent e) {
            }
        });
        desc.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
            }

            public void keyReleased(KeyEvent e) {
                mmo.setDesc(desc.getText());
            }

            public void keyTyped(KeyEvent e) {
            }
        });
        color.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                mmo.setColour((Color) color.getSelectedItem());
            }
        });
    }

    private void setDefaults() {
        name.setText(mmo.getName());

        desc.setText(mmo.getDesc());
        boolean hasColour = false;
        for (int i = 0; i < color.getItemCount(); i++) {
            if (color.getItemAt(i).equals(mmo.getColour())) {
                hasColour = true;
                break;
            }
        }
        if (!hasColour) {
            color.addItem(mmo.getColour());
        }
        if (mmo.getColour() != null) {
            color.setSelectedItem(mmo.getColour());
        } else {
            color.setSelectedIndex(0);
            mmo.setColour((Color) color.getSelectedItem());
        }
    }

    Action saveAction = new AbstractAction("Save Transformation") {
        public void actionPerformed(ActionEvent e) {
            applyChanges();
        }
    };
    Action customColour = new AbstractAction("Custom Colour...") {
        public void actionPerformed(ActionEvent arg0) {
            Color colour = swingSession.getCustomColour(mmo.getColour());
            if (colour != null) {
                // TODO add colour(Color) only if it's not in the color(JComboBox) yet
                color.addItem(colour);
                color.setSelectedItem(colour);
            }
        }
    };

    private List<ValidateResult> stepPrecheckResults;

    /**
     * Saves the process, possibly adding it to the parent project given in the
     * constructor if the process is not already a child of that project.
     */
    public boolean applyChanges() {
        ValidateResult result = handler.getWorstValidationStatus();
        if (result.getStatus() == Status.FAIL) {
            JOptionPane.showMessageDialog(swingSession.getFrame(),
                    "You have to fix the error before you can save the transformation", "Save",
                    JOptionPane.ERROR_MESSAGE);
            return false;
        }

        MungeProcessGraphModel gm = new MungeProcessGraphModel(mmo.getChildren(MungeStep.class));
        DepthFirstSearch<MungeStep, MungeProcessGraphModel.Edge> dfs = new DepthFirstSearch<MungeStep, MungeProcessGraphModel.Edge>();
        dfs.performSearch(gm);

        if (dfs.isCyclic()) {
            int responds = JOptionPane.showConfirmDialog(
                    swingSession.getFrame(), "Your transformation contains at least one cycle, "
                            + "and may result in unexpected results. \n" + "Do you want to continue saving?",
                    "Save", JOptionPane.WARNING_MESSAGE);
            if (responds != JOptionPane.YES_OPTION) {
                return false;
            }
        }

        if (mmo.getParent() == null) {
            parentProject.addChild(mmo);
        }
        return super.applyChanges();
    }

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

    @Override
    public void cleanup() {
        for (MungeStep step : getCurrentEditingMMO().getChildren(MungeStep.class)) {
            if (step instanceof AbstractMungeStep) {
                ((AbstractMungeStep) step).setPreviewMode(false);
            }
        }
        mungePen.cleanup();
        super.cleanup();
    }

    private class MungeProcessNameValidator implements Validator {
        private static final int MAX_RULE_SET_NAME_CHAR = 30;

        public ValidateResult validate(Object contents) {
            String value = (String) contents;
            if (value == null || value.length() == 0) {
                return ValidateResult.createValidateResult(Status.FAIL, "Transformation name is required");
            } else if (!value.equals(mmo.getName())
                    && parentProject.getMungeProcessByName(name.getText()) != null) {
                return ValidateResult.createValidateResult(Status.FAIL,
                        "Transformation name is invalid or already exists.");
            } else if (value.length() > MAX_RULE_SET_NAME_CHAR) {
                return ValidateResult.createValidateResult(Status.FAIL,
                        "Transformation name cannot be more than " + MAX_RULE_SET_NAME_CHAR + " characters long");
            } else if (mmo.getParent() == null && parentProject.getMungeProcessByName(name.getText()) != null) {
                return ValidateResult.createValidateResult(Status.FAIL,
                        "Transformation name is invalid or already exists.");
            }
            return ValidateResult.createValidateResult(Status.OK, "");
        }
    }

    private class CleanseProjectProcessPriorityValidator implements Validator {
        public ValidateResult validate(Object contents) {

            if (contents == null) {
                return ValidateResult.createValidateResult(Status.WARN, "No priority set, assuming 0");
            }

            short value = Short.parseShort((String) contents.toString());

            for (MungeProcess mp : parentProject.getChildren(MungeProcess.class)) {
                if (mp == null)
                    throw new NullPointerException("Null munge process in project!");
                short otherPriority = 0;
                if (mp.getMatchPriority() != null) {
                    otherPriority = mp.getMatchPriority().shortValue();
                }
                if (otherPriority == value && mp != mmo) {
                    return ValidateResult.createValidateResult(Status.WARN, "Duplicate Priority. "
                            + "If two cleansing process have the same priority, they may not always run in the same order.");
                }
            }
            return ValidateResult.createValidateResult(Status.OK, "");
        }
    }

    public MungeProcess getProcess() {
        return mmo;
    }

    public void setSelectedStep(MungeStep step) {
        mungePen.setSelectedStep(step);
    }

    public void setSelectedStepOutput(MungeStepOutput mso) {
        //TODO select the mso
    }

    public void undoEventFired(PropertyChangeEvent evt) {
        setDefaults();
    }

    public MungePen getMungePen() {
        return mungePen;
    }
}