View Javadoc

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 }