de.monticore.mojo.GenerateMojo.java Source code

Java tutorial

Introduction

Here is the source code for de.monticore.mojo.GenerateMojo.java

Source

/*
 * ******************************************************************************
 * MontiCore Language Workbench
 * Copyright (c) 2015, MontiCore, All rights reserved.
 *
 * This project 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.0 of the License, or (at your option) any later version.
 * This library 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 project. If not, see <http://www.gnu.org/licenses/>.
 * ******************************************************************************
 */
package de.monticore.mojo;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import de.monticore.MontiCoreConfiguration;
import de.monticore.MontiCoreScript;
import de.se_rwth.commons.configuration.Configuration;
import de.se_rwth.commons.configuration.ConfigurationPropertiesMapContributor;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.*;

import static com.google.common.base.MoreObjects.firstNonNull;
import static de.monticore.MontiCoreConfiguration.Options.*;

/**
 * Invokes {@link MontiCore} using the given configuration parameters.
 * 
 * @author (last commit) $Author$
 * @version $Revision$, $Date$
 */
@Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE)
public final class GenerateMojo extends AbstractMojo {

    /**
     * The current Maven project.
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject mavenProject;

    /**
     * @return mavenProject
     */
    protected MavenProject getMavenProject() {
        return this.mavenProject;
    }

    /**
     * The set of grammars/directories to be passed to MontiCore. This is the
     * "grammar" option of MontiCore and defaults to "src/main/grammars".
     */
    @Parameter
    private List<File> grammars;

    /**
     * @return the value of the "grammars" configuration parameter.
     */
    protected Set<File> getGrammars() {
        ImmutableSet.Builder<File> grammarFilesBuilder = ImmutableSet.builder();

        if (this.grammars != null) {
            this.grammars.forEach(g -> grammarFilesBuilder.add(fromBasePath(g)));
        }

        ImmutableSet<File> grammarFiles = grammarFilesBuilder.build();
        return grammarFiles.isEmpty() ? ImmutableSet.of(getDefaultGrammarDirectory()) : grammarFiles;
    }

    /**
     * @return the default directory for MontiCore grammars.
     */
    public File getDefaultGrammarDirectory() {
        return fromBasePath("src/main/grammars");
    }

    /**
     * The output directory for source code generated by MontiCore. This is the
     * "outputDirectory" option of MontiCore.
     */
    @Parameter(defaultValue = "${project.build.directory}/generated-sources/${plugin.goalPrefix}/sourcecode")
    private File outputDirectory;

    /**
     * @return the value of the "outputDirectory" configuration parameter.
     */
    protected File getOutputDirectory() {
        return fromBasePath(this.outputDirectory);
    }

    /**
     * The output directory for symbol tables generated by MontiCore. This is the
     * "symbolTableDirectory" option of MontiCore.
     */
    @Parameter(defaultValue = "${project.build.directory}/generated-sources/${plugin.goalPrefix}/symboltable")
    private File symbolTableDirectory;

    /**
     * @return the value of the "symbolTableDirectory" configuration parameter.
     */
    protected File getSymbolTableDirectory() {
        return fromBasePath(this.symbolTableDirectory);
    }

    /**
     * The set of directories containing handwritten code to be integrated into
     * generated code. This is the "handcodedPath" option of MontiCore and
     * defaults to "src/main/java".
     */
    @Parameter
    private List<File> handcodedPaths;

    /**
     * @return the value of the "targetPaths" configuration parameter.
     */
    protected Set<File> getHandcodedPaths() {
        ImmutableSet.Builder<File> handcodedPathsBuilder = ImmutableSet.builder();

        if (this.handcodedPaths != null) {
            this.handcodedPaths.forEach(t -> handcodedPathsBuilder.add(fromBasePath(t)));
        }

        ImmutableSet<File> handcodedPathFiles = handcodedPathsBuilder.build();
        return handcodedPathFiles.isEmpty() ? ImmutableSet.of(getDefaultHandcodedPath()) : handcodedPathFiles;
    }

    /**
     * @return the default path for handwritten code.
     */
    public File getDefaultHandcodedPath() {
        return fromBasePath("src/main/java");
    }

    /**
     * The set of directories containing templates to be integrated into the code
     * generation process. This is the "templatePath" option of MontiCore and
     * defaults to "src/main/resources".
     */
    @Parameter
    private List<File> templatePaths;

    /**
     * @return the value of the "templatePaths" configuration parameter.
     */
    protected Set<File> getTemplatePaths() {
        ImmutableSet.Builder<File> templatePathsBuilder = ImmutableSet.builder();

        if (this.templatePaths != null) {
            this.templatePaths.forEach(t -> templatePathsBuilder.add(fromBasePath(t)));
        }

        ImmutableSet<File> templatePathFiles = templatePathsBuilder.build();
        return templatePathFiles.isEmpty()
                ? getDefaultTemplatePath().map(ImmutableSet::of).orElse(ImmutableSet.of())
                : templatePathFiles;
    }

    /**
     * @return the default path for templates. Returns empty if the directory doesn't exist.
     */
    public Optional<File> getDefaultTemplatePath() {
        return Optional.of(fromBasePath("src/main/resources")).filter(File::exists);
    }

    /**
     * The set of models/directories to be passed to MontiCore as part of the
     * model path. By default all grammar directories as specified in the
     * "grammars" parameter as well as any matching project dependency artifacts
     * as specified by the parameters "modelPathDependencies", "scopes", and
     * "classifiers" are added to the modelpath. This is the "modelPath" option of
     * MontiCore.
     */
    @Parameter
    private List<File> modelPaths;

    /**
     * @return the value of the "modelPaths" configuration parameter.
     */
    protected Set<File> getModelPaths() {
        ImmutableSet.Builder<File> modelPaths = ImmutableSet.builder();

        // 1st: we take all modelpaths directly from the configuration
        if (this.modelPaths != null) {
            this.modelPaths.forEach(mP -> modelPaths.add(fromBasePath(mP)));
        }

        // 2nd: if specified we add any grammar directories (default)
        if (addGrammarDirectoriesToModelPath()) {
            for (File grammarFile : getGrammars()) {
                if (grammarFile.isDirectory()) {
                    modelPaths.add(grammarFile);
                }
            }
        }

        // 3rd: if specified we add the project source directories (non default)
        if (addSourceDirectoriesToModelPath()) {
            getMavenProject().getCompileSourceRoots().forEach(csR -> modelPaths.add(new File(csR)));
        }

        // 4th: if specified we add the entire project "compile" classpath (non
        // default)
        if (addClassPathToModelPath()) {
            try {
                getMavenProject().getCompileClasspathElements().forEach(cpDir -> modelPaths.add(new File(cpDir)));
            } catch (DependencyResolutionRequiredException e) {
                Throwables.propagate(e);
            }
        }

        for (Artifact artifact : getMavenProject().getArtifacts()) {
            // 5th: we add any explicitly specified project dependencies
            if (getModelPathDependencies()
                    .contains(getArtifactDescription(artifact.getGroupId(), artifact.getArtifactId()))) {
                modelPaths.add(artifact.getFile());
            }
            // 6th: we add any project dependencies matching the classifier/scope
            // filter (default: all "grammars", "grammar", and "symbols" dependencies
            // of any scope)
            // FIXME (minor): this does not work in reactor builds which do not invoke
            // the install phase (e.g., mvn dependency:analyze)
            else if (getClassifiers().contains(artifact.getClassifier())
                    && (getScopes().isEmpty() || getScopes().contains(artifact.getScope()))) {
                modelPaths.add(artifact.getFile());
            }
        }

        return modelPaths.build();
    }

    /**
     * A list of dependencies to be added to the model path. The dependencies must
     * be stated as "groupID:artifactID" and must be declared in the project's
     * dependencies.
     */
    @Parameter
    private List<String> modelPathDependencies;

    /**
     * @return modelDependencies
     */
    protected List<String> getModelPathDependencies() {
        return firstNonNull(this.modelPathDependencies, ImmutableList.<String>of());
    }

    /**
     * Indicates whether any grammar directories should be added to the modelpath
     * (true by default).
     */
    @Parameter(defaultValue = "true")
    private boolean addGrammarDirectoriesToModelPath = true;

    /**
     * @return the value of the "addGrammarDirectoriesToModelPath" configuration
     * parameter.
     */
    protected boolean addGrammarDirectoriesToModelPath() {
        return this.addGrammarDirectoriesToModelPath;
    }

    /**
     * Indicates whether the project source directories should be added to the
     * modelpath (false by default).
     */
    @Parameter(defaultValue = "false")
    private boolean addSourceDirectoriesToModelPath = false;

    /**
     * @return the value of the "addSourceDirectoriesToModelPath" configuration
     * parameter.
     */
    protected boolean addSourceDirectoriesToModelPath() {
        return this.addSourceDirectoriesToModelPath;
    }

    /**
     * Indicates whether the (compile) classpath (entries) of the Maven project
     * should be added to the modelpath (false by default).
     */
    @Parameter(defaultValue = "false")
    private boolean addClassPathToModelPath = false;

    /**
     * @return the value of the "addClassPathToModelPath" configuration parameter.
     */
    protected boolean addClassPathToModelPath() {
        return this.addClassPathToModelPath;
    }

    /**
     * Indicates whether the output directory should be added to the modelpath
     * (true by default).
     */
    @Parameter(defaultValue = "true")
    private boolean addOutputDirectoryToModelPath = false;

    /**
     * @return the value of the "addOutputDirectoryToModelPath" configuration
     * parameter.
     */
    protected boolean addOutputDirectoryToModelPath() {
        return this.addOutputDirectoryToModelPath;
    }

    /**
     * The scopes to be considered for dependencies to add to the modelpath (any
     * scope by default).
     */
    @Parameter
    private List<String> scopes = ImmutableList.of();

    /**
     * @return the value of the "scopes" configuration parameter.
     */
    protected List<String> getScopes() {
        return this.scopes;
    }

    /**
     * The classifiers to be considered for dependencies to add to the modelpath
     * (defaults to "grammars", "grammar", "symbols").
     */
    @Parameter(defaultValue = "grammars, grammar, symbols")
    private List<String> classifiers = ImmutableList.of();

    /**
     * @return the value of the "classifiers" configuration parameter.
     */
    protected List<String> getClassifiers() {
        return firstNonNull(this.classifiers,
                ImmutableList.<String>builder().add("grammars").add("grammar").add("symbols").build());
    }

    /**
     * An optional alternative Groovy script to execute. This may either be an
     * absolute or relative path in the current project classpath.
     */
    @Parameter
    private String script = null;

    /**
     * @return the value of the "script" configuration parameter.
     */
    protected String getScript() {
        return this.script;
    }

    /**
     * A set of custom key value arguments to be passed to the Groovy script. The
     * value of an argument may be a space separated list of values. This has no
     * impact on the default script. It is solely passed to custom scripts
     * provided by the "script" parameter.
     */
    @Parameter
    private Map<String, String> arguments = Collections.emptyMap();

    /**
     * @return the value of the "arguments" configuration parameter.
     */
    protected Map<String, String> getArguments() {
        return this.arguments;
    }

    /**
     * Switch to control the "force" configuration parameter to force generation
     * bypassing the incremental check (defaults to false).
     */
    @Parameter(defaultValue = "false")
    private boolean force = false;

    /**
     * @return the value of the "force" configuration parameter.
     */
    protected boolean getForce() {
        return this.force;
    }

    /**
     * Switch to skip the plugin execution. Defaults to false.
     */
    @Parameter(defaultValue = "false")
    private boolean skip = false;

    /**
     * @return whether this plugin should be skipped.
     */
    protected boolean skip() {
        return this.skip;
    }

    /**
     * @see org.apache.maven.plugin.Mojo#execute()
     */
    @Override
    public final void execute() throws MojoExecutionException, MojoFailureException {
        if (skip()) {
            getLog().info("Plugin execution skipped per configuration.");
            return;
        }

        // create all necessary directories
        getOutputDirectory().mkdirs();
        getSymbolTableDirectory().mkdirs();

        // setup configuration
        Map<String, Iterable<String>> parameters = new HashMap<>();
        parameters.put(GRAMMARS.toString(), toStringSet(getGrammars()));
        parameters.put(MODELPATH.toString(), toStringSet(getModelPaths()));
        parameters.put(HANDCODEDPATH.toString(), toStringSet(getHandcodedPaths()));
        parameters.put(TEMPLATEPATH.toString(), toStringSet(getTemplatePaths()));
        parameters.put(OUT.toString(), Arrays.asList(getOutputDirectory().getAbsolutePath()));
        parameters.put(OUTTOMODELPATH.toString(), Arrays.asList(Boolean.toString(addOutputDirectoryToModelPath())));

        if (getForce()) {
            parameters.put(FORCE.toString(), new ArrayList<>());
        }

        Configuration configuration = ConfigurationPropertiesMapContributor.fromSplitMap(parameters);

        // this temporary wrap in a MontiCoreConfiguration is only here to make the
        // check for .mc4 files easier
        MontiCoreConfiguration mcConf = MontiCoreConfiguration.withConfiguration(configuration);
        // we check if there are any ".mc4" files in the input parameter
        Iterator<Path> grammarPaths = mcConf.getGrammars().getResolvedPaths();
        if (!grammarPaths.hasNext()) {
            getLog().warn(
                    "0xA1035 Skipping MontiCore as there are no \".mc4\" files in the input parameter. Check your \"grammars\" parameter configuration.");
            return;
        }

        // run the default script
        if (getScript() == null) {
            // run script
            new MontiCoreScript().run(configuration);
        }
        // or a provided custom script
        else {

            try {
                String script = null;
                ClassLoader l = getClass().getClassLoader();
                URL r = l.getResource(getScript());
                if (r == null) {
                    r = fromBasePath(getScript()).toURI().toURL();
                }
                Scanner scr = new Scanner(r.openStream());
                script = scr.useDelimiter("\\A").next();
                scr.close();

                // adds any custom arguments additionally to the default ones
                getArguments().forEach((key, value) -> parameters.put(key, Arrays.asList(value.split(" "))));
                // this is a Configuration which has the generic arguments in addition
                configuration = ConfigurationPropertiesMapContributor.fromSplitMap(parameters);

                // if (getLog().isDebugEnabled()) {
                getLog().debug("Configuration content:");
                configuration.getAllValues()
                        .forEach((key, value) -> getLog().debug("Key: " + key + " -> " + value.toString()));
                // }

                new MontiCoreScript().run(script, configuration);
            } catch (IOException e) {
                throw new MojoFailureException("0xA4089 Failed to load the specified script.", e);
            }
        }

        // if everything went well we also need to add the generated output to the
        // Maven project compile roots
        getMavenProject().addCompileSourceRoot(getOutputDirectory().getPath());
        getLog().debug("Adding compile source root: " + getOutputDirectory().getPath());
    }

    /**
     * @return a new {@link File} that is absolutized based on the current Maven
     * project's base directory.
     */
    protected File fromBasePath(File file) {
        return fromBasePath(file.getPath());
    }

    /**
     * @return a new {@link File} that is absolutized based on the current Maven
     * project's base directory.
     */
    protected File fromBasePath(String filePath) {
        File file = new File(filePath);
        return !file.isAbsolute() ? new File(getMavenProject().getBasedir(), filePath) : file;
    }

    /**
     * @param groupId
     * @param artifactId
     * @return groupId + ":" + artifactId
     */
    protected String getArtifactDescription(String groupId, String artifactId) {
        return groupId.trim() + ":" + artifactId.trim();
    }

    /**
     * @param files
     * @return a set of all the file names of the given collection of files
     */
    protected Set<String> toStringSet(Collection<File> files) {
        return ImmutableSet.<String>builder().addAll(Iterables.transform(files, file -> file.getPath())).build();
    }
}