1 package org.codehaus.mojo.javacc;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.List;
27
28 import org.codehaus.plexus.util.FileUtils;
29 import org.codehaus.plexus.util.cli.StreamConsumer;
30
31 /**
32 * Provides a facade for the mojos to invoke JTB.
33 *
34 * @author Benjamin Bentmann
35 * @version $Id: JTB.java 6463 2008-03-15 22:20:09Z bentmann $
36 * @see <a href="http://compilers.cs.ucla.edu/jtb/">Java Tree Builder</a>
37 */
38 class JTB
39 extends ToolFacade
40 {
41
42 /**
43 * The default package name for syntax tree files.
44 */
45 private static final String SYNTAX_TREE = "syntaxtree";
46
47 /**
48 * The default package name for visitor files.
49 */
50 private static final String VISITOR = "visitor";
51
52 /**
53 * The input grammar.
54 */
55 private File inputFile;
56
57 /**
58 * The base directory for the option "-o".
59 */
60 private File outputDirectory;
61
62 /**
63 * The output directory for the syntax tree files.
64 */
65 private File nodeDirectory;
66
67 /**
68 * The output directory for the visitor files.
69 */
70 private File visitorDirectory;
71
72 /**
73 * The option "-p".
74 */
75 private String packageName;
76
77 /**
78 * The option "-np".
79 */
80 private String nodePackageName;
81
82 /**
83 * The option "-vp".
84 */
85 private String visitorPackageName;
86
87 /**
88 * The option "-e".
89 */
90 private Boolean supressErrorChecking;
91
92 /**
93 * The option "-jd".
94 */
95 private Boolean javadocFriendlyComments;
96
97 /**
98 * The option "-f".
99 */
100 private Boolean descriptiveFieldNames;
101
102 /**
103 * The option "-ns".
104 */
105 private String nodeParentClass;
106
107 /**
108 * The option "-pp".
109 */
110 private Boolean parentPointers;
111
112 /**
113 * The option "-tk".
114 */
115 private Boolean specialTokens;
116
117 /**
118 * The toolkit option "-scheme".
119 */
120 private Boolean scheme;
121
122 /**
123 * The toolkit option "-printer".
124 */
125 private Boolean printer;
126
127 /**
128 * Sets the absolute path to the grammar file to pass into JTB for preprocessing.
129 *
130 * @param value The absolute path to the grammar file to pass into JTB for preprocessing.
131 */
132 public void setInputFile( File value )
133 {
134 if ( value != null && !value.isAbsolute() )
135 {
136 throw new IllegalArgumentException( "path is not absolute: " + value );
137 }
138 this.inputFile = value;
139 }
140
141 /**
142 * Sets the absolute path to the output directory for the generated grammar file.
143 *
144 * @param value The absolute path to the output directory for the generated grammar file. If this directory does not
145 * exist yet, it is created. Note that this path should already include the desired package hierarchy
146 * because JTB will not append the required sub directories automatically.
147 */
148 public void setOutputDirectory( File value )
149 {
150 if ( value != null && !value.isAbsolute() )
151 {
152 throw new IllegalArgumentException( "path is not absolute: " + value );
153 }
154 this.outputDirectory = value;
155 }
156
157 /**
158 * Gets the absolute path to the enhanced grammar file generated by JTB.
159 *
160 * @return The absolute path to the enhanced grammar file generated by JTB or <code>null</code> if either the
161 * input file or the output directory have not been set.
162 */
163 public File getOutputFile()
164 {
165 File outputFile = null;
166 if ( this.outputDirectory != null && this.inputFile != null )
167 {
168 String fileName = FileUtils.removeExtension( this.inputFile.getName() ) + ".jj";
169 outputFile = new File( this.outputDirectory, fileName );
170 }
171 return outputFile;
172 }
173
174 /**
175 * Sets the absolute path to the output directory for the syntax tree files.
176 *
177 * @param value The absolute path to the output directory for the generated syntax tree files, may be
178 * <code>null</code> to use a sub directory in the output directory of the grammar file. If this
179 * directory does not exist yet, it is created. Note that this path should already include the desired
180 * package hierarchy because JTB will not append the required sub directories automatically.
181 */
182 public void setNodeDirectory( File value )
183 {
184 if ( value != null && !value.isAbsolute() )
185 {
186 throw new IllegalArgumentException( "path is not absolute: " + value );
187 }
188 this.nodeDirectory = value;
189 }
190
191 /**
192 * Gets the absolute path to the output directory for the syntax tree files.
193 *
194 * @return The absolute path to the output directory for the syntax tree files, only <code>null</code> if neither
195 * {@link #outputDirectory} nor {@link #nodeDirectory} have been set.
196 */
197 private File getEffectiveNodeDirectory()
198 {
199 if ( this.nodeDirectory != null )
200 {
201 return this.nodeDirectory;
202 }
203 else if ( this.outputDirectory != null )
204 {
205 return new File( this.outputDirectory, getLastPackageName( getEffectiveNodePackageName() ) );
206 }
207 return null;
208 }
209
210 /**
211 * Sets the absolute path to the output directory for the visitor files.
212 *
213 * @param value The absolute path to the output directory for the generated visitor files, may be <code>null</code>
214 * to use a sub directory in the output directory of the grammar file. If this directory does not exist
215 * yet, it is created. Note that this path should already include the desired package hierarchy because
216 * JTB will not append the required sub directories automatically.
217 */
218 public void setVisitorDirectory( File value )
219 {
220 if ( value != null && !value.isAbsolute() )
221 {
222 throw new IllegalArgumentException( "path is not absolute: " + value );
223 }
224 this.visitorDirectory = value;
225 }
226
227 /**
228 * Gets the absolute path to the output directory for the visitor files.
229 *
230 * @return The absolute path to the output directory for the visitor, only <code>null</code> if neither
231 * {@link #outputDirectory} nor {@link #visitorDirectory} have been set.
232 */
233 private File getEffectiveVisitorDirectory()
234 {
235 if ( this.visitorDirectory != null )
236 {
237 return this.visitorDirectory;
238 }
239 else if ( this.outputDirectory != null )
240 {
241 return new File( this.outputDirectory, getLastPackageName( getEffectiveVisitorPackageName() ) );
242 }
243 return null;
244 }
245
246 /**
247 * Sets the option "-p". Will overwrite the options "-np" and "-vp" if specified.
248 *
249 * @param value The option value, may be <code>null</code>.
250 */
251 public void setPackageName( String value )
252 {
253 this.packageName = value;
254 }
255
256 /**
257 * Sets the option "-np".
258 *
259 * @param value The option value, may be <code>null</code>.
260 */
261 public void setNodePackageName( String value )
262 {
263 this.nodePackageName = value;
264 }
265
266 /**
267 * Gets the effective package name for the syntax tree files.
268 *
269 * @return The effective package name for the syntax tree files, never <code>null</code>.
270 */
271 private String getEffectiveNodePackageName()
272 {
273 if ( this.packageName != null )
274 {
275 return ( this.packageName.length() <= 0 ) ? SYNTAX_TREE : this.packageName + '.' + SYNTAX_TREE;
276 }
277 else if ( this.nodePackageName != null )
278 {
279 return this.nodePackageName;
280 }
281 else
282 {
283 return SYNTAX_TREE;
284 }
285 }
286
287 /**
288 * Sets the option "-vp".
289 *
290 * @param value The option value, may be <code>null</code>.
291 */
292 public void setVisitorPackageName( String value )
293 {
294 this.visitorPackageName = value;
295 }
296
297 /**
298 * Gets the effective package name for the visitor files.
299 *
300 * @return The effective package name for the visitor files, never <code>null</code>.
301 */
302 private String getEffectiveVisitorPackageName()
303 {
304 if ( this.packageName != null )
305 {
306 return ( this.packageName.length() <= 0 ) ? VISITOR : this.packageName + '.' + VISITOR;
307 }
308 else if ( this.visitorPackageName != null )
309 {
310 return this.visitorPackageName;
311 }
312 else
313 {
314 return VISITOR;
315 }
316 }
317
318 /**
319 * Sets the option "-e".
320 *
321 * @param value The option value, may be <code>null</code>.
322 */
323 public void setSupressErrorChecking( Boolean value )
324 {
325 this.supressErrorChecking = value;
326 }
327
328 /**
329 * Sets the option "-jd".
330 *
331 * @param value The option value, may be <code>null</code>.
332 */
333 public void setJavadocFriendlyComments( Boolean value )
334 {
335 this.javadocFriendlyComments = value;
336 }
337
338 /**
339 * Sets the option "-f".
340 *
341 * @param value The option value, may be <code>null</code>.
342 */
343 public void setDescriptiveFieldNames( Boolean value )
344 {
345 this.descriptiveFieldNames = value;
346 }
347
348 /**
349 * Sets the option "-ns".
350 *
351 * @param value The option value, may be <code>null</code>.
352 */
353 public void setNodeParentClass( String value )
354 {
355 this.nodeParentClass = value;
356 }
357
358 /**
359 * Sets the option "-pp".
360 *
361 * @param value The option value, may be <code>null</code>.
362 */
363 public void setParentPointers( Boolean value )
364 {
365 this.parentPointers = value;
366 }
367
368 /**
369 * Sets the option "-tk".
370 *
371 * @param value The option value, may be <code>null</code>.
372 */
373 public void setSpecialTokens( Boolean value )
374 {
375 this.specialTokens = value;
376 }
377
378 /**
379 * Sets the toolkit option "-scheme".
380 *
381 * @param value The option value, may be <code>null</code>.
382 */
383 public void setScheme( Boolean value )
384 {
385 this.scheme = value;
386 }
387
388 /**
389 * Sets the toolkit option "-printer".
390 *
391 * @param value The option value, may be <code>null</code>.
392 */
393 public void setPrinter( Boolean value )
394 {
395 this.printer = value;
396 }
397
398 /**
399 * {@inheritDoc}
400 */
401 protected int execute()
402 throws Exception
403 {
404 String[] args = generateArguments();
405
406 if ( this.outputDirectory != null && !this.outputDirectory.exists() )
407 {
408 this.outputDirectory.mkdirs();
409 }
410
411 // fork JTB because of its lack to re-initialize its static parser
412 ForkedJvm jvm = new ForkedJvm();
413 jvm.setMainClass( "EDU.purdue.jtb.JTB" );
414 jvm.addArguments( args );
415 jvm.setSystemOut( new MojoLogStreamConsumer( false ) );
416 jvm.setSystemErr( new MojoLogStreamConsumer( true ) );
417 if ( getLog().isDebugEnabled() )
418 {
419 getLog().debug( "Forking: " + jvm );
420 }
421 int exitcode = jvm.run();
422
423 moveJavaFiles();
424
425 return exitcode;
426 }
427
428 /**
429 * Assembles the command line arguments for the invocation of JTB according to the configuration.
430 *
431 * @return A string array that represents the command line arguments to use for JTB.
432 */
433 private String[] generateArguments()
434 {
435 List argsList = new ArrayList();
436
437 argsList.add( "-np" );
438 argsList.add( getEffectiveNodePackageName() );
439
440 argsList.add( "-vp" );
441 argsList.add( getEffectiveVisitorPackageName() );
442
443 if ( this.supressErrorChecking != null && this.supressErrorChecking.booleanValue() )
444 {
445 argsList.add( "-e" );
446 }
447
448 if ( this.javadocFriendlyComments != null && this.javadocFriendlyComments.booleanValue() )
449 {
450 argsList.add( "-jd" );
451 }
452
453 if ( this.descriptiveFieldNames != null && this.descriptiveFieldNames.booleanValue() )
454 {
455 argsList.add( "-f" );
456 }
457
458 if ( this.nodeParentClass != null )
459 {
460 argsList.add( "-ns" );
461 argsList.add( this.nodeParentClass );
462 }
463
464 if ( this.parentPointers != null && this.parentPointers.booleanValue() )
465 {
466 argsList.add( "-pp" );
467 }
468
469 if ( this.specialTokens != null && this.specialTokens.booleanValue() )
470 {
471 argsList.add( "-tk" );
472 }
473
474 if ( this.scheme != null && this.scheme.booleanValue() )
475 {
476 argsList.add( "-scheme" );
477 }
478
479 if ( this.printer != null && this.printer.booleanValue() )
480 {
481 argsList.add( "-printer" );
482 }
483
484 File outputFile = getOutputFile();
485 if ( outputFile != null )
486 {
487 argsList.add( "-o" );
488 argsList.add( outputFile.getAbsolutePath() );
489 }
490
491 if ( this.inputFile != null )
492 {
493 argsList.add( this.inputFile.getAbsolutePath() );
494 }
495
496 return (String[]) argsList.toArray( new String[argsList.size()] );
497 }
498
499 /**
500 * Gets the last identifier from the specified package name. For example, returns "apache" upon input of
501 * "org.apache". JTB uses this approach to derive the output directories for the visitor and syntax tree files.
502 *
503 * @param name The package name from which to retrieve the last sub package, may be <code>null</code>.
504 * @return The name of the last sub package or <code>null</code> if the input was <code>null</code>
505 */
506 private String getLastPackageName( String name )
507 {
508 if ( name != null )
509 {
510 return name.substring( name.lastIndexOf( '.' ) + 1 );
511 }
512 return null;
513 }
514
515 /**
516 * Moves the previously generated Java files to their proper target directories. JTB simply assumes that the current
517 * working directory represents the parent package of the configured node/visitor packages which does not meet our
518 * needs.
519 *
520 * @throws IOException If the move failed.
521 */
522 private void moveJavaFiles()
523 throws IOException
524 {
525 File nodeSrcDir = new File( getLastPackageName( getEffectiveNodePackageName() ) ).getAbsoluteFile();
526 File nodeDstDir = getEffectiveNodeDirectory();
527 moveDirectory( nodeSrcDir, nodeDstDir );
528
529 File visitorSrcDir = new File( getLastPackageName( getEffectiveVisitorPackageName() ) ).getAbsoluteFile();
530 File visitorDstDir = getEffectiveVisitorDirectory();
531 moveDirectory( visitorSrcDir, visitorDstDir );
532 }
533
534 /**
535 * Moves all Java files generated by JTB from the specified source directory to the given target directory. Existing
536 * files in the target directory will be overwritten. Note that this move assumes a flat source directory, i.e.
537 * copying of sub directories is not supported.<br/><br/>This method must be used instead of
538 * {@link java.io.File#renameTo(java.io.File)} which would fail if the target directory already existed (at least on
539 * Windows).
540 *
541 * @param sourceDir The absolute path to the source directory, must not be <code>null</code>.
542 * @param targetDir The absolute path to the target directory, must not be <code>null</code>.
543 * @throws IOException If the move failed.
544 */
545 private void moveDirectory( File sourceDir, File targetDir )
546 throws IOException
547 {
548 getLog().debug( "Moving JTB output files: " + sourceDir + " -> " + targetDir );
549 /*
550 * NOTE: The source directory might be the current working directory if JTB was told to output into the default
551 * package. The current working directory might be quite anything and will likely contain sub directories not
552 * created by JTB. Therefore, we do a defensive move and only delete the expected Java source files.
553 */
554 File[] sourceFiles = sourceDir.listFiles();
555 if ( sourceFiles == null )
556 {
557 return;
558 }
559 for ( int i = 0; i < sourceFiles.length; i++ )
560 {
561 File sourceFile = sourceFiles[i];
562 if ( sourceFile.isFile() && sourceFile.getName().endsWith( ".java" ) )
563 {
564 try
565 {
566 FileUtils.copyFileToDirectory( sourceFile, targetDir );
567 if ( !sourceFile.delete() )
568 {
569 getLog().error( "Failed to delete original JTB output file: " + sourceFile );
570 }
571 }
572 catch ( Exception e )
573 {
574 throw new IOException( "Failed to move JTB output file: " + sourceFile + " -> " + targetDir );
575 }
576 }
577 }
578 if ( sourceDir.list().length <= 0 )
579 {
580 if ( !sourceDir.delete() )
581 {
582 getLog().error( "Failed to delete original JTB output directory: " + sourceDir );
583 }
584 }
585 else
586 {
587 getLog().debug( "Keeping non empty JTB output directory: " + sourceDir );
588 }
589 }
590
591 /**
592 * Gets a string representation of the command line arguments.
593 *
594 * @return A string representation of the command line arguments.
595 */
596 public String toString()
597 {
598 return Arrays.asList( generateArguments() ).toString();
599 }
600
601 /**
602 * Consume and log command line output from the JJDoc process.
603 */
604 class MojoLogStreamConsumer
605 implements StreamConsumer
606 {
607
608 /**
609 * The line prefix used by JTB to report infos.
610 */
611 private static final String INFO_PREFIX = "JTB: ";
612
613 /**
614 * Determines if the stream consumer is being used for <code>System.out</code> or <code>System.err</code>.
615 */
616 private boolean err;
617
618 /**
619 * Single param constructor.
620 *
621 * @param error If set to <code>true</code>, all consumed lines will be logged at the error level.
622 */
623 public MojoLogStreamConsumer( boolean error )
624 {
625 this.err = error;
626 }
627
628 /**
629 * Consume a line of text.
630 *
631 * @param line The line to consume.
632 */
633 public void consumeLine( String line )
634 {
635 if ( line.startsWith( "JTB version" ) )
636 {
637 getLog().debug( line );
638 }
639 else if ( line.startsWith( INFO_PREFIX ) )
640 {
641 getLog().debug( line.substring( INFO_PREFIX.length() ) );
642 }
643 else if ( this.err && line.length() > 0 )
644 {
645 getLog().error( line );
646 }
647 else
648 {
649 getLog().debug( line );
650 }
651 }
652 }
653
654 }