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