de.unibi.techfak.bibiserv.util.codegen.Main.java Source code

Java tutorial

Introduction

Here is the source code for de.unibi.techfak.bibiserv.util.codegen.Main.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package de.unibi.techfak.bibiserv.util.codegen;

import de.unibi.techfak.bibiserv.util.codegen.logfilter.VerboseOutputFilter;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * CodeGen Main class
 *
 * offers cmdline interface for CodeGen.
 *
 *
 * @author Jan Krger - jkrueger(at)cebitec.uni-bielefeld.de
 *
 */
public class Main {

    public static final Logger log = LoggerFactory.getLogger(Main.class);

    public static Properties config = new Properties();

    public enum RESOURCETYPE {
        isFile, isDirectory
    };

    public static void main(String[] args) {
        // check &   validate cmdline options
        OptionGroup opt_g = getCMDLineOptionsGroups();
        Options opt = getCMDLineOptions();
        opt.addOptionGroup(opt_g);

        CommandLineParser cli = new DefaultParser();
        try {
            CommandLine cl = cli.parse(opt, args);

            if (cl.hasOption("v")) {
                VerboseOutputFilter.SHOW_VERBOSE = true;
            }

            switch (opt_g.getSelected()) {
            case "V":
                try {
                    URL jarUrl = Main.class.getProtectionDomain().getCodeSource().getLocation();
                    String jarPath = URLDecoder.decode(jarUrl.getFile(), "UTF-8");
                    JarFile jarFile = new JarFile(jarPath);
                    Manifest m = jarFile.getManifest();
                    StringBuilder versionInfo = new StringBuilder();
                    for (Object key : m.getMainAttributes().keySet()) {
                        versionInfo.append(key).append(":").append(m.getMainAttributes().getValue(key.toString()))
                                .append("\n");
                    }
                    System.out.println(versionInfo.toString());
                } catch (Exception e) {
                    log.error("Version info could not be read.");
                }
                break;
            case "h":
                HelpFormatter help = new HelpFormatter();
                String header = ""; //TODO: missing infotext 
                StringBuilder footer = new StringBuilder("Supported configuration properties :");
                help.printHelp("CodeGen -h | -V | -g  [...]", header, opt, footer.toString());
                break;
            case "g":
                // target dir
                if (cl.hasOption("t")) {
                    File target = new File(cl.getOptionValue("t"));
                    if (target.isDirectory() && target.canExecute() && target.canWrite()) {
                        config.setProperty("target.dir", cl.getOptionValue("t"));
                    } else {
                        log.error("Target dir '{}' is inaccessible!", cl.getOptionValue("t"));
                        break;
                    }
                } else {
                    config.setProperty("target.dir", System.getProperty("java.io.tmpdir"));
                }

                // project dir
                if (cl.hasOption("p")) {
                    File project = new File(cl.getOptionValue("p"));
                    if (!project.exists()) {
                        if (!project.mkdirs()) {
                            log.error("Project dir '{}' can't be created!", cl.getOptionValue("p"));
                            break;
                        }
                    }

                    if (project.isDirectory() && project.canExecute() && project.canWrite()) {
                        config.setProperty("project.dir", cl.getOptionValue("p"));
                    } else {
                        log.error("Project dir '{}' is inaccessible!", cl.getOptionValue("p"));
                        break;
                    }
                }

                generateAppfromXML(cl.getOptionValue("g"));
                break;
            }
        } catch (ParseException e) {
            log.error("ParseException occurred while parsing cmdline arguments!\n{}", e.getLocalizedMessage());
        }

    }

    private static boolean generateAppfromXML(String fn) {

        long starttime = System.currentTimeMillis();

        // fn must not be null or empty
        if (fn == null || fn.isEmpty()) {
            log.error("Empty filename!");
            return false;
        }
        // fn must be a valid file and readable
        File runnableitem = new File(fn);
        if (!runnableitem.exists() || !runnableitem.canRead()) {
            log.error("{} doesn't exists or can't be read! ", fn);
            return false;
        }
        // load as xml file, validate it, and ...
        Document doc;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            //dbf.setValidating(true);
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.parse(runnableitem);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            log.error("{} occured: {}", e.getClass().getSimpleName(), e.getLocalizedMessage());
            return false;
        }
        // extract project id, name  and version from it.
        String projectid = doc.getDocumentElement().getAttribute("id");
        if ((projectid == null) || projectid.isEmpty()) {
            log.error("Missing project id in description file!");
            return false;
        }
        String projectname;
        try {
            projectname = doc.getElementsByTagNameNS("bibiserv:de.unibi.techfak.bibiserv.cms", "name").item(0)
                    .getTextContent();
        } catch (NullPointerException e) {
            log.error("Missing project name in description file!");
            return false;
        }

        String projectversion = "unknown";
        try {
            projectversion = doc.getElementsByTagNameNS("bibiserv:de.unibi.techfak.bibiserv.cms", "version").item(0)
                    .getTextContent();
        } catch (NullPointerException e) {
            log.warn("Missing project version in description file!");

        }

        File projectdir = new File(
                config.getProperty("project.dir", config.getProperty("target.dir") + "/" + projectid));

        mkdirs(projectdir + "/src/main/java");
        mkdirs(projectdir + "/src/main/config");
        mkdirs(projectdir + "/src/main/libs");
        mkdirs(projectdir + "/src/main/pages");
        mkdirs(projectdir + "/src/main/resources");
        mkdirs(projectdir + "/src/main/downloads");

        // place runnableitem in config dir
        try {
            Files.copy(runnableitem.toPath(), new File(projectdir + "/src/main/config/runnableitem.xml").toPath(),
                    StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            log.error("{} occurred : {}", e.getClass().getSimpleName(), e.getLocalizedMessage());
            return false;
        }

        // copy files from SKELETON to projectdir and replace wildcard expression
        String[] SKELETON_INPUT_ARRAY = { "/pom.xml", "/src/main/config/log4j-tool.properties" };
        String SKELETON_INPUT = null;
        try {
            for (int c = 0; c < SKELETON_INPUT_ARRAY.length; c++) {
                SKELETON_INPUT = SKELETON_INPUT_ARRAY[c];

                InputStream in = Main.class.getResourceAsStream("/SKELETON" + SKELETON_INPUT);
                if (in == null) {
                    throw new IOException();
                }

                CopyAndReplace(in, new FileOutputStream(new File(projectdir, SKELETON_INPUT)), projectid,
                        projectname, projectversion);
            }

        } catch (IOException e) {
            log.error("Exception occurred while calling 'copyAndReplace(/SKELETON{},{}/{},{},{},{})'",
                    SKELETON_INPUT, projectdir, SKELETON_INPUT, projectid, projectname, projectversion);
            return false;
        }

        log.info("Empty project created! ");

        try {
            // _base 

            generate(CodeGen_Implementation.class, runnableitem, projectdir);
            log.info("Implementation generated!");

            generate(CodeGen_Implementation_Threadworker.class, runnableitem, projectdir);
            log.info("Implementation_Threadworker generated!");

            generate(CodeGen_Utilities.class, runnableitem, projectdir);
            log.info("Utilities generated!");

            generate(CodeGen_Common.class, runnableitem, projectdir, "/templates/common", RESOURCETYPE.isDirectory);

            log.info("Common generated!");

            // _REST
            generate(CodeGen_REST.class, runnableitem, projectdir);
            generate(CodeGen_REST_general.class, runnableitem, projectdir);

            log.info("REST generated!");

            //_HTML
            generate(CodeGen_WebSubmissionPage.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionPage_Input.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionPage_Param.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionPage_Result.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionPage_Visualization.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionPage_Formatchooser.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionPage_Resulthandler.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionBean_Function.class, runnableitem, projectdir);
            generate(CodeGen_Session_Reset.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionBean_Controller.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionBean_Input.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionBean_Param.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionBean_Result.class, runnableitem, projectdir);
            generate(CodeGen_WebSubmissionBean_Resulthandler.class, runnableitem, projectdir);
            generate(CodeGen_Webstart.class, runnableitem, projectdir); // can be removed ???
            generate(CodeGen_WebToolBeanContextConfig.class, runnableitem, projectdir);
            generate(CodeGen_WebManual.class, runnableitem, projectdir);
            generate(CodeGen_WebPage.class, runnableitem, projectdir, "/templates/pages", RESOURCETYPE.isDirectory);

            log.info("XHTML pages generated!");

            long time = (System.currentTimeMillis() - starttime) / 1000;

            log.info("Project \"{}\" (id:{}, version:{}) created at '{}' in {} seconds.", projectname, projectid,
                    projectversion, projectdir, time);

        } catch (CodeGenParserException e) {
            log.error("CodeGenParserException occurred :", e);
            return false;
        }
        return true;
    }

    /**
     * Returns "Choice" optiongroup for CodeGeneration.
     *
     * @return
     */
    private static OptionGroup getCMDLineOptionsGroups() {
        OptionGroup optionsgroup = new OptionGroup();
        optionsgroup.setRequired(true);
        Option generate = new Option("g", "generate", true, "Generate app from xml description.");
        generate.setArgName("runnableitem.xml");
        optionsgroup.addOption(new Option("V", "version", false, "version"))
                .addOption(new Option("h", "help", false, "help")).addOption(generate);

        return optionsgroup;
    }

    /**
     * Return Option for CodeGeneration
     *
     * @return
     */
    private static Options getCMDLineOptions() {
        Options cmdLineOptions = new Options();
        cmdLineOptions.addOption("t", "target", true,
                "Path to target dir. If not set java temporary directory will be used!");
        cmdLineOptions.addOption("p", "project", true,
                "Path to project dir. If unset ${target}+${toolid} will be used!");
        cmdLineOptions.addOption("v", "verbose", false, "more verbose output");
        return cmdLineOptions;
    }

    /**
     * Encapsulate File.mkdirs method in a static method
     *
     * @param fn
     * @return
     */
    private static boolean mkdirs(String fn) {
        File tmp = new File(fn);
        return tmp.mkdirs();
    }

    public static void generate(Class clazz, File runnableitem, File projectdir) throws CodeGenParserException {
        generate(clazz, runnableitem, projectdir, null, null, null);
    }

    public static void generate(Class clazz, File runnableitem, File projectdir, File file)
            throws CodeGenParserException {
        if (file.isFile()) {
            generate(clazz, runnableitem, projectdir, file, null, RESOURCETYPE.isFile);
        } else if (file.isDirectory()) {
            generate(clazz, runnableitem, projectdir, file, null, RESOURCETYPE.isDirectory);

        } else {
            throw new CodeGenParserException("File '" + file + "' is not either a file or directory!");
        }
    }

    public static void generate(Class clazz, File runnableitem, File projectdir, String path, RESOURCETYPE type)
            throws CodeGenParserException {
        generate(clazz, runnableitem, projectdir, null, path, type);
    }

    public static void generate(Class clazz, File runnableitem, File projectdir, File file, String path,
            RESOURCETYPE type) throws CodeGenParserException {
        try {
            Constructor constructor = clazz.getConstructor();
            Object object = constructor.newInstance();

            if (!(object instanceof CodeGen)) {
                throw new CodeGenParserException(
                        "'" + clazz.getSimpleName() + "' does not implement the CodeGen Interface!");
            }

            CodeGen codegen = (CodeGen) object;
            codegen.setRunnableFile(runnableitem);
            codegen.setResultDir(projectdir);

            // distinguish if path describes a file or resource
            if (file != null) { // template resource is a file
                switch (type) {
                case isFile:
                    codegen.setTemplateFile(file);
                    codegen.generate();
                    break;
                case isDirectory:
                    for (File f : file.listFiles()) {
                        codegen.setTemplateFile(f);
                        codegen.generate();
                    }
                    break;
                }
            } else if (path != null) { // template resource is a path
                switch (type) {
                case isFile:
                    codegen.setTemplateResource(path);
                    codegen.generate();
                    break;
                case isDirectory:
                    for (String entry : getClasspathEntriesByPath(path)) {
                        codegen.setTemplateResource(entry);
                        codegen.generate();
                    }
                }
            } else { // otherwise use default template defined by CodeGen Implementation
                codegen.generate();
            }

        } catch (NoSuchMethodException e) {
            throw new CodeGenParserException(
                    "Unknown implementation '" + clazz.getSimpleName() + "' of CodeGen interface.");
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new CodeGenParserException("Can not instantiate class '" + clazz.getSimpleName() + "'");
        } catch (IOException e) {
            throw new CodeGenParserException("tbd");
        }

    }

    /**
     * Get a list of resources from class path directory. Attention: path must
     * be a path !
     *
     * @param path
     * @return
     * @throws IOException
     * @throws de.unibi.techfak.bibiserv.util.codegen.CodeGenParserException
     */
    public static List<String> getClasspathEntriesByPath(String path) throws IOException, CodeGenParserException {

        try {
            List<String> tmp = new ArrayList<>();

            URL jarUrl = Main.class.getProtectionDomain().getCodeSource().getLocation();
            String jarPath = URLDecoder.decode(jarUrl.getFile(), "UTF-8");
            JarFile jarFile = new JarFile(jarPath);

            String prefix = path.startsWith("/") ? path.substring(1) : path;

            Enumeration<JarEntry> enu = jarFile.entries();
            while (enu.hasMoreElements()) {
                JarEntry je = enu.nextElement();
                if (!je.isDirectory()) {
                    String name = je.getName();
                    if (name.startsWith(prefix)) {
                        tmp.add("/" + name);
                    }
                }
            }
            return tmp;
        } catch (Exception e) {
            // maybe we start Main.class not from Jar.
        }

        InputStream is = Main.class.getResourceAsStream(path);

        if (is == null) {
            throw new CodeGenParserException("Path '" + path + "' not found in Classpath!");
        }

        StringBuilder sb = new StringBuilder();

        byte[] buffer = new byte[1024];
        while (is.read(buffer) != -1) {
            sb.append(new String(buffer, Charset.defaultCharset()));
        }
        is.close();
        return Arrays.asList(sb.toString().split("\n")) // Convert StringBuilder to individual lines
                .stream() // Stream the list
                .filter(line -> line.trim().length() > 0) // Filter out empty lines
                .map(line -> path + "/" + line) // add path for each entry
                .collect(Collectors.toList()); // Collect remaining lines into a List again

    }

    /**
     * Copy a text based file from in to out and replace TEMPLATE_ID with 'id'
     * and TEMPLATE_NAME with 'name'
     *
     *
     * @param in
     * @param out
     * @param id
     * @param name
     * @throws IOException
     */
    public static void CopyAndReplace(InputStream in, OutputStream out, String id, String name, String version)
            throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));

        String line;

        while ((line = br.readLine()) != null) {
            bw.append(line.replace("TEMPLATE_ID", id).replace("TEMPLATE_NAME", name).replace("TEMPLATE_VERSION",
                    version));
            bw.newLine();
        }
        br.close();
        bw.close();

    }
}