org.datacleaner.windows.AnalysisJobBuilderWindowImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.datacleaner.windows.AnalysisJobBuilderWindowImpl.java

Source

/**
 * DataCleaner (community edition)
 * Copyright (C) 2014 Neopost - Customer Information Management
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.datacleaner.windows;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.Schema;
import org.apache.metamodel.util.FileHelper;
import org.datacleaner.Version;
import org.datacleaner.actions.NewAnalysisJobActionListener;
import org.datacleaner.actions.OpenAnalysisJobActionListener;
import org.datacleaner.actions.RunAnalysisActionListener;
import org.datacleaner.actions.SaveAnalysisJobActionListener;
import org.datacleaner.api.InputColumn;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.components.maxrows.MaxRowsFilter;
import org.datacleaner.components.maxrows.MaxRowsFilter.Category;
import org.datacleaner.configuration.DataCleanerConfiguration;
import org.datacleaner.configuration.DataCleanerConfigurationImpl;
import org.datacleaner.configuration.DataCleanerEnvironmentImpl;
import org.datacleaner.connection.Datastore;
import org.datacleaner.connection.DatastoreConnection;
import org.datacleaner.data.MutableInputColumn;
import org.datacleaner.database.DatabaseDriverCatalog;
import org.datacleaner.descriptors.ConfiguredPropertyDescriptor;
import org.datacleaner.guice.DCModule;
import org.datacleaner.guice.JobFile;
import org.datacleaner.guice.Nullable;
import org.datacleaner.job.AnalysisJob;
import org.datacleaner.job.ComponentValidationException;
import org.datacleaner.job.FilterOutcome;
import org.datacleaner.job.JaxbJobWriter;
import org.datacleaner.job.SimpleComponentRequirement;
import org.datacleaner.job.builder.AnalysisJobBuilder;
import org.datacleaner.job.builder.AnalysisJobChangeListener;
import org.datacleaner.job.builder.AnalyzerChangeListener;
import org.datacleaner.job.builder.AnalyzerComponentBuilder;
import org.datacleaner.job.builder.ComponentBuilder;
import org.datacleaner.job.builder.FilterChangeListener;
import org.datacleaner.job.builder.FilterComponentBuilder;
import org.datacleaner.job.builder.SourceColumnChangeListener;
import org.datacleaner.job.builder.TransformerChangeListener;
import org.datacleaner.job.builder.TransformerComponentBuilder;
import org.datacleaner.job.builder.UnconfiguredConfiguredPropertyException;
import org.datacleaner.job.concurrent.SingleThreadedTaskRunner;
import org.datacleaner.panels.DCGlassPane;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.panels.DatastoreManagementPanel;
import org.datacleaner.panels.ExecuteJobWithoutAnalyzersDialog;
import org.datacleaner.panels.SchemaTreePanel;
import org.datacleaner.panels.SelectDatastoreContainerPanel;
import org.datacleaner.panels.WelcomePanel;
import org.datacleaner.result.renderer.RendererFactory;
import org.datacleaner.user.MutableDatastoreCatalog;
import org.datacleaner.user.UsageLogger;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.ImageManager;
import org.datacleaner.util.LabelUtils;
import org.datacleaner.util.StringUtils;
import org.datacleaner.util.WidgetFactory;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.util.WindowSizePreferences;
import org.datacleaner.widgets.CollapsibleTreePanel;
import org.datacleaner.widgets.DCLabel;
import org.datacleaner.widgets.DCPersistentSizedPanel;
import org.datacleaner.widgets.LicenceAndEditionStatusLabel;
import org.datacleaner.widgets.PopupButton;
import org.datacleaner.widgets.tabs.JobClassicView;
import org.datacleaner.widgets.visualization.JobGraph;
import org.jdesktop.swingx.JXStatusBar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The main window in the DataCleaner GUI. This window is called the
 * AnalysisJobBuilderWindow because it's main purpose is to present a job that
 * is being built. Behind the covers this job state is respresented in the
 * {@link AnalysisJobBuilder} class.
 */
@Singleton
public final class AnalysisJobBuilderWindowImpl extends AbstractWindow
        implements AnalysisJobBuilderWindow, WindowListener {

    private class WindowAnalysisJobChangeListener implements AnalysisJobChangeListener {
        @Override
        public void onActivation(final AnalysisJobBuilder builder) {
            builder.addAnalyzerChangeListener(_analyzerChangeListener);
            builder.addTransformerChangeListener(_transformerChangeListener);
            builder.addFilterChangeListener(_filterChangeListener);
            builder.addSourceColumnChangeListener(_sourceColumnChangeListener);
            builder.addAnalysisJobChangeListener(this);

            // We'll need to listen to already added output data stream job
            // builders
            for (AnalysisJobBuilder analysisJobBuilder : builder.getConsumedOutputDataStreamsJobBuilders()) {
                onActivation(analysisJobBuilder);
            }
        }

        @Override
        public void onDeactivation(final AnalysisJobBuilder builder) {
            builder.removeAnalyzerChangeListener(_analyzerChangeListener);
            builder.removeTransformerChangeListener(_transformerChangeListener);
            builder.removeFilterChangeListener(_filterChangeListener);
            builder.removeSourceColumnChangeListener(_sourceColumnChangeListener);
            builder.removeAnalysisJobChangeListener(this);
        }
    }

    private class WindowAnalyzerChangeListener implements AnalyzerChangeListener {
        @Override
        public void onAdd(final AnalyzerComponentBuilder<?> analyzerJobBuilder) {
            if (_classicView != null) {
                _classicView.addAnalyzer(analyzerJobBuilder);
            }
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onRemove(final AnalyzerComponentBuilder<?> analyzerJobBuilder) {
            if (_classicView != null) {
                _classicView.removeAnalyzer(analyzerJobBuilder);
            }
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onConfigurationChanged(final AnalyzerComponentBuilder<?> analyzerJobBuilder) {
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onRequirementChanged(final AnalyzerComponentBuilder<?> analyzerJobBuilder) {
            _graph.refresh();
        }
    }

    private class WindowTransformerChangeListener implements TransformerChangeListener {

        @Override
        public void onAdd(final TransformerComponentBuilder<?> transformerJobBuilder) {
            if (_classicView != null) {
                _classicView.addTransformer(transformerJobBuilder);
            }
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onRemove(final TransformerComponentBuilder<?> transformerJobBuilder) {
            if (_classicView != null) {
                _classicView.removeTransformer(transformerJobBuilder);
            }
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onOutputChanged(final TransformerComponentBuilder<?> transformerJobBuilder,
                List<MutableInputColumn<?>> outputColumns) {
            _graph.refresh();
        }

        @Override
        public void onRequirementChanged(final TransformerComponentBuilder<?> transformerJobBuilder) {
            _graph.refresh();
        }

        @Override
        public void onConfigurationChanged(final TransformerComponentBuilder<?> transformerJobBuilder) {
            updateStatusLabel();
            _graph.refresh();
        }
    }

    private class WindowFilterChangeListener implements FilterChangeListener {

        @Override
        public void onAdd(final FilterComponentBuilder<?, ?> filterJobBuilder) {
            if (_classicView != null) {
                _classicView.addFilter(filterJobBuilder);
            }
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onRemove(final FilterComponentBuilder<?, ?> filterJobBuilder) {
            if (_classicView != null) {
                _classicView.removeFilter(filterJobBuilder);
            }
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onConfigurationChanged(final FilterComponentBuilder<?, ?> filterJobBuilder) {
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onRequirementChanged(final FilterComponentBuilder<?, ?> filterJobBuilder) {
            _graph.refresh();
        }
    }

    private class WindowSourceColumnChangeListener implements SourceColumnChangeListener {

        @Override
        public void onAdd(final InputColumn<?> sourceColumn) {
            onSourceColumnsChanged();
            updateStatusLabel();
            _graph.refresh();
        }

        @Override
        public void onRemove(final InputColumn<?> sourceColumn) {
            onSourceColumnsChanged();
            updateStatusLabel();
            _graph.refresh();
        }
    }

    private static final String USER_PREFERENCES_PROPERTY_EDITING_MODE_PREFERENCE = "editing_mode_preference";

    private static final long serialVersionUID = 1L;

    private static final Logger logger = LoggerFactory.getLogger(AnalysisJobBuilderWindow.class);
    private static final ImageManager imageManager = ImageManager.get();

    private static final int DEFAULT_WINDOW_WIDTH = 1000;
    private static final int DEFAULT_WINDOW_HEIGHT = 710;

    private final List<PopupButton> _superCategoryButtons = new ArrayList<>();
    private final AnalysisJobBuilder _analysisJobBuilder;
    private final DataCleanerConfiguration _configuration;
    private final RendererFactory _presenterRendererFactory;
    private final DCLabel _statusLabel = DCLabel.bright("");
    private final CollapsibleTreePanel _leftPanel;
    private final SchemaTreePanel _schemaTreePanel;
    private final JButton _saveButton;
    private final JButton _saveAsButton;
    private final JButton _executeButton;
    private final JButton _executionAlternativesButton;
    private final Provider<SaveAnalysisJobActionListener> _saveAnalysisJobActionListenerProvider;
    private final Provider<NewAnalysisJobActionListener> _newAnalysisJobActionListenerProvider;
    private final Provider<OpenAnalysisJobActionListener> _openAnalysisJobActionListenerProvider;
    private final Provider<ReferenceDataDialog> _referenceDataDialogProvider;
    private final Provider<MonitorConnectionDialog> _monitorConnectionDialogProvider;
    private final Provider<OptionsDialog> _optionsDialogProvider;
    private final DCGlassPane _glassPane;
    private final WelcomePanel _welcomePanel;
    private final DatastoreManagementPanel _datastoreManagementPanel;
    private final SelectDatastoreContainerPanel _selectDatastorePanel;
    private final UserPreferences _userPreferences;
    private final DCModule _dcModule;
    private final JToggleButton _classicViewButton;
    private final JToggleButton _graphViewButton;
    private final JobGraph _graph;
    private final DCPanel _contentContainerPanel;
    private final JComponent _editingContentView;
    private final UsageLogger _usageLogger;
    private final AnalyzerChangeListener _analyzerChangeListener = new WindowAnalyzerChangeListener();
    private final TransformerChangeListener _transformerChangeListener = new WindowTransformerChangeListener();
    private final FilterChangeListener _filterChangeListener = new WindowFilterChangeListener();
    private final SourceColumnChangeListener _sourceColumnChangeListener = new WindowSourceColumnChangeListener();
    private final AnalysisJobChangeListener _analysisJobChangeListener = new WindowAnalysisJobChangeListener();
    private JobClassicView _classicView;
    private FileObject _jobFilename;
    private Datastore _datastore;
    private DatastoreConnection _datastoreConnection;
    private boolean _datastoreSelectionEnabled;
    private JComponent _windowContent;
    private WindowSizePreferences _windowSizePreference;
    private AnalysisWindowPanelType _currentPanelType;

    @Inject
    protected AnalysisJobBuilderWindowImpl(DataCleanerConfiguration configuration, WindowContext windowContext,
            SchemaTreePanel schemaTreePanel, AnalysisJobBuilder analysisJobBuilder, DCModule dcModule,
            UserPreferences userPreferences, @Nullable @JobFile FileObject jobFilename,
            Provider<NewAnalysisJobActionListener> newAnalysisJobActionListenerProvider,
            Provider<OpenAnalysisJobActionListener> openAnalysisJobActionListenerProvider,
            Provider<SaveAnalysisJobActionListener> saveAnalysisJobActionListenerProvider,
            Provider<ReferenceDataDialog> referenceDataDialogProvider, UsageLogger usageLogger,
            Provider<OptionsDialog> optionsDialogProvider,
            Provider<MonitorConnectionDialog> monitorConnectionDialogProvider,
            OpenAnalysisJobActionListener openAnalysisJobActionListener,
            DatabaseDriverCatalog databaseDriverCatalog) {
        super(windowContext);
        _jobFilename = jobFilename;
        _configuration = configuration;
        _dcModule = dcModule;
        _newAnalysisJobActionListenerProvider = newAnalysisJobActionListenerProvider;
        _openAnalysisJobActionListenerProvider = openAnalysisJobActionListenerProvider;
        _saveAnalysisJobActionListenerProvider = saveAnalysisJobActionListenerProvider;
        _referenceDataDialogProvider = referenceDataDialogProvider;
        _monitorConnectionDialogProvider = monitorConnectionDialogProvider;
        _optionsDialogProvider = optionsDialogProvider;
        _userPreferences = userPreferences;
        _usageLogger = usageLogger;
        _windowSizePreference = new WindowSizePreferences(_userPreferences, getClass(), DEFAULT_WINDOW_WIDTH,
                DEFAULT_WINDOW_HEIGHT);

        if (analysisJobBuilder == null) {
            _analysisJobBuilder = new AnalysisJobBuilder(_configuration);
        } else {
            _analysisJobBuilder = analysisJobBuilder;
            DatastoreConnection con = _analysisJobBuilder.getDatastoreConnection();
            if (con != null) {
                _datastore = con.getDatastore();
            }
        }

        _datastoreSelectionEnabled = true;
        _presenterRendererFactory = new RendererFactory(configuration);
        _glassPane = new DCGlassPane(this);

        _graph = new JobGraph(windowContext, userPreferences, analysisJobBuilder, _presenterRendererFactory,
                usageLogger);

        _analysisJobChangeListener.onActivation(_analysisJobBuilder);

        _saveButton = createToolbarButton("Save", IconUtils.ACTION_SAVE_BRIGHT);
        _saveAsButton = createToolbarButton("Save As...", IconUtils.ACTION_SAVE_BRIGHT);

        _executeButton = createToolbarButton("Execute", IconUtils.MENU_EXECUTE);
        _executionAlternativesButton = createToolbarButton("\uf0d7", null);
        _executionAlternativesButton.setFont(WidgetUtils.FONT_FONTAWESOME);

        _welcomePanel = new WelcomePanel(this, _userPreferences, _openAnalysisJobActionListenerProvider.get(),
                _dcModule);

        _datastoreManagementPanel = new DatastoreManagementPanel(_configuration, this, _glassPane,
                _optionsDialogProvider, _dcModule, databaseDriverCatalog, _userPreferences);
        _selectDatastorePanel = new SelectDatastoreContainerPanel(this, _dcModule, databaseDriverCatalog,
                (MutableDatastoreCatalog) configuration.getDatastoreCatalog(), _userPreferences);

        _editingContentView = new DCPanel();
        _editingContentView.setLayout(new BorderLayout());

        _contentContainerPanel = new DCPanel(WidgetUtils.COLOR_DEFAULT_BACKGROUND);
        _contentContainerPanel.setLayout(new CardLayout());
        _contentContainerPanel.add(_welcomePanel, AnalysisWindowPanelType.WELCOME.getName());
        _contentContainerPanel.add(_editingContentView, AnalysisWindowPanelType.EDITING_CONTEXT.getName());
        _contentContainerPanel.add(_datastoreManagementPanel, AnalysisWindowPanelType.MANAGE_DS.getName());
        _contentContainerPanel.add(_selectDatastorePanel, AnalysisWindowPanelType.SELECT_DS.getName());

        final boolean graphPreferred = isGraphPreferred();

        if (graphPreferred) {
            setEditingViewGraph();
        } else {
            setEditingViewClassic();
        }

        _classicViewButton = createViewToggleButton("Classic view", "images/actions/editing-view-classic.png");
        _classicViewButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setEditingViewClassic();
            }
        });

        _classicViewButton.setSelected(!graphPreferred);
        _graphViewButton = createViewToggleButton("Graph view", "images/actions/editing-view-graph.png");
        _graphViewButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setEditingViewGraph();
            }
        });
        _graphViewButton.setSelected(graphPreferred);

        final ActionListener viewToggleButtonActionListener = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == _classicViewButton) {
                    _classicViewButton.setSelected(true);
                    _graphViewButton.setSelected(false);
                    _userPreferences.getAdditionalProperties()
                            .put(USER_PREFERENCES_PROPERTY_EDITING_MODE_PREFERENCE, "Classic");
                } else {
                    _classicViewButton.setSelected(false);
                    _graphViewButton.setSelected(true);
                    _userPreferences.getAdditionalProperties()
                            .put(USER_PREFERENCES_PROPERTY_EDITING_MODE_PREFERENCE, "Graph");
                }
            }
        };
        _classicViewButton.addActionListener(viewToggleButtonActionListener);
        _graphViewButton.addActionListener(viewToggleButtonActionListener);

        _schemaTreePanel = schemaTreePanel;

        _leftPanel = new CollapsibleTreePanel(_schemaTreePanel);
        _leftPanel.setVisible(false);
        _leftPanel.setCollapsed(true);
        _schemaTreePanel.setUpdatePanel(_leftPanel);
    }

    @Override
    public void changePanel(AnalysisWindowPanelType panel) {
        if (_datastore == null) {
            _currentPanelType = panel;
        } else {
            _currentPanelType = AnalysisWindowPanelType.EDITING_CONTEXT;
        }
        updateCurrentPanel();
    }

    private void updateCurrentPanel() {
        ((CardLayout) _contentContainerPanel.getLayout()).show(_contentContainerPanel, _currentPanelType.getName());
        updateLeftPanelVisibility(_currentPanelType == AnalysisWindowPanelType.EDITING_CONTEXT);
        updateWindowTitle();
    }

    private boolean isGraphPreferred() {
        final Map<String, String> additionalProperties = _userPreferences.getAdditionalProperties();
        final String property = additionalProperties.get(USER_PREFERENCES_PROPERTY_EDITING_MODE_PREFERENCE);
        if ("Classic".equals(property)) {
            return false;
        }
        return true;
    }

    private JButton createToolbarButton(String text, String iconPath) {
        final ImageIcon icon;
        if (iconPath == null) {
            icon = null;
        } else {
            icon = imageManager.getImageIcon(iconPath, IconUtils.ICON_SIZE_SMALL);
        }
        final JButton button = new JButton(text, icon);
        button.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                button.setForeground(WidgetUtils.BG_COLOR_BLUE_BRIGHT);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                button.setForeground(WidgetUtils.BG_COLOR_BRIGHTEST);
            }
        });

        WidgetUtils.setDarkButtonStyle(button);
        return button;
    }

    /**
     * Gets whether or not the datastore has been set in this window (ie. if the
     * tree is showing a datastore).
     * 
     * @return true if a datastore is set.
     */
    @Override
    public boolean isDatastoreSet() {
        return _datastore != null;
    }

    /**
     * Initializes the window to use a particular datastore in the schema tree.
     * 
     * @param datastore
     */
    @Override
    public void setDatastore(final Datastore datastore) {
        setDatastore(datastore, false);
    }

    /**
     * Initializes the window to use a particular datastore in the schema tree.
     * 
     * @param datastore
     * @param expandTree
     *            true if the datastore tree should be initially expanded.
     */
    @Override
    public void setDatastore(final Datastore datastore, boolean expandTree) {
        final DatastoreConnection con;
        if (datastore == null) {
            con = null;
        } else {
            con = datastore.openConnection();
        }

        _datastore = datastore;
        if (_datastoreConnection != null) {
            _datastoreConnection.close();
        }
        _analysisJobBuilder.setDatastore(datastore);
        _datastoreConnection = con;

        if (datastore == null) {
            _analysisJobBuilder.reset();
            changePanel(AnalysisWindowPanelType.WELCOME);
        } else {
            changePanel(AnalysisWindowPanelType.EDITING_CONTEXT);
            addTableToSource(con);
        }

        setSchemaTree(datastore, expandTree, con);
        updateStatusLabel();
    }

    private void setSchemaTree(final Datastore datastore, boolean expandTree, final DatastoreConnection con) {
        if (con != null) {
            final Schema defaultSchema = con.getSchemaNavigator().getDefaultSchema();
            final int datastoreSize = defaultSchema.getTables().length;
            if (datastoreSize == 1) {
                _schemaTreePanel.setDatastore(datastore, true);
            } else {
                _schemaTreePanel.setDatastore(datastore, expandTree);
            }
        }
    }

    private void addTableToSource(final DatastoreConnection con) {
        if (con != null) {
            final Schema defaultSchema = con.getSchemaNavigator().getDefaultSchema();
            final int datastoreSize = defaultSchema.getTables().length;
            if (datastoreSize == 1) {
                final Column[] columns = defaultSchema.getTable(0).getColumns();
                _analysisJobBuilder.addSourceColumns(columns);
            }
        }
    }

    private void updateLeftPanelVisibility(boolean show) {
        if (show) {
            _leftPanel.setVisible(true);
            if (_leftPanel.isCollapsed()) {
                _leftPanel.setCollapsed(false);
            }
        } else {
            if (!_leftPanel.isCollapsed()) {
                _leftPanel.setCollapsed(true);
            }
            final Timer timer = new Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (_leftPanel.isCollapsed()) {
                        _leftPanel.setVisible(false);
                    }
                }
            });
            timer.setRepeats(false);
            timer.start();
        }
    }

    private void setEditingViewGraph() {
        setEditingView(_graph.getPanel());
        _classicView = null;
    }

    private void setEditingViewClassic() {
        if (_classicView == null) {
            _classicView = new JobClassicView(getWindowContext(), _analysisJobBuilder, _presenterRendererFactory,
                    _usageLogger);
        }
        setEditingView(_classicView);
    }

    private void setEditingView(JComponent component) {
        final Component[] components = _editingContentView.getComponents();
        for (Component existing : components) {
            if (component == existing) {
                existing.setVisible(true);
            } else {
                existing.setVisible(false);
            }
        }

        _editingContentView.add(component, BorderLayout.CENTER);
        _editingContentView.updateUI();
    }

    @Override
    protected void onWindowVisible() {
        changePanel(AnalysisWindowPanelType.WELCOME);
    }

    public void updateStatusLabel() {
        boolean executeable = false;

        if (_datastore == null) {
            setStatusLabelText("Welcome to DataCleaner " + Version.getDistributionVersion());
            _statusLabel.setIcon(imageManager.getImageIcon(IconUtils.APPLICATION_ICON, IconUtils.ICON_SIZE_SMALL));
        } else {
            if (!_analysisJobBuilder.getSourceColumns().isEmpty()) {
                executeable = true;
            }
            try {
                if (_analysisJobBuilder.isConfigured(true)) {
                    setStatusLabelText("Job is correctly configured");
                    setStatusLabelValid();
                } else {
                    setStatusLabelText("Job is not correctly configured");
                    setStatusLabelWarning();
                }
            } catch (Exception ex) {
                logger.debug("Job not correctly configured", ex);
                final String errorMessage;
                if (ex instanceof UnconfiguredConfiguredPropertyException) {
                    executeable = false;
                    final UnconfiguredConfiguredPropertyException unconfiguredConfiguredPropertyException = (UnconfiguredConfiguredPropertyException) ex;
                    final ConfiguredPropertyDescriptor configuredProperty = unconfiguredConfiguredPropertyException
                            .getConfiguredProperty();
                    final ComponentBuilder componentBuilder = unconfiguredConfiguredPropertyException
                            .getComponentBuilder();
                    errorMessage = "Property '" + configuredProperty.getName() + "' in "
                            + LabelUtils.getLabel(componentBuilder) + " is not set!";
                } else if (ex instanceof ComponentValidationException) {
                    executeable = false;
                    final ComponentValidationException componentValidationException = (ComponentValidationException) ex;
                    errorMessage = componentValidationException.getComponentDescriptor().getDisplayName()
                            + " validation failed: " + ex.getMessage();
                } else {
                    errorMessage = ex.getMessage();
                }
                setStatusLabelText("Job error status: " + errorMessage);
                setStatusLabelError();
            }
        }

        _executeButton.setEnabled(executeable);
        _executionAlternativesButton.setEnabled(executeable);
    }

    @Override
    public void setStatusLabelError() {
        _statusLabel.setIcon(imageManager.getImageIcon(IconUtils.STATUS_ERROR, IconUtils.ICON_SIZE_SMALL));
    }

    @Override
    public void setStatusLabelNotice() {
        _statusLabel.setIcon(imageManager.getImageIcon(IconUtils.STATUS_INFO, IconUtils.ICON_SIZE_SMALL));
    }

    @Override
    public void setStatusLabelValid() {
        _statusLabel.setIcon(imageManager.getImageIcon(IconUtils.STATUS_VALID, IconUtils.ICON_SIZE_SMALL));
    }

    @Override
    public void setStatusLabelWarning() {
        _statusLabel.setIcon(imageManager.getImageIcon(IconUtils.STATUS_WARNING, IconUtils.ICON_SIZE_SMALL));
    }

    @Override
    public String getStatusLabelText() {
        return _statusLabel.getText();
    }

    @Override
    public void setStatusLabelText(String statusLabelText) {
        _statusLabel.setText(statusLabelText);
    }

    @Override
    protected boolean onWindowClosing() {
        if (!super.onWindowClosing()) {
            return false;
        }

        switch (_currentPanelType) {
        case WELCOME:
            final int count = getWindowContext().getWindowCount(AnalysisJobBuilderWindow.class);
            if (count == 1) {
                if (getWindowContext().showExitDialog()) {
                    cleanupForWindowClose();
                    getWindowContext().exit();
                }
            } else {
                cleanupForWindowClose();
                return true;
            }
            break;
        case EDITING_CONTEXT:
            // if datastore is set and datastore selection is enabled,
            // return to datastore selection.

            if (isJobUnsaved(getJobFile(), _analysisJobBuilder) && (_saveButton.isEnabled())) {

                final Object[] buttons = { "Save changes", "Discard changes", "Cancel" };
                final int unsavedChangesChoice = JOptionPane.showOptionDialog(this,
                        "The job has unsaved changes. What would you like to do?", "Unsaved changes detected",
                        JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, buttons, buttons[1]);

                if (unsavedChangesChoice == 0) { // save changes
                    _saveButton.doClick();
                } else if (unsavedChangesChoice != 1) { // cancel closing
                    return false;
                }
            }

            resetJob();
            break;
        default:
            changePanel(AnalysisWindowPanelType.WELCOME);
        }

        return false;
    }

    private void cleanupForWindowClose() {
        _analysisJobChangeListener.onDeactivation(_analysisJobBuilder);
        _analysisJobBuilder.close();
        if (_datastoreConnection != null) {
            _datastoreConnection.close();
        }
        getContentPane().removeAll();
    }

    private boolean isJobUnsaved(FileObject lastSavedJobFile, AnalysisJobBuilder analysisJobBuilder) {
        if (lastSavedJobFile == null) {
            if (analysisJobBuilder.getComponentCount() == 0) {
                // user didn't actually do anything yet
                return false;
            }
            return true;
        }
        try {
            if (!lastSavedJobFile.exists()) {
                return true;
            }
        } catch (FileSystemException e) {
            logger.warn("Error while determining if the job file already exists", e);
        }

        InputStream lastSavedOutputStream = null;
        ByteArrayOutputStream currentOutputStream = null;
        try {
            File jobFile = new File(getJobFile().getURL().getFile());
            if (jobFile.length() == 0) {
                return true;
            }

            String lastSavedJob = FileHelper.readFileAsString(jobFile);
            String lastSavedJobNoMetadata = lastSavedJob.replaceAll("\n", "")
                    .replaceAll("<job-metadata>.*</job-metadata>", "");

            JaxbJobWriter writer = new JaxbJobWriter(_configuration);
            currentOutputStream = new ByteArrayOutputStream();
            writer.write(_analysisJobBuilder.toAnalysisJob(false), currentOutputStream);
            String currentJob = new String(currentOutputStream.toByteArray());
            String currentJobNoMetadata = currentJob.replaceAll("\n", "")
                    .replaceAll("<job-metadata>.*</job-metadata>", "");

            return !currentJobNoMetadata.equals(lastSavedJobNoMetadata);
        } catch (FileSystemException e) {
            throw new IllegalStateException(e);
        } finally {
            FileHelper.safeClose(currentOutputStream);
            FileHelper.safeClose(lastSavedOutputStream);
        }
    }

    private void resetJob() {
        setDatastore(null);
        setJobFile(null);
    }

    @Override
    public void setJobFile(FileObject jobFile) {
        _jobFilename = jobFile;
        updateWindowTitle();
    }

    @Override
    public FileObject getJobFile() {
        return _jobFilename;
    }

    @Override
    public String getWindowTitle() {
        String title;
        if (_currentPanelType != null) {
            title = _currentPanelType.getName();
        } else {
            title = AnalysisWindowPanelType.WELCOME.getName();
        }

        if (_datastore != null) {
            String datastoreName = _datastore.getName();
            if (!StringUtils.isNullOrEmpty(datastoreName)) {
                title = datastoreName + " | " + title;
            }
        }
        if (_jobFilename != null) {
            title = _jobFilename.getName().getBaseName() + " | " + title;
        }
        return title;
    }

    @Override
    public Image getWindowIcon() {
        return imageManager.getImage(IconUtils.APPLICATION_ICON);
    }

    @Override
    protected boolean isWindowResizable() {
        return true;
    }

    @Override
    protected JComponent getWindowContent() {
        if (_windowContent == null) {
            _windowContent = getWindowPanelContent();
        }
        return _windowContent;
    }

    private JComponent getWindowPanelContent() {
        if (_datastore != null) {
            setDatastore(_datastore);
        }

        final SaveAnalysisJobActionListener saveAnalysisJobActionListener = _saveAnalysisJobActionListenerProvider
                .get();
        _saveButton.addActionListener(saveAnalysisJobActionListener);
        _saveAsButton.addActionListener(saveAnalysisJobActionListener);
        _saveAsButton.setActionCommand(SaveAnalysisJobActionListener.ACTION_COMMAND_SAVE_AS);

        // Run analysis
        _executeButton.addActionListener(execute(_analysisJobBuilder));

        _executionAlternativesButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final JMenuItem executeNormallyMenutItem = WidgetFactory.createMenuItem("Run normally",
                        IconUtils.ACTION_EXECUTE);
                executeNormallyMenutItem.addActionListener(execute(_analysisJobBuilder));

                final JMenuItem executePreviewMenuItem = WidgetFactory.createMenuItem("Run first N records",
                        IconUtils.ACTION_PREVIEW);
                executePreviewMenuItem.addActionListener(executePreview());

                final JMenuItem executeSingleThreadedMenuItem = WidgetFactory.createMenuItem("Run single-threaded",
                        IconUtils.MODEL_ROW);
                executeSingleThreadedMenuItem.addActionListener(executeSingleThreaded());

                final JPopupMenu menu = new JPopupMenu();
                menu.add(executeNormallyMenutItem);
                menu.addSeparator();
                menu.add(executePreviewMenuItem);
                menu.add(executeSingleThreadedMenuItem);

                final int horizontalPosition = -1 * menu.getPreferredSize().width
                        + _executionAlternativesButton.getWidth();
                menu.show(_executionAlternativesButton, horizontalPosition,
                        _executionAlternativesButton.getHeight());
            }
        });

        final JButton newJobButton = createToolbarButton("New", IconUtils.MENU_NEW);
        newJobButton.addActionListener(_newAnalysisJobActionListenerProvider.get());

        final JButton openJobButton = createToolbarButton("Open", IconUtils.MENU_OPEN);
        openJobButton.addActionListener(_openAnalysisJobActionListenerProvider.get());

        final JToggleButton moreButton = createMoreMenuButton();

        final JButton logoButton = new JButton(imageManager.getImageIcon("images/menu/dc-logo-30.png"));
        logoButton.setToolTipText("About DataCleaner");
        logoButton.setBorder(new EmptyBorder(0, 4, 0, 10));
        logoButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                new AboutDialog(getWindowContext()).open();
            }
        });

        final JToolBar toolBar = WidgetFactory.createToolBar();
        toolBar.add(logoButton);
        toolBar.add(newJobButton);
        toolBar.add(openJobButton);
        toolBar.add(_saveButton);
        toolBar.add(_saveAsButton);
        toolBar.add(DCLabel.bright(" | "));
        toolBar.add(moreButton);

        toolBar.add(WidgetFactory.createToolBarSeparator());
        toolBar.add(_executeButton);
        toolBar.add(DCLabel.bright("|"));
        toolBar.add(_executionAlternativesButton);

        final JXStatusBar statusBar = WidgetFactory.createStatusBar(_statusLabel);
        statusBar.add(_classicViewButton);
        statusBar.add(_graphViewButton);
        statusBar.add(Box.createHorizontalStrut(10));

        final LicenceAndEditionStatusLabel statusLabel = new LicenceAndEditionStatusLabel(_glassPane);
        statusBar.add(statusLabel);

        final DCPanel toolBarPanel = new DCPanel(WidgetUtils.BG_COLOR_DARK);
        toolBarPanel.setLayout(new BorderLayout());
        toolBarPanel.add(toolBar, BorderLayout.CENTER);

        final DCPanel panel = new DCPersistentSizedPanel(_windowSizePreference);
        panel.setLayout(new BorderLayout());
        panel.add(toolBarPanel, BorderLayout.NORTH);
        panel.add(_leftPanel, BorderLayout.WEST);

        // newPanel.add(_tabbedPane, BorderLayout.NORTH);
        panel.add(_contentContainerPanel, BorderLayout.CENTER);

        panel.add(statusBar, BorderLayout.SOUTH);

        // invoke to trigger enablement/disablement of buttons.
        onSourceColumnsChanged();
        updateStatusLabel();

        WidgetUtils.centerOnScreen(this);
        return panel;
    }

    private ActionListener execute(final AnalysisJobBuilder analysisJobBuilder) {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                applyPropertyValues();

                if (analysisJobBuilder.getResultProducingComponentBuilders().isEmpty()) {
                    if (analysisJobBuilder.getConsumedOutputDataStreamsJobBuilders().isEmpty()) {
                        // Present choices to user to write file somewhere,
                        // and then run a copy of the job based on that.
                        ExecuteJobWithoutAnalyzersDialog executeJobWithoutAnalyzersPanel = new ExecuteJobWithoutAnalyzersDialog(
                                _dcModule, getWindowContext(), analysisJobBuilder, _userPreferences);
                        executeJobWithoutAnalyzersPanel.open();
                        return;
                    }
                }

                final RunAnalysisActionListener runAnalysis = new RunAnalysisActionListener(_dcModule,
                        analysisJobBuilder);
                runAnalysis.run();
            }
        };
    }

    private ActionListener executeSingleThreaded() {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final DataCleanerConfigurationImpl configuration = new DataCleanerConfigurationImpl(_configuration)
                        .withEnvironment(new DataCleanerEnvironmentImpl(_configuration.getEnvironment())
                                .withTaskRunner(new SingleThreadedTaskRunner()));

                final AnalysisJob jobCopy = _analysisJobBuilder.toAnalysisJob(false);
                final AnalysisJobBuilder jobBuilderCopy = new AnalysisJobBuilder(configuration, jobCopy);

                execute(jobBuilderCopy).actionPerformed(e);
            }
        };
    }

    private ActionListener executePreview() {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Integer maxRows = WidgetFactory.showMaxRowsDialog(100);

                if (maxRows != null) {
                    final AnalysisJob jobCopy = _analysisJobBuilder.toAnalysisJob(false);
                    final AnalysisJobBuilder jobBuilderCopy = new AnalysisJobBuilder(_configuration, jobCopy);
                    final FilterComponentBuilder<MaxRowsFilter, Category> maxRowsFilter = jobBuilderCopy
                            .addFilter(MaxRowsFilter.class);
                    maxRowsFilter.getComponentInstance().setMaxRows(maxRows.intValue());
                    maxRowsFilter.addInputColumn(jobBuilderCopy.getSourceColumns().get(0));
                    final FilterOutcome filterOutcome = maxRowsFilter
                            .getFilterOutcome(MaxRowsFilter.Category.VALID);
                    final Collection<ComponentBuilder> componentBuilders = jobBuilderCopy.getComponentBuilders();
                    for (ComponentBuilder componentBuilder : componentBuilders) {
                        if (componentBuilder != maxRowsFilter
                                && componentBuilder.getComponentRequirement() == null) {
                            componentBuilder.setComponentRequirement(new SimpleComponentRequirement(filterOutcome));
                        }
                    }

                    execute(jobBuilderCopy).actionPerformed(e);
                }
            }
        };
    }

    private JToggleButton createMoreMenuButton() {
        final JMenuItem optionsMenuItem = WidgetFactory.createMenuItem("Options", IconUtils.MENU_OPTIONS);
        optionsMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                OptionsDialog optionsDialog = _optionsDialogProvider.get();
                optionsDialog.open();
            }
        });

        final JMenuItem monitorMenuItem = WidgetFactory.createMenuItem("DataCleaner monitor",
                IconUtils.MENU_DQ_MONITOR);
        monitorMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                MonitorConnectionDialog dialog = _monitorConnectionDialogProvider.get();
                dialog.open();
            }
        });

        final JMenuItem dictionariesMenuItem = WidgetFactory.createMenuItem("Dictionaries",
                IconUtils.DICTIONARY_IMAGEPATH);
        dictionariesMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ReferenceDataDialog referenceDataDialog = _referenceDataDialogProvider.get();
                referenceDataDialog.selectDictionariesTab();
                referenceDataDialog.open();
            }
        });

        final JMenuItem synonymCatalogsMenuItem = WidgetFactory.createMenuItem("Synonyms",
                IconUtils.SYNONYM_CATALOG_IMAGEPATH);
        synonymCatalogsMenuItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ReferenceDataDialog referenceDataDialog = _referenceDataDialogProvider.get();
                referenceDataDialog.selectSynonymsTab();
                referenceDataDialog.open();
            }
        });

        final JMenuItem stringPatternsMenuItem = WidgetFactory.createMenuItem("String patterns",
                IconUtils.STRING_PATTERN_IMAGEPATH);
        stringPatternsMenuItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                ReferenceDataDialog referenceDataDialog = _referenceDataDialogProvider.get();
                referenceDataDialog.selectStringPatternsTab();
                referenceDataDialog.open();
            }
        });

        final PopupButton popupButton = new PopupButton("More",
                imageManager.getImageIcon(IconUtils.ACTION_SCROLLDOWN_BRIGHT));
        applyMenuPopupButttonStyling(popupButton);

        final JMenu windowsMenuItem = WidgetFactory.createMenu("Windows", 'w');
        windowsMenuItem.setIcon(imageManager.getImageIcon("images/menu/windows.png", IconUtils.ICON_SIZE_SMALL));
        final List<DCWindow> windows = getWindowContext().getWindows();

        getWindowContext().addWindowListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                windowsMenuItem.removeAll();
                for (final DCWindow window : windows) {
                    final Image windowIcon = window.getWindowIcon();
                    final String title = window.getWindowTitle();
                    final ImageIcon icon = new ImageIcon(windowIcon.getScaledInstance(IconUtils.ICON_SIZE_SMALL,
                            IconUtils.ICON_SIZE_SMALL, Image.SCALE_DEFAULT));
                    final JMenuItem switchToWindowItem = WidgetFactory.createMenuItem(title, icon);
                    switchToWindowItem.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            window.toFront();
                        }
                    });
                    windowsMenuItem.add(switchToWindowItem);
                }

                windowsMenuItem.add(new JSeparator());

                JMenuItem closeAllWindowsItem = WidgetFactory.createMenuItem("Close all dialogs", (ImageIcon) null);
                closeAllWindowsItem.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        List<DCWindow> windows = new ArrayList<>(getWindowContext().getWindows());
                        for (DCWindow window : windows) {
                            if (window instanceof AbstractDialog) {
                                window.close();
                            }
                        }
                    }
                });
                windowsMenuItem.add(closeAllWindowsItem);
            }

        });

        popupButton.getMenu().removeAll();
        popupButton.getMenu().add(dictionariesMenuItem);
        popupButton.getMenu().add(synonymCatalogsMenuItem);
        popupButton.getMenu().add(stringPatternsMenuItem);
        popupButton.getMenu().add(new JSeparator());
        popupButton.getMenu().add(windowsMenuItem);
        popupButton.getMenu().add(new JSeparator());
        popupButton.getMenu().add(monitorMenuItem);
        popupButton.getMenu().add(optionsMenuItem);

        return popupButton;
    }

    private void applyMenuPopupButttonStyling(PopupButton popupButton) {
        popupButton.setBorder(new EmptyBorder(10, 4, 10, 4));
        popupButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        popupButton.setFocusPainted(false);
        WidgetUtils.setDarkButtonStyle(popupButton);
        popupButton.setHorizontalTextPosition(SwingConstants.LEFT);
    }

    private JToggleButton createViewToggleButton(final String text, final String iconPath) {
        final ImageIcon icon = imageManager.getImageIcon(iconPath);
        final JToggleButton button = new JToggleButton(text, icon);
        button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        button.setFont(WidgetUtils.FONT_SMALL);
        button.setForeground(WidgetUtils.BG_COLOR_BRIGHTEST);
        button.setBackground(WidgetUtils.BG_COLOR_DARK);
        button.setBorderPainted(false);
        button.setBorder(new CompoundBorder(WidgetUtils.BORDER_THIN, new EmptyBorder(0, 4, 0, 4)));
        return button;
    }

    /**
     * Applies property values for all components visible in the window.
     */
    @Override
    public void applyPropertyValues() {
        if (_classicView != null) {
            _classicView.applyPropertyValues();
        }
    }

    private void onSourceColumnsChanged() {
        boolean everythingEnabled = !_analysisJobBuilder.getSourceColumns().isEmpty();

        if (_classicView != null) {
            _classicView.onSourceColumnsChanged(everythingEnabled);
        }

        _saveButton.setEnabled(everythingEnabled);
        _saveAsButton.setEnabled(everythingEnabled);

        for (PopupButton superCategoryButton : _superCategoryButtons) {
            superCategoryButton.setEnabled(everythingEnabled);
        }
    }

    @Override
    public void setDatastoreSelectionEnabled(boolean datastoreSelectionEnabled) {
        _datastoreSelectionEnabled = datastoreSelectionEnabled;
    }

    @Override
    public boolean isDatastoreSelectionEnabled() {
        return _datastoreSelectionEnabled;
    }

    @Override
    protected boolean isCentered() {
        return true;
    }

    @Override
    public AnalysisJobBuilder getAnalysisJobBuilder() {
        return _analysisJobBuilder;
    }

    public void windowClosed(WindowEvent e) {
        if (this.getExtendedState() == JFrame.MAXIMIZED_BOTH) {
            _windowSizePreference.setUserPreferredSize(null, true);
        } else {
            _windowSizePreference.setUserPreferredSize(getSize(), false);
        }
    }

    @Override
    protected boolean maximizeWindow() {
        return _windowSizePreference.isWindowMaximized();
    }
}