com.genericworkflownodes.knime.nodegeneration.NodeGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.genericworkflownodes.knime.nodegeneration.NodeGenerator.java

Source

/*
 * Copyright (c) 2011-2012, Marc Rttig.
 * Copyright (c) 2012, Bjrn Kahlert.
 * Copyright (c) 2012, Stephan Aiche.
 *
 * This file is part of GenericKnimeNodes.
 * 
 * GenericKnimeNodes is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.genericworkflownodes.knime.nodegeneration;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import com.genericworkflownodes.knime.config.INodeConfiguration;
import com.genericworkflownodes.knime.nodegeneration.exceptions.UnknownMimeTypeException;
import com.genericworkflownodes.knime.nodegeneration.model.directories.Directory;
import com.genericworkflownodes.knime.nodegeneration.model.directories.Directory.PathnameIsNoDirectoryException;
import com.genericworkflownodes.knime.nodegeneration.model.directories.FragmentDirectory;
import com.genericworkflownodes.knime.nodegeneration.model.directories.NodesBuildDirectory;
import com.genericworkflownodes.knime.nodegeneration.model.directories.NodesSourceDirectory;
import com.genericworkflownodes.knime.nodegeneration.model.directories.build.NodesBuildKnimeNodesDirectory;
import com.genericworkflownodes.knime.nodegeneration.model.directories.source.DescriptorsDirectory;
import com.genericworkflownodes.knime.nodegeneration.model.files.CTDFile;
import com.genericworkflownodes.knime.nodegeneration.model.meta.ContributingPluginMeta;
import com.genericworkflownodes.knime.nodegeneration.model.meta.FeatureMeta;
import com.genericworkflownodes.knime.nodegeneration.model.meta.FragmentMeta;
import com.genericworkflownodes.knime.nodegeneration.model.meta.GeneratedPluginMeta;
import com.genericworkflownodes.knime.nodegeneration.templates.BuildPropertiesTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.ManifestMFTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.PluginActivatorTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.PluginXMLTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.ProjectTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.feature.FeatureBuildPropertiesTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.feature.FeatureProjectTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.feature.FeatureXMLTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.fragment.FragmentBuildPropertiesTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.fragment.FragmentManifestMFTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.fragment.FragmentP2InfTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.fragment.FragmentProjectTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.knime_node.NodeDialogTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.knime_node.NodeFactoryTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.knime_node.NodeFactoryXMLTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.knime_node.NodeModelTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.testingfeature.TestingFeatureBuildPropertiesTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.testingfeature.TestingFeatureProjectTemplate;
import com.genericworkflownodes.knime.nodegeneration.templates.testingfeature.TestingFeatureXMLTemplate;
import com.genericworkflownodes.knime.nodegeneration.util.UnZipFailureException;
import com.genericworkflownodes.knime.nodegeneration.util.Utils;
import com.genericworkflownodes.knime.nodegeneration.writer.PropertiesWriter;

/**
 * This class is responsible for generating KNIME plugins.
 * 
 * @author bkahlert
 * 
 */
public class NodeGenerator {
    private static final Logger LOGGER = Logger.getLogger(NodeGenerator.class.getCanonicalName());

    public static class NodeGeneratorException extends Exception {
        private static final long serialVersionUID = 1L;

        private NodeGeneratorException(Throwable t) {
            super(t);
        }

        private NodeGeneratorException(String message, Throwable t) {
            super(message, t);
        }

        private NodeGeneratorException(String message) {
            super(message);
        }
    }

    private final String nodeGeneratorLastChangeDate;

    private final NodesSourceDirectory srcDir;
    private final GeneratedPluginMeta generatedPluginMeta;
    private final FeatureMeta featureMeta;
    private List<FragmentMeta> fragmentMetas;
    private List<ContributingPluginMeta> contributingPluginMetas;
    private NodesBuildDirectory pluginBuildDir;

    /**
     * The directory where all the individual, generated plugins will be
     * located.
     */
    private final Directory baseBinaryDirectory;

    /**
     * Initializes a new {@link NodeGenerator} instance.
     * 
     * @param sourceDir
     *            directory that contains the plugin's sources.
     * @param buildDir
     *            directory that will contain the generated plugin (the
     *            directory will not be flushed before the generation)
     * @param lastChangeDate
     *            Last change date of the node generator / GKN.
     * @throws NodeGeneratorException
     */
    public NodeGenerator(File sourceDir, File buildDir, String lastChangeDate) throws NodeGeneratorException {
        try {
            if (buildDir == null)
                throw new NodeGeneratorException("buildDir must not be null");

            nodeGeneratorLastChangeDate = lastChangeDate;

            baseBinaryDirectory = new Directory(buildDir);
            baseBinaryDirectory.mkdir();
            if (baseBinaryDirectory.list().length != 0) {
                LOGGER.warning("The given buildDir is not empty: Will clean the directory.");
                for (File file : baseBinaryDirectory.listFiles())
                    if (file.isDirectory())
                        FileUtils.deleteDirectory(file);
                    else
                        file.delete();
            }

            srcDir = new NodesSourceDirectory(sourceDir);
            generatedPluginMeta = new GeneratedPluginMeta(srcDir, nodeGeneratorLastChangeDate);
            featureMeta = new FeatureMeta(srcDir, generatedPluginMeta);
            pluginBuildDir = new NodesBuildDirectory(buildDir, generatedPluginMeta.getPackageRoot());
            contributingPluginMetas = srcDir.getContributingPluginsDirectory().getContributingPluginMetas();
            fragmentMetas = new ArrayList<FragmentMeta>();
        } catch (Exception e) {
            throw new NodeGeneratorException(e);
        }
    }

    public void generate() throws NodeGeneratorException {
        LOGGER.info("Creating KNIME plugin sources\n\tFrom: " + srcDir + "\n\tTo: " + pluginBuildDir);

        try {
            DescriptorsDirectory descriptorsDirectory = srcDir.getDescriptorsDirectory();

            // build.properties - only useful if you re-import the generated
            // node in
            // Eclipse
            new BuildPropertiesTemplate().write(pluginBuildDir.getBuildProperties());

            // META-INF/MANIFEST.MF
            new ManifestMFTemplate(generatedPluginMeta).write(pluginBuildDir.getManifestMf());

            // src/[PACKAGE]/knime/plugin.properties
            new PropertiesWriter(new File(pluginBuildDir.getKnimeDirectory(), "plugin.properties"))
                    .write(new HashMap<String, String>() {
                        private static final long serialVersionUID = 1L;
                        {
                            put("executor", srcDir.getProperty("executor", "LocalToolExecutor"));
                            put("commandGenerator", srcDir.getProperty("commandGenerator", "CLICommandGenerator"));
                        }
                    });

            PluginXMLTemplate pluginXML = new PluginXMLTemplate();
            List<String> nodeNames = new LinkedList<String>();
            List<INodeConfiguration> configurations = new ArrayList<INodeConfiguration>();

            // src/[PACKAGE]/knime/nodes/*/*
            for (CTDFile ctdFile : descriptorsDirectory.getCTDFiles()) {
                LOGGER.info("Start processing ctd file: " + ctdFile.getName());

                configurations.add(ctdFile.getNodeConfiguration());
                nodeNames.add(ctdFile.getNodeConfiguration().getName());

                String factoryClass = copyNodeSources(ctdFile);

                String absoluteCategory = "/" + generatedPluginMeta.getNodeRepositoryRoot() + "/"
                        + generatedPluginMeta.getName() + "/" + ctdFile.getNodeConfiguration().getCategory();
                pluginXML.registerNode(factoryClass, absoluteCategory);
            }

            // src/[PACKAGE]/knime/PluginActivator.java
            new PluginActivatorTemplate(generatedPluginMeta, configurations)
                    .write(new File(pluginBuildDir.getKnimeDirectory(), "PluginActivator.java"));

            // icons/*
            copyFolderIcon();
            registerSplashIcon(pluginXML);

            // register the mime types
            pluginXML.registerMIMETypeEntries(srcDir.getMIMETypes());

            // plugin.xml
            pluginXML.saveTo(pluginBuildDir.getPluginXml());

            // .project
            new ProjectTemplate(generatedPluginMeta.getPackageRoot()).write(pluginBuildDir.getProjectFile());

            // src/[PACKAGE]/knime/nodes/binres/*.ini *.zip
            if (srcDir.getPayloadDirectory() != null) {
                // create payload fragments
                fragmentMetas = createPayloadFragments();
            }

            // copy assets
            copyAsset(".classpath");
            copyAsset("buckminster.cspex");

            copyContributingPlugins();

            // create feature
            generateFeature();

            // create testing feature
            generateTestingFeature();

            LOGGER.info("KNIME plugin sources successfully created in:\n\t" + pluginBuildDir);
        } catch (Exception e) {
            LOGGER.info("KNIME plugin source creation failed");
            throw new NodeGeneratorException(e);
        }
    }

    private void copyContributingPlugins() {
        for (ContributingPluginMeta contributingPluginMeta : contributingPluginMetas) {
            try {
                // TODO: Handle compiled classes in bin/ or build/ (maybe check
                // build.properties for output folder)
                FileUtils.copyDirectory(contributingPluginMeta.getContributingPluginDirectory(), new File(
                        baseBinaryDirectory, contributingPluginMeta.getContributingPluginDirectory().getName()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Creates a separate fragment for each binaries_..zip file found in the
     * payload directory.
     * 
     * @throws NodeGeneratorException
     * @throws PathnameIsNoDirectoryException
     * @throws IOException
     * @throws UnZipFailureException
     */
    private List<FragmentMeta> createPayloadFragments()
            throws NodeGeneratorException, PathnameIsNoDirectoryException, IOException, UnZipFailureException {

        List<FragmentMeta> createdFragments = srcDir.getPayloadDirectory().getFragmentMetas(generatedPluginMeta);

        for (FragmentMeta fragmentMeta : createdFragments) {
            LOGGER.info(String.format("Creating binary fragment %s", fragmentMeta.getId()));

            FragmentDirectory fragmentDir = new FragmentDirectory(baseBinaryDirectory, fragmentMeta);

            // create project file
            new ProjectTemplate(fragmentMeta.getId()).write(fragmentDir.getProjectFile());

            // build.properties
            new FragmentBuildPropertiesTemplate().write(fragmentDir.getBuildProperties());

            // manifest.mf
            new FragmentManifestMFTemplate(fragmentMeta).write(fragmentDir.getManifestMf());

            new FragmentProjectTemplate(fragmentMeta.getId()).write(new File(fragmentDir, ".project"));

            // copy the binaries
            List<String> executablePaths = fragmentDir.getBinaryResourcesDirectory()
                    .copyPayload(fragmentMeta.getPayloadFile());

            new FragmentP2InfTemplate(executablePaths).write(fragmentDir.getP2Inf());

        }

        return createdFragments;
    }

    private void generateTestingFeature() throws PathnameIsNoDirectoryException, IOException {
        // create feature directory
        Directory featureDir = new Directory(
                new File(baseBinaryDirectory, generatedPluginMeta.getPackageRoot() + ".testing.feature"));
        featureDir.mkdir();

        // find all packages in the current directory
        new TestingFeatureBuildPropertiesTemplate().write(new File(featureDir, "build.properties"));

        new TestingFeatureXMLTemplate(generatedPluginMeta, featureMeta, fragmentMetas, contributingPluginMetas)
                .write(new File(featureDir, "feature.xml"));

        new TestingFeatureProjectTemplate(generatedPluginMeta.getPackageRoot())
                .write(new File(featureDir, ".project"));
    }

    private void generateFeature() throws PathnameIsNoDirectoryException, IOException {
        // create feature directory
        Directory featureDir = new Directory(
                new File(baseBinaryDirectory, generatedPluginMeta.getPackageRoot() + ".feature"));
        featureDir.mkdir();

        // find all packages in the current directory
        new FeatureBuildPropertiesTemplate().write(new File(featureDir, "build.properties"));

        new FeatureXMLTemplate(generatedPluginMeta, featureMeta, fragmentMetas, contributingPluginMetas)
                .write(new File(featureDir, "feature.xml"));

        new FeatureProjectTemplate(generatedPluginMeta.getPackageRoot()).write(new File(featureDir, ".project"));
    }

    private void copyAsset(String assetResourcePath, String targetPath) throws IOException {
        InputStream in = NodeGenerator.class.getResourceAsStream("assets/" + assetResourcePath);

        FileOutputStream fostream = new FileOutputStream(new File(targetPath, assetResourcePath));
        IOUtils.copy(in, fostream);
        fostream.close();
    }

    private void copyAsset(String assetResourcePath) throws IOException {
        copyAsset(assetResourcePath, pluginBuildDir.getAbsolutePath());
    }

    public File getSourceDirectory() {
        return srcDir;
    }

    public File getBuildDirectory() {
        return pluginBuildDir;
    }

    public String getPluginName() {
        return generatedPluginMeta.getName();
    }

    public String getPluginVersion() {
        return generatedPluginMeta.getVersion();
    }

    public void copyFolderIcon() throws IOException {

        File categoryIcon = srcDir.getIconsDirectory().getCategoryIcon();
        if (categoryIcon != null && categoryIcon.canRead()) {
            // TODO: only set icon file in plugin.xml for categories if this
            // method was called
            FileUtils.copyFile(categoryIcon, new File(pluginBuildDir.getIconsDirectory(), "category.png"));
        }
    }

    public void registerSplashIcon(PluginXMLTemplate pluginXML) throws IOException {
        File splashIcon = srcDir.getIconsDirectory().getSplashIcon();
        if (splashIcon != null && splashIcon.canRead()) {
            FileUtils.copyFile(splashIcon, new File(pluginBuildDir.getIconsDirectory(), "splash.png"));
            pluginXML.registerSplashIcon(generatedPluginMeta, new File("icons/splash.png"));
        }
    }

    /**
     * Copies the java sources needed to invoke a tool (described by a
     * {@link CTDFile}) to the specified {@link NodesBuildKnimeNodesDirectory}.
     * 
     * @param ctdFile
     *            which described the wrapped tool
     * @param iconsDir
     *            location where node icons reside
     * @param nodesDir
     *            location where to create a sub directory containing the
     *            generated sources
     * @param pluginMeta
     *            meta information used to adapt the java files
     * @return the fully qualified name of the NodeFactory class able to build
     *         instances of the node.
     * @throws IOException
     * @throws UnknownMimeTypeException
     */
    public String copyNodeSources(CTDFile ctdFile) throws IOException, UnknownMimeTypeException {

        INodeConfiguration nodeConfiguration = ctdFile.getNodeConfiguration();
        String nodeName = Utils.fixKNIMENodeName(nodeConfiguration.getName());

        File nodeSourceDir = new File(pluginBuildDir.getKnimeNodesDirectory(), nodeName);
        nodeSourceDir.mkdirs();

        File nodeIcon = srcDir.getIconsDirectory().getNodeIcon(nodeConfiguration);
        if (nodeIcon != null) {
            FileUtils.copyFileToDirectory(nodeIcon, nodeSourceDir);
        } else {
            // use generic icon
            copyAsset("generic_node.png", nodeSourceDir.getAbsolutePath());
            nodeIcon = new File(nodeSourceDir, "generic_node.png");
        }

        /*
         * all files placed into src/[PACKAGE]/knime/nodes/[NODE_NAME]
         */
        new NodeDialogTemplate(generatedPluginMeta.getPackageRoot(), nodeName)
                .write(new File(nodeSourceDir, nodeName + "NodeDialog.java"));
        new NodeModelTemplate(generatedPluginMeta.getPackageRoot(), nodeName, nodeConfiguration)
                .write(new File(nodeSourceDir, nodeName + "NodeModel.java"));
        new NodeFactoryXMLTemplate(nodeName, nodeConfiguration, nodeIcon.getName())
                .write(new File(nodeSourceDir, nodeName + "NodeFactory.xml"));
        new NodeFactoryTemplate(generatedPluginMeta.getPackageRoot(), nodeName)
                .write(new File(nodeSourceDir, nodeName + "NodeFactory.java"));

        File nodeConfigDir = new File(nodeSourceDir, "config");
        nodeConfigDir.mkdirs();

        /*
         * all files placed into src/[PACKAGE]/knime/nodes/[NODE_NAME]/config
         */
        FileUtils.copyFile(ctdFile, new File(nodeConfigDir, "config.xml"));

        return generatedPluginMeta.getPackageRoot() + ".knime.nodes." + nodeName + "." + nodeName + "NodeFactory";
    }
}