ca.sqlpower.matchmaker.swingui.engine.EngineSettingsPanel.java Source code

Java tutorial

Introduction

Here is the source code for ca.sqlpower.matchmaker.swingui.engine.EngineSettingsPanel.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.engine;

import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import ca.sqlpower.matchmaker.EngineEvent;
import ca.sqlpower.matchmaker.EngineListener;
import ca.sqlpower.matchmaker.MatchMakerEngine;
import ca.sqlpower.matchmaker.MatchMakerSessionContext;
import ca.sqlpower.matchmaker.MatchMakerSettings;
import ca.sqlpower.matchmaker.MungeSettings;
import ca.sqlpower.matchmaker.MungeSettings.AutoValidateSetting;
import ca.sqlpower.matchmaker.MungeSettings.PoolFilterSetting;
import ca.sqlpower.matchmaker.Project;
import ca.sqlpower.matchmaker.address.AddressDatabase;
import ca.sqlpower.matchmaker.munge.MungeProcess;
import ca.sqlpower.matchmaker.swingui.AbstractUIUpdateManager;
import ca.sqlpower.matchmaker.swingui.CleanupModel;
import ca.sqlpower.matchmaker.swingui.MMSUtils;
import ca.sqlpower.matchmaker.swingui.MatchMakerSwingSession;
import ca.sqlpower.matchmaker.swingui.SpinnerUpdateManager;
import ca.sqlpower.object.AbstractPoolingSPListener;
import ca.sqlpower.object.AbstractSPListener;
import ca.sqlpower.object.SPChildEvent;
import ca.sqlpower.object.SPListener;
import ca.sqlpower.swingui.BrowseFileAction;
import ca.sqlpower.swingui.DataEntryPanel;
import ca.sqlpower.swingui.DataEntryPanelBuilder;
import ca.sqlpower.util.SQLPowerUtils;
import ca.sqlpower.util.TransactionEvent;
import ca.sqlpower.validation.FileNameValidator;
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.ButtonBarBuilder;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.debug.FormDebugPanel;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.sleepycat.je.DatabaseException;

/**
 * A panel that provides a GUI for setting the parameters for running the MatchMakerEngine,
 * as well as running the MatchMakerEngine itself and displaying its output on the GUI.
 */
public class EngineSettingsPanel implements DataEntryPanel, CleanupModel {

    /**
     * These class keeps the checkBoxes synchronized across multiple clients. Everytime you make
     * a new one, you must add it to the list updaters.
     */
    private class CheckBoxModelUpdater extends AbstractSPListener {
        private final JCheckBox checkBox;
        private final String propertyName;

        public CheckBoxModelUpdater(JCheckBox checkBox, String propertyName) {
            this.checkBox = checkBox;
            this.propertyName = propertyName;

        }

        @Override
        public void propertyChanged(PropertyChangeEvent evt) {
            if (evt.getPropertyName().equals(propertyName)) {
                if (evt.getNewValue() instanceof Boolean) {
                    checkBox.setSelected((Boolean) evt.getNewValue());
                } else {
                    throw new RuntimeException(
                            "The argument should be a boolean value, not a " + evt.getNewValue().getClass());
                }
            }
        }
    };

    /**
     * This is this list of all the checkbox updaters so that we can easily remove them.
     */
    private List<CheckBoxModelUpdater> updaters = new ArrayList<CheckBoxModelUpdater>();

    /**
     * Pref nodes for the log file settings.
     */
    private final String LOG_FILE = "engineLogFile";
    private final String APPEND_TO_LOG = "engineAppendToLog";

    private static final String ADDRESS_CORRECTION_ENGINE_PANEL_ROW_SPECS = "4dlu,pref,4dlu,pref,4dlu,pref,10dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,fill:pref:grow,4dlu,pref,4dlu";
    //  1    2    3    4    5    6     7    8    9   10   11   12   13   14   15   16   17   18   19   20   21   22   23             24   25     26   27

    private static final String ADDRESS_COMMITTING_ENGINE_PANEL_ROW_SPECS = "4dlu,pref,4dlu,pref,4dlu,pref,10dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,fill:pref:grow,4dlu,pref,4dlu";
    //  1    2    3    4    5    6     7    8    9   10   11   12   13   14   15   16   17   18   19             20   21   22   23 

    private static final String MATCH_ENGINE_PANEL_ROW_SPECS = "4dlu,pref,4dlu,pref,4dlu,pref,10dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,fill:pref:grow,4dlu,pref,4dlu";
    //  1    2    3    4    5    6     7    8    9   10   11   12   13   14   15   16   17   18   19   20   21   22   23             24   25   26   27    

    private static final String MERGE_ENGINE_PANEL_ROW_SPECS = "4dlu,pref,4dlu,pref,4dlu,pref,10dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,fill:pref:grow,4dlu,pref,4dlu";
    //  1    2    3    4    5    6     7    8    9   10   11   12   13   14   15   16   17             18   19   20   21     

    private static final String CLEANSE_ENGINE_PANEL_ROW_SPECS = "4dlu,pref,4dlu,pref,4dlu,pref,10dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,fill:pref:grow,4dlu,pref,4dlu";
    //  1    2    3    4    5    6     7    8    9   10   11   12   13   14   15   16   17   18   19   20   21             22   23   24   25    

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

    /**
     * An enumeration for all the different types of MatchMakerEngines.
     */
    public enum EngineType {
        MATCH_ENGINE("Match Engine"), MERGE_ENGINE("Merge Engine"), CLEANSE_ENGINE(
                "Cleanse Engine"), ADDRESS_CORRECTION_ENGINE(
                        "Address Correction Engine"), VALIDATED_ADDRESS_COMMITING_ENGINE(
                                "Validated Address Committing Engine");

        String engineName;

        private EngineType(String engineName) {
            this.engineName = engineName;
        }

        @Override
        public String toString() {
            return engineName;
        }
    }

    /**
     * A listener that keeps the runActionStatus refreshed.
     */
    public final SPListener engineStatusListener;

    /**
     * The session this panel belongs to.
     */
    private MatchMakerSwingSession swingSession;

    /**
     * A listener to keep the UI in synch with the model
     */
    private SPListener activeListener;

    /**
     * A listener to keep the UI in synch with the model
     */
    private SPListener newMungeProcessListener;

    /**
     * The file path to which the engine logs will be written to.
     * Must be a valid file path that the user has write permissions on.
     */
    private JTextField logFilePath;

    /**
     * Opens a file chooser for the user to select the log file they wish
     * to use for engine output.
     */
    private BrowseFileAction browseLogFileAction;

    /**
     * Denotes whether or not the log file should be overwritten or
     * appended to.
     */
    private JCheckBox appendToLog;

    /**
     * A field for the user to specify how many records they want the
     * engine to process.
     */
    private JSpinner recordsToProcess;

    /**
     * A flag for the engine to run in debug mode or not.
     */
    private JCheckBox debugMode;

    /**
     * A flag for telling the engine to delete all the records from the match result table before running the match
     */
    private JCheckBox clearMatchPool;

    /**
     * A flag for telling the engine whether to try to use batch execute when
     * running insert and update statements into the match/address pool.
     */
    private JCheckBox useBatchExecute;

    /**
     * A flag specific to the Address Correcton Engine.
     * <p>
     * If checked, then the engine should automatically correct the results
     * and then write these corrections directly to the source table without
     * requiring user intervention.
     * <p>
     * If not checked, then the engine should write the results of the Address
     * Correction Munge Step to the project's result table and then allow the
     * user to manually correct the addresses before writing them back to the
     * source table.
     */
    private JCheckBox autoWriteAutoValidatedAddresses;

    /**
     * The frame that this editor lives in.
     */
    private JFrame parentFrame;

    /**
     * The project object the engine should run against.
     */
    private Project project;

    /**
     * The panel that displays all the information for the engine.
     */
    private JPanel panel;

    /**
     * Displays the validation status of the engine preconditions.
     */
    private StatusComponent status = new StatusComponent();

    /**
     * Displays a warning if current address database is out of date and a fail if 
     * it's missing.
     */
    private JEditorPane expiryDatePane;

    /**
     * The validation handler used to validate the configuration portion
     * of the editor pane.
     */
    private FormValidationHandler handler;

    /**
     * Listens to changes in the model so as to keep the UI in sync.
     */
    private AbstractUIUpdateManager spinnerUpdateManager;

    /**
     * The collection of components that show the user what the engine is doing.
     */
    private final EngineOutputPanel engineOutputPanel;

    /**
     * An action to run the engine and print the output to the engineOutputPanel
     */
    private Action runEngineAction;

    /**
     * The MatchMakerEngine for this panel
     */
    private final MatchMakerEngine engine;

    /**
     * Keeps track of which level to show the logger info to the panel
     */
    private JComboBox messageLevel;

    /**
     * The abort button
     */
    private JButton abortButton;

    /**
     * The action that is called when the engine is finished
     */
    private Runnable engineFinish = new Runnable() {
        public void run() {
            abortButton.setEnabled(false);
        }
    };

    /**
     * The action that is called when the engine is started
     */
    private Runnable engineStart = new Runnable() {
        public void run() {
            abortButton.setEnabled(true);
        }
    };

    /**
     * The engine type for this panel.
     */
    private final EngineType type;

    /**
     * Contains the saved user settings for this engine panel.
     */
    private final MatchMakerSettings engineSettings;

    /**
     * A set of event listeners that were converted into fields so that they can
     * be removed later via the {@link #cleanup()} method.
     */
    private PropertyChangeListener propertyChangeListener;
    private ItemListener itemListener;
    private AbstractAction messageLevelActionListener;

    /**
     * This is the date that the address database expires on.
     * <p>
     * TODO: Create a settings panel that extends this that is only for the address validation
     * to handle the date.
     */
    private Date expiryDate;

    private EngineListener engineListener;

    private final JButton refreshButton = new JButton(new AbstractAction("Refresh") {

        @Override
        public void actionPerformed(ActionEvent e) {
            recordsToProcess.setValue(engineSettings.getProcessCount());
            refreshButton.setVisible(false);
            spinnerUpdateManager.clearWarnings();
            handler.performFormValidation();
        }
    });

    public EngineSettingsPanel(final MatchMakerSwingSession swingSession, Project project, JFrame parentFrame,
            EngineType engineType) {
        this.swingSession = swingSession;
        this.parentFrame = parentFrame;
        this.project = project;
        this.type = engineType;

        handler = new FormValidationHandler(status);
        engineStatusListener = new SPListener() {
            @Override
            public void childAdded(SPChildEvent e) {
                // no-op
            }

            @Override
            public void childRemoved(SPChildEvent e) {
                // no-op
            }

            @Override
            public void propertyChanged(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals("engineRunning")) {
                    refreshRunActionStatus();
                }
            }

            @Override
            public void transactionEnded(TransactionEvent e) {
                // no-op
            }

            @Override
            public void transactionRollback(TransactionEvent e) {
                // no-op
            }

            @Override
            public void transactionStarted(TransactionEvent e) {
                // no-op
            }

        };
        propertyChangeListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                refreshRunActionStatus();
            }
        };
        handler.addPropertyChangeListener(propertyChangeListener);
        this.engineOutputPanel = new EngineOutputPanel(parentFrame);

        if (type == EngineType.MATCH_ENGINE) {
            engine = project.getMatchingEngine();
            engineSettings = project.getMungeSettings();
        } else if (type == EngineType.MERGE_ENGINE) {
            engine = project.getMergingEngine();
            engineSettings = project.getMergeSettings();
        } else if (type == EngineType.CLEANSE_ENGINE) {
            engine = project.getCleansingEngine();
            engineSettings = project.getMungeSettings();
        } else if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            engine = project.getAddressCorrectionEngine();
            engineSettings = project.getMungeSettings();
        } else if (type == EngineType.VALIDATED_ADDRESS_COMMITING_ENGINE) {
            engine = project.getAddressCommittingEngine();
            engineSettings = project.getMungeSettings();
        } else {
            throw new IllegalArgumentException("There is no engine type with a string " + type);
        }

        Preferences p = Preferences.userNodeForPackage(MatchMakerSessionContext.class);
        Preferences prefs = p.node(project.getUUID());
        engineSettings.setLog(new File(prefs.get(LOG_FILE, project.getName() + ".log")));
        engineSettings.setAppendToLog(Boolean.parseBoolean(prefs.get(APPEND_TO_LOG, "false")));

        if (type == EngineType.MERGE_ENGINE || type == EngineType.CLEANSE_ENGINE
                || type == EngineType.VALIDATED_ADDRESS_COMMITING_ENGINE) {
            this.runEngineAction = new RunWarningEngineAction(swingSession, project, engine, "Run Engine",
                    engineOutputPanel, this, engineStart, engineFinish,
                    "This engine will make changes to your source data.\n"
                            + "It is strongly recommended that you backup your source data before running this engine.\n");
        } else {
            this.runEngineAction = new RunEngineAction(swingSession, project, engine, "Run Engine",
                    engineOutputPanel, this, engineStart, engineFinish);
        }

        engineListener = new EngineListener() {
            public void engineStopped(EngineEvent e) {
                runEngineAction.setEnabled(true);
            }

            public void engineStarted(EngineEvent e) {
                runEngineAction.setEnabled(false);
            }
        };

        engine.addEngineListener(engineListener);

        this.panel = buildUI();

        SQLPowerUtils.listenToShallowHierarchy(engineStatusListener, project);
    }

    /**
     * Performs a form validation on the configuration portion and sets the
     * status accordingly as well as disabling the button to run the engine if
     * necessary.
     */
    private void refreshRunActionStatus() {
        ValidateResult worst = handler.getWorstValidationStatus();
        boolean valid = !(worst.getStatus() == Status.FAIL || project.getRunningEngine() != null);
        runEngineAction.setEnabled(valid);
    }

    /**
     * An action that just calls {@link #doSave}.
     */
    private final class SaveAction extends AbstractAction {
        private SaveAction() {
            super("Save");
        }

        public void actionPerformed(ActionEvent e) {
            applyChanges();
        }
    }

    /**
     * Builds the UI for this editor pane. This is broken into two parts,
     * the configuration and output. Configuration is done in this method
     * while the output section is handled by the EngineOutputPanel and
     * this method simply lays out the components that class provides.
     */
    private JPanel buildUI() {

        logger.debug("We are building the UI of an engine settings panel.");
        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            expiryDatePane = new JEditorPane();
            expiryDatePane.setEditable(false);
            final AddressDatabase addressDatabase;
            try {
                addressDatabase = new AddressDatabase(
                        new File(swingSession.getContext().getAddressCorrectionDataPath()));
                expiryDate = addressDatabase.getExpiryDate();
                expiryDatePane.setText(DateFormat.getDateInstance().format(expiryDate));
            } catch (DatabaseException e1) {
                MMSUtils.showExceptionDialog(parentFrame,
                        "An error occured while loading the Address Correction Data", e1);
                expiryDatePane.setText("Database missing, expiry date invalid");
            }

            logger.debug("We are adding the listener");
            swingSession.getContext().addPreferenceChangeListener(new PreferenceChangeListener() {
                public void preferenceChange(PreferenceChangeEvent evt) {
                    if (MatchMakerSessionContext.ADDRESS_CORRECTION_DATA_PATH.equals(evt.getKey())) {
                        logger.debug("The new database path is: " + evt.getNewValue());
                        final AddressDatabase addressDatabase;
                        try {
                            addressDatabase = new AddressDatabase(new File(evt.getNewValue()));
                            expiryDate = addressDatabase.getExpiryDate();
                            expiryDatePane.setText(DateFormat.getDateInstance().format(expiryDate));
                        } catch (DatabaseException ex) {
                            MMSUtils.showExceptionDialog(parentFrame,
                                    "An error occured while loading the Address Correction Data", ex);
                            expiryDate = null;
                            expiryDatePane.setText("Database missing, expiry date invalid");
                        }
                    }
                }
            });
            // handler listens to expiryDatePane so whenever the expiryDatePane's text has been changed, the below method will be called.
            handler.addValidateObject(expiryDatePane, new Validator() {
                public ValidateResult validate(Object contents) {
                    if (expiryDate == null) {
                        return ValidateResult.createValidateResult(Status.FAIL,
                                "Address Correction Database is missing. Please reset your Address Correction Data Path in Preferences.");
                    }
                    if (Calendar.getInstance().getTime().after(expiryDate)) {
                        return ValidateResult.createValidateResult(Status.WARN,
                                "Address Correction Database is expired. The results of this engine run cannot be SERP valid.");
                    }
                    return ValidateResult.createValidateResult(Status.OK, "");
                }
            });
        }

        File logFile = engineSettings.getLog();

        logFilePath = new JTextField(logFile.getAbsolutePath());
        handler.addValidateObject(logFilePath, new FileNameValidator("Log"));

        browseLogFileAction = new BrowseFileAction(parentFrame, logFilePath);

        appendToLog = new JCheckBox("Append to old Log File?", engineSettings.getAppendToLog());

        recordsToProcess = new JSpinner(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 100));
        if (engineSettings.getProcessCount() != null) {
            recordsToProcess.setValue(engineSettings.getProcessCount());
        }

        spinnerUpdateManager = new SpinnerUpdateManager(recordsToProcess, engineSettings, "processCount", handler,
                this, refreshButton);

        debugMode = new JCheckBox("Debug Mode (Changes will be rolled back)", engineSettings.getDebug());
        updaters.add(new CheckBoxModelUpdater(debugMode, "debug"));
        engineSettings.addSPListener(updaters.get(updaters.size() - 1));
        itemListener = new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                if (((JCheckBox) e.getSource()).isSelected()) {
                    if (type == EngineType.MATCH_ENGINE || type == EngineType.ADDRESS_CORRECTION_ENGINE) {
                        clearMatchPool.setSelected(false);
                        // I've currently disabled the clear match pool option because the match
                        // in debug mode, changes should be rolled back, but if the clearing of the match
                        // pool is rolled back but the engine thinks that it is cleared, it can cause
                        // unique key violations when it tries to insert 'new' matches. But if the engine
                        // is made aware of the rollback, then it would be as if clear match pool wasn't
                        // selected in the first place, so I don't see the point in enabling it in debug mode
                        clearMatchPool.setEnabled(false);
                    }
                    recordsToProcess.setValue(new Integer(1));
                    engine.setMessageLevel(Level.ALL);
                    messageLevel.setSelectedItem(engine.getMessageLevel());
                } else {
                    if (type == EngineType.MATCH_ENGINE || type == EngineType.ADDRESS_CORRECTION_ENGINE) {
                        clearMatchPool.setEnabled(true);
                    }
                    recordsToProcess.setValue(new Integer(0));
                }
                engineSettings.setDebug(debugMode.isSelected());
            }
        };
        debugMode.addItemListener(itemListener);

        if (type == EngineType.MATCH_ENGINE || type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            if (type == EngineType.MATCH_ENGINE) {
                clearMatchPool = new JCheckBox("Clear match pool",
                        ((MungeSettings) engineSettings).isClearMatchPool());
            } else {
                clearMatchPool = new JCheckBox("Clear address pool",
                        ((MungeSettings) engineSettings).isClearMatchPool());
            }
            itemListener = new ItemListener() {
                public void itemStateChanged(ItemEvent e) {
                    ((MungeSettings) engineSettings).setClearMatchPool(clearMatchPool.isSelected());
                }
            };
            clearMatchPool.addItemListener(itemListener);
            updaters.add(new CheckBoxModelUpdater(clearMatchPool, "clearMatchPool"));
            engineSettings.addSPListener(updaters.get(updaters.size() - 1));
            if (debugMode.isSelected()) {
                clearMatchPool.setSelected(false);
                // See comment just above about why this is disabled
                clearMatchPool.setEnabled(false);
            }
        }

        if (engineSettings instanceof MungeSettings) {
            useBatchExecute = new JCheckBox("Batch execute SQL statments",
                    ((MungeSettings) engineSettings).isUseBatchExecution());
            itemListener = new ItemListener() {
                public void itemStateChanged(ItemEvent e) {
                    ((MungeSettings) engineSettings).setUseBatchExecution(useBatchExecute.isSelected());
                }
            };
            useBatchExecute.addItemListener(itemListener);
            updaters.add(new CheckBoxModelUpdater(useBatchExecute, "useBatchExecution"));
            engineSettings.addSPListener(updaters.get(updaters.size() - 1));
        }

        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            autoWriteAutoValidatedAddresses = new JCheckBox("Immediately commit auto-corrected addresses",
                    ((MungeSettings) engineSettings).isAutoWriteAutoValidatedAddresses());
            itemListener = new ItemListener() {
                public void itemStateChanged(ItemEvent e) {
                    ((MungeSettings) engineSettings)
                            .setAutoWriteAutoValidatedAddresses(autoWriteAutoValidatedAddresses.isSelected());
                }
            };
            autoWriteAutoValidatedAddresses.addItemListener(itemListener);
            updaters.add(
                    new CheckBoxModelUpdater(autoWriteAutoValidatedAddresses, "autoWriteAutoValidatedAddresses"));
            engineSettings.addSPListener(updaters.get(updaters.size() - 1));
        }

        messageLevel = new JComboBox(new Level[] { Level.OFF, Level.FATAL, Level.ERROR, Level.WARN, Level.INFO,
                Level.DEBUG, Level.ALL });
        messageLevel.setSelectedItem(engine.getMessageLevel());
        messageLevel.setRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                    boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                setText(value == null ? null : value.toString());
                return this;
            }
        });

        messageLevelActionListener = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                Level sel = (Level) messageLevel.getSelectedItem();
                engine.setMessageLevel(sel);
            }
        };
        messageLevel.addActionListener(messageLevelActionListener);

        String rowSpecs;
        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            rowSpecs = ADDRESS_CORRECTION_ENGINE_PANEL_ROW_SPECS;
        } else if (type == EngineType.VALIDATED_ADDRESS_COMMITING_ENGINE) {
            rowSpecs = ADDRESS_COMMITTING_ENGINE_PANEL_ROW_SPECS;
        } else if (type == EngineType.MERGE_ENGINE) {
            rowSpecs = MERGE_ENGINE_PANEL_ROW_SPECS;
        } else if (type == EngineType.CLEANSE_ENGINE) {
            rowSpecs = CLEANSE_ENGINE_PANEL_ROW_SPECS;
        } else {
            rowSpecs = MATCH_ENGINE_PANEL_ROW_SPECS;
        }

        String columnSpecs = "4dlu,fill:pref,4dlu,pref,pref,40dlu,fill:pref:grow,pref,4dlu";

        FormLayout layout = new FormLayout(columnSpecs, rowSpecs);

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

        CellConstraints cc = new CellConstraints();

        pb.add(status, cc.xyw(4, 2, 6, "l,c"));
        pb.add(refreshButton, cc.xy(6, 2, "l, c"));
        refreshButton.setVisible(false);

        int y = 4;
        pb.add(new JLabel("Log File:"), cc.xy(2, y, "r,f"));
        pb.add(logFilePath, cc.xyw(4, y, 4, "f,f"));
        pb.add(new JButton(browseLogFileAction), cc.xy(8, y, "l,f"));
        y += 2;
        pb.add(appendToLog, cc.xy(4, y, "l,t"));
        pb.add(new JButton(new ShowLogFileAction(logFilePath)), cc.xy(5, y, "r,t"));

        if (type == EngineType.MATCH_ENGINE || type == EngineType.CLEANSE_ENGINE) {
            y += 2;

            pb.add(new JLabel("Tranformations to run: "), cc.xy(2, y, "r,t"));
            final MungeProcessSelectionList selectionButton = new MungeProcessSelectionList(project) {
                @Override
                public boolean getValue(MungeProcess mp) {
                    return mp.getActive();
                }

                @Override
                public void setValue(MungeProcess mp, boolean value) {
                    mp.setActive(value);
                }
            };

            activeListener = new AbstractPoolingSPListener() {
                boolean isSelectionListUpdate = false;

                @Override
                protected void finalCommitImpl(TransactionEvent e) {
                    if (isSelectionListUpdate) {
                        selectionButton.checkModel();
                    }
                }

                @Override
                public void propertyChangeImpl(PropertyChangeEvent evt) {
                    logger.debug("checking property with name " + evt.getPropertyName());
                    if (evt.getPropertyName().equals("active")) {
                        isSelectionListUpdate = true;
                    } else {
                        isSelectionListUpdate = false;
                    }
                }
            };

            swingSession.getRootNode().addSPListener(activeListener);
            for (MungeProcess mp : project.getMungeProcesses()) {
                mp.addSPListener(activeListener);
            }

            newMungeProcessListener = new AbstractSPListener() {
                @Override
                public void childAdded(SPChildEvent e) {
                    selectionButton.refreshList();
                }

                @Override
                public void childRemoved(SPChildEvent e) {
                    selectionButton.refreshList();
                }
            };
            project.addSPListener(newMungeProcessListener);

            pb.add(selectionButton, cc.xyw(4, y, 2, "l,c"));
        }

        y += 2;
        pb.add(new JLabel("# of records to process:"), cc.xy(2, y, "r,c"));
        pb.add(recordsToProcess, cc.xy(4, y, "l,c"));
        pb.add(new JLabel(" (Set to 0 to process all)"), cc.xy(5, y, "l, c"));

        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            pb.add(new JLabel("Address Filter Setting:"), cc.xy(7, y));
        }

        if (engineSettings instanceof MungeSettings) {
            MungeSettings mungeSettings = (MungeSettings) engineSettings;
            y += 2;
            pb.add(useBatchExecute, cc.xyw(4, y, 2, "l,c"));

            if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
                final JLabel poolSettingLabel = new JLabel(
                        mungeSettings.getPoolFilterSetting().getLongDescription());
                SPListener poolFilterSettingChangeListener = new SPListener() {
                    public void childAdded(SPChildEvent evt) {
                        // no-op
                    }

                    public void childRemoved(SPChildEvent evt) {
                        // no-op
                    }

                    public void propertyChanged(PropertyChangeEvent evt) {
                        if (evt.getPropertyName() == "poolFilterSetting") {
                            PoolFilterSetting newValue = (PoolFilterSetting) evt.getNewValue();
                            poolSettingLabel.setText(newValue.getLongDescription());
                        }
                    }

                    public void transactionStarted(TransactionEvent evt) {
                        // no-op
                    }

                    public void transactionRollback(TransactionEvent evt) {
                        // no-op
                    }

                    public void transactionEnded(TransactionEvent evt) {

                    }

                };
                mungeSettings.addSPListener(poolFilterSettingChangeListener);
                Font f = poolSettingLabel.getFont();
                Font newFont = f.deriveFont(Font.ITALIC);
                poolSettingLabel.setFont(newFont);
                pb.add(poolSettingLabel, cc.xy(7, y));
            }
        }

        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            y += 2;
            pb.add(autoWriteAutoValidatedAddresses, cc.xyw(4, y, 2, "l,c"));
            if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
                pb.add(new JLabel("Auto-correction Setting:"), cc.xy(7, y));
            }
        }

        if (type == EngineType.MATCH_ENGINE || type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            y += 2;
            pb.add(clearMatchPool, cc.xyw(4, y, 2, "l,c"));
            if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
                MungeSettings mungeSettings = (MungeSettings) engineSettings;
                final JLabel autoValidateSettingLabel = new JLabel(
                        ((MungeSettings) engineSettings).getAutoValidateSetting().getLongDescription());
                SPListener poolFilterSettingChangeListener = new SPListener() {
                    public void childAdded(SPChildEvent evt) {
                        // no-op
                    }

                    public void childRemoved(SPChildEvent evt) {
                        // no-op
                    }

                    public void propertyChanged(PropertyChangeEvent evt) {
                        if (evt.getPropertyName() == "autoValidateSetting") {
                            AutoValidateSetting newValue = (AutoValidateSetting) evt.getNewValue();
                            autoValidateSettingLabel.setText(newValue.getLongDescription());
                        }
                    }

                    public void transactionStarted(TransactionEvent evt) {
                        // no-op
                    }

                    public void transactionRollback(TransactionEvent evt) {
                        // no-op
                    }

                    public void transactionEnded(TransactionEvent evt) {
                        // no-op
                    }
                };
                mungeSettings.addSPListener(poolFilterSettingChangeListener);
                Font f = autoValidateSettingLabel.getFont();
                Font newFont = f.deriveFont(Font.ITALIC);
                autoValidateSettingLabel.setFont(newFont);
                pb.add(autoValidateSettingLabel, cc.xy(7, y));
            }
        }

        y += 2;
        pb.add(debugMode, cc.xyw(4, y, 2, "l,c"));
        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            final AddressValidationSettingsPanel avsp = new AddressValidationSettingsPanel(
                    (MungeSettings) engineSettings);
            final JDialog validationSettingsDialog = DataEntryPanelBuilder.createDataEntryPanelDialog(avsp,
                    swingSession.getFrame(), "Address Validation Settings", "OK", new Callable<Boolean>() {
                        public Boolean call() throws Exception {
                            boolean returnValue = avsp.applyChanges();
                            return returnValue;
                        }
                    }, new Callable<Boolean>() {
                        public Boolean call() throws Exception {
                            return true;
                        }
                    });
            validationSettingsDialog.setLocationRelativeTo(pb.getPanel());

            JButton addressValidationSettings = new JButton(new AbstractAction("Validation Settings...") {
                public void actionPerformed(ActionEvent e) {
                    validationSettingsDialog.setVisible(true);
                }
            });
            pb.add(addressValidationSettings, cc.xy(7, y, "l,c"));
        }

        y += 2;
        pb.add(new JLabel("Message Level:"), cc.xy(2, y, "r,t"));
        pb.add(messageLevel, cc.xy(4, y, "l,t"));

        abortButton = new JButton(new AbstractAction("Abort!") {
            public void actionPerformed(ActionEvent e) {
                engine.setCancelled(true);
            }
        });

        abortButton.setEnabled(false);

        ButtonBarBuilder bbb = new ButtonBarBuilder();
        bbb.addFixed(new JButton(new SaveAction()));
        bbb.addRelatedGap();
        bbb.addFixed(new JButton(new ShowCommandAction(parentFrame, this, engine)));
        bbb.addRelatedGap();
        bbb.addFixed(new JButton(runEngineAction));
        bbb.addRelatedGap();
        bbb.addFixed(abortButton);

        y += 2;
        pb.add(bbb.getPanel(), cc.xyw(2, y, 7, "r,c"));

        y += 2;
        pb.add(engineOutputPanel.getProgressBar(), cc.xyw(2, y, 7));

        y += 2;
        pb.add(engineOutputPanel.getOutputComponent(), cc.xyw(2, y, 7));

        y += 2;
        pb.add(engineOutputPanel.getButtonBar(), cc.xyw(2, y, 7));

        refreshRunActionStatus();

        return pb.getPanel();
    }

    /**
     * Saves the engine settings
     */
    public boolean applyChanges() {
        refreshRunActionStatus();
        engineSettings.setDebug(debugMode.isSelected());
        if (type == EngineType.MATCH_ENGINE || type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            ((MungeSettings) engineSettings).setClearMatchPool(clearMatchPool.isSelected());
        }
        if (engineSettings instanceof MungeSettings) {
            ((MungeSettings) engineSettings).setUseBatchExecution(useBatchExecute.isSelected());
        }
        if (type == EngineType.ADDRESS_CORRECTION_ENGINE) {
            ((MungeSettings) engineSettings)
                    .setAutoWriteAutoValidatedAddresses(autoWriteAutoValidatedAddresses.isSelected());
        }
        engineSettings.setLog(new File(logFilePath.getText()));
        engineSettings.setAppendToLog(appendToLog.isSelected());
        Preferences p = Preferences.userNodeForPackage(MatchMakerSessionContext.class);
        Preferences prefs = p.node(project.getUUID());
        prefs.put(LOG_FILE, engineSettings.getLog().getAbsolutePath());
        prefs.put(APPEND_TO_LOG, Boolean.toString(engineSettings.getAppendToLog()));
        if (recordsToProcess.getValue().equals(new Integer(0))) {
            engineSettings.setProcessCount(null);
        } else {
            engineSettings.setProcessCount((Integer) recordsToProcess.getValue());
        }
        return true;
    }

    public JComponent getPanel() {
        return panel;
    }

    public boolean hasUnsavedChanges() {
        //XXX This is stubbed for now, should look over the check boxes
        //and text fields in the configuration section
        return false;
    }

    public void discardChanges() {
        logger.error("Cannot discard changes");
    }

    /**
     * Clean up all resources being held up by the EngineSettingsPanel.
     * including removing all event listeners.
     */
    public void cleanup() {
        handler.removePropertyChangeListener(propertyChangeListener);
        debugMode.removeItemListener(itemListener);
        messageLevel.removeActionListener(messageLevelActionListener);
        spinnerUpdateManager.cleanup();
        for (CheckBoxModelUpdater cbmu : updaters) {
            engineSettings.removeSPListener(cbmu);
        }
        swingSession.getRootNode().removeSPListener(activeListener);
        for (MungeProcess mp : project.getMungeProcesses()) {
            mp.removeSPListener(activeListener);
        }
        project.removeSPListener(newMungeProcessListener);
        //      engine.removeEngineListener(engineListener);
    }
}