1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.antlr.mojo.antlr4;
31
32 import org.antlr.v4.Tool;
33 import org.antlr.v4.codegen.CodeGenerator;
34 import org.antlr.v4.runtime.misc.MultiMap;
35 import org.antlr.v4.runtime.misc.NotNull;
36 import org.antlr.v4.runtime.misc.Utils;
37 import org.antlr.v4.tool.Grammar;
38 import org.apache.maven.plugin.AbstractMojo;
39 import org.apache.maven.plugin.MojoExecutionException;
40 import org.apache.maven.plugin.MojoFailureException;
41 import org.apache.maven.plugin.logging.Log;
42 import org.apache.maven.plugins.annotations.Component;
43 import org.apache.maven.plugins.annotations.LifecyclePhase;
44 import org.apache.maven.plugins.annotations.Mojo;
45 import org.apache.maven.plugins.annotations.Parameter;
46 import org.apache.maven.plugins.annotations.ResolutionScope;
47 import org.apache.maven.project.MavenProject;
48 import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
49 import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
50 import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
51 import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
52 import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
53 import org.sonatype.plexus.build.incremental.BuildContext;
54
55 import java.io.BufferedWriter;
56 import java.io.File;
57 import java.io.IOException;
58 import java.io.OutputStream;
59 import java.io.OutputStreamWriter;
60 import java.io.StringWriter;
61 import java.io.Writer;
62 import java.net.URI;
63 import java.util.ArrayList;
64 import java.util.Arrays;
65 import java.util.Collections;
66 import java.util.HashSet;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70
71
72
73
74
75
76
77 @Mojo(
78 name = "antlr4",
79 defaultPhase = LifecyclePhase.GENERATE_SOURCES,
80 requiresDependencyResolution = ResolutionScope.COMPILE,
81 requiresProject = true)
82 public class Antlr4Mojo extends AbstractMojo {
83
84
85
86
87
88
89
90
91
92 @Parameter(property = "antlr4.atn", defaultValue = "false")
93 protected boolean atn;
94
95
96
97
98 @Parameter(property = "project.build.sourceEncoding")
99 protected String encoding;
100
101
102
103
104 @Parameter(property = "antlr4.listener", defaultValue = "true")
105 protected boolean listener;
106
107
108
109
110 @Parameter(property = "antlr4.visitor", defaultValue = "false")
111 protected boolean visitor;
112
113
114
115
116 @Parameter(property = "antlr4.treatWarningsAsErrors", defaultValue = "false")
117 protected boolean treatWarningsAsErrors;
118
119
120
121
122 @Parameter(property = "antlr4.forceATN", defaultValue = "false")
123 protected boolean forceATN;
124
125
126
127
128
129
130 @Parameter
131 protected Map<String, String> options;
132
133
134
135
136 @Parameter
137 protected List<String> arguments;
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 @Parameter
155 protected Set<String> includes = new HashSet<String>();
156
157
158
159
160
161 @Parameter
162 protected Set<String> excludes = new HashSet<String>();
163
164
165
166 @Parameter(property = "project", required = true, readonly = true)
167 protected MavenProject project;
168
169
170
171
172 @Parameter(defaultValue = "${basedir}/src/main/antlr4")
173 private File sourceDirectory;
174
175
176
177
178 @Parameter(defaultValue = "${project.build.directory}/generated-sources/antlr4")
179 private File outputDirectory;
180
181
182
183
184 @Parameter(defaultValue = "${basedir}/src/main/antlr4/imports")
185 private File libDirectory;
186
187 @Component
188 private BuildContext buildContext;
189
190 public File getSourceDirectory() {
191 return sourceDirectory;
192 }
193
194 public File getOutputDirectory() {
195 return outputDirectory;
196 }
197
198 public File getLibDirectory() {
199 return libDirectory;
200 }
201
202 void addSourceRoot(File outputDir) {
203 project.addCompileSourceRoot(outputDir.getPath());
204 }
205
206
207
208
209 protected Tool tool;
210
211
212
213
214
215
216
217
218
219
220 @Override
221 public void execute() throws MojoExecutionException, MojoFailureException {
222
223 Log log = getLog();
224
225 if (log.isDebugEnabled()) {
226 for (String e : excludes) {
227 log.debug("ANTLR: Exclude: " + e);
228 }
229
230 for (String e : includes) {
231 log.debug("ANTLR: Include: " + e);
232 }
233
234 log.debug("ANTLR: Output: " + outputDirectory);
235 log.debug("ANTLR: Library: " + libDirectory);
236 }
237
238 if (!sourceDirectory.isDirectory()) {
239 log.info("No ANTLR 4 grammars to compile in " + sourceDirectory.getAbsolutePath());
240 return;
241 }
242
243
244
245
246 File outputDir = getOutputDirectory();
247
248 if (!outputDir.exists()) {
249 outputDir.mkdirs();
250 }
251
252
253
254
255 List<List<String>> argumentSets;
256 try {
257 List<String> args = getCommandArguments();
258 argumentSets = processGrammarFiles(args, sourceDirectory);
259 } catch (InclusionScanException ie) {
260 log.error(ie);
261 throw new MojoExecutionException("Fatal error occured while evaluating the names of the grammar files to analyze", ie);
262 }
263
264 log.debug("Output directory base will be " + outputDirectory.getAbsolutePath());
265 log.info("ANTLR 4: Processing source directory " + sourceDirectory.getAbsolutePath());
266 for (List<String> args : argumentSets) {
267 try {
268
269 tool = new CustomTool(args.toArray(new String[args.size()]));
270 } catch (Exception e) {
271 log.error("The attempt to create the ANTLR 4 build tool failed, see exception report for details", e);
272 throw new MojoFailureException("Error creating an instanceof the ANTLR tool.", e);
273 }
274
275
276 tool.inputDirectory = sourceDirectory;
277
278 tool.processGrammarsOnCommandLine();
279
280
281
282 if (tool.getNumErrors() > 0) {
283 throw new MojoExecutionException("ANTLR 4 caught " + tool.getNumErrors() + " build errors.");
284 }
285 }
286
287 if (project != null) {
288
289 addSourceRoot(this.getOutputDirectory());
290 }
291 }
292
293 private List<String> getCommandArguments() {
294 List<String> args = new ArrayList<String>();
295
296 if (getOutputDirectory() != null) {
297 args.add("-o");
298 args.add(outputDirectory.getAbsolutePath());
299 }
300
301
302 if (getLibDirectory() != null && getLibDirectory().isDirectory()) {
303 args.add("-lib");
304 args.add(libDirectory.getAbsolutePath());
305 }
306
307
308
309 if (atn) {
310 args.add("-atn");
311 }
312
313 if (encoding != null && !encoding.isEmpty()) {
314 args.add("-encoding");
315 args.add(encoding);
316 }
317
318 if (listener) {
319 args.add("-listener");
320 }
321 else {
322 args.add("-no-listener");
323 }
324
325 if (visitor) {
326 args.add("-visitor");
327 }
328 else {
329 args.add("-no-visitor");
330 }
331
332 if (treatWarningsAsErrors) {
333 args.add("-Werror");
334 }
335
336 if (forceATN) {
337 args.add("-Xforce-atn");
338 }
339
340 if (options != null) {
341 for (Map.Entry<String, String> option : options.entrySet()) {
342 args.add(String.format("-D%s=%s", option.getKey(), option.getValue()));
343 }
344 }
345
346 if (arguments != null) {
347 args.addAll(arguments);
348 }
349
350 return args;
351 }
352
353
354
355
356
357
358 @NotNull
359 private List<List<String>> processGrammarFiles(List<String> args, File sourceDirectory) throws InclusionScanException {
360
361 SourceMapping mapping = new SuffixMapping("g4", Collections.<String>emptySet());
362
363
364 Set<String> includes = getIncludesPatterns();
365
366
367
368
369 excludes.add("imports/**");
370
371 SourceInclusionScanner scan = new SimpleSourceInclusionScanner(includes, excludes);
372 scan.addSourceMapping(mapping);
373 Set<File> grammarFiles = scan.getIncludedSources(sourceDirectory, null);
374
375 if (grammarFiles.isEmpty()) {
376 getLog().info("No grammars to process");
377 return Collections.emptyList();
378 }
379
380 MultiMap<String, File> grammarFileByFolder = new MultiMap<String, File>();
381
382
383 for (File grammarFile : grammarFiles) {
384 if (!buildContext.hasDelta(grammarFile)) {
385 continue;
386 }
387
388 buildContext.removeMessages(grammarFile);
389
390 getLog().debug("Grammar file '" + grammarFile.getPath() + "' detected.");
391
392 String relPathBase = findSourceSubdir(sourceDirectory, grammarFile.getPath());
393 String relPath = relPathBase + grammarFile.getName();
394 getLog().debug(" ... relative path is: " + relPath);
395
396 grammarFileByFolder.map(relPathBase, grammarFile);
397 }
398
399 List<List<String>> result = new ArrayList<List<String>>();
400 for (Map.Entry<String, List<File>> entry : grammarFileByFolder.entrySet()) {
401 List<String> folderArgs = new ArrayList<String>(args);
402 if (!folderArgs.contains("-package") && !entry.getKey().isEmpty()) {
403 folderArgs.add("-package");
404 folderArgs.add(getPackageName(entry.getKey()));
405 }
406
407 for (File file : entry.getValue()) {
408 folderArgs.add(entry.getKey() + file.getName());
409 }
410
411 result.add(folderArgs);
412 }
413
414 return result;
415 }
416
417 private static String getPackageName(String relativeFolderPath) {
418 if (relativeFolderPath.contains("..")) {
419 throw new UnsupportedOperationException("Cannot handle relative paths containing '..'");
420 }
421
422 List<String> parts = new ArrayList<String>(Arrays.asList(relativeFolderPath.split("[/\\\\\\.]+")));
423 while (parts.remove("")) {
424
425 }
426
427 return Utils.join(parts.iterator(), ".");
428 }
429
430 public Set<String> getIncludesPatterns() {
431 if (includes == null || includes.isEmpty()) {
432 return Collections.singleton("**/*.g4");
433 }
434 return includes;
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448 private String findSourceSubdir(File sourceDirectory, String grammarFileName) {
449 String srcPath = sourceDirectory.getPath() + File.separator;
450
451 if (!grammarFileName.startsWith(srcPath)) {
452 throw new IllegalArgumentException("expected " + grammarFileName + " to be prefixed with " + sourceDirectory);
453 }
454
455 File unprefixedGrammarFileName = new File(grammarFileName.substring(srcPath.length()));
456 if (unprefixedGrammarFileName.getParent() == null) {
457 return "";
458 }
459
460 return unprefixedGrammarFileName.getParent() + File.separator;
461 }
462
463 private final class CustomTool extends Tool {
464
465 public CustomTool(String[] args) {
466 super(args);
467 addListener(new Antlr4ErrorLog(this, buildContext, getLog()));
468 }
469
470 @Override
471 public void process(Grammar g, boolean gencode) {
472 getLog().info("Processing grammar: " + g.fileName);
473 super.process(g, gencode);
474 }
475
476 @Override
477 public Writer getOutputFileWriter(Grammar g, String fileName) throws IOException {
478 if (outputDirectory == null) {
479 return new StringWriter();
480 }
481
482
483
484
485
486 File outputDir;
487 if ( fileName.endsWith(CodeGenerator.VOCAB_FILE_EXTENSION) ) {
488 outputDir = new File(outputDirectory);
489 }
490 else {
491 outputDir = getOutputDirectory(g.fileName);
492 }
493
494 File outputFile = new File(outputDir, fileName);
495 if (!outputDir.exists()) {
496 outputDir.mkdirs();
497 }
498
499 URI relativePath = project.getBasedir().toURI().relativize(outputFile.toURI());
500 getLog().debug(" Writing file: " + relativePath);
501 OutputStream outputStream = buildContext.newFileOutputStream(outputFile);
502 return new BufferedWriter(new OutputStreamWriter(outputStream));
503 }
504 }
505 }