esa.mo.tools.stubgen.StubGenerator.java Source code

Java tutorial

Introduction

Here is the source code for esa.mo.tools.stubgen.StubGenerator.java

Source

/* ----------------------------------------------------------------------------
 * Copyright (C) 2013      European Space Agency
 *                         European Space Operations Centre
 *                         Darmstadt
 *                         Germany
 * ----------------------------------------------------------------------------
 * System                : CCSDS MO Service Stub Generator
 * ----------------------------------------------------------------------------
 * Licensed under the European Space Agency Public License, Version 2.0
 * You may not use this file except in compliance with the License.
 *
 * Except as expressly set forth in this License, the Software is provided to
 * You on an "as is" basis and without warranties of any kind, including without
 * limitation merchantability, fitness for a particular purpose, absence of
 * defects or errors, accuracy or non-infringement of intellectual property rights.
 * 
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 * ----------------------------------------------------------------------------
 */
package esa.mo.tools.stubgen;

import esa.mo.xsd.SpecificationType;
import esa.mo.xsd.util.XmlHelper;
import esa.mo.xsd.util.XmlHelper.XmlSpecification;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.SystemStreamLog;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import w3c.xsd.Schema;

/**
 * Generates stubs and skeletons for CCSDS MO Service specifications.
 *
 * @goal generate
 *
 * @phase generate-sources
 */
public class StubGenerator extends AbstractMojo {
    /**
     * The directory for XML files
     *
     * @parameter default-value="${basedir}/src/main/xml"
     * @required
     */
    protected File xmlDirectory;
    /**
     * The directory for XML reference files
     *
     * @parameter default-value="${basedir}/src/main/xml-ref"
     * @required
     */
    protected File xmlRefDirectory;
    /**
     * The directory for XSD type reference files
     *
     * @parameter default-value="${basedir}/src/main/xsd-ref"
     * @required
     */
    protected File xsdRefDirectory;
    /**
     * The working directory to create the generated java source files.
     *
     * @parameter default-value="${project.build.directory}/generated-sources/stub"
     * @required
     */
    protected File outputDirectory;
    /**
     * The target language to create.
     *
     * @parameter
     */
    protected String[] targetLanguages;
    /**
     * Generate structures code?
     *
     * @parameter default-value="true"
     */
    protected boolean generateStructures;
    /**
     * Generate COM code?
     *
     * @parameter default-value="true"
     */
    protected boolean generateCOM;
    /**
     * Force generation
     *
     * @parameter default-value="false"
     */
    protected boolean forceGeneration;
    /**
     * Extra generator specific properties, held in name/value pairs
     *
     * @parameter
     */
    protected Map<String, String> extraProperties;
    /**
     * Package bindings, held in AREA/package pairs
     * For JAXB bindings, held in URI/package pairs
     *
     * @parameter
     */
    protected Map<String, String> packageBindings;
    private static final Map<String, Generator> GENERATOR_MAP = new TreeMap<String, Generator>();
    private static boolean generatorsLoaded = false;

    /**
     * The main entry point when running from the command line.
     *
     * @param args the command line arguments, run with -h option to see help.
     */
    public static void main(final String[] args) {
        final StubGenerator gen = new StubGenerator();
        // default a few values
        gen.generateStructures = true;
        gen.generateCOM = true;
        gen.extraProperties = new TreeMap<String, String>();
        gen.packageBindings = new TreeMap<String, String>();

        boolean printHelp = false;

        if (0 < args.length) {
            for (int i = 0; i < args.length; i++) {
                final String arg = args[i];

                if ("-h".equalsIgnoreCase(arg)) {
                    // print out help and exit
                    printHelp = true;
                    break;
                } else if ("-?".equalsIgnoreCase(arg)) {
                    // print out help and exit
                    printHelp = true;
                    break;
                } else if ("-l".equalsIgnoreCase(arg)) {
                    // print out list of supported generators and exit
                    List<Map.Entry<String, String>> generators = getSupportedLanguages(new SystemStreamLog());

                    System.out.println("The following language generators are supported:");

                    for (Map.Entry<String, String> g : generators) {
                        System.out.println(String.format("%8s", g.getKey()) + "  :  " + g.getValue());
                    }

                    return;
                } else if ("-x".equalsIgnoreCase(arg)) {
                    // XML directory is held in next argument
                    i++;
                    gen.xmlDirectory = new File(args[i]);
                } else if ("-r".equals(arg)) {
                    // XML reference directory is held in next argument
                    i++;
                    gen.xmlRefDirectory = new File(args[i]);
                } else if ("-R".equals(arg)) {
                    // XSD reference directory is held in next argument
                    i++;
                    gen.xsdRefDirectory = new File(args[i]);
                } else if ("-o".equalsIgnoreCase(arg)) {
                    // output directory is held in next argument
                    i++;
                    gen.outputDirectory = new File(args[i]);
                } else if ("-t".equalsIgnoreCase(arg)) {
                    // target languages is held in next argument as a comma separated list
                    i++;
                    final String targets = args[i];

                    gen.targetLanguages = targets.split(",");
                }
            }
        } else {
            printHelp = true;
        }

        if (printHelp) {
            printHelp(System.out);
        } else {
            try {
                gen.execute();
            } catch (MojoExecutionException ex) {
                System.err.println("ERROR: Exception thrown : " + ex.getMessage());
            }
        }
    }

    /**
     * Returns a list of the currently supported generators in a list of name/description pairs. The name value is the one
     * that should be passed to the generator via the setTargetLanguage method.
     *
     * @param logger The logger for the language generators to use when executing.
     * @return The list of available generators.
     */
    public static List<Map.Entry<String, String>> getSupportedLanguages(
            final org.apache.maven.plugin.logging.Log logger) {
        loadGenerators(logger);

        List<Map.Entry<String, String>> rv = new ArrayList<Map.Entry<String, String>>(GENERATOR_MAP.size());

        for (Map.Entry<String, Generator> entry : GENERATOR_MAP.entrySet()) {
            final Generator g = entry.getValue();

            rv.add(new AbstractMap.SimpleEntry<String, String>(g.getShortName(), g.getDescription()));
        }

        return rv;
    }

    /**
     * The main entry point when running the stub generator externally from Maven.
     *
     * @param xmlDirectory The directory for XML files
     * @param xmlRefDirectory The directory for XML reference files
     * @param xsdRefDirectory The directory for XSD type reference files
     * @param outputDirectory The working directory to create the generated java source files
     * @return the new stub generator instance
     */
    public static StubGenerator createStubGenerator(final File xmlDirectory, final File xmlRefDirectory,
            final File xsdRefDirectory, final File outputDirectory) {
        final StubGenerator gen = new StubGenerator();

        gen.setXmlDirectory(xmlDirectory);
        gen.setXmlRefDirectory(xmlRefDirectory);
        gen.setXsdRefDirectory(xsdRefDirectory);
        gen.setOutputDirectory(outputDirectory);

        return gen;
    }

    /**
     * Sets the directory for XML files
     *
     * @param xmlDirectory The directory for XML files
     */
    public void setXmlDirectory(File xmlDirectory) {
        this.xmlDirectory = xmlDirectory;
    }

    /**
     * Sets the directory for XML reference files
     *
     * @param xmlRefDirectory The directory for XML reference files
     */
    public void setXmlRefDirectory(File xmlRefDirectory) {
        this.xmlRefDirectory = xmlRefDirectory;
    }

    /**
     * Sets the directory for XSD type reference files
     *
     * @param xsdRefDirectory The directory for XSD type reference files
     */
    public void setXsdRefDirectory(File xsdRefDirectory) {
        this.xsdRefDirectory = xsdRefDirectory;
    }

    /**
     * Sets the working directory to create the generated java source files.
     *
     * @param outputDirectory The working directory to create the generated java source files.
     */
    public void setOutputDirectory(File outputDirectory) {
        this.outputDirectory = outputDirectory;
    }

    /**
     * Sets the target languages to create.
     *
     * @param targetLanguages The target languages to create.
     */
    public void setTargetLanguages(String[] targetLanguages) {
        this.targetLanguages = targetLanguages;
    }

    /**
     * Generate structures code.
     *
     * @param generateStructures If True, generate structure code.
     */
    public void setGenerateStructures(boolean generateStructures) {
        this.generateStructures = generateStructures;
    }

    /**
     * Generate COM code.
     *
     * @param generateCOM If True, generate COM specific code.
     */
    public void setGenerateCOM(boolean generateCOM) {
        this.generateCOM = generateCOM;
    }

    /**
     * Sets the extra generator specific properties, held in name/value pairs
     *
     * @param extraProperties Extra generator specific properties, held in name/value pairs
     */
    public void setExtraProperties(Map<String, String> extraProperties) {
        this.extraProperties = extraProperties;
    }

    /**
     * Sets the package bindings, held in AREA/package pairs or URI/package pairs for JAXB
     *
     * @param packageBindings
     */
    public void setPackageBindings(Map<String, String> packageBindings) {
        this.packageBindings = packageBindings;
    }

    @Override
    public void execute() throws MojoExecutionException {
        loadGenerators(getLog());

        if (null == extraProperties) {
            extraProperties = new TreeMap<String, String>();
        }

        // if the directoy containing the xml specifications exists
        if (xmlDirectory.exists()) {
            // load in any reference specifications
            try {
                final List<Map.Entry<SpecificationType, XmlSpecification>> refSpecs = XmlHelper
                        .loadSpecifications(xmlRefDirectory);

                // load in any reference XML schema
                final List<Map.Entry<Schema, XmlSpecification>> refXsd = loadXsdSpecifications(xsdRefDirectory);

                // load in the specifications
                final List<Map.Entry<SpecificationType, XmlSpecification>> specs = XmlHelper
                        .loadSpecifications(xmlDirectory);

                // work out the latest timestamp of the input files
                long inputTimestamp = getLatestTimestamp(0, refSpecs);
                inputTimestamp = getLatestSchemaTimestamp(inputTimestamp, refXsd);
                inputTimestamp = getLatestTimestamp(inputTimestamp, specs);

                // run the specifications through each generator
                // first process the list of languages to generate
                if ((null != targetLanguages) && (0 < targetLanguages.length)) {

                    if (forceGeneration || (outputDirectory.lastModified() < inputTimestamp)) {
                        if (forceGeneration) {
                            getLog().info("Generation being forced");
                        }
                        for (String targetLanguage : targetLanguages) {
                            final Generator gen = GENERATOR_MAP.get(targetLanguage.toLowerCase());
                            if (null != gen) {
                                processWithGenerator(gen, refSpecs, refXsd, specs);
                            } else {
                                getLog().warn("Could not find generator for language: " + targetLanguage);
                            }
                        }

                        outputDirectory.setLastModified(System.currentTimeMillis());
                    } else {
                        getLog().info("No change in input files detected, generation skipped");
                    }
                } else {
                    getLog().error("No generators selected - could not process files");
                }
            } catch (IOException ex) {
                throw new MojoExecutionException("Exception thrown during the processing of XML file: ", ex);
            } catch (JAXBException ex) {
                throw new MojoExecutionException("Exception thrown during the processing of XML file: ", ex);
            }
        } else {
            getLog().error("XML directory is not valid");
        }
    }

    private static void printHelp(java.io.PrintStream out) {
        out.println("Usage: stub-generator [-options]");
        out.println("");
        out.println("where options include:");
        out.println("    -x <directory containing the XML service specification>");
        out.println("                  Specify the location of the XML specifications to process");
        out.println("    -r <directory containing the reference XML service specification>");
        out.println("                  Specify the location of the XML specifications to process");
        out.println("                  that are referenced but do not require any generation");
        out.println("    -o <output directory>");
        out.println("                  Specify the location of the output directory");
        out.println("    -t <target languages to generate>");
        out.println("                  A , separated list of language generators");
        out.println("    -l");
        out.println("                  Lists supported language generators");
        out.println("    -? -h         Print this help message");
    }

    private static List<Map.Entry<Schema, XmlSpecification>> loadXsdSpecifications(final File directory)
            throws IOException, JAXBException {
        final List<Map.Entry<Schema, XmlSpecification>> specList = new LinkedList<Map.Entry<Schema, XmlSpecification>>();

        if (directory.exists()) {
            final File xmlFiles[] = directory.listFiles();

            for (File file : xmlFiles) {
                if (file.isFile()) {
                    specList.add(loadXsdSpecification(file));
                }
            }
        }

        return specList;
    }

    private static AbstractMap.SimpleEntry<Schema, XmlSpecification> loadXsdSpecification(final File is)
            throws IOException, JAXBException {
        final JAXBContext jc = JAXBContext.newInstance("w3c.xsd");
        final Unmarshaller unmarshaller = jc.createUnmarshaller();
        return new AbstractMap.SimpleEntry<Schema, XmlSpecification>((Schema) unmarshaller.unmarshal(is),
                new XmlSpecification(is, null));
    }

    private static void loadGenerators(final org.apache.maven.plugin.logging.Log logger) {
        if (!generatorsLoaded) {
            generatorsLoaded = true;

            final Reflections reflections = new Reflections(new ConfigurationBuilder()
                    .setUrls(ClasspathHelper.forClassLoader()).setScanners(new SubTypesScanner()));

            final Set<Class<? extends Generator>> classes = reflections.getSubTypesOf(Generator.class);

            for (Class<? extends Generator> cls : classes) {
                final int mods = cls.getModifiers();
                if (!Modifier.isAbstract(mods)) {
                    try {
                        final Generator g = (Generator) cls
                                .getConstructor(new Class[] { org.apache.maven.plugin.logging.Log.class })
                                .newInstance(new Object[] { logger });

                        GENERATOR_MAP.put(g.getShortName().toLowerCase(), g);
                    } catch (Exception ex) {
                        logger.warn("Could not construct generator : " + cls.getName());
                    }
                }
            }
        }
    }

    private void processWithGenerator(final Generator generator,
            final List<Map.Entry<SpecificationType, XmlSpecification>> refSpecs,
            final List<Map.Entry<Schema, XmlSpecification>> refXsd,
            final List<Map.Entry<SpecificationType, XmlSpecification>> specs) throws MojoExecutionException {
        try {
            generator.reset();
            generator.init(outputDirectory.getPath(), generateStructures, generateCOM, packageBindings,
                    extraProperties);
            generator.postinit(outputDirectory.getPath(), generateStructures, generateCOM, packageBindings,
                    extraProperties);
        } catch (IOException ex) {
            throw new MojoExecutionException("Exception thrown during the opening of the generator", ex);
        }

        // pre process the reference specifications
        for (Map.Entry<SpecificationType, XmlSpecification> spec : refSpecs) {
            try {
                generator.preProcess(spec.getKey());
            } catch (Exception ex) {
                throw new MojoExecutionException(
                        "Exception thrown during the pre-processing of reference XML file: "
                                + spec.getValue().file.getPath(),
                        ex);
            }
        }

        // pre process the reference XSD specifications
        for (Map.Entry<Schema, XmlSpecification> spec : refXsd) {
            try {
                generator.preProcess(spec.getKey());
            } catch (Exception ex) {
                throw new MojoExecutionException(
                        "Exception thrown during the pre-processing of reference XSD file: "
                                + spec.getValue().file.getPath(),
                        ex);
            }
        }

        // pre process the specifications
        for (Map.Entry<SpecificationType, XmlSpecification> spec : specs) {
            try {
                generator.preProcess(spec.getKey());
            } catch (Exception ex) {
                throw new MojoExecutionException(
                        "Exception thrown during the pre-processing of XML file: " + spec.getValue().file.getPath(),
                        ex);
            }
        }

        // now generator from each specification
        for (Map.Entry<SpecificationType, XmlSpecification> spec : specs) {
            try {
                getLog().info("Generating " + generator.getShortName());
                generator.compile(outputDirectory.getPath(), spec.getKey(), spec.getValue().rootElement);
            } catch (Exception ex) {
                throw new MojoExecutionException(
                        "Exception thrown during the processing of XML file: " + spec.getValue().file.getPath(),
                        ex);
            }
        }

        try {
            generator.close(outputDirectory.getPath());
        } catch (IOException ex) {
            throw new MojoExecutionException("Exception thrown during the closing of the generator", ex);
        }
    }

    private long getLatestTimestamp(long inputTimestamp,
            final List<Map.Entry<SpecificationType, XmlSpecification>> specs) {
        for (Map.Entry<SpecificationType, XmlSpecification> spec : specs) {
            long fileTimestamp = spec.getValue().file.lastModified();
            if (fileTimestamp > inputTimestamp) {
                inputTimestamp = fileTimestamp;
            }
        }

        return inputTimestamp;
    }

    private long getLatestSchemaTimestamp(long inputTimestamp,
            final List<Map.Entry<Schema, XmlSpecification>> specs) {
        for (Map.Entry<Schema, XmlSpecification> spec : specs) {
            long fileTimestamp = spec.getValue().file.lastModified();
            if (fileTimestamp > inputTimestamp) {
                inputTimestamp = fileTimestamp;
            }
        }

        return inputTimestamp;
    }
}