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

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

package org.javacc.mojo;

/*
 * 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$
 */
public abstract class AbstractJavaCCMojo
    extends AbstractMojo
{

    /**
     * The current Maven project.
     * 
     * @component
     */
    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 (\uxxxx) 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;
    }

    /**
     * Skip processing.
     * 
     * @parameter property="skip"  default-value="false"
     * @since 3.0
     */

    private boolean skip;

    private boolean getSkip() {
    	return skip;
    }

   /**
     * The target code generator for compiling this grammar.
     * 
     * @parameter property="codeGenerator"
     * @since 3.0
     */
    
    private String codeGenerator;

    /**
     * Gets the backend code generator.
     * 
     * @return The name of the code generator (Java, C++, C#) 
     * @since 3.0
     */
    protected String getCodeGenerator()
    {
        return this.codeGenerator;
    }

    /**
     * The target code generator for compiling this grammar.
     * 
     * @parameter property="outputLanguage"
     * @since 3.0
     */

    private String outputLanguage;

    /**
     * Gets the backend code generator.
     * 
     * @return The name of the code generator (Java, C++, C#) 
     * @since 3.0
     */
    protected String getOutputLanguage()
    {
        return this.outputLanguage;
    }

    /**
     * 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
    {
    	if (skip)
    		return;
    	
        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 ( int i = 0; i < grammarInfos.length; i++ )
            {
                processGrammar( grammarInfos[i] );
            }

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

        Collection compileSourceRoots = new LinkedHashSet( Arrays.asList( getCompileSourceRoots() ) );
        for ( Iterator it = compileSourceRoots.iterator(); it.hasNext(); )
        {
            addSourceRoot( (File) it.next() );
        }
    }

    /**
     * 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
        {
            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 ( 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( File tempDirectory )
    {
        try
        {
            FileUtils.deleteDirectory( tempDirectory );
        }
        catch ( 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 sourceRoot The (absolute) path to the compile source root into which the output files should eventually be
     *            copied, must not be null.
     * @param packageName The name of the destination package for the output files, 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( File sourceRoot, String packageName, File tempDirectory, String updatePattern )
        throws MojoExecutionException
    {
        try
        {
            Collection tempFiles = null;
            if ((StringUtils.isBlank(codeGenerator) && StringUtils.isBlank(outputLanguage)) || 
            	StringUtils.equalsIgnoreCase(codeGenerator, "Java") || StringUtils.equalsIgnoreCase(outputLanguage, "Java"))
            {
            	tempFiles = FileUtils.getFiles( tempDirectory, "*." + Suffix.Java.string(), null );
            } else
            if (StringUtils.equalsIgnoreCase(codeGenerator, "C++") || StringUtils.equalsIgnoreCase(outputLanguage, "C++"))
            {
            	tempFiles = FileUtils.getFiles( tempDirectory, "*." + Suffix.Cpp.string(), null );
            	tempFiles.addAll(FileUtils.getFiles( tempDirectory, "*.h", null ));
            } else
            if (StringUtils.equalsIgnoreCase(codeGenerator, "C#") || StringUtils.equalsIgnoreCase(outputLanguage, "C#"))
            {
            	tempFiles = FileUtils.getFiles( tempDirectory, "*." + Suffix.CSharp.string(), null );
            }

            for ( Iterator it = tempFiles.iterator(); it.hasNext(); )
            {
                File tempFile = (File) it.next();

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

                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 ( 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 ( 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
        {
            String targetPrefix =
                new File( this.project.getBuild().getDirectory() ).getCanonicalPath() + File.separator;
            Collection sourceRoots = this.project.getCompileSourceRoots();
            for ( Iterator it = sourceRoots.iterator(); it.hasNext(); )
            {
                File sourceRoot = new File( it.next().toString() );
                if ( !sourceRoot.isAbsolute() )
                {
                    sourceRoot = new File( this.project.getBasedir(), sourceRoot.getPath() );
                }
                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 ( 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( String filename )
    {
        Collection sourceRoots = this.nonGeneratedSourceRoots;
        for ( Iterator it = sourceRoots.iterator(); it.hasNext(); )
        {
            File sourceRoot = (File) it.next();
            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( 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( 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()
    {
        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 );
        javacc.setCodeGenerator(this.codeGenerator);
        javacc.setOutputLanguage(this.outputLanguage);
        return javacc;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy