001: /*
002: * Copyright 2001-2005 The Codehaus.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.geotools.maven;
017:
018: // J2SE dependencies
019: import java.io.BufferedReader;
020: import java.io.BufferedWriter;
021: import java.io.File;
022: import java.io.FileReader;
023: import java.io.FileWriter;
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Set;
029: import java.io.IOException;
030:
031: // JavaCC dependencies
032: import org.javacc.parser.Main;
033: import org.javacc.jjtree.JJTree;
034:
035: // Maven and Plexus dependencies
036: import org.apache.maven.plugin.AbstractMojo;
037: import org.apache.maven.plugin.MojoFailureException;
038: import org.apache.maven.plugin.MojoExecutionException;
039: import org.apache.maven.project.MavenProject;
040: import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
041: import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
042: import org.codehaus.plexus.compiler.util.scan.StaleSourceScanner;
043: import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
044: import org.codehaus.plexus.util.FileUtils;
045:
046: // Note: javadoc in class and fields descriptions must be XHTML.
047: /**
048: * Generates <code>.java</code> sources from <code>.jjt</code> files during Geotools build. This
049: * <A HREF="http://maven.apache.org/maven2/">Maven 2</A> plugin executes <code>jjtree</code>
050: * first, followed by <code>javacc</code>. Both of them are part of the
051: * <A HREF="https://javacc.dev.java.net/">JavaCC</A> project.
052: * <p/>
053: * This code is a derived work from the Mojo
054: * <code><A HREF="http://mojo.codehaus.org/maven-javacc-plugin/">maven-javacc-plugin</A></code>,
055: * which explain why we retains the Apache copyright header. We didn't used The Mojo JavaCC plugin
056: * because:
057: * <p/>
058: * <ul>
059: * <li>It seems easier to control execution order in a single plugin (obviously <code>jjtree</code>
060: * must be executed before <code>javacc</code>, but I don't know how to enforce this order if
061: * both of them are independent plugins registered in the <code>generate-sources</code> build
062: * phase).</li>
063: * <li><code>maven-javacc-plugin</code> overwrites the values specified in the <code>.jjt</code>
064: * file with its own default values, even if no such values were specified in the
065: * <code>pom.xml</code> file. This behavior conflicts with Geotools setting for the
066: * <code>STATIC</code> option.</li>
067: * </ul>
068: *
069: * Note: The default directories in this plugin are Maven default, even if this plugin target
070: * Geotools build (which use a different directory structure).
071: *
072: * @goal generate
073: * @phase generate-sources
074: * @description Parses a JJT file and transform it to Java Files.
075: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/build/maven/jjtree-javacc/src/main/java/org/geotools/maven/JJTreeJavaCC.java $
076: * @version $Id: JJTreeJavaCC.java 24765 2007-03-15 03:50:56Z desruisseaux $
077: *
078: * @author jruiz
079: * @author Jesse McConnell
080: * @author Martin Desruisseaux
081: */
082: public class JJTreeJavaCC extends AbstractMojo {
083: /**
084: * The package to generate the node classes into.
085: *
086: * @parameter expression=""
087: * @required
088: */
089: private String nodePackage;
090:
091: /**
092: * Directory where user-specified <code>Node.java</code> and <code>SimpleNode.java</code>
093: * files are located. If no node exist, JJTree will create ones.
094: *
095: * @parameter expression="${basedir}/src/main/jjtree"
096: * @required
097: */
098: private String nodeDirectory;
099:
100: /**
101: * Directory where the JJT file(s) are located.
102: *
103: * @parameter expression="${basedir}/src/main/jjtree"
104: * @required
105: */
106: private String sourceDirectory;
107:
108: /**
109: * Directory where the output Java files will be located.
110: *
111: * @parameter expression="${project.build.directory}/generated-sources/jjtree-javacc"
112: * @required
113: */
114: private String outputDirectory;
115:
116: /**
117: * Concatenation of {@link #outputDirectory} with {@link #nodePackage}.
118: * For internal use only.
119: */
120: private File outputPackageDirectory;
121:
122: /**
123: * The directory to store the processed <code>.jjt</code> files.
124: *
125: * @parameter expression="${project.build.directory}/timestamp"
126: */
127: private String timestampDirectory;
128:
129: /**
130: * The granularity in milliseconds of the last modification
131: * date for testing whether a source needs recompilation
132: *
133: * @parameter expression="${lastModGranularityMs}" default-value="0"
134: */
135: private int staleMillis;
136:
137: /**
138: * The Maven project running this plugin.
139: *
140: * @parameter expression="${project}"
141: * @required
142: */
143: private MavenProject project;
144:
145: /**
146: * Generates the source code from all {@code .jjt} and {@code .jj} files found in the source
147: * directory. First, all {@code .jjt} files are processed using {@code jjtree}. Then, all
148: * generated {@code .jj} files are processed.
149: *
150: * @throws MojoExecutionException if the plugin execution failed.
151: */
152: public void execute() throws MojoExecutionException,
153: MojoFailureException {
154: // if not windows, don't rewrite file
155: final boolean windowsOs = System.getProperty("os.name")
156: .indexOf("Windows") != -1;
157:
158: outputPackageDirectory = createPackageDirectory(outputDirectory);
159: if (!FileUtils.fileExists(timestampDirectory)) {
160: FileUtils.mkdir(timestampDirectory);
161: }
162: /*
163: * Copies the user-supplied Node.java files (if any) from the source directory (by default
164: * "src/main/jjtree") to the output directory (by default "target/generated-sources"). Only
165: * java files found in the node package are processed. NOTE: current version do not handle
166: * properly subpackages.
167: */
168: final Set userNodes = searchNodeFiles();
169: for (final Iterator it = userNodes.iterator(); it.hasNext();) {
170: final File nodeFile = (File) it.next();
171: try {
172: FileUtils.copyFileToDirectory(nodeFile,
173: outputPackageDirectory);
174: } catch (IOException e) {
175: throw new MojoExecutionException(
176: "Failed to copy Node.java files for JJTree.", e);
177: }
178: }
179: /*
180: * Reprocess the .jjt files found in the source directory (by default "src/main/jjtree").
181: * The default output directory is "generated-sources/jjtree-javacc" (it doesn't contains
182: * javacc output yet, but it will).
183: */
184: final Set staleTrees = searchStaleGrammars(new File(
185: sourceDirectory), ".jjt");
186: for (final Iterator it = staleTrees.iterator(); it.hasNext();) {
187: final File sourceFile = (File) it.next();
188: final JJTree parser = new JJTree();
189: final String[] args = generateJJTreeArgumentList(sourceFile
190: .getPath());
191: final int status = parser.main(args);
192: if (status != 0) {
193: throw new MojoFailureException(
194: "JJTree failed with error code " + status + '.');
195: }
196: try {
197: FileUtils.copyFileToDirectory(sourceFile, new File(
198: timestampDirectory));
199: } catch (IOException e) {
200: throw new MojoExecutionException(
201: "Failed to copy processed .jjt file.", e);
202: }
203: }
204: /*
205: * Reprocess the .jj files found in the generated-sources directory.
206: */
207: final Set staleGrammars = searchStaleGrammars(new File(
208: outputDirectory), ".jj");
209: for (final Iterator it = staleGrammars.iterator(); it.hasNext();) {
210: final File sourceFile = (File) it.next();
211: try {
212: if (windowsOs) {
213: fixHeader(sourceFile);
214: }
215: } catch (IOException e) {
216: throw new MojoExecutionException(
217: "Failed to fix header for .jj file.", e);
218: }
219: final String[] args = generateJavaCCArgumentList(sourceFile
220: .getPath());
221: final int status;
222: try {
223: status = Main.mainProgram(args);
224: } catch (Exception e) {
225: throw new MojoExecutionException(
226: "Failed to run javacc.", e);
227: }
228: if (status != 0) {
229: throw new MojoFailureException(
230: "JavaCC failed with error code " + status + '.');
231: }
232: try {
233: FileUtils.copyFileToDirectory(sourceFile, new File(
234: timestampDirectory));
235: } catch (IOException e) {
236: throw new MojoExecutionException(
237: "Failed to copy processed .jj file.", e);
238: }
239: }
240: /*
241: * Reprocess generated java files so that they won't contain invalid escape characters
242: */
243: if (windowsOs) {
244: try {
245: String[] files = FileUtils.getFilesFromExtension(
246: outputDirectory, new String[] { "java" });
247: for (int i = 0; i < files.length; i++) {
248: System.out.println("Fixing " + files[i]);
249: fixHeader(new File(files[i]));
250: }
251: } catch (IOException e) {
252: throw new MojoExecutionException(
253: "Failed to fix header for java file.", e);
254: }
255: }
256: /*
257: * Add the generated-sources directory to the compilation root for the remaining
258: * maven build.
259: */
260: if (project != null) {
261: project.addCompileSourceRoot(outputDirectory);
262: }
263: }
264:
265: /**
266: * Takes a file generated from javacc, and changes the first line so that it does not
267: * contain escape characters on windows (the filename may contain things like \ u
268: * which are invalid escape chars)
269: *
270: * @param sourceFile the file to process.
271: * @throws IOException if the file can't be read or the resutl can't be writen.
272: */
273: private void fixHeader(final File sourceFile) throws IOException {
274: BufferedReader reader = null;
275: BufferedWriter writer = null;
276: File fixedFile = new File(sourceFile.getParentFile(),
277: sourceFile.getName() + ".fix");
278: try {
279: reader = new BufferedReader(new FileReader(sourceFile));
280: writer = new BufferedWriter(new FileWriter(fixedFile));
281: String line;
282: while ((line = reader.readLine()) != null) {
283: if (line
284: .startsWith("/*@bgen(jjtree) Generated By:JJTree:")
285: || line.startsWith("/* Generated By:JJTree:")) {
286: line = line.replace('\\', '/');
287: }
288: writer.write(line);
289: writer.newLine();
290: }
291: } finally {
292: if (reader != null)
293: reader.close();
294: if (writer != null)
295: writer.close();
296: }
297: sourceFile.delete();
298: fixedFile.renameTo(sourceFile);
299: }
300:
301: /**
302: * Returns the concatenation of {@code directory} with {@link #nodePackage}. This is used in
303: * order to construct a directory path which include the Java package. The directory will be
304: * created if it doesn't exists.
305: */
306: private File createPackageDirectory(final String directory)
307: throws MojoExecutionException {
308: File packageDirectory = new File(directory);
309: if (nodePackage != null && nodePackage.trim().length() != 0) {
310: packageDirectory = new File(packageDirectory, nodePackage
311: .replace('.', '/'));
312: if (!packageDirectory.exists()) {
313: if (!packageDirectory.mkdirs()) {
314: throw new MojoExecutionException(
315: "Failed to create the destination directory.");
316: }
317: }
318: }
319: return packageDirectory;
320: }
321:
322: /**
323: * Gets the set of user-specified {@code Node.java} files. If none are found, {@code jjtree}
324: * will generate automatically a default one. This method search only in the package defined
325: * in the {@link #nodePackage} attribute.
326: */
327: private Set searchNodeFiles() throws MojoExecutionException {
328: final SuffixMapping mapping = new SuffixMapping(".java",
329: ".java");
330: final SuffixMapping mappingCAP = new SuffixMapping(".JAVA",
331: ".JAVA");
332: final SourceInclusionScanner scanner = new StaleSourceScanner(
333: staleMillis);
334: scanner.addSourceMapping(mapping);
335: scanner.addSourceMapping(mappingCAP);
336: File directory = new File(nodeDirectory);
337: if (nodePackage != null && nodePackage.trim().length() != 0) {
338: directory = new File(directory, nodePackage.replace('.',
339: '/'));
340: }
341: if (!directory.isDirectory()) {
342: return Collections.EMPTY_SET;
343: }
344: final File outDir = new File(timestampDirectory);
345: try {
346: return scanner.getIncludedSources(directory, outDir);
347: } catch (InclusionScanException e) {
348: throw new MojoExecutionException(
349: "Error scanning \"" + directory.getPath()
350: + "\" for Node.java to copy.", e);
351: }
352: }
353:
354: /**
355: * Gets the set of {@code .jjt} or {@code .jj} files to reprocess.
356: *
357: * @param sourceDir The source directory.
358: * @param ext The extension to search of ({@code .jjt} or {@code .jj}).
359: */
360: private Set searchStaleGrammars(final File sourceDir,
361: final String ext) throws MojoExecutionException {
362: final String extCAP = ext.toUpperCase();
363: final SuffixMapping mapping = new SuffixMapping(ext, ext);
364: final SuffixMapping mappingCAP = new SuffixMapping(extCAP,
365: extCAP);
366: final SourceInclusionScanner scanner = new StaleSourceScanner(
367: staleMillis);
368: scanner.addSourceMapping(mapping);
369: scanner.addSourceMapping(mappingCAP);
370: final File outDir = new File(timestampDirectory);
371: try {
372: return scanner.getIncludedSources(sourceDir, outDir);
373: } catch (InclusionScanException e) {
374: throw new MojoExecutionException(
375: "Error scanning source root \""
376: + sourceDir.getPath()
377: + "\" for stale grammars to reprocess.", e);
378: }
379: }
380:
381: /**
382: * Gets the arguments to pass to {@code jjtree}.
383: *
384: * @param sourceFilename The {@code .jjt} file name (including the path).
385: * @return The arguments to pass to {@code jjtree}.
386: */
387: private String[] generateJJTreeArgumentList(
388: final String sourceFilename) {
389: final List argsList = new ArrayList();
390: if (nodePackage != null && nodePackage.trim().length() != 0) {
391: argsList.add("-NODE_PACKAGE:" + nodePackage);
392: }
393: argsList.add("-OUTPUT_DIRECTORY:"
394: + outputPackageDirectory.getPath());
395: argsList.add(sourceFilename);
396: getLog().debug("jjtree arguments list: " + argsList.toString());
397: return (String[]) argsList.toArray(new String[argsList.size()]);
398: }
399:
400: /**
401: * Gets the arguments to pass to {@code javacc}.
402: *
403: * @param sourceFilename The {@code .jj} file name (including the path).
404: * @return The arguments to pass to {@code javacc}.
405: */
406: private String[] generateJavaCCArgumentList(final String sourceInput) {
407: final List argsList = new ArrayList();
408: argsList.add("-OUTPUT_DIRECTORY:"
409: + outputPackageDirectory.getPath());
410: argsList.add(sourceInput);
411: getLog().debug("javacc arguments list: " + argsList.toString());
412: return (String[]) argsList.toArray(new String[argsList.size()]);
413: }
414: }
|