All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.javacc.AbstractJavaCCMojo Maven / Gradle / Ivy

There is a newer version: 4.1.5
Show newest version
package org.codehaus.mojo.javacc;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.SelectorUtils;
import org.codehaus.plexus.util.StringUtils;

/**
 * Provides common services for all mojos that compile JavaCC grammar files.
 *
 * @author [email protected]
 * @author jesse 
 * @version $Id: AbstractJavaCCMojo.java 10774 2009-09-26 11:40:48Z bentmann $
 */
public abstract class AbstractJavaCCMojo extends AbstractMojo
{

  /**
   * The current Maven project.
   *
   * @parameter default-value="${project}"
   * @readonly
   * @required
   */
  private MavenProject project;

  /**
   * The set of compile source roots whose contents are not generated as part of
   * the build, i.e. those that usually reside somewhere below "${basedir}/src"
   * in the project structure. Files in these source roots are owned by the user
   * and must not be overwritten with generated files.
   */
  private Collection  nonGeneratedSourceRoots;

  /**
   * The Java version for which to generate source code. Default value is
   * 1.5 for plugin version 2.6+ and 1.4 in older
   * versions.
   *
   * @parameter property=jdkVersion
   * @since 2.4
   */
  private String jdkVersion;

  /**
   * The number of tokens to look ahead before making a decision at a choice
   * point during parsing. The default value is 1.
   *
   * @parameter property=lookAhead
   */
  private Integer lookAhead;

  /**
   * This is the number of tokens considered in checking choices of the form "A
   * | B | ..." for ambiguity. Default value is 2.
   *
   * @parameter property=choiceAmbiguityCheck
   */
  private Integer choiceAmbiguityCheck;

  /**
   * This is the number of tokens considered in checking all other kinds of
   * choices (i.e., of the forms "(A)*", "(A)+", and "(A)?") for ambiguity.
   * Default value is 1.
   *
   * @parameter property=otherAmbiguityCheck
   */
  private Integer otherAmbiguityCheck;

  /**
   * If true, all methods and class variables are specified as
   * static in the generated parser and token manager. This allows only one
   * parser object to be present, but it improves the performance of the parser.
   * Default value is true.
   *
   * @parameter property=isStatic
   */
  private Boolean isStatic;

  /**
   * This option is used to obtain debugging information from the generated
   * parser. Setting this option to true causes the parser to
   * generate a trace of its actions. Default value is false.
   *
   * @parameter property=debugParser
   */
  private Boolean debugParser;

  /**
   * This is a boolean option whose default value is false. Setting
   * this option to true causes the parser to generate all the
   * tracing information it does when the option debugParser is
   * true, and in addition, also causes it to generated a trace of
   * actions performed during lookahead operation.
   *
   * @parameter property=debugLookAhead
   */
  private Boolean debugLookAhead;

  /**
   * This option is used to obtain debugging information from the generated
   * token manager. Default value is false.
   *
   * @parameter property=debugTokenManager
   */
  private Boolean debugTokenManager;

  /**
   * Setting it to false causes errors due to parse errors to be
   * reported in somewhat less detail. Default value is true.
   *
   * @parameter property=errorReporting
   */
  private Boolean errorReporting;

  /**
   * When set to true, the generated parser uses an input stream
   * object that processes Java Unicode escapes (\u
   * xxxx) before sending characters to the token manager. Default value
   * is false.
   *
   * @parameter property=javaUnicodeEscape
   */
  private Boolean javaUnicodeEscape;

  /**
   * When set to true, the generated parser uses uses an input
   * stream object that reads Unicode files. By default, ASCII files are
   * assumed. Default value is false.
   *
   * @parameter property=unicodeInput
   */
  private Boolean unicodeInput;

  /**
   * Setting this option to true causes the generated token manager
   * to ignore case in the token specifications and the input files. Default
   * value is false.
   *
   * @parameter property=ignoreCase
   */
  private Boolean ignoreCase;

  /**
   * When set to true, every call to the token manager's method
   * getNextToken() (see the description of the
   * Java Compiler
   * Compiler API) will cause a call to a user-defined method
   * CommonTokenAction() after the token has been scanned in by the
   * token manager. Default value is false.
   *
   * @parameter property=commonTokenAction
   */
  private Boolean commonTokenAction;

  /**
   * The default action is to generate a token manager that works on the
   * specified grammar tokens. If this option is set to true, then
   * the parser is generated to accept tokens from any token manager of type
   * TokenManager - this interface is generated into the generated
   * parser directory. Default value is false.
   *
   * @parameter property=userTokenManager
   */
  private Boolean userTokenManager;

  /**
   * This flag controls whether the token manager will read characters from a
   * character stream reader as defined by the options
   * javaUnicodeEscape and unicodeInput or whether the
   * token manager reads from a user-supplied implementation of
   * CharStream. Default value is false.
   *
   * @parameter property=userCharStream
   */
  private Boolean userCharStream;

  /**
   * A flag that controls whether the parser file (*Parser.java)
   * should be generated or not. If set to false, only the token
   * manager is generated. Default value is true.
   *
   * @parameter property=buildParser
   */
  private Boolean buildParser;

  /**
   * A flag that controls whether the token manager file (
   * *TokenManager.java) should be generated or not. Setting this
   * to false can speed up the generation process if only the
   * parser part of the grammar changed. Default value is true.
   *
   * @parameter property=buildTokenManager
   */
  private Boolean buildTokenManager;

  /**
   * When set to true, the generated token manager will include a
   * field called parser that references the instantiating parser
   * instance. Default value is false.
   *
   * @parameter property=tokenManagerUsesParser
   */
  private Boolean tokenManagerUsesParser;

  /**
   * The name of the base class for the generated Token class.
   * Default value is java.lang.Object.
   *
   * @parameter property=tokenExtends
   * @since 2.5
   */
  private String tokenExtends;

  /**
   * The name of a custom factory class used to create Token
   * objects. This class must have a method with the signature
   * public static Token newToken(int ofKind, String image). By
   * default, tokens are created by calling Token.newToken().
   *
   * @parameter property=tokenFactory
   * @since 2.5
   */
  private String tokenFactory;

  /**
   * Enables/disables many syntactic and semantic checks on the grammar file
   * during parser generation. Default value is true.
   *
   * @parameter property=sanityCheck
   */
  private Boolean sanityCheck;

  /**
   * This option setting controls lookahead ambiguity checking performed by
   * JavaCC. Default value is false.
   *
   * @parameter property=forceLaCheck
   */
  private Boolean forceLaCheck;

  /**
   * Setting this option to true causes the generated parser to
   * lookahead for extra tokens ahead of time. Default value is
   * false.
   *
   * @parameter property=cacheTokens
   */
  private Boolean cacheTokens;

  /**
   * A flag whether to keep line and column information along with a token.
   * Default value is true.
   *
   * @parameter property=keepLineColumn
   */
  private Boolean keepLineColumn;

  /**
   * A flag whether the generated support classes of the parser should have
   * public or package-private visibility. Default value is true.
   *
   * @parameter property=supportClassVisibilityPublic
   * @since 2.6
   */
  private Boolean supportClassVisibilityPublic;

  /**
   * The file encoding to use for reading the grammar files.
   *
   * @parameter property=grammarEncoding
   *            default-value="${project.build.sourceEncoding}"
   * @since 2.6
   */
  private String grammarEncoding;

  /**
   * Gets the file encoding of the grammar files.
   *
   * @return The file encoding of the grammar files or null if the
   *         user did not specify this mojo parameter.
   */
  protected String getGrammarEncoding ()
  {
    return this.grammarEncoding;
  }

  /**
   * Gets the Java version for which to generate source code.
   *
   * @return The Java version for which to generate source code, will be
   *         null if the user did not specify this mojo parameter.
   */
  protected String getJdkVersion ()
  {
    return this.jdkVersion;
  }

  /**
   * Gets the flag whether to generate static parser.
   *
   * @return The flag whether to generate static parser, will be
   *         null if the user did not specify this mojo parameter.
   */
  protected Boolean getIsStatic ()
  {
    return this.isStatic;
  }

  /**
   * Gets the absolute path to the directory where the grammar files are
   * located.
   *
   * @return The absolute path to the directory where the grammar files are
   *         located, never null.
   */
  protected abstract File getSourceDirectory ();

  /**
   * Gets a set of Ant-like inclusion patterns used to select files from the
   * source directory for processing.
   *
   * @return A set of Ant-like inclusion patterns used to select files from the
   *         source directory for processing, can be null if all
   *         files should be included.
   */
  protected abstract String [] getIncludes ();

  /**
   * Gets a set of Ant-like exclusion patterns used to unselect files from the
   * source directory for processing.
   *
   * @return A set of Ant-like inclusion patterns used to unselect files from
   *         the source directory for processing, can be null if no
   *         files should be excluded.
   */
  protected abstract String [] getExcludes ();

  /**
   * Gets the absolute path to the directory where the generated Java files for
   * the parser will be stored.
   *
   * @return The absolute path to the directory where the generated Java files
   *         for the parser will be stored, never null.
   */
  protected abstract File getOutputDirectory ();

  /**
   * Gets the granularity in milliseconds of the last modification date for
   * testing whether a source needs recompilation.
   *
   * @return The granularity in milliseconds of the last modification date for
   *         testing whether a source needs recompilation.
   */
  protected abstract int getStaleMillis ();

  /**
   * Gets all the output directories to register with the project for
   * compilation.
   *
   * @return The compile source roots to register with the project, never
   *         null.
   */
  protected abstract File [] getCompileSourceRoots ();

  /**
   * Gets the package into which the generated parser files should be stored.
   *
   * @return The package into which the generated parser files should be stored,
   *         can be null to use the package declaration from the
   *         grammar file.
   */
  // TODO: Once the parameter "packageName" from the javacc mojo has been
  // deleted, remove this method, too.
  protected String getParserPackage ()
  {
    return null;
  }

  /**
   * Execute the tool.
   *
   * @throws MojoExecutionException
   *         If the invocation of the tool failed.
   * @throws MojoFailureException
   *         If the tool reported a non-zero exit code.
   */
  public void execute () throws MojoExecutionException, MojoFailureException
  {
    final GrammarInfo [] grammarInfos = scanForGrammars ();

    if (grammarInfos == null)
    {
      getLog ().info ("Skipping non-existing source directory: " + getSourceDirectory ());
      return;
    }
    else
      if (grammarInfos.length <= 0)
      {
        getLog ().info ("Skipping - all parsers are up to date");
      }
      else
      {
        determineNonGeneratedSourceRoots ();

        if (StringUtils.isEmpty (grammarEncoding))
        {
          getLog ().warn ("File encoding for grammars has not been configured" +
                          ", using platform default encoding, i.e. build is platform dependent!");
        }

        for (final GrammarInfo grammarInfo : grammarInfos)
        {
          processGrammar (grammarInfo);
        }

        getLog ().info ("Processed " + grammarInfos.length + " grammar" + (grammarInfos.length != 1 ? "s" : ""));
      }

    final Collection  compileSourceRoots = new LinkedHashSet <> (Arrays.asList (getCompileSourceRoots ()));
    for (final File file : compileSourceRoots)
    {
      addSourceRoot (file);
    }
  }

  /**
   * Passes the specified grammar file through the tool.
   *
   * @param grammarInfo
   *        The grammar info describing the grammar file to process, must not be
   *        null.
   * @throws MojoExecutionException
   *         If the invocation of the tool failed.
   * @throws MojoFailureException
   *         If the tool reported a non-zero exit code.
   */
  protected abstract void processGrammar (GrammarInfo grammarInfo) throws MojoExecutionException, MojoFailureException;

  /**
   * Scans the configured source directory for grammar files which need
   * processing.
   *
   * @return An array of grammar infos describing the found grammar files or
   *         null if the source directory does not exist.
   * @throws MojoExecutionException
   *         If the source directory could not be scanned.
   */
  private GrammarInfo [] scanForGrammars () throws MojoExecutionException
  {
    if (!getSourceDirectory ().isDirectory ())
    {
      return null;
    }

    GrammarInfo [] grammarInfos;

    getLog ().debug ("Scanning for grammars: " + getSourceDirectory ());
    try
    {
      final GrammarDirectoryScanner scanner = new GrammarDirectoryScanner ();
      scanner.setSourceDirectory (getSourceDirectory ());
      scanner.setIncludes (getIncludes ());
      scanner.setExcludes (getExcludes ());
      scanner.setOutputDirectory (getOutputDirectory ());
      scanner.setParserPackage (getParserPackage ());
      scanner.setStaleMillis (getStaleMillis ());
      scanner.scan ();
      grammarInfos = scanner.getIncludedGrammars ();
    }
    catch (final Exception e)
    {
      throw new MojoExecutionException ("Failed to scan for grammars: " + getSourceDirectory (), e);
    }
    getLog ().debug ("Found grammars: " + Arrays.asList (grammarInfos));

    return grammarInfos;
  }

  /**
   * Gets a temporary directory within the project's build directory.
   *
   * @return The path to the temporary directory, never null.
   */
  protected File getTempDirectory ()
  {
    return new File (this.project.getBuild ().getDirectory (), "javacc-" + System.currentTimeMillis ());
  }

  /**
   * Deletes the specified temporary directory.
   *
   * @param tempDirectory
   *        The directory to delete, must not be null.
   */
  protected void deleteTempDirectory (final File tempDirectory)
  {
    try
    {
      FileUtils.deleteDirectory (tempDirectory);
    }
    catch (final IOException e)
    {
      getLog ().warn ("Failed to delete temporary directory: " + tempDirectory, e);
    }
  }

  /**
   * Scans the filesystem for output files and copies them to the specified
   * compile source root. An output file is only copied to the compile source
   * root if it doesn't already exist in another compile source root. This
   * prevents duplicate class errors during compilation in case the user
   * provided customized files in src/main/java or similar.
   *
   * @param packageName
   *        The name of the destination package for the output files, must not
   *        be null.
   * @param sourceRoot
   *        The (absolute) path to the compile source root into which the output
   *        files should eventually be copied, must not be null.
   * @param tempDirectory
   *        The (absolute) path to the directory to scan for generated output
   *        files, must not be null.
   * @param updatePattern
   *        A glob pattern that matches the (simple) names of those files which
   *        should always be updated in case we are outputting directly into
   *        src/main/java, may be null. A leading "!"
   *        may be used to negate the pattern.
   * @throws MojoExecutionException
   *         If the output files could not be copied.
   */
  protected void copyGrammarOutput (final File sourceRoot,
                                    final String packageName,
                                    final File tempDirectory,
                                    final String updatePattern) throws MojoExecutionException
  {
    try
    {
      final Collection tempFiles = FileUtils.getFiles (tempDirectory, "*.java", null);
      for (final Iterator it = tempFiles.iterator (); it.hasNext ();)
      {
        final File tempFile = (File) it.next ();

        String outputPath = "";
        if (packageName.length () > 0)
        {
          outputPath = packageName.replace ('.', '/') + '/';
        }
        outputPath += tempFile.getName ();
        final File outputFile = new File (sourceRoot, outputPath);

        final File sourceFile = findSourceFile (outputPath);

        boolean alwaysUpdate = false;
        if (updatePattern != null && sourceFile != null)
        {
          if (updatePattern.startsWith ("!"))
          {
            alwaysUpdate = !SelectorUtils.match (updatePattern.substring (1), tempFile.getName ());
          }
          else
          {
            alwaysUpdate = SelectorUtils.match (updatePattern, tempFile.getName ());
          }
        }

        if (sourceFile == null || (alwaysUpdate && sourceFile.equals (outputFile)))
        {
          getLog ().debug ("Copying generated file: " + outputPath);
          try
          {
            FileUtils.copyFile (tempFile, outputFile);
          }
          catch (final IOException e)
          {
            throw new MojoExecutionException ("Failed to copy generated source file to output directory:" +
                                              tempFile +
                                              " -> " +
                                              outputFile,
                                              e);
          }
        }
        else
        {
          getLog ().debug ("Skipping customized file: " + outputPath);
        }
      }
    }
    catch (final IOException e)
    {
      throw new MojoExecutionException ("Failed to copy generated source files", e);
    }
  }

  /**
   * Determines those compile source roots of the project that do not reside
   * below the project's build directories. These compile source roots are
   * assumed to contain hand-crafted sources that must not be overwritten with
   * generated files. In most cases, this is simply
   * "${project.build.sourceDirectory}".
   *
   * @throws MojoExecutionException
   *         If the compile source rotos could not be determined.
   */
  private void determineNonGeneratedSourceRoots () throws MojoExecutionException
  {
    this.nonGeneratedSourceRoots = new LinkedHashSet <> ();
    try
    {
      final String targetPrefix = new File (this.project.getBuild ().getDirectory ()).getCanonicalPath () +
                                  File.separator;
      final Collection sourceRoots = this.project.getCompileSourceRoots ();
      for (final Iterator it = sourceRoots.iterator (); it.hasNext ();)
      {
        File sourceRoot = new File (it.next ().toString ());
        if (!sourceRoot.isAbsolute ())
        {
          sourceRoot = new File (this.project.getBasedir (), sourceRoot.getPath ());
        }
        final String sourcePath = sourceRoot.getCanonicalPath ();
        if (!sourcePath.startsWith (targetPrefix))
        {
          this.nonGeneratedSourceRoots.add (sourceRoot);
          getLog ().debug ("Non-generated compile source root: " + sourceRoot);
        }
        else
        {
          getLog ().debug ("Generated compile source root: " + sourceRoot);
        }
      }
    }
    catch (final IOException e)
    {
      throw new MojoExecutionException ("Failed to determine non-generated source roots", e);
    }
  }

  /**
   * Determines whether the specified source file is already present in any of
   * the compile source roots registered with the current Maven project.
   *
   * @param filename
   *        The source filename to check, relative to a source root, must not be
   *        null.
   * @return The (absolute) path to the existing source file if any,
   *         null otherwise.
   */
  private File findSourceFile (final String filename)
  {
    final Collection  sourceRoots = this.nonGeneratedSourceRoots;
    for (final File sourceRoot : sourceRoots)
    {
      final File sourceFile = new File (sourceRoot, filename);
      if (sourceFile.exists ())
      {
        return sourceFile;
      }
    }
    return null;
  }

  /**
   * Determines whether the specified directory denotes a compile source root of
   * the current project.
   *
   * @param directory
   *        The directory to check, must not be null.
   * @return true if the specified directory is a compile source
   *         root of the project, false otherwise.
   */
  protected boolean isSourceRoot (final File directory)
  {
    return this.nonGeneratedSourceRoots.contains (directory);
  }

  /**
   * Registers the specified directory as a compile source root for the current
   * project.
   *
   * @param directory
   *        The absolute path to the source root, must not be null.
   */
  private void addSourceRoot (final File directory)
  {
    if (this.project != null)
    {
      getLog ().debug ("Adding compile source root: " + directory);
      this.project.addCompileSourceRoot (directory.getAbsolutePath ());
    }
  }

  /**
   * Creates a new facade to invoke JavaCC. Most options for the invocation are
   * derived from the current values of the corresponding mojo parameters. The
   * caller is responsible to set the input file and output directory on the
   * returned facade.
   *
   * @return The facade for the tool invocation, never null.
   */
  protected JavaCC newJavaCC ()
  {
    final JavaCC javacc = new JavaCC ();
    javacc.setLog (getLog ());
    javacc.setGrammarEncoding (this.grammarEncoding);
    javacc.setJdkVersion (this.jdkVersion);
    javacc.setStatic (this.isStatic);
    javacc.setBuildParser (this.buildParser);
    javacc.setBuildTokenManager (this.buildTokenManager);
    javacc.setCacheTokens (this.cacheTokens);
    javacc.setChoiceAmbiguityCheck (this.choiceAmbiguityCheck);
    javacc.setCommonTokenAction (this.commonTokenAction);
    javacc.setDebugLookAhead (this.debugLookAhead);
    javacc.setDebugParser (this.debugParser);
    javacc.setDebugTokenManager (this.debugTokenManager);
    javacc.setErrorReporting (this.errorReporting);
    javacc.setForceLaCheck (this.forceLaCheck);
    javacc.setIgnoreCase (this.ignoreCase);
    javacc.setJavaUnicodeEscape (this.javaUnicodeEscape);
    javacc.setKeepLineColumn (this.keepLineColumn);
    javacc.setLookAhead (this.lookAhead);
    javacc.setOtherAmbiguityCheck (this.otherAmbiguityCheck);
    javacc.setSanityCheck (this.sanityCheck);
    javacc.setTokenManagerUsesParser (this.tokenManagerUsesParser);
    javacc.setTokenExtends (this.tokenExtends);
    javacc.setTokenFactory (this.tokenFactory);
    javacc.setUnicodeInput (this.unicodeInput);
    javacc.setUserCharStream (this.userCharStream);
    javacc.setUserTokenManager (this.userTokenManager);
    javacc.setSupportClassVisibilityPublic (this.supportClassVisibilityPublic);
    return javacc;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy