/* * Copyright 2001-2005 The Codehaus. * * Licensed 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. */ package org.geotools.maven; // J2SE dependencies import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.io.IOException; // JavaCC dependencies import org.javacc.parser.Main; import org.javacc.jjtree.JJTree; // Maven and Plexus dependencies import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner; import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; import org.codehaus.plexus.util.FileUtils; // Note: javadoc in class and fields descriptions must be XHTML. /** * Generates <code>.java</code> sources from <code>.jjt</code> files during Geotools build. This * <A HREF="http://maven.apache.org/maven2/">Maven 2</A> plugin executes <code>jjtree</code> * first, followed by <code>javacc</code>. Both of them are part of the * <A HREF="https://javacc.dev.java.net/">JavaCC</A> project. * <p/> * This code is a derived work from the Mojo * <code><A HREF="http://mojo.codehaus.org/maven-javacc-plugin/">maven-javacc-plugin</A></code>, * which explain why we retains the Apache copyright header. We didn't used The Mojo JavaCC plugin * because: * <p/> * <ul> * <li>It seems easier to control execution order in a single plugin (obviously <code>jjtree</code> * must be executed before <code>javacc</code>, but I don't know how to enforce this order if * both of them are independent plugins registered in the <code>generate-sources</code> build * phase).</li> * <li><code>maven-javacc-plugin</code> overwrites the values specified in the <code>.jjt</code> * file with its own default values, even if no such values were specified in the * <code>pom.xml</code> file. This behavior conflicts with Geotools setting for the * <code>STATIC</code> option.</li> * </ul> * * Note: The default directories in this plugin are Maven default, even if this plugin target * Geotools build (which use a different directory structure). * * @goal generate * @phase generate-sources * @description Parses a JJT file and transform it to Java Files. * @source $URL$ * @version $Id$ * * @author jruiz * @author Jesse McConnell * @author Martin Desruisseaux */ public class JJTreeJavaCC extends AbstractMojo { /** * The package to generate the node classes into. * * @parameter expression="" * @required */ private String nodePackage; /** * Directory where user-specified <code>Node.java</code> and <code>SimpleNode.java</code> * files are located. If no node exist, JJTree will create ones. * * @parameter expression="${basedir}/src/main/jjtree" * @required */ private String nodeDirectory; /** * Directory where the JJT file(s) are located. * * @parameter expression="${basedir}/src/main/jjtree" * @required */ private String sourceDirectory; /** * Directory where the output Java files will be located. * * @parameter expression="${project.build.directory}/generated-sources/jjtree-javacc" * @required */ private String outputDirectory; /** * Concatenation of {@link #outputDirectory} with {@link #nodePackage}. * For internal use only. */ private File outputPackageDirectory; /** * The directory to store the processed <code>.jjt</code> files. * * @parameter expression="${project.build.directory}/timestamp" */ private String timestampDirectory; /** * The granularity in milliseconds of the last modification * date for testing whether a source needs recompilation * * @parameter expression="${lastModGranularityMs}" default-value="0" */ private int staleMillis; /** * The Maven project running this plugin. * * @parameter expression="${project}" * @required */ private MavenProject project; /** * Generates the source code from all {@code .jjt} and {@code .jj} files found in the source * directory. First, all {@code .jjt} files are processed using {@code jjtree}. Then, all * generated {@code .jj} files are processed. * * @throws MojoExecutionException if the plugin execution failed. */ public void execute() throws MojoExecutionException, MojoFailureException { // if not windows, don't rewrite file final boolean windowsOs = System.getProperty("os.name").indexOf("Windows") != -1; outputPackageDirectory = createPackageDirectory(outputDirectory); if (!FileUtils.fileExists(timestampDirectory)) { FileUtils.mkdir(timestampDirectory); } /* * Copies the user-supplied Node.java files (if any) from the source directory (by default * "src/main/jjtree") to the output directory (by default "target/generated-sources"). Only * java files found in the node package are processed. NOTE: current version do not handle * properly subpackages. */ final Set userNodes = searchNodeFiles(); for (final Iterator it=userNodes.iterator(); it.hasNext();) { final File nodeFile = (File) it.next(); try { FileUtils.copyFileToDirectory(nodeFile, outputPackageDirectory); } catch (IOException e) { throw new MojoExecutionException("Failed to copy Node.java files for JJTree.", e); } } /* * Reprocess the .jjt files found in the source directory (by default "src/main/jjtree"). * The default output directory is "generated-sources/jjtree-javacc" (it doesn't contains * javacc output yet, but it will). */ final Set staleTrees = searchStaleGrammars(new File(sourceDirectory), ".jjt"); for (final Iterator it=staleTrees.iterator(); it.hasNext();) { final File sourceFile = (File) it.next(); final JJTree parser = new JJTree(); final String[] args = generateJJTreeArgumentList(sourceFile.getPath()); final int status = parser.main(args); if (status != 0) { throw new MojoFailureException("JJTree failed with error code " + status + '.'); } try { FileUtils.copyFileToDirectory(sourceFile, new File(timestampDirectory)); } catch (IOException e) { throw new MojoExecutionException("Failed to copy processed .jjt file.", e); } } /* * Reprocess the .jj files found in the generated-sources directory. */ final Set staleGrammars = searchStaleGrammars(new File(outputDirectory), ".jj"); for (final Iterator it=staleGrammars.iterator(); it.hasNext();) { final File sourceFile = (File) it.next(); try { if (windowsOs) { fixHeader(sourceFile); } } catch (IOException e) { throw new MojoExecutionException("Failed to fix header for .jj file.", e); } final String[] args = generateJavaCCArgumentList(sourceFile.getPath()); final int status; try { status = Main.mainProgram(args); } catch (Exception e) { throw new MojoExecutionException("Failed to run javacc.", e); } if (status != 0) { throw new MojoFailureException("JavaCC failed with error code " + status + '.'); } try { FileUtils.copyFileToDirectory(sourceFile, new File(timestampDirectory)); } catch (IOException e) { throw new MojoExecutionException("Failed to copy processed .jj file.", e); } } /* * Reprocess generated java files so that they won't contain invalid escape characters */ if (windowsOs) { try { String[] files = FileUtils.getFilesFromExtension(outputDirectory, new String[] {"java"}); for (int i = 0; i < files.length; i++) { System.out.println("Fixing " + files[i]); fixHeader(new File(files[i])); } } catch (IOException e) { throw new MojoExecutionException("Failed to fix header for java file.", e); } } /* * Add the generated-sources directory to the compilation root for the remaining * maven build. */ if (project != null) { project.addCompileSourceRoot(outputDirectory); } } /** * Takes a file generated from javacc, and changes the first line so that it does not * contain escape characters on windows (the filename may contain things like \ u * which are invalid escape chars) * * @param sourceFile the file to process. * @throws IOException if the file can't be read or the resutl can't be writen. */ private void fixHeader(final File sourceFile) throws IOException { BufferedReader reader = null; BufferedWriter writer = null; File fixedFile = new File(sourceFile.getParentFile(), sourceFile.getName() + ".fix"); try { reader = new BufferedReader(new FileReader(sourceFile)); writer = new BufferedWriter(new FileWriter(fixedFile)); String line; while ((line = reader.readLine()) != null) { if (line.startsWith("/*@bgen(jjtree) Generated By:JJTree:") || line.startsWith("/* Generated By:JJTree:")) { line = line.replace('\\', '/'); } writer.write(line); writer.newLine(); } } finally { if (reader != null) reader.close(); if (writer != null) writer.close(); } sourceFile.delete(); fixedFile.renameTo(sourceFile); } /** * Returns the concatenation of {@code directory} with {@link #nodePackage}. This is used in * order to construct a directory path which include the Java package. The directory will be * created if it doesn't exists. */ private File createPackageDirectory(final String directory) throws MojoExecutionException { File packageDirectory = new File(directory); if (nodePackage!=null && nodePackage.trim().length()!=0) { packageDirectory = new File(packageDirectory, nodePackage.replace('.', '/')); if (!packageDirectory.exists()) { if (!packageDirectory.mkdirs()) { throw new MojoExecutionException("Failed to create the destination directory."); } } } return packageDirectory; } /** * Gets the set of user-specified {@code Node.java} files. If none are found, {@code jjtree} * will generate automatically a default one. This method search only in the package defined * in the {@link #nodePackage} attribute. */ private Set searchNodeFiles() throws MojoExecutionException { final SuffixMapping mapping = new SuffixMapping(".java", ".java"); final SuffixMapping mappingCAP = new SuffixMapping(".JAVA", ".JAVA"); final SourceInclusionScanner scanner = new StaleSourceScanner(staleMillis); scanner.addSourceMapping(mapping); scanner.addSourceMapping(mappingCAP); File directory = new File(nodeDirectory); if (nodePackage!=null && nodePackage.trim().length()!=0) { directory = new File(directory, nodePackage.replace('.', '/')); } if (!directory.isDirectory()) { return Collections.EMPTY_SET; } final File outDir = new File(timestampDirectory); try { return scanner.getIncludedSources(directory, outDir); } catch (InclusionScanException e) { throw new MojoExecutionException("Error scanning \"" + directory.getPath() + "\" for Node.java to copy.", e); } } /** * Gets the set of {@code .jjt} or {@code .jj} files to reprocess. * * @param sourceDir The source directory. * @param ext The extension to search of ({@code .jjt} or {@code .jj}). */ private Set searchStaleGrammars(final File sourceDir, final String ext) throws MojoExecutionException { final String extCAP = ext.toUpperCase(); final SuffixMapping mapping = new SuffixMapping(ext, ext); final SuffixMapping mappingCAP = new SuffixMapping(extCAP, extCAP); final SourceInclusionScanner scanner = new StaleSourceScanner(staleMillis); scanner.addSourceMapping(mapping); scanner.addSourceMapping(mappingCAP); final File outDir = new File(timestampDirectory); try { return scanner.getIncludedSources(sourceDir, outDir); } catch (InclusionScanException e) { throw new MojoExecutionException("Error scanning source root \"" + sourceDir.getPath() + "\" for stale grammars to reprocess.", e); } } /** * Gets the arguments to pass to {@code jjtree}. * * @param sourceFilename The {@code .jjt} file name (including the path). * @return The arguments to pass to {@code jjtree}. */ private String[] generateJJTreeArgumentList(final String sourceFilename) { final List argsList = new ArrayList(); if (nodePackage!=null && nodePackage.trim().length()!=0) { argsList.add("-NODE_PACKAGE:" + nodePackage); } argsList.add("-OUTPUT_DIRECTORY:" + outputPackageDirectory.getPath()); argsList.add(sourceFilename); getLog().debug("jjtree arguments list: " + argsList.toString()); return (String[]) argsList.toArray(new String[argsList.size()]); } /** * Gets the arguments to pass to {@code javacc}. * * @param sourceFilename The {@code .jj} file name (including the path). * @return The arguments to pass to {@code javacc}. */ private String[] generateJavaCCArgumentList(final String sourceInput) { final List argsList = new ArrayList(); argsList.add("-OUTPUT_DIRECTORY:" + outputPackageDirectory.getPath()); argsList.add(sourceInput); getLog().debug("javacc arguments list: " + argsList.toString()); return (String[]) argsList.toArray(new String[argsList.size()]); } }