org.pentaho.reporting.tools.configeditor.ConfigDescriptionEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.reporting.tools.configeditor.ConfigDescriptionEditor.java

Source

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Hitachi Vantara and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.tools.configeditor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.boot.AbstractBoot;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.FilesystemFilter;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.ResourceBundleSupport;
import org.pentaho.reporting.libraries.xmlns.common.ParserUtil;
import org.pentaho.reporting.tools.configeditor.model.ClassConfigDescriptionEntry;
import org.pentaho.reporting.tools.configeditor.model.ConfigDescriptionEntry;
import org.pentaho.reporting.tools.configeditor.model.ConfigDescriptionModel;
import org.pentaho.reporting.tools.configeditor.model.EnumConfigDescriptionEntry;
import org.pentaho.reporting.tools.configeditor.model.TextConfigDescriptionEntry;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;

/**
 * The config description editor is used to edit the configuration metadata used in the ConfigEditor to describe the
 * ReportConfiguration keys.
 *
 * @author Thomas Morgner
 */
public class ConfigDescriptionEditor extends JFrame {
    private static final Log logger = LogFactory.getLog(ConfigDescriptionEditor.class);

    private class CloseHandler extends WindowAdapter {

        private CloseHandler() {
        }

        /**
         * Invoked when a window is in the process of being closed. The close operation can be overridden at this point.
         */
        public void windowClosing(final WindowEvent e) {
            attempExit();
        }
    }

    /**
     * A configuration key to define the Font used in the editor.
     */
    protected static final String EDITOR_FONT_KEY = "org.pentaho.reporting.engine.classic.core.modules.gui.config.EditorFont"; //$NON-NLS-1$
    /**
     * A configuration key to define the Font size used in the editor.
     */
    protected static final String EDITOR_FONT_SIZE_KEY = "org.pentaho.reporting.engine.classic.core.modules.gui.config.EditorFontSize"; //$NON-NLS-1$

    /**
     * An internal constant to activate the class detail editor.
     */
    private static final String CLASS_DETAIL_EDITOR_NAME = "Class"; //$NON-NLS-1$
    /**
     * An internal constant to activate the enumeration detail editor.
     */
    private static final String ENUM_DETAIL_EDITOR_NAME = "Enum"; //$NON-NLS-1$
    /**
     * An internal constant to activate the text detail editor.
     */
    private static final String TEXT_DETAIL_EDITOR_NAME = "Text"; //$NON-NLS-1$

    /**
     * Handles close requests in this editor.
     */
    private class CloseAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private CloseAction() {
            putValue(Action.NAME, getResources().getString("action.exit.name")); //$NON-NLS-1$
        }

        /**
         * Handles the close request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            attempExit();
        }
    }

    /**
     * Handles save requests in this editor.
     */
    private class SaveAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private SaveAction() {
            putValue(Action.NAME, getResources().getString("action.save.name")); //$NON-NLS-1$
            putValue(Action.SMALL_ICON, getResources().getIcon("action.save.small-icon")); //$NON-NLS-1$
        }

        /**
         * Handles the save request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            save();
        }
    }

    /**
     * Handles import requests in this editor. Imports try to build a new description model from a given report
     * configuration.
     */
    private class ImportAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private ImportAction() {
            putValue(Action.NAME, getResources().getString("action.import.name")); //$NON-NLS-1$
            putValue(Action.SMALL_ICON, getResources().getIcon("action.import.small-icon")); //$NON-NLS-1$
            setEnabled(configurationToEdit != null);
        }

        /**
         * Handles the import request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            if (configurationToEdit == null) {
                return;
            }

            final ConfigDescriptionModel model = getModel();
            model.importFromConfig(configurationToEdit);
            model.sort();
            setStatusText(getResources().getString("config-description-editor.import-complete")); //$NON-NLS-1$
        }
    }

    /**
     * Handles requests to add a new entry in this editor.
     */
    private class AddEntryAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private AddEntryAction() {
            putValue(Action.NAME, getResources().getString("action.add-entry.name")); //$NON-NLS-1$
            putValue(Action.SMALL_ICON, getResources().getIcon("action.add-entry.small-icon")); //$NON-NLS-1$
        }

        /**
         * Handles the add-entry request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            final TextConfigDescriptionEntry te = new TextConfigDescriptionEntry(
                    getResources().getString("config-description-editor.unnamed-entry")); //$NON-NLS-1$
            final ConfigDescriptionModel model = getModel();
            model.add(te);
            getEntryList().setSelectedIndex(model.getSize() - 1);
        }
    }

    /**
     * Handles requests to remove an entry from this editor.
     */
    private class RemoveEntryAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private RemoveEntryAction() {
            putValue(Action.NAME, getResources().getString("action.remove-entry.name")); //$NON-NLS-1$
            putValue(Action.SMALL_ICON, getResources().getIcon("action.remove-entry.small-icon")); //$NON-NLS-1$
        }

        /**
         * Handles the remove entry request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            final int[] selectedEntries = getEntryList().getSelectedIndices();
            setSelectedEntry(null);
            final ConfigDescriptionModel model = getModel();
            model.removeAll(selectedEntries);
            getEntryList().clearSelection();
        }
    }

    /**
     * Handles load requests in this editor.
     */
    private class LoadAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private LoadAction() {
            putValue(Action.NAME, getResources().getString("action.load.name")); //$NON-NLS-1$
            putValue(Action.SMALL_ICON, getResources().getIcon("action.load.small-icon")); //$NON-NLS-1$
        }

        /**
         * Handles the laod request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            load();
        }
    }

    /**
     * Handles update requests in the detail editor.
     */
    private class UpdateAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private UpdateAction() {
            putValue(Action.NAME, getResources().getString("action.update.name")); //$NON-NLS-1$
        }

        /**
         * Handles the update request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            writeSelectedEntry();
        }
    }

    /**
     * Handles cancel requests in the detail editor.
     */
    private class CancelAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private CancelAction() {
            putValue(Action.NAME, getResources().getString("action.cancel.name")); //$NON-NLS-1$
        }

        /**
         * Handles the cancel request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            final ConfigDescriptionEntry ce = getSelectedEntry();
            setSelectedEntry(null);
            setSelectedEntry(ce);
        }
    }

    /**
     * Handles editor type selections within the detail editor.
     */
    private class SelectTypeAction extends AbstractAction {
        /**
         * the selected type.
         */
        private final int type;

        /**
         * Creates a new select type action for the given name and type.
         *
         * @param name the name of the action.
         * @param type the type that should be selected whenever this action gets called.
         */
        private SelectTypeAction(final String name, final int type) {
            putValue(Action.NAME, name);
            this.type = type;
        }

        /**
         * Handles the select type request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            setEntryType(type);
        }
    }

    /**
     * Handles the list selection in the list of available config keys.
     */
    private class ConfigListSelectionListener implements ListSelectionListener {
        private boolean inUpdate;

        /**
         * Defaultconstructor.
         */
        private ConfigListSelectionListener() {
        }

        /**
         * Called whenever the value of the selection changes.
         *
         * @param e the event that characterizes the change.
         */
        public void valueChanged(final ListSelectionEvent e) {
            if (inUpdate) {
                return;
            }

            inUpdate = true;
            try {
                final ConfigDescriptionEntry newEntry;
                final int selectedIndex = getEntryList().getSelectedIndex();
                if (selectedIndex == -1) {
                    newEntry = null;
                } else {
                    newEntry = getModel().get(selectedIndex);
                }

                final ConfigDescriptionEntry oldEntry = getSelectedEntry();
                if (oldEntry != null) {
                    writeSelectedEntry();
                }

                if (newEntry != null) {
                    final int index = getModel().indexOf(newEntry);
                    if (getEntryList().getSelectedIndex() != index) {
                        getEntryList().setSelectedIndex(index);
                    }
                    setSelectedEntry(newEntry);
                } else {
                    getEntryList().setSelectedIndex(-1);
                    setSelectedEntry(null);
                }
            } finally {
                inUpdate = false;
            }

        }
    }

    /**
     * Handles list selections in the enumeration detail editor.
     */
    private class EnumerationListSelectionHandler implements ListSelectionListener {
        /**
         * Defaultconstructor.
         */
        private EnumerationListSelectionHandler() {
        }

        /**
         * Called whenever the value of the selection changes.
         *
         * @param e the event that characterizes the change.
         */
        public void valueChanged(final ListSelectionEvent e) {
            if (getEnumEntryList().getSelectedIndex() == -1) {
                getEnumEntryEditField().setText(""); //$NON-NLS-1$
            } else {
                getEnumEntryEditField()
                        .setText((String) getEnumEntryListModel().get(getEnumEntryList().getSelectedIndex()));
            }
        }
    }

    /**
     * A ShortCut action to redefine the entries of the enumeration detail editor to represent a boolean value.
     */
    private class SetBooleanEnumEntryAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private SetBooleanEnumEntryAction() {
            putValue(Action.NAME, getResources().getString("action.boolean.name")); //$NON-NLS-1$
        }

        /**
         * Handles the boolean redefinition request.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            final DefaultListModel enumEntryListModel = getEnumEntryListModel();
            enumEntryListModel.clear();
            getEnumEntryEditField().setText(""); //$NON-NLS-1$
            enumEntryListModel.addElement("true"); //$NON-NLS-1$
            enumEntryListModel.addElement("false"); //$NON-NLS-1$
        }
    }

    /**
     * Handles the request to add a new enumeration entry to the detail editor.
     */
    private class AddEnumEntryAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private AddEnumEntryAction() {
            putValue(Action.NAME, getResources().getString("action.add-enum-entry.name")); //$NON-NLS-1$
        }

        /**
         * Handles the request to add a new enumeration entry to the detail editor.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            getEnumEntryListModel().addElement(getEnumEntryEditField().getText());
        }
    }

    /**
     * Handles the request to remove an enumeration entry to the detail editor.
     */
    private class RemoveEnumEntryAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private RemoveEnumEntryAction() {
            putValue(Action.NAME, getResources().getString("action.remove-enum-entry.name")); //$NON-NLS-1$
        }

        /**
         * Handles the request to remove an enumeration entry to the detail editor.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            final JList enumEntryList = getEnumEntryList();
            final DefaultListModel enumEntryListModel = getEnumEntryListModel();
            final int[] selectedEntries = enumEntryList.getSelectedIndices();
            for (int i = selectedEntries.length - 1; i >= 0; i--) {
                enumEntryListModel.remove(selectedEntries[i]);
            }
            enumEntryList.clearSelection();
        }
    }

    /**
     * Handles the request to update an enumeration entry to the detail editor.
     */
    private class UpdateEnumEntryAction extends AbstractAction {
        /**
         * Defaultconstructor.
         */
        private UpdateEnumEntryAction() {
            putValue(Action.NAME, getResources().getString("action.update-enum-entry.name")); //$NON-NLS-1$
        }

        /**
         * Handles the request to update an enumeration entry to the detail editor.
         *
         * @param e not used.
         */
        public void actionPerformed(final ActionEvent e) {
            final int idx = getEnumEntryList().getSelectedIndex();
            if (idx == -1) {
                getEnumEntryListModel().addElement(getEnumEntryEditField().getText());
            } else {
                getEnumEntryListModel().setElementAt(getEnumEntryEditField().getText(), idx);
            }
        }
    }

    /**
     * An internal value to mark a text detail editor type.
     */
    private static final int TYPE_TEXT = 0;
    /**
     * An internal value to mark a class detail editor type.
     */
    private static final int TYPE_CLASS = 1;
    /**
     * An internal value to mark a enumeration detail editor type.
     */
    private static final int TYPE_ENUM = 2;

    /**
     * A radio button to select the text editor type for the current key.
     */
    private JRadioButton rbText;
    /**
     * A radio button to select the class editor type for the current key.
     */
    private JRadioButton rbClass;
    /**
     * A radio button to select the enumeration editor type for the current key.
     */
    private JRadioButton rbEnum;
    /**
     * The list model used to collect and manage all available keys.
     */
    private ConfigDescriptionModel model;
    /**
     * The name of the currently edited key.
     */
    private JTextField keyNameField;
    /**
     * The description field contains a short description of the current key.
     */
    private JTextArea descriptionField;
    /**
     * Allows to check, whether the key is a global (boot-time) key.
     */
    private JCheckBox globalField;
    /**
     * Allows to check, whether the key is hidden.
     */
    private JCheckBox hiddenField;
    /**
     * The name of the base class for the class detail editor.
     */
    private JTextField baseClassField;
    /**
     * contains the currently selected entry of the enumeration detail editor.
     */
    private JTextField enumEntryEditField;
    /**
     * contains all entries of the enumeration detail editor.
     */
    private DefaultListModel enumEntryListModel;
    /**
     * The current resource bundle used to translate the strings in this dialog.
     */
    private ResourceBundleSupport resources;
    /**
     * This cardlayout is used to display the currently selected detail editor.
     */
    private CardLayout detailManager;
    /**
     * Contains the detail editor manager.
     */
    private JPanel detailManagerPanel;
    /**
     * Contains the detail editor for the key.
     */
    private JPanel detailEditorPane;
    /**
     * The list is used to manage all available keys.
     */
    private JList entryList;
    /**
     * This list is used to manage the available entries of the enumeration detail editor.
     */
    private JList enumEntryList;
    /**
     * the currently selected description entry.
     */
    private ConfigDescriptionEntry selectedEntry;
    /**
     * The file chooser is used to select the file for the load/save operations.
     */
    private JFileChooser fileChooser;
    /**
     * Serves as statusline for the dialog.
     */
    private JLabel statusHolder;
    /**
     * The currently selected detail editor type.
     */
    private int type;

    private Configuration configurationToEdit;

    public ConfigDescriptionEditor() {
        this(ConfigEditorBoot.class.getName());
    }

    /**
     * Constructs a ConfigDescriptionEditor that is initially invisible.
     */
    public ConfigDescriptionEditor(final AbstractBoot boot) {
        if (boot != null) {
            boot.start();
            this.configurationToEdit = boot.getGlobalConfig();
        }

        init();
    }

    /**
     * Constructs a ConfigDescriptionEditor that is initially invisible.
     */
    public ConfigDescriptionEditor(final String booterClass) {
        final AbstractBoot boot = ObjectUtilities.loadAndInstantiate(booterClass, ConfigDescriptionEditor.class,
                AbstractBoot.class);
        if (boot != null) {
            boot.start();
            this.configurationToEdit = boot.getGlobalConfig();
        }

        init();
    }

    private void init() {
        this.resources = Messages.getInstance();

        setTitle(resources.getString("config-description-editor.title")); //$NON-NLS-1$
        final JPanel contentPane = new JPanel();
        contentPane.setLayout(new BorderLayout());

        detailEditorPane = createEditPane();
        final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createEntryList(),
                detailEditorPane);

        contentPane.add(splitPane, BorderLayout.CENTER);
        contentPane.add(createButtonPane(), BorderLayout.SOUTH);

        final JPanel cPaneStatus = new JPanel();
        cPaneStatus.setLayout(new BorderLayout());
        cPaneStatus.add(contentPane, BorderLayout.CENTER);
        cPaneStatus.add(createStatusBar(), BorderLayout.SOUTH);

        setContentPane(cPaneStatus);
        setEntryType(ConfigDescriptionEditor.TYPE_TEXT);
        setSelectedEntry(null);

        fileChooser = new JFileChooser();
        fileChooser.addChoosableFileFilter(
                new FilesystemFilter(".xml", resources.getString("config-description-editor.xml-files"))); //$NON-NLS-1$ //$NON-NLS-2$
        fileChooser.setMultiSelectionEnabled(false);

        setStatusText(resources.getString("config-description-editor.welcome")); //$NON-NLS-1$

        addWindowListener(new CloseHandler());
    }

    /**
     * Creates and returns the entry list component that will hold all config description entries within a list.
     *
     * @return the created entry list.
     */
    private JPanel createEntryList() {
        final Action addEntryAction = new AddEntryAction();
        final Action removeEntryAction = new RemoveEntryAction();

        model = new ConfigDescriptionModel();
        entryList = new JList(model);
        entryList.addListSelectionListener(new ConfigListSelectionListener());

        final JToolBar toolbar = new JToolBar();
        toolbar.setFloatable(false);
        toolbar.add(addEntryAction);
        toolbar.add(removeEntryAction);

        final JPanel panel = new JPanel();
        panel.setMinimumSize(new Dimension(200, 0));
        panel.setLayout(new BorderLayout());
        panel.add(toolbar, BorderLayout.NORTH);
        panel.add(new JScrollPane(entryList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
        return panel;
    }

    /**
     * Returns the JList component containing all entries of the enumeration detail editor.
     *
     * @return the enumeration entry list.
     */
    protected JList getEnumEntryList() {
        return enumEntryList;
    }

    /**
     * Returns the text field containing the currently edited enumeration entry.
     *
     * @return the textfield containing the current entry.
     */
    protected JTextField getEnumEntryEditField() {
        return enumEntryEditField;
    }

    /**
     * Returns the List Model containing all entries of the current enumeration entry editor.
     *
     * @return the entry list.
     */
    protected DefaultListModel getEnumEntryListModel() {
        return enumEntryListModel;
    }

    /**
     * Returns the JList component containing all configuration entries.
     *
     * @return the entry list.
     */
    protected JList getEntryList() {
        return entryList;
    }

    /**
     * Creates a panel containing all dialog control buttons, like close, load, save and import.
     *
     * @return the button panel.
     */
    private JPanel createButtonPane() {
        final Action closeAction = new CloseAction();
        final Action saveAction = new SaveAction();
        final Action loadAction = new LoadAction();
        final Action importAction = new ImportAction();

        final JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
        panel.setBorder(new EmptyBorder(5, 5, 5, 5));

        final JPanel buttonHolder = new JPanel();
        buttonHolder.setLayout(new GridLayout(1, 4, 5, 5));
        buttonHolder.add(new JButton(importAction));
        buttonHolder.add(new JButton(loadAction));
        buttonHolder.add(new JButton(saveAction));
        buttonHolder.add(new JButton(closeAction));

        panel.add(buttonHolder);
        return panel;
    }

    /**
     * Creates the detail editor panel. This panel will contain all specific editors for the keys.
     *
     * @return the detail editor panel.
     */
    private JPanel createEditPane() {

        final JPanel buttonHolder = new JPanel();
        buttonHolder.setLayout(new GridLayout(1, 4, 5, 5));
        buttonHolder.add(new JButton(new CancelAction()));
        buttonHolder.add(new JButton(new UpdateAction()));

        final JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
        buttonPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
        buttonPanel.add(buttonHolder);

        final JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(createDetailEditorPanel(), BorderLayout.CENTER);
        panel.add(buttonPanel, BorderLayout.SOUTH);
        return panel;
    }

    /**
     * Creates the enumeration detail editor.
     *
     * @return the enumeration detail editor.
     */
    private JPanel createEnumerationEditor() {
        enumEntryEditField = new JTextField();
        enumEntryListModel = new DefaultListModel();

        enumEntryList = new JList(enumEntryListModel);
        enumEntryList.addListSelectionListener(new EnumerationListSelectionHandler());

        final JPanel listPanel = new JPanel();
        listPanel.setLayout(new BorderLayout());
        listPanel.add(enumEntryEditField, BorderLayout.NORTH);
        listPanel.add(new JScrollPane(enumEntryList), BorderLayout.CENTER);

        final JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new GridLayout(5, 1));
        buttonPanel.add(new JButton(new AddEnumEntryAction()));
        buttonPanel.add(new JButton(new RemoveEnumEntryAction()));
        buttonPanel.add(new JButton(new UpdateEnumEntryAction()));
        buttonPanel.add(new JPanel());
        buttonPanel.add(new JButton(new SetBooleanEnumEntryAction()));

        final JPanel buttonCarrier = new JPanel();
        buttonCarrier.setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
        buttonCarrier.add(buttonPanel);

        final JPanel editorPanel = new JPanel();
        editorPanel.setLayout(new BorderLayout());
        editorPanel.add(listPanel, BorderLayout.CENTER);
        editorPanel.add(buttonCarrier, BorderLayout.EAST);
        return editorPanel;
    }

    /**
     * Creates the class detail editor.
     *
     * @return the class detail editor.
     */
    private JPanel createClassEditor() {
        baseClassField = new JTextField();
        final JLabel baseClassValidateMessage = new JLabel(" ");

        final JLabel textLabel = new JLabel(resources.getString("config-description-editor.baseclass")); //$NON-NLS-1$
        final JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add(textLabel, BorderLayout.WEST);
        panel.add(baseClassField, BorderLayout.CENTER);
        panel.add(baseClassValidateMessage, BorderLayout.SOUTH);

        final JPanel carrier = new JPanel();
        carrier.setLayout(new BorderLayout());
        carrier.add(panel, BorderLayout.NORTH);
        return carrier;
    }

    /**
     * Creates the text detail editor.
     *
     * @return the text detail editor.
     */
    private JPanel createTextEditor() {
        final JLabel textLabel = new JLabel(resources.getString("config-description-editor.text-editor-message")); //$NON-NLS-1$
        final JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout());
        panel.add(textLabel);
        return panel;
    }

    /**
     * Creates the common entry detail editor. This editor contains all shared properties.
     *
     * @return the common entry editor.
     */
    private JPanel createDetailEditorPanel() {
        final JLabel keyNameLabel = new JLabel(resources.getString("config-description-editor.keyname")); //$NON-NLS-1$
        final JLabel descriptionLabel = new JLabel(resources.getString("config-description-editor.description")); //$NON-NLS-1$
        final JLabel typeLabel = new JLabel(resources.getString("config-description-editor.type")); //$NON-NLS-1$
        final JLabel globalLabel = new JLabel(resources.getString("config-description-editor.global")); //$NON-NLS-1$
        final JLabel hiddenLabel = new JLabel(resources.getString("config-description-editor.hidden")); //$NON-NLS-1$

        hiddenField = new JCheckBox();
        globalField = new JCheckBox();
        final String font = ConfigEditorBoot.getInstance().getGlobalConfig()
                .getConfigProperty(ConfigDescriptionEditor.EDITOR_FONT_KEY, "Monospaced"); //$NON-NLS-1$
        final int fontSize = ParserUtil.parseInt(ConfigEditorBoot.getInstance().getGlobalConfig()
                .getConfigProperty(ConfigDescriptionEditor.EDITOR_FONT_SIZE_KEY), 12);
        descriptionField = new JTextArea();
        descriptionField.setFont(new Font(font, Font.PLAIN, fontSize));
        descriptionField.setLineWrap(true);
        descriptionField.setWrapStyleWord(true);
        keyNameField = new JTextField();

        final JPanel enumerationEditor = createEnumerationEditor();
        final JPanel textEditor = createTextEditor();
        final JPanel classEditor = createClassEditor();

        detailManagerPanel = new JPanel();
        detailManager = new CardLayout();
        detailManagerPanel.setLayout(detailManager);
        detailManagerPanel.add(classEditor, ConfigDescriptionEditor.CLASS_DETAIL_EDITOR_NAME);
        detailManagerPanel.add(textEditor, ConfigDescriptionEditor.TEXT_DETAIL_EDITOR_NAME);
        detailManagerPanel.add(enumerationEditor, ConfigDescriptionEditor.ENUM_DETAIL_EDITOR_NAME);

        final JPanel commonEntryEditorPanel = new JPanel();
        commonEntryEditorPanel.setLayout(new GridBagLayout());
        commonEntryEditorPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.insets = new Insets(3, 1, 1, 1);
        commonEntryEditorPanel.add(keyNameLabel, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(3, 1, 1, 1);
        gbc.ipadx = 120;
        commonEntryEditorPanel.add(keyNameField, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.insets = new Insets(3, 1, 1, 1);
        commonEntryEditorPanel.add(descriptionLabel, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 1;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(3, 1, 1, 1);
        gbc.ipadx = 120;
        gbc.ipady = 120;
        commonEntryEditorPanel.add(new JScrollPane(descriptionField, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.insets = new Insets(3, 1, 1, 1);
        commonEntryEditorPanel.add(globalLabel, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 2;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(3, 1, 1, 1);
        gbc.ipadx = 120;
        commonEntryEditorPanel.add(globalField, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.insets = new Insets(3, 1, 1, 1);
        commonEntryEditorPanel.add(hiddenLabel, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 3;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(3, 1, 1, 1);
        gbc.ipadx = 120;
        commonEntryEditorPanel.add(hiddenField, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 4;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.insets = new Insets(3, 1, 1, 1);
        commonEntryEditorPanel.add(typeLabel, gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 4;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(3, 1, 1, 1);
        gbc.ipadx = 120;
        commonEntryEditorPanel.add(createTypeSelectionPane(), gbc);

        gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 5;
        gbc.gridwidth = 2;
        gbc.weighty = 1;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1;
        gbc.fill = GridBagConstraints.BOTH;
        gbc.insets = new Insets(3, 1, 1, 1);
        gbc.ipadx = 120;
        commonEntryEditorPanel.add(detailManagerPanel, gbc);

        return commonEntryEditorPanel;
    }

    /**
     * Creates the type selection panel containing some radio buttons to define the detail editor type.
     *
     * @return the type selection panel.
     */
    private JPanel createTypeSelectionPane() {
        final JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(3, 1));

        rbText = new JRadioButton(new SelectTypeAction(resources.getString("config-description-editor.type-text"),
                ConfigDescriptionEditor.TYPE_TEXT)); //$NON-NLS-1$
        rbClass = new JRadioButton(new SelectTypeAction(resources.getString("config-description-editor.type-class"),
                ConfigDescriptionEditor.TYPE_CLASS)); //$NON-NLS-1$
        rbEnum = new JRadioButton(new SelectTypeAction(resources.getString("config-description-editor.type-enum"),
                ConfigDescriptionEditor.TYPE_ENUM)); //$NON-NLS-1$

        final ButtonGroup bg = new ButtonGroup();
        bg.add(rbText);
        bg.add(rbClass);
        bg.add(rbEnum);

        panel.add(rbText);
        panel.add(rbClass);
        panel.add(rbEnum);

        return panel;
    }

    /**
     * Creates the statusbar for this frame. Use setStatus() to display text on the status bar.
     *
     * @return the status bar.
     */
    protected JPanel createStatusBar() {
        final JPanel statusPane = new JPanel();
        statusPane.setLayout(new BorderLayout());
        statusPane.setBorder(BorderFactory.createLineBorder(UIManager.getDefaults().getColor("controlShadow"))); //$NON-NLS-1$
        statusHolder = new JLabel(" "); //$NON-NLS-1$
        statusPane.setMinimumSize(statusHolder.getPreferredSize());
        statusPane.add(statusHolder, BorderLayout.WEST);

        return statusPane;
    }

    /**
     * Defines the status text for this dialog.
     *
     * @param text the new status text.
     */
    protected void setStatusText(final String text) {
        statusHolder.setText(text);
    }

    /**
     * Returns the currently visible status text of this dialog.
     *
     * @return the status text.
     */
    protected String getStatusText() {
        return statusHolder.getText();
    }

    /**
     * Sets the entry type for the current config description entry. This also selects and activates the correct detail
     * editor for this type.
     *
     * @param type the type of the currently selected entry.
     */
    protected void setEntryType(final int type) {
        this.type = type;
        if (type == ConfigDescriptionEditor.TYPE_CLASS) {
            detailManager.show(detailManagerPanel, ConfigDescriptionEditor.CLASS_DETAIL_EDITOR_NAME);
            rbClass.setSelected(true);
        } else if (type == ConfigDescriptionEditor.TYPE_ENUM) {
            detailManager.show(detailManagerPanel, ConfigDescriptionEditor.ENUM_DETAIL_EDITOR_NAME);
            rbEnum.setSelected(true);
        } else {
            detailManager.show(detailManagerPanel, ConfigDescriptionEditor.TEXT_DETAIL_EDITOR_NAME);
            rbText.setSelected(true);
        }
        invalidate();
    }

    /**
     * Returns the current entry type.
     *
     * @return the current entry type.
     */
    protected int getEntryType() {
        return type;
    }

    /**
     * Returns the currently select entry from the entry list model.
     *
     * @return the currently selected entry.
     */
    protected ConfigDescriptionEntry getSelectedEntry() {
        return selectedEntry;
    }

    /**
     * Defines the currently selected entry from the entry list model and updates the detail editor to reflect the data
     * from the entry.
     *
     * @param selectedEntry the selected entry.
     */
    protected void setSelectedEntry(final ConfigDescriptionEntry selectedEntry) {
        this.selectedEntry = selectedEntry;

        enumEntryEditField.setText(""); //$NON-NLS-1$
        enumEntryListModel.clear();
        baseClassField.setText(""); //$NON-NLS-1$

        if (this.selectedEntry == null) {
            deepEnable(detailEditorPane, false);
        } else {
            deepEnable(detailEditorPane, true);
            keyNameField.setText(selectedEntry.getKeyName());
            globalField.setSelected(selectedEntry.isGlobal());
            hiddenField.setSelected(selectedEntry.isHidden());
            descriptionField.setText(selectedEntry.getDescription());
            if (selectedEntry instanceof ClassConfigDescriptionEntry) {
                final ClassConfigDescriptionEntry ce = (ClassConfigDescriptionEntry) selectedEntry;
                setEntryType(ConfigDescriptionEditor.TYPE_CLASS);
                if (ce.getBaseClass() != null) {
                    baseClassField.setText(ce.getBaseClass().getName());
                }
            } else if (selectedEntry instanceof EnumConfigDescriptionEntry) {
                final EnumConfigDescriptionEntry en = (EnumConfigDescriptionEntry) selectedEntry;
                final String[] enums = en.getOptions();
                for (int i = 0; i < enums.length; i++) {
                    enumEntryListModel.addElement(enums[i]);
                }
                setEntryType(ConfigDescriptionEditor.TYPE_ENUM);
            } else {
                setEntryType(ConfigDescriptionEditor.TYPE_TEXT);
            }
        }
    }

    /**
     * A utility method to enable or disable a component and all childs.
     *
     * @param comp  the component that should be enabled or disabled.
     * @param state the new enable state.
     */
    private void deepEnable(final Component comp, final boolean state) {
        comp.setEnabled(state);
        if (comp instanceof Container) {
            final Container cont = (Container) comp;
            final Component[] childs = cont.getComponents();
            for (int i = 0; i < childs.length; i++) {
                deepEnable(childs[i], state);
            }
        }
    }

    /**
     * Saves the config description model in a xml file.
     */
    protected void save() {
        fileChooser.setVisible(true);

        final int option = fileChooser.showSaveDialog(this);
        if (option == JFileChooser.APPROVE_OPTION) {
            OutputStream out = null;
            try {
                out = new BufferedOutputStream(new FileOutputStream(fileChooser.getSelectedFile()));
                model.save(out, "UTF-8"); //$NON-NLS-1$
                out.close();
                setStatusText(resources.getString("config-description-editor.save-complete")); //$NON-NLS-1$
            } catch (Exception ioe) {
                ConfigDescriptionEditor.logger.debug("Failed", ioe); //$NON-NLS-1$
                final String message = MessageFormat.format(
                        resources.getString("config-description-editor.save-failed"), //$NON-NLS-1$
                        new Object[] { ioe.getMessage() });
                setStatusText(message);
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        // ignored .. at least we tried it ..
                    }
                }
            }
        }
    }

    /**
     * Loads the config description model from a xml file.
     */
    protected void load() {
        fileChooser.setVisible(true);

        final int option = fileChooser.showOpenDialog(this);
        if (option == JFileChooser.APPROVE_OPTION) {
            InputStream in = null;
            try {
                final FileInputStream fileIn = new FileInputStream(fileChooser.getSelectedFile());
                in = new BufferedInputStream(fileIn);
                model.load(in);
                model.sort();
                setStatusText(resources.getString("config-description-editor.load-complete")); //$NON-NLS-1$
            } catch (Exception ioe) {
                ConfigDescriptionEditor.logger.debug("Load Failed", ioe); //$NON-NLS-1$
                final String message = MessageFormat.format(
                        resources.getString("config-description-editor.load-failed"), //$NON-NLS-1$
                        new Object[] { ioe.getMessage() });
                setStatusText(message);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        // ignored .. at least we tried it ..
                    }
                }
            }
        }
    }

    /**
     * Updates the currently selected entry from the values found in the detail editor.
     */
    protected void writeSelectedEntry() {
        final ConfigDescriptionEntry entry;
        switch (getEntryType()) {
        case ConfigDescriptionEditor.TYPE_CLASS: {
            final ClassConfigDescriptionEntry ce = new ClassConfigDescriptionEntry(keyNameField.getText());
            ce.setDescription(descriptionField.getText());
            ce.setGlobal(globalField.isSelected());
            ce.setHidden(hiddenField.isSelected());
            try {
                final String className = baseClassField.getText();
                if (className == null) {
                    ce.setBaseClass(Object.class);
                } else {
                    final ClassLoader classLoader = ObjectUtilities.getClassLoader(getClass());
                    final Class c = Class.forName(className, false, classLoader);
                    ce.setBaseClass(c);
                }
            } catch (Exception e) {
                // invalid
                ConfigDescriptionEditor.logger.debug("Class is invalid; defaulting to Object.class"); //$NON-NLS-1$
                ce.setBaseClass(Object.class);
            }
            entry = ce;
            break;
        }
        case ConfigDescriptionEditor.TYPE_ENUM: {
            final EnumConfigDescriptionEntry ece = new EnumConfigDescriptionEntry(keyNameField.getText());
            ece.setDescription(descriptionField.getText());
            ece.setGlobal(globalField.isSelected());
            ece.setHidden(hiddenField.isSelected());
            final String[] enumEntries = new String[enumEntryListModel.getSize()];
            for (int i = 0; i < enumEntryListModel.getSize(); i++) {
                enumEntries[i] = String.valueOf(enumEntryListModel.get(i));
            }
            ece.setOptions(enumEntries);
            entry = ece;
            break;
        }
        default: {
            final TextConfigDescriptionEntry te = new TextConfigDescriptionEntry(keyNameField.getText());
            te.setDescription(descriptionField.getText());
            te.setGlobal(globalField.isSelected());
            te.setHidden(hiddenField.isSelected());
            entry = te;
            break;
        }
        }

        final ConfigDescriptionEntry selectedEntry = getSelectedEntry();
        if (selectedEntry == null) {
            model.add(entry);
            return;
        }

        if (ObjectUtilities.equal(selectedEntry.getKeyName(), entry.getKeyName()) == false) {
            model.remove(selectedEntry);
        }

        model.add(entry);
    }

    /**
     * Returns the config description model containing all metainformation about the configuration.
     *
     * @return the config description model.
     */
    protected ConfigDescriptionModel getModel() {
        return model;
    }

    /**
     * Handles the attemp to quit the program. This method shuts down the VM.
     */
    protected void attempExit() {
        System.exit(0);
    }

    /**
     * Returns the resource bundle of this editor for translating strings.
     *
     * @return the resource bundle.
     */
    protected ResourceBundleSupport getResources() {
        return resources;
    }

    /**
     * The main entry point to start the detail editor.
     *
     * @param args ignored.
     */
    @SuppressWarnings("UseOfSystemOutOrSystemErr")
    public static void main(final String[] args) {
        ConfigEditorBoot.getInstance().start();

        if (args.length == 0) {
            final ConfigDescriptionEditor ed = new ConfigDescriptionEditor();
            ed.pack();
            ed.setVisible(true);
        } else {
            final AbstractBoot boot = AbstractBoot.loadBooter(args[0], ConfigDescriptionEditor.class);
            if (boot == null) {
                System.out.println("Error: Unable to load the specified booter class: " + args[0]);
                System.exit(-1);
            }
            final ConfigDescriptionEditor ed = new ConfigDescriptionEditor(boot);
            ed.pack();
            ed.setVisible(true);
        }
    }
}