1/**
2[The "BSD licence"]
3
4ANTLR        - Copyright (c) 2005-2008 Terence Parr
5Maven Plugin - Copyright (c) 2009      Jim Idle
6
7All rights reserved.
8
9Redistribution and use in source and binary forms, with or without
10modification, are permitted provided that the following conditions
11are met:
121. Redistributions of source code must retain the above copyright
13notice, this list of conditions and the following disclaimer.
142. Redistributions in binary form must reproduce the above copyright
15notice, this list of conditions and the following disclaimer in the
16documentation and/or other materials provided with the distribution.
173. The name of the author may not be used to endorse or promote products
18derived from this software without specific prior written permission.
19
20THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/* ========================================================================
33 * This is the definitive ANTLR3 Mojo set. All other sets are belong to us.
34 */
35package org.antlr.mojo.antlr3;
36
37import org.apache.maven.plugin.AbstractMojo;
38import org.apache.maven.plugin.MojoExecutionException;
39import org.apache.maven.plugin.MojoFailureException;
40import org.apache.maven.project.MavenProject;
41
42import java.io.File;
43import java.io.IOException;
44import java.util.Collections;
45import java.util.HashSet;
46import java.util.Set;
47import org.antlr.Tool;
48import org.antlr.runtime.RecognitionException;
49import org.apache.maven.plugin.logging.Log;
50import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
51import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
52import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
53import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
54import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
55
56/**
57 * Goal that picks up all the ANTLR grammars in a project and moves those that
58 * are required for generation of the compilable sources into the location
59 * that we use to compile them, such as target/generated-sources/antlr3 ...
60 *
61 * @goal antlr
62 *
63 * @phase process-sources
64 * @requiresDependencyResolution compile
65 * @requiresProject true
66 *
67 * @author <a href="mailto:jimi@temporal-wave.com">Jim Idle</a>
68 */
69public class Antlr3Mojo
70        extends AbstractMojo {
71
72    // First, let's deal with the options that the ANTLR tool itself
73    // can be configured by.
74    //
75    /**
76     * If set to true, then after the tool has processed an input grammar file
77     * it will report various statistics about the parser, such as information
78     * on cyclic DFAs, which rules may use backtracking, and so on.
79     *
80     * @parameter default-value="false"
81     */
82    protected boolean report;
83    /**
84     * If set to true, then the ANTLR tool will print a version of the input
85     * grammar which is devoid of any actions that may be present in the input file.
86     *
87     * @parameter default-value="false"
88     */
89    protected boolean printGrammar;
90    /**
91     * If set to true, then the code generated by the ANTLR code generator will
92     * be set to debug mode. This means that when run, the code will 'hang' and
93     * wait for a debug connection on a TCP port (49100 by default).
94     *
95     * @parameter default-value="false"
96     */
97    protected boolean debug;
98    /**
99     * If set to true, then then the generated parser will compute and report on
100     * profile information at runtime.
101     *
102     * @parameter default-value="false"
103     */
104    protected boolean profile;
105    /**
106     * If set to true then the ANTLR tool will generate a description of the nfa
107     * for each rule in <a href="http://www.graphviz.org">Dot format</a>
108     *
109     * @parameter default-value="false"
110     */
111    protected boolean nfa;
112    /**
113     * If set to true then the ANTLR tool will generate a description of the DFA
114     * for each decision in the grammar in <a href="http://www.graphviz.org">Dot format</a>
115     *
116     * @parameter default-value="false"
117     */
118    protected boolean dfa;
119    /**
120     * If set to true, the generated parser code will log rule entry and exit points
121     * to stdout as an aid to debugging.
122     *
123     * @parameter default-value="false"
124     */
125    protected boolean trace;
126    /**
127     * If this parameter is set, it indicates that any warning or error messages returned
128     * by ANLTR, should be formatted in the specified way. Currently, ANTLR supports the
129     * built-in formats of antlr, gnu and vs2005.
130     *
131     * @parameter default-value="antlr"
132     */
133    protected String messageFormat;
134    /**
135     * If this parameter is set to true, then ANTLR will report all sorts of things
136     * about what it is doing such as the names of files and the version of ANTLR and so on.
137     *
138     * @parameter default-value="true"
139     */
140    protected boolean verbose;
141
142    /**
143     * The number of alts, beyond which ANTLR will not generate a switch statement
144     * for the DFA.
145     *
146     * @parameter default-value="300"
147     */
148    private int maxSwitchCaseLabels;
149
150    /**
151     * The number of alts, below which ANTLR will not choose to generate a switch
152     * statement over an if statement.
153     */
154    private int minSwitchAlts;
155
156    /* --------------------------------------------------------------------
157     * The following are Maven specific parameters, rather than specificlly
158     * options that the ANTLR tool can use.
159     */
160    /**
161     * Provides an explicit list of all the grammars that should
162     * be included in the generate phase of the plugin. Note that the plugin
163     * is smart enough to realize that imported grammars should be included but
164     * not acted upon directly by the ANTLR Tool.
165     *
166     * Unless otherwise specified, the include list scans for and includes all
167     * files that end in ".g" in any directory beneath src/main/antlr3. Note that
168     * this version of the plugin looks for the directory antlr3 and not the directory
169     * antlr, so as to avoid clashes and confusion for projects that use both v2 and v3 grammars
170     * such as ANTLR itself.
171     *
172     * @parameter
173     */
174    protected Set includes = new HashSet();
175    /**
176     * Provides an explicit list of any grammars that should be excluded from
177     * the generate phase of the plugin. Files listed here will not be sent for
178     * processing by the ANTLR tool.
179     *
180     * @parameter
181     */
182    protected Set excludes = new HashSet();
183    /**
184     * @parameter expression="${project}"
185     * @required
186     * @readonly
187     */
188    protected MavenProject project;
189    /**
190     * Specifies the Antlr directory containing grammar files. For
191     * antlr version 3.x we default this to a directory in the tree
192     * called antlr3 because the antlr directory is occupied by version
193     * 2.x grammars.
194     *
195     * @parameter default-value="${basedir}/src/main/antlr3"
196     * @required
197     */
198    private File sourceDirectory;
199    /**
200     * Location for generated Java files. For antlr version 3.x we default
201     * this to a directory in the tree called antlr3 because the antlr
202     * directory is occupied by version 2.x grammars.
203     *
204     * @parameter default-value="${project.build.directory}/generated-sources/antlr3"
205     * @required
206     */
207    private File outputDirectory;
208    /**
209     * Location for imported token files, e.g. <code>.tokens</code> and imported grammars.
210     * Note that ANTLR will not try to process grammars that it finds to be imported
211     * into other grammars (in the same processing session).
212     *
213     * @parameter default-value="${basedir}/src/main/antlr3/imports"
214     */
215    private File libDirectory;
216
217    public File getSourceDirectory() {
218        return sourceDirectory;
219    }
220
221    public File getOutputDirectory() {
222        return outputDirectory;
223    }
224
225    public File getLibDirectory() {
226        return libDirectory;
227    }
228
229    void addSourceRoot(File outputDir) {
230        project.addCompileSourceRoot(outputDir.getPath());
231    }
232    /**
233     * An instance of the ANTLR tool build
234     */
235    protected Tool tool;
236
237    /**
238     * The main entry point for this Mojo, it is responsible for converting
239     * ANTLR 3.x grammars into the target language specified by the grammar.
240     *
241     * @throws org.apache.maven.plugin.MojoExecutionException When something is discovered such as a missing source
242     * @throws org.apache.maven.plugin.MojoFailureException When something really bad happens such as not being able to create the ANTLR Tool
243     */
244    public void execute()
245            throws MojoExecutionException, MojoFailureException {
246
247        Log log = getLog();
248
249        // Check to see if the user asked for debug information, then dump all the
250        // parameters we have picked up if they did.
251        //
252        if (log.isDebugEnabled()) {
253
254            // Excludes
255            //
256            for (String e : (Set<String>) excludes) {
257
258                log.debug("ANTLR: Exclude: " + e);
259            }
260
261            // Includes
262            //
263            for (String e : (Set<String>) includes) {
264
265                log.debug("ANTLR: Include: " + e);
266            }
267
268            // Output location
269            //
270            log.debug("ANTLR: Output: " + outputDirectory);
271
272            // Library directory
273            //
274            log.debug("ANTLR: Library: " + libDirectory);
275
276            // Flags
277            //
278            log.debug("ANTLR: report              : " + report);
279            log.debug("ANTLR: printGrammar        : " + printGrammar);
280            log.debug("ANTLR: debug               : " + debug);
281            log.debug("ANTLR: profile             : " + profile);
282            log.debug("ANTLR: nfa                 : " + nfa);
283            log.debug("ANTLR: dfa                 : " + dfa);
284            log.debug("ANTLR: trace               : " + trace);
285            log.debug("ANTLR: messageFormat       : " + messageFormat);
286            log.debug("ANTLR: maxSwitchCaseLabels : " + maxSwitchCaseLabels);
287            log.debug("ANTLR: minSwitchAlts       : " + minSwitchAlts);
288            log.debug("ANTLR: verbose             : " + verbose);
289        }
290
291        // Ensure that the output directory path is all in tact so that
292        // ANTLR can just write into it.
293        //
294        File outputDir = getOutputDirectory();
295
296        if (!outputDir.exists()) {
297            outputDir.mkdirs();
298        }
299
300        // First thing we need is an instance of the ANTLR 3.1 build tool
301        //
302        try {
303            // ANTLR Tool buld interface
304            //
305            tool = new Tool();
306        } catch (Exception e) {
307            log.error("The attempt to create the ANTLR build tool failed, see exception report for details");
308
309            throw new MojoFailureException("Jim failed you!");
310        }
311
312        // Next we need to set the options given to us in the pom into the
313        // tool instance we have created.
314        //
315        tool.setDebug(debug);
316        tool.setGenerate_DFA_dot(dfa);
317        tool.setGenerate_NFA_dot(nfa);
318        tool.setProfile(profile);
319        tool.setReport(report);
320        tool.setPrintGrammar(printGrammar);
321        tool.setTrace(trace);
322        tool.setVerbose(verbose);
323        tool.setMessageFormat(messageFormat);
324        tool.setMaxSwitchCaseLabels(maxSwitchCaseLabels);
325        tool.setMinSwitchAlts(minSwitchAlts);
326
327        // Where do we want ANTLR to produce its output? (Base directory)
328        //
329        if (log.isDebugEnabled())
330        {
331            log.debug("Output directory base will be " + outputDirectory.getAbsolutePath());
332        }
333        tool.setOutputDirectory(outputDirectory.getAbsolutePath());
334
335        // Tell ANTLR that we always want the output files to be produced in the output directory
336        // using the same relative path as the input file was to the input directory.
337        //
338        tool.setForceRelativeOutput(true);
339
340        // Where do we want ANTLR to look for .tokens and import grammars?
341        //
342        tool.setLibDirectory(libDirectory.getAbsolutePath());
343
344        if (!sourceDirectory.exists()) {
345            if (log.isInfoEnabled()) {
346                log.info("No ANTLR grammars to compile in " + sourceDirectory.getAbsolutePath());
347            }
348            return;
349        } else {
350            if (log.isInfoEnabled()) {
351                log.info("ANTLR: Processing source directory " + sourceDirectory.getAbsolutePath());
352            }
353        }
354
355        // Set working directory for ANTLR to be the base source directory
356        //
357        tool.setInputDirectory(sourceDirectory.getAbsolutePath());
358
359        try {
360
361            // Now pick up all the files and process them with the Tool
362            //
363            processGrammarFiles(sourceDirectory, outputDirectory);
364
365        } catch (InclusionScanException ie) {
366
367            log.error(ie);
368            throw new MojoExecutionException("Fatal error occured while evaluating the names of the grammar files to analyze");
369
370        } catch (Exception e) {
371
372            getLog().error(e);
373            throw new MojoExecutionException(e.getMessage());
374        }
375
376
377
378        tool.process();
379
380        // If any of the grammar files caused errors but did nto throw exceptions
381        // then we should have accumulated errors in the counts
382        //
383        if (tool.getNumErrors() > 0) {
384            throw new MojoExecutionException("ANTLR caught " + tool.getNumErrors() + " build errors.");
385        }
386
387        // All looks good, so we need to tel Maven about the sources that
388        // we just created.
389        //
390        if (project != null) {
391            // Tell Maven that there are some new source files underneath
392            // the output directory.
393            //
394            addSourceRoot(this.getOutputDirectory());
395        }
396
397    }
398
399
400    /**
401     *
402     * @param sourceDirectory
403     * @param outputDirectory
404     * @throws antlr.TokenStreamException
405     * @throws antlr.RecognitionException
406     * @throws java.io.IOException
407     * @throws org.codehaus.plexus.compiler.util.scan.InclusionScanException
408     */
409    private void processGrammarFiles(File sourceDirectory, File outputDirectory)
410            throws RecognitionException, IOException, InclusionScanException {
411        // Which files under the source set should we be looking for as grammar files
412        //
413        SourceMapping mapping = new SuffixMapping("g", Collections.EMPTY_SET);
414
415        // What are the sets of includes (defaulted or otherwise).
416        //
417        Set includes = getIncludesPatterns();
418
419        // Now, to the excludes, we need to add the imports directory
420        // as this is autoscanned for importd grammars and so is auto-excluded from the
421        // set of gramamr fiels we shuold be analyzing.
422        //
423        excludes.add("imports/**");
424
425        SourceInclusionScanner scan = new SimpleSourceInclusionScanner(includes, excludes);
426
427        scan.addSourceMapping(mapping);
428        Set grammarFiles = scan.getIncludedSources(sourceDirectory, null);
429
430        if (grammarFiles.isEmpty()) {
431            if (getLog().isInfoEnabled()) {
432                getLog().info("No grammars to process");
433            }
434        } else {
435
436            // Tell the ANTLR tool that we want sorted build mode
437            //
438            tool.setMake(true);
439
440            // Iterate each grammar file we were given and add it into the tool's list of
441            // grammars to process.
442            //
443            for (File grammar : (Set<File>) grammarFiles) {
444
445                if (getLog().isDebugEnabled()) {
446                    getLog().debug("Grammar file '" + grammar.getPath() + "' detected.");
447                }
448
449
450                String relPath = findSourceSubdir(sourceDirectory, grammar.getPath()) + grammar.getName();
451
452                if (getLog().isDebugEnabled()) {
453                    getLog().debug("  ... relative path is: " + relPath);
454                }
455                tool.addGrammarFile(relPath);
456
457            }
458
459        }
460
461
462    }
463
464    public Set getIncludesPatterns() {
465        if (includes == null || includes.isEmpty()) {
466            return Collections.singleton("**/*.g");
467        }
468        return includes;
469    }
470
471    /**
472     * Given the source directory File object and the full PATH to a
473     * grammar, produce the path to the named grammar file in relative
474     * terms to the sourceDirectory. This will then allow ANTLR to
475     * produce output relative to the base of the output directory and
476     * reflect the input organization of the grammar files.
477     *
478     * @param sourceDirectory The source directory File object
479     * @param grammarFileName The full path to the input grammar file
480     * @return The path to the grammar file relative to the source directory
481     */
482    private String findSourceSubdir(File sourceDirectory, String grammarFileName) {
483        String srcPath = sourceDirectory.getPath() + File.separator;
484
485        if (!grammarFileName.startsWith(srcPath)) {
486            throw new IllegalArgumentException("expected " + grammarFileName + " to be prefixed with " + sourceDirectory);
487        }
488
489        File unprefixedGrammarFileName = new File(grammarFileName.substring(srcPath.length()));
490
491        return unprefixedGrammarFileName.getParent() + File.separator;
492    }
493}
494