org.rdkit.knime.wizards.RDKitNodesWizardsPage.java Source code

Java tutorial

Introduction

Here is the source code for org.rdkit.knime.wizards.RDKitNodesWizardsPage.java

Source

/*
 * ------------------------------------------------------------------
 * This source code, its documentation and all appendant files
 * are protected by copyright law. All rights reserved.
 *
 * Copyright (C) 2012
 * Novartis Institutes for BioMedical Research
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, Version 3, 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see <http://www.gnu.org/licenses>.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs.
 *  Hence, KNIME and ECLIPSE are both independent programs and are not
 *  derived from each other. Should, however, the interpretation of the
 *  GNU GPL Version 3 ("License") under any applicable laws result in
 *  KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants
 *  you the additional permission to use and propagate KNIME together with
 *  ECLIPSE with only the license terms in place for ECLIPSE applying to
 *  ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the
 *  license terms of ECLIPSE themselves allow for the respective use and
 *  propagation of ECLIPSE together with KNIME.
 *
 *  Additional permission relating to nodes for KNIME that extend the Node
 *  Extension (and in particular that are based on subclasses of NodeModel,
 *  NodeDialog, and NodeView) and that only interoperate with KNIME through
 *  standard APIs ("Nodes"):
 *  Nodes are deemed to be separate and independent programs and to not be
 *  covered works.  Notwithstanding anything to the contrary in the
 *  License, the License does not apply to Nodes, you are not required to
 *  license Nodes under the License, and you are granted a license to
 *  prepare and propagate Nodes, in each case even if such Nodes are
 *  propagated with or for interoperation with KNIME.  The owner of a Node
 *  may freely choose the license terms applicable to such Node, including
 *  when such Node is propagated with or for interoperation with KNIME.
 * ---------------------------------------------------------------------
 */
package org.rdkit.knime.wizards;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.ui.IJavaElementSearchConstants;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;
import org.eclipse.ui.plugin.AbstractUIPlugin;

/**
 * This page enables the user to enter the information needed to create the
 * extension plugin project. The Wizard collects the values via a substitution
 * map, that is used to fill out the templates.
 *
 * @author Manuel Schwarze
 */
@SuppressWarnings("restriction")
public class RDKitNodesWizardsPage extends WizardPage implements Listener {

    //
    // Constants
    //

    /**
     * An array with deprecated node types. Deprecated node type templates can still
     * be present in the template folder, but will not show up anymore in the wizard
     * for the creation of new nodes.
     */
    public static final String[] DEPRECACTED_NODE_TYPES = new String[] {};

    /** Placeholder used in the template files. */
    public static final String SUBST_PROJECT_NAME = "__PROJECT_NAME__";

    /** Placeholder used in the template files. */
    public static final String SUBST_BASE_PACKAGE = "__BASE_PACKAGE__";

    /** Placeholder used in the template files. */
    public static final String SUBST_NODE_MENU_NAME = "__NODE_MENU_NAME__";

    /** Placeholder used in the template files. */
    public static final String SUBST_NODE_NAME = "__NODE_NAME__";

    /** Placeholder used in the template files. */
    public static final String SUBST_DESCRIPTION = "__DESCRIPTION__";

    /** Placeholder used in the template files. */
    public static final String SUBST_JAR_NAME = "__JAR_NAME__";

    /** Placeholder used in the template files. */
    public static final String SUBST_VENDOR_NAME = "__VENDOR_NAME__";

    /** Placeholder used in the template files. */
    public static final String SUBST_PRE_PROC_PERCENTAGE = "__PRE_PROC_PERCENTAGE__";

    /** Placeholder used in the template files. */
    public static final String SUBST_POST_PROC_PERCENTAGE = "__POST_PROC_PERCENTAGE__";

    /** Placeholder used in the template files. */
    public static final String SUBST_CURRENT_YEAR = "__CURRENT_YEAR__";

    /** Placeholder used in the template files. */
    public static final String SUBST_NODE_TYPE = "__NODE_TYPE__";

    /** Placeholder used in the template files. */
    public static final String SUBST_PARALLEL_PROCESSING = "__PARALLEL_PROCESSING__";

    /** The icon used as logo for the RDKit Node Wizard. */
    private static final ImageDescriptor ICON = AbstractUIPlugin
            .imageDescriptorFromPlugin(RDKitNodesWizardsPlugin.ID, "icons/rdkit_wizard.png");

    /** The icon used for the info button to show the wizard help. */
    private static final Image IMAGE_INFO = AbstractUIPlugin
            .imageDescriptorFromPlugin(RDKitNodesWizardsPlugin.ID, "icons/info.png").createImage();

    //
    // Members
    //

    /** Combobox showing known existing projects. */
    private Combo m_comboExistingProjects;

    /** Text field to define the node name. */
    private Text m_textNodeName;

    /** Text field to define the node package. */
    private Text m_textBasePackage;

    /** Text field to define the node author. */
    private Text m_textVendor;

    /** Text field to define the node description. */
    private Text m_textDescription;

    /** Combobox to define the node type. */
    private Combo m_comboNodeType;

    /** Text field to define the pre-processing percentage for the node. */
    private Text m_textPreProcPerc;

    /** Text field to define the post-processing percentage for the node. */
    private Text m_textPostProcPerc;

    /** Stores whatever the user had selected in the navigation tree before the wizard was started. */
    private TreeSelection m_selection;

    /** The browse button to select packages. */
    private Button m_packageBrowseButton;

    /**
     * Stores whatever was determined as current Java project based on the user's selection
     * in the navigation tree before the wizard was started.
     */
    private IJavaProject m_currentJavaProject;

    /**
     * Option flag to set, if parallel processing should be supported for the new node.
     */
    private Button m_allowParallelProcessing;

    /**
     * Option flag to set, if complex or simple code shall be generated for the new node.
     */
    private Button m_generateComplexCode;

    /**
     * Constructor for WizardPage.
     *
     * @param selection The initial selection
     */
    public RDKitNodesWizardsPage(final ISelection selection) {
        super("wizardPage");

        setTitle("Create new RDKit Node");
        setDescription("This wizard creates a framework for a new RDKit Node.");
        setImageDescriptor(ICON);

        if (selection instanceof TreeSelection) {
            m_selection = (TreeSelection) selection;
        }
    }

    /**
     * Generates the substitution map for values in the templates that
     * are based on wizard settings.
     *
     * @return The substitution map
     */
    public Properties getSubstitutionMap() {
        Properties map = new Properties();

        map.put(SUBST_PROJECT_NAME, getProjectName());
        map.put(SUBST_BASE_PACKAGE, getNodePackage());
        map.put(SUBST_NODE_NAME, getNodeClassName());
        map.put(SUBST_NODE_MENU_NAME, getNodeMenuName());
        map.put(SUBST_DESCRIPTION, getNodeDescription().replaceAll("\\n", " * \\n"));
        map.put(SUBST_VENDOR_NAME, getNodeVendor());
        map.put(SUBST_JAR_NAME, getNodeName().toLowerCase() + ".jar");
        map.put(SUBST_PRE_PROC_PERCENTAGE, "" + getPreProcessingPercentage());
        map.put(SUBST_POST_PROC_PERCENTAGE, "" + getPostProcessingPercentage());
        map.put(SUBST_NODE_TYPE, "Manipulator"); // Constant setting
        map.put(SUBST_PARALLEL_PROCESSING, "" + isParallelProcessingSupported());
        return map;
    }

    /**
     * Shows the help dialog window.
     */
    public void showHelp() {
        // Copy help files to temp directory
        URL fileUrlTemp = null;

        try {
            fileUrlTemp = makeHelpFilesAvailable();
        } catch (IOException exc) {
            // Ignored - handled later
        }

        final Shell parentShell = getShell();

        if (fileUrlTemp != null) {
            final URL fileUrl = fileUrlTemp;
            Dialog dialog = new Dialog(parentShell) {
                @Override
                protected Control createDialogArea(final Composite parent) {
                    Composite composite = (Composite) super.createDialogArea(parent);
                    Browser browser = new Browser(composite, SWT.BORDER | SWT.FILL);
                    browser.setUrl(fileUrl.toString());
                    GridData data = new GridData(GridData.FILL_BOTH);
                    browser.setLayoutData(data);
                    return composite;
                }

                @Override
                protected void createButtonsForButtonBar(final Composite parent) {
                    // create OK button
                    createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
                }

                @Override
                protected Point getInitialSize() {
                    return new Point(700, 700);
                }

                @Override
                protected Point getInitialLocation(final Point initialSize) {
                    Dimension dimScreen = Toolkit.getDefaultToolkit().getScreenSize();
                    return new Point(dimScreen.width / 2 - initialSize.x / 2,
                            dimScreen.height / 2 - initialSize.y / 2);
                }

                @Override
                protected void configureShell(final Shell newShell) {
                    super.configureShell(newShell);
                    newShell.setText("RDKit Node Types");
                }
            };

            dialog.setBlockOnOpen(true);
            dialog.open(); // Blocks
        } else {
            MessageBox msgBox = new MessageBox(parentShell, SWT.ICON_ERROR | SWT.OK);
            msgBox.setText("Error");
            msgBox.setMessage("Sorry. The help file is not available.");
            msgBox.open();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void createControl(final Composite parent) {

        Composite composite = new Composite(parent, SWT.NULL);
        composite.setFont(parent.getFont());

        initializeDialogUnits(parent);

        PlatformUI.getWorkbench().getHelpSystem().setHelp(composite, IIDEHelpContextIds.NEW_PROJECT_WIZARD_PAGE);

        composite.setLayout(new GridLayout());
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));

        setPageComplete(validatePage());

        // Show description on opening
        setErrorMessage(null);
        setMessage(null);
        setControl(composite);

        // Existing project specification group
        final Composite existProjectGroup = new Composite(composite, SWT.NONE);
        existProjectGroup.setLayout(new GridLayout(3, false));
        existProjectGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        // Project name to select
        CLabel projectLabel = new CLabel(existProjectGroup, SWT.NONE | SWT.RIGHT);
        projectLabel.setMargins(0, 0, 15, 0);
        projectLabel.setText("Select an existing project:");
        projectLabel.setFont(existProjectGroup.getFont());
        m_comboExistingProjects = createProjectsCombo(existProjectGroup);
        m_comboExistingProjects.setFont(composite.getFont());
        m_comboExistingProjects.addListener(SWT.Modify, new Listener() {

            /**
             * Revalidates all settings when the user changes the project name.
             * @param e Event.
             */
            @Override
            public void handleEvent(final Event e) {
                m_currentJavaProject = JavaModelManager.getJavaModelManager().getJavaModel()
                        .getJavaProject(m_comboExistingProjects.getText());
                boolean valid = validatePage();
                setPageComplete(valid);
            }
        });

        // Group for KNIME settings
        Group settingsGroup = new Group(composite, SWT.NONE);
        settingsGroup.setText("RDKit Node Settings");
        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.marginTop = 5;
        settingsGroup.setLayout(gridLayout);
        settingsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        // Node name as base name for the classes to generate
        CLabel label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setAlignment(SWT.RIGHT);
        label.setText("Node name: ");
        label.setToolTipText("Generated classes will have 'NodeModel', 'NodeDialog', etc. appended to this name.");
        label.setFont(composite.getFont());
        m_textNodeName = new Text(settingsGroup, SWT.BORDER);
        m_textNodeName.setFont(composite.getFont());
        m_textNodeName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        m_textNodeName.addListener(SWT.Modify, this);
        m_textNodeName.addListener(SWT.Modify, new Listener() {

            /**
             * Calculates package, class and menu name, when the user changes the node name.
             * @param e Event.
             */
            @Override
            public void handleEvent(final Event e) {
                String strCurrentPackageName = m_textBasePackage.getText();
                int iIndex = strCurrentPackageName.lastIndexOf(".");
                String strNewPackageName = strCurrentPackageName.substring(0, iIndex + 1)
                        + getNodeClassName(false).toLowerCase();
                m_textBasePackage.setText(strNewPackageName);
                m_textNodeName.setToolTipText(
                        "Class Name: " + getNodeClassName() + "\n" + "Menu Name: " + getNodeMenuName());
            }
        });

        // Base package
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setText("Node package name: ");
        label.setFont(composite.getFont());
        Composite compo = new Composite(settingsGroup, SWT.NONE);
        GridLayout layout = new GridLayout(2, false);
        layout.marginLeft = 0;
        layout.marginRight = 0;
        layout.marginWidth = 0;
        compo.setLayout(layout);
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalIndent = 0;
        compo.setLayoutData(data);
        m_textBasePackage = new Text(compo, SWT.BORDER);
        m_textBasePackage.setText("org.rdkit.knime.nodes.<new name>");
        m_textBasePackage.setFont(composite.getFont());
        m_textBasePackage.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        m_textBasePackage.addListener(SWT.Modify, this);

        String strSelectedPackage = getSelectedPackage();
        if (strSelectedPackage != null && !strSelectedPackage.isEmpty()) {
            m_textBasePackage.setText(strSelectedPackage);
        }

        m_packageBrowseButton = new Button(compo, SWT.PUSH);
        m_packageBrowseButton.setText("Browse...");
        m_packageBrowseButton.addSelectionListener(new SelectionListener() {

            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                widgetSelected(e);
            }

            /**
             * Let's the user choose a project from a pop-up dialog.
             * @param e Selection Event when Browse button is clicked.
             */
            @Override
            public void widgetSelected(final SelectionEvent e) {
                if (m_currentJavaProject != null) {
                    try {
                        SelectionDialog dialog = JavaUI.createPackageDialog(getShell(), m_currentJavaProject,
                                IJavaElementSearchConstants.CONSIDER_REQUIRED_PROJECTS);
                        dialog.open();
                        Object[] results = dialog.getResult();
                        if (results != null && results.length >= 1) {
                            if (results[0] instanceof IPackageFragment) {
                                m_textBasePackage.setText(((IPackageFragment) results[0]).getElementName());
                            }
                        }
                    } catch (Exception ex) {
                        // Empty by purpose - do nothing
                    }
                }
            }
        });

        // Vendor name
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 10, 10, 0);
        label.setText("Node author (your name): ");
        label.setToolTipText("This name will appear in the source files as author information.");
        label.setFont(composite.getFont());
        m_textVendor = new Text(settingsGroup, SWT.BORDER);
        m_textVendor.setText(System.getProperty("user.name", "NIBR"));
        m_textVendor.setFont(composite.getFont());
        m_textVendor.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        m_textVendor.addListener(SWT.Modify, this);

        // Node Description
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 10, 10, 0);
        label.setText("Node description text: ");
        m_textDescription = new Text(settingsGroup, SWT.BORDER | SWT.MULTI | SWT.WRAP | SWT.V_SCROLL);
        data = new GridData(GridData.FILL_BOTH);
        data.minimumHeight = 60;
        m_textDescription.setFont(composite.getFont());
        m_textDescription.setLayoutData(data);
        m_textDescription.addListener(SWT.Modify, this);

        // Node type
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 10, 10, 0);
        label.setText("Node type:");

        compo = new Composite(settingsGroup, SWT.NONE);
        layout = new GridLayout(2, false);
        layout.marginLeft = 0;
        layout.marginRight = 0;
        layout.marginWidth = 0;
        compo.setLayout(layout);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalIndent = 0;
        compo.setLayoutData(data);
        m_comboNodeType = createCategoryCombo(compo);
        m_comboNodeType.setFont(composite.getFont());
        m_comboNodeType.addListener(SWT.Modify, this);

        // Help button to get Node Type Descriptions
        Button btnHelp = new Button(compo, SWT.PUSH);
        btnHelp.setImage(IMAGE_INFO);
        btnHelp.addSelectionListener(new SelectionListener() {

            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                widgetSelected(e);
            }

            /**
             * Let's the user choose a project from a pop-up dialog.
             * @param e Selection Event when Browse button is clicked.
             */
            @Override
            public void widgetSelected(final SelectionEvent e) {
                showHelp();
            };
        });

        // Parallel processing
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 10, 10, 0);
        label.setText("Multi-Threading: ");
        label.setFont(composite.getFont());
        m_allowParallelProcessing = new Button(settingsGroup, SWT.CHECK);
        m_allowParallelProcessing.setText("Allow parallel processing, if possible");
        m_allowParallelProcessing.setFont(settingsGroup.getFont());
        m_allowParallelProcessing.setLayoutData(new GridData(GridData.FILL_BOTH));
        m_allowParallelProcessing.setSelection(true);

        // Node complexity
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 10, 10, 0);
        label.setText("Complexity: ");
        label.setFont(composite.getFont());
        m_generateComplexCode = new Button(settingsGroup, SWT.CHECK);
        m_generateComplexCode.setText("Generate complex code");
        m_generateComplexCode.setFont(settingsGroup.getFont());
        m_generateComplexCode.setLayoutData(new GridData(GridData.FILL_BOTH));
        m_generateComplexCode.setSelection(false);
        m_generateComplexCode.addSelectionListener(new SelectionListener() {

            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                widgetSelected(e);
            }

            @Override
            public void widgetSelected(final SelectionEvent e) {
                boolean bComplex = m_generateComplexCode.getSelection();
                m_textPreProcPerc.setEnabled(bComplex);
                m_textPostProcPerc.setEnabled(bComplex);
            }
        });

        // Progress percentages for pre processing
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 0, 40, 0);
        label.setText("Pre-processing activities: ");
        label.setFont(composite.getFont());
        compo = new Composite(settingsGroup, SWT.NONE);
        layout = new GridLayout(2, false);
        layout.marginLeft = 0;
        layout.marginRight = 0;
        layout.marginWidth = 0;
        compo.setLayout(layout);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalIndent = 0;
        compo.setLayoutData(data);
        m_textPreProcPerc = new Text(compo, SWT.BORDER | SWT.RIGHT);
        m_textPreProcPerc.setText("0");
        m_textPreProcPerc.setFont(composite.getFont());
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.minimumWidth = 80;
        m_textPreProcPerc.setLayoutData(data);
        m_textPreProcPerc.addListener(SWT.Modify, this);
        label = new CLabel(compo, SWT.NONE);
        label.setMargins(0, 0, 0, 0);
        label.setText("% of total");
        label.setFont(composite.getFont());
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        // Progress percentages for post processing
        label = new CLabel(settingsGroup, SWT.NONE | SWT.RIGHT);
        label.setMargins(0, 0, 40, 0);
        label.setText("Post-processing activities: ");
        label.setFont(composite.getFont());
        compo = new Composite(settingsGroup, SWT.NONE);
        layout = new GridLayout(2, false);
        layout.marginLeft = 0;
        layout.marginRight = 0;
        layout.marginWidth = 0;
        compo.setLayout(layout);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalIndent = 0;
        compo.setLayoutData(data);
        m_textPostProcPerc = new Text(compo, SWT.BORDER | SWT.RIGHT);
        m_textPostProcPerc.setText("0");
        m_textPostProcPerc.setFont(composite.getFont());
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.minimumWidth = 80;
        m_textPostProcPerc.setLayoutData(data);
        m_textPostProcPerc.addListener(SWT.Modify, this);
        label = new CLabel(compo, SWT.NONE);
        label.setMargins(0, 0, 0, 0);
        label.setText("% of total");
        label.setFont(composite.getFont());
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        // Initial complex settings enabling/disabling
        boolean bComplex = m_generateComplexCode.getSelection();
        m_textPreProcPerc.setEnabled(bComplex);
        m_textPostProcPerc.setEnabled(bComplex);
    }

    /**
     * Tries to create the specified directory, if it does not exist yet.
     *
     * @param dir Directory to be created.
     *
     * @throws IOException Thrown, if the directory does not exist and could not be created.
     */
    private void prepareDirectory(final File dir) throws IOException {
        if (dir.exists()) {
            if (!dir.isDirectory()) {
                throw new IOException("'" + dir + "' is not a directory. Cannot use it.");
            }
        } else if (!dir.mkdirs()) {
            throw new IOException(
                    "'" + dir + "' or one of its parent directories could not be directory. Cannot use it.");
        }
    }

    /**
     * Tries to create a copy of the help files.
     *
     * @return Copies all help files into a temporary directory and returns the
     * path to them.
     *
     * @throws IOException Thrown, if help files could not be copied.
     */
    @SuppressWarnings("rawtypes")
    private URL makeHelpFilesAvailable() throws IOException {
        URL url = null;
        String strTempDir = System.getProperty("java.io.tmpdir");

        if (strTempDir != null) {
            // Create temp sub directory
            File dirTemp = new File(strTempDir, "rdkitNodeTypes");
            prepareDirectory(dirTemp);

            // Copy all files from the wizards help directory
            Enumeration e = RDKitNodesWizardsPlugin.getDefault().getBundle().getEntryPaths("help/");

            if (e != null) {
                while (e.hasMoreElements()) {
                    String strEntry = e.nextElement().toString();
                    // Don't copy over any .svn stuff - basically we forbid all . files
                    if (!strEntry.startsWith(".")) {
                        URL fileOrig = RDKitNodesWizardsPlugin.getDefault().getBundle().getResource(strEntry);
                        // Don't copy over any directories - we stick to a flat structure to keep it simple
                        try {
                            FileUtils.copyURLToFile(fileOrig,
                                    new File(dirTemp, strEntry.substring(strEntry.indexOf("/"))));
                        } catch (Exception exc) {
                            // Ignored
                        }
                    }
                }
            } else {
                throw new IOException("No help resource files of found.");
            }

            File fileStart = new File(dirTemp, "index.html");
            if (fileStart.canRead()) {
                url = fileStart.toURI().toURL();
            } else {
                throw new IOException("The index.html file of the help cannot be read.");
            }
        } else {
            throw new IOException("No temp directory found to copy help resources.");
        }

        return url;
    }

    /**
     * Determines based on the user selection in the Eclipse navigation pane what
     * package the user has selected.
     *
     * @return Selected package or empty string, if unknown.
     */
    private String getSelectedPackage() {
        if (m_selection == null || m_selection.isEmpty()) {
            return "";
        }

        Object o = m_selection.getFirstElement();
        if (o instanceof IJavaElement) {
            if (o instanceof IPackageFragment) {
                return ((IPackageFragment) o).getElementName();
            } else {
                IJavaElement je = (IJavaElement) o;
                do {
                    je = je.getParent();
                } while (je != null && !(je instanceof IPackageFragment));

                return (je == null ? "" : je.getElementName());
            }
        }

        return "";
    }

    /**
     * Creates the combo box with the possible node types.
     *
     * @param parent The parent composite of the combo box.
     *
     * @return The created combo box
     */
    private Combo createCategoryCombo(final Composite parent) {

        // Read all .template files from templates package and derive the template names
        @SuppressWarnings("rawtypes")
        Enumeration enumResourcePath = RDKitNodesWizardsPlugin.getDefault().getBundle().findEntries("/",
                "*.template", true);

        Set<String> setTemplates = new HashSet<String>();

        if (enumResourcePath != null) {
            while (enumResourcePath.hasMoreElements()) {
                String strPath = enumResourcePath.nextElement().toString();
                int index = strPath.indexOf("/templates/");
                if (index != -1) {
                    int indexStart = index + 11;
                    int indexEnd = strPath.indexOf("/", indexStart);
                    if (indexEnd != -1) {
                        String strTemplateName = strPath.substring(indexStart, indexEnd);
                        if (!"complex".equals(strTemplateName)) {
                            setTemplates.add(strTemplateName);
                        }
                    }
                }
            }
        }

        // Get a sorted list of templates
        List<String> listTemplates = sort(setTemplates);
        for (String strType : DEPRECACTED_NODE_TYPES) {
            listTemplates.remove(strType);
        }

        Combo typeCombo = new Combo(parent, SWT.READ_ONLY | SWT.BORDER);

        for (String strType : listTemplates) {
            typeCombo.add(strType);
        }

        typeCombo.select(0);

        return typeCombo;
    }

    /**
     * Creates the combo box with the possible node types. Uses the information
     * from the core factory defining the types.
     *
     * @param parent the parent composite of the combo box
     *
     * @return the created combo box
     */
    private Combo createProjectsCombo(final Composite parent) {
        Combo projectsCombo = new Combo(parent, SWT.READ_ONLY | SWT.BORDER);

        int iDefault = -1;
        String strDefaultProject = "org.rdkit.knime.nodes";
        IJavaProject defaultProject = null;

        int i = 0;
        for (IProject project : RDKitNodesWizards.getProjectsInWorkspace()) {
            // unknown is just an internal type

            String projectName = project.getName();

            if (strDefaultProject.equals(projectName)) {
                iDefault = i;
                if (project instanceof IJavaElement) {
                    defaultProject = ((IJavaElement) project).getJavaProject();
                } else {
                    defaultProject = JavaModelManager.getJavaModelManager().getJavaModel()
                            .getJavaProject(project.getProject());
                }
            }

            projectsCombo.add(projectName);

            // Set default selection
            if (m_selection != null && !m_selection.isEmpty()) {
                IProject toCompare;
                Object o = m_selection.getFirstElement();
                if (o instanceof IJavaElement) {
                    m_currentJavaProject = ((IJavaElement) o).getJavaProject();
                    toCompare = m_currentJavaProject.getProject();
                } else if (o instanceof IResource) {
                    toCompare = ((IResource) o).getProject();
                    m_currentJavaProject = JavaModelManager.getJavaModelManager().getJavaModel()
                            .getJavaProject(toCompare);
                } else {
                    continue;
                }

                if (toCompare.equals(project)) {
                    projectsCombo.select(i);
                }
            }

            i++;
        }

        // If nothing is selected and our default was found, then select it
        if (projectsCombo.getSelectionIndex() == -1 && iDefault != -1) {
            projectsCombo.select(iDefault);
        }

        if (m_currentJavaProject == null && defaultProject != null) {
            m_currentJavaProject = defaultProject;
        }

        return projectsCombo;
    }

    /**
     * This checks the text fields after a modify event and sets the
     * error message if necessary. This calls <code>validatePage</code> to
     * actually validate the fields.
     *
     * @param event Event that triggers page validation.
     */
    @Override
    public void handleEvent(final Event event) {
        if (event.type != SWT.Modify) {
            return;
        }
        boolean valid = validatePage();
        setPageComplete(valid);
    }

    /**
     * Validates the page, e.g. checks whether the text fields contain valid
     * values.
     *
     * @return Returns true, if all information on page is correct. False otherwise.
     */
    protected boolean validatePage() {
        // Check existing project setting
        if (getProjectName().trim().equals("")) { //$NON-NLS-1$
            setErrorMessage(null);
            setMessage("Please select an existing project.");
            return false;
        }

        // Check the node name
        String nodeName = m_textNodeName.getText();
        if (nodeName.trim().isEmpty()) {
            setErrorMessage(null);
            setMessage("Please provide a valid node name.");
            return false;
        }
        if ((!Character.isLetter(nodeName.charAt(0))) || (nodeName.charAt(0) != nodeName.toUpperCase().charAt(0))) {
            setErrorMessage("The node name must start with an uppercase letter.");
            return false;
        }

        String strClassName = getNodeClassName();
        for (int i = 0; i < strClassName.length(); i++) {
            char c = strClassName.charAt(i);
            if (!(i == 0 && Character.isJavaIdentifierStart(c)) && !(i > 0 && Character.isJavaIdentifierPart(c))) {
                setErrorMessage("The class name '" + strClassName + "' is invalid.");
                return false;
            }
        }

        // Check package name
        String basePackage = m_textBasePackage.getText();
        if (basePackage.length() == 0) {
            setErrorMessage(null);
            setMessage("Please provide a package name.");
            return false;
        }
        for (int i = 0; i < basePackage.length(); i++) {
            char c = basePackage.charAt(i);
            if (!(Character.isLowerCase(c) || Character.isDigit(c) || c == '.' || c == '_')) {
                setErrorMessage("The package name '" + basePackage + "' is invalid.");
                return false;
            }
        }

        // Check for existing classes (naming conflict?)
        IProject project = RDKitNodesWizards.getProjectForName(getProjectName());
        String path = "src/" + m_textBasePackage.getText().trim().replace('.', '/') + "/" + nodeName;
        IFile file = project.getFile(new Path(path + "NodeModel.java"));
        if (file.exists()) {
            setErrorMessage("A node with the given name exists already. Please provide another name or package.");
            return false;
        }

        // Check percentages for pre- and post processing
        try {
            double dPrePerc = getPreProcessingPercentage();
            double dPostPerc = getPostProcessingPercentage();

            if (dPrePerc + dPostPerc > 1.0d) {
                setErrorMessage("The total of pre and post processing activities cannot be greater than 100%.");
                return false;
            }
        } catch (NumberFormatException exc) {
            setErrorMessage("Bad number format: " + exc.getMessage());
            return false;
        }

        // Everything is ok so far
        setErrorMessage(null);
        setMessage(null);
        return true;
    }

    /**
     * Returns the selected project name.
     *
     * @return Selected project name. Empty string, if undefined.
     */
    public String getProjectName() {
        return m_comboExistingProjects == null ? "" : m_comboExistingProjects.getText().trim();
    }

    /**
     * Returns the specified node name (stump).
     *
     * @return Specified node name. Empty string, if undefined.
     */
    public String getNodeName() {
        return m_textNodeName == null ? "" : m_textNodeName.getText().trim();
    }

    /**
     * Returns the specified node name to be used as class name (stump without any spaces).
     * Prepends RDKit.
     *
     * @return Specified node name. Empty string, if undefined.
     */
    public String getNodeClassName() {
        return getNodeClassName(true);
    }

    /**
     * Returns the specified node name to be used as class name (stump without any spaces).
     *
     * @param bPrependRdkit Set to true to prepend "RDKit ".
     *
     * @return Specified node name. Empty string, if undefined.
     */
    private String getNodeClassName(final boolean bPrependRdkit) {
        String strEnteredName = getNodeName().trim();
        StringBuilder sbClassName = new StringBuilder();
        boolean bMakeUpperCase = true;

        for (char ch : strEnteredName.toCharArray()) {
            if (sbClassName.length() == 0 && Character.isJavaIdentifierStart(ch)) {
                // Make the first character upper case anyway
                sbClassName.append(Character.toUpperCase(ch));
                bMakeUpperCase = false;
            } else if (sbClassName.length() > 0 && Character.isJavaIdentifierPart(ch)) {
                // Make a character upper case only, if a character was found before that was eliminated
                if (bMakeUpperCase) {
                    sbClassName.append(Character.toUpperCase(ch));
                    bMakeUpperCase = false;
                } else {
                    sbClassName.append(ch);
                }
            } else {
                // Eliminate and make next character upper case
                bMakeUpperCase = true;
            }
        }

        String strClassName = sbClassName.toString();

        // Prepend RDKit to the class identifier
        if (bPrependRdkit && !strClassName.toLowerCase().startsWith("rdkit")) {
            strClassName = "RDKit" + strClassName;
        }

        return strClassName;
    }

    /**
     * Returns the specified node name to be used as menu name (name with spaces inside).
     *
     * @return Specified node name. Empty string, if undefined.
     */
    public String getNodeMenuName() {
        String strEnteredName = getNodeName().trim();
        String strMenuName = null;

        // Only work on the name, if the user entered an identifier (which would not contain spaces)
        if (strEnteredName.indexOf(" ") == -1) {
            strMenuName = RDKitNodesWizardsPage.generateFriendlyName(strEnteredName);
        } else {
            strMenuName = strEnteredName;
        }

        // Prepend RDKit to the menu name
        if (!strMenuName.toLowerCase().startsWith("rdkit ")) {
            strMenuName = "RDKit " + strMenuName;
        }

        return strMenuName;
    }

    /**
     * Returns the specified node target package.
     *
     * @return Specified node target package. Empty string, if undefined.
     */
    public String getNodePackage() {
        return m_textBasePackage == null ? "" : m_textBasePackage.getText().trim();
    }

    /**
     * Returns the specified node type.
     *
     * @return Specified node type. Empty string, if undefined.
     */
    public String getNodeType() {
        return m_comboNodeType == null ? "" : m_comboNodeType.getText().trim();
    }

    /**
     * Returns the specified node description.
     *
     * @return Specified node description. Empty string, if undefined.
     */
    public String getNodeDescription() {
        return m_textDescription == null ? "" : m_textDescription.getText().trim();
    }

    /**
     * Returns the specified node vendor.
     *
     * @return Specified node vendor. Empty string, if undefined.
     */
    public String getNodeVendor() {
        return m_textVendor == null ? "" : m_textVendor.getText().trim();
    }

    /**
     * Returns the entered percentage value as double (devided by 100) for
     * pre processing actions.
     *
     * @return Value between 0 and 1.
     *
     * @throws NumberFormatException Thrown, if the user entered an invalid value in the
     *       text field for the pre-processing percentage.
     */
    public double getPreProcessingPercentage() throws NumberFormatException {
        double dPerc = 0.0d;

        if (m_textPreProcPerc != null && m_textPreProcPerc.getEnabled()) {
            dPerc = Double.parseDouble(m_textPreProcPerc.getText().trim()) / 100.0d;
            if (dPerc < 0) {
                throw new NumberFormatException(
                        "Percentage of pre-processing activities must be greater than or equal to 0%.");
            }
            if (dPerc > 1) {
                throw new NumberFormatException(
                        "Percentage of pre-processing activities must be lower than or equal to 100%.");
            }
        }

        return dPerc;
    }

    /**
     * Returns the entered percentage value as double (devided by 100) for
     * pre processing actions.
     *
     * @return Value between 0 and 1.
     *
     * @throws NumberFormatException Thrown, if the user entered an invalid value in the
     *       text field for the post-processing percentage.
     */
    public double getPostProcessingPercentage() throws NumberFormatException {
        double dPerc = 0.0d;

        if (m_textPostProcPerc != null && m_textPostProcPerc.getEnabled()) {
            dPerc = Double.parseDouble(m_textPostProcPerc.getText().trim()) / 100.0d;
            if (dPerc < 0) {
                throw new NumberFormatException(
                        "Percentage of post-processing activities must be greater than or equal to 0%.");
            }
            if (dPerc > 1) {
                throw new NumberFormatException(
                        "Percentage of post-processing activities must be lower than or equal to 100%.");
            }
        }

        return dPerc;
    }

    /**
     * Returns if the developer allows the code to be optimized for parallel processing.
     *
     * @return Flag for parallel processing support. False, if undefined.
     */
    public boolean isParallelProcessingSupported() {
        return m_allowParallelProcessing == null ? false : m_allowParallelProcessing.getSelection();
    }

    /**
     * Returns if the developer wants to generate more complex code for the new node.
     *
     * @return Flag for more complex code. False, if undefined.
     */
    public boolean isGenerateComplexCode() {
        return m_generateComplexCode == null ? false : m_generateComplexCode.getSelection();
    }

    //
    // Private Static Methods
    //

    /**
     * Sorts the specified string list. O(N) is n log(n).
     *
     * @param set String set containing the elements to be sorted. Can be
     *            <code>null</code>.
     *
     * @return Sorted string list or <code>null</code> if <code>null</code> was
     *         passed in.
     */
    private static List<String> sort(final Set<String> set) {
        List<String> listSorted = null;

        if (set != null) {
            listSorted = new ArrayList<String>();
            for (String str : set) {
                listSorted.add(getIndexForSortedInsert(listSorted, str), str);
            }
        }

        return listSorted;
    }

    /**
     * Determines for a sorted list and an object, what index the string
     * could be inserted. Note: This method just returns the index, but does not
     * insert the string.
     *
     * @param listSorted
     *            Sorted string list. Can be <code>null</code>.
     * @param item
     *            String to be inserted. Can be <code>null</code>.
     *
     * @return Index for string insertion.
     */
    private static int getIndexForSortedInsert(final List<String> listSorted, final String item) {
        if (listSorted == null || item == null) {
            return 0;
        }

        int low = 0;
        int high = listSorted.size() - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            String midVal = listSorted.get(mid);
            int cmp = midVal.compareToIgnoreCase(item);

            if (cmp < 0) {
                low = mid + 1;
            } else if (cmp > 0) {
                high = mid - 1;
            } else {
                return mid; // key found
            }
        }

        return low; // key not found.
    }

    /**
     * Generates a friendly and easily readable name based on the passed in name.
     * Replaces for instance underscores with spaces, corrects lower and upper
     * cases when necessary.
     *
     * @param name A name of something. Can be null.
     *
     * @return A friendly name. Is empty when null was passed in.
     */
    public static String generateFriendlyName(final String name) {
        String workName = "";

        if (name != null) {
            workName = name.trim();

            // Don't touch, if length is <= 2 (usually a variable like x, y, z)
            if (workName.length() > 2) {
                workName = workName.replaceAll("_", " ");
                workName = workName.replaceAll("\\s", " ");

                final StringBuilder sbFriendlyName = new StringBuilder();

                int iCountUpperCase = 0;
                boolean bWasSpace = false;
                char[] chars = workName.toCharArray();
                chars[0] = Character.toUpperCase(chars[0]);

                for (char c : chars) {
                    final boolean bIsUpperCase = Character.isUpperCase(c);
                    final boolean bIsLowerCase = Character.isLowerCase(c);

                    if (bIsUpperCase && iCountUpperCase == 0) {
                        sbFriendlyName.append(" ");
                    }

                    if (bIsLowerCase && iCountUpperCase > 1) {
                        sbFriendlyName.insert(sbFriendlyName.length() - 1, " ");
                    }

                    if (bWasSpace) {
                        c = Character.toUpperCase(c);
                    }

                    sbFriendlyName.append(c);

                    if (bIsUpperCase) {
                        iCountUpperCase++;
                    } else {
                        iCountUpperCase = 0;
                    }

                    bWasSpace = (c == ' ');
                }

                workName = sbFriendlyName.toString().trim();

                // If more than one whitespace was added in the middle, reduce
                // multiple to single space
                workName = workName.trim().replaceAll("\\s+", " ");
            }
        }

        return workName;
    }
}