1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2 * JFlex Maven2 plugin *
3 * Copyright (c) 2007 Régis Décamps <decamps@users.sf.net> *
4 * *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License. See the file *
8 * COPYRIGHT for more information. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License along *
16 * with this program; if not, write to the Free Software Foundation, Inc., *
17 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
18 * *
19 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
20 package org.codehaus.mojo.jflex;
21
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Iterator;
28 import java.util.List;
29
30 import org.apache.commons.io.FileUtils;
31 import org.apache.maven.plugin.AbstractMojo;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugin.MojoFailureException;
34 import org.apache.maven.project.MavenProject;
35
36 import JFlex.Main;
37 import JFlex.Options;
38
39 /**
40 * Generates lexical scanners from one or more <a href="http://jflex.de/">JFlex</a>
41 * grammer files.
42 *
43 * @goal generate
44 * @phase generate-sources
45 * @author Régis Décamps (decamps@users.sf.net)
46 *
47 */
48 public class JFlexMojo extends AbstractMojo {
49 /**
50 * Name of the directory where to look for jflex files by default.
51 */
52 public static final String SRC_MAIN_JFLEX = "src/main/jflex";
53
54 /**
55 * @parameter expression="${project}"
56 * @required
57 * @readonly
58 */
59 private MavenProject project;
60
61 // cannot use {@value SRC_MAIN_JFLEX} because Maven site goals.html
62 // is kept raw.
63 /**
64 * List of grammar definitions to run the JFlex parser generator on.
65 * Each path may either specify a single grammar file or a directory.
66 * Directories will be recursively scanned for files with one of the
67 * following extensions: ".jflex", ".flex", ".jlex" or ".lex".
68 * By default, all files in <code>src/main/jflex</code> will be
69 * processed.
70 *
71 * @see #SRC_MAIN_JFLEX
72 * @parameter
73 */
74 private File[] lexDefinitions;
75
76 /**
77 * Name of the directory into which JFlex should generate the parser.
78 *
79 * @parameter expression="${project.build.directory}/generated-sources/jflex"
80 */
81 private File outputDirectory;
82
83 /**
84 * The granularity in milliseconds of the last modification date for
85 * testing whether a source needs regeneration.
86 *
87 * @parameter expression="${lastModGranularityMs}" default-value="0"
88 */
89 private int staleMillis;
90
91 /**
92 * Whether source code generation should be verbose.
93 *
94 * @parameter default-value="false"
95 */
96 private boolean verbose;
97
98 /**
99 * Whether to produce graphviz .dot files for the generated automata. This
100 * feature is EXPERIMENTAL.
101 *
102 * @parameter default-value="false"
103 */
104 private boolean dot;
105
106 /**
107 * Use external skeleton file.
108 *
109 * @parameter
110 */
111 private File skeleton;
112
113 /**
114 * Strict JLex compatibility.
115 *
116 * @parameter default-value="false"
117 */
118 private boolean jlex;
119
120 /**
121 * The generation method to use for the scanner. Valid values are
122 * <code>switch</code>, <code>table</code> and <code>pack</code>.
123 * Please see the <a href="http://jflex.de/manual.html#CodeGeneration">JFlex
124 * manual</a> for more details about the various methods.
125 *
126 * @parameter default-value="pack"
127 */
128 private String generationMethod = "pack";
129
130 /**
131 * A flag whether to perform the DFA minimization step during scanner
132 * generation.
133 *
134 * @parameter default-value="true"
135 */
136 private boolean minimize = true;
137
138 /**
139 * A flag whether to enable the generation of a backup copy if the generated
140 * source file already exists.
141 *
142 * @parameter default-value="true"
143 */
144 private boolean backup = true;
145
146 /**
147 * Generate java parsers from lexer definition files.
148 *
149 * This methods is checks parameters, sets options and calls
150 * JFlex.Main.generate()
151 */
152 public void execute() throws MojoExecutionException, MojoFailureException {
153 this.outputDirectory = getAbsolutePath(this.outputDirectory);
154
155 // compiling the generated source in target/generated-sources/ is
156 // the whole point of this plugin compared to running the ant plugin
157 project.addCompileSourceRoot(outputDirectory.getPath());
158
159 List/*<File>*/ filesIt;
160 if (lexDefinitions != null) {
161 // use arguments provided in the plugin configuration
162 filesIt = Arrays.asList(lexDefinitions);
163
164 getLog().debug("Parsing " + lexDefinitions.length
165 + " jflex files or directories given in configuration");
166 } else {
167 // use default lexfiles if none provided
168 getLog().debug("Use lexer files found in (default) " + SRC_MAIN_JFLEX);
169 filesIt = new ArrayList/*<File>*/();
170 File defaultDir = getAbsolutePath(new File(SRC_MAIN_JFLEX));
171 if (defaultDir.isDirectory()) {
172 filesIt.add(defaultDir);
173 }
174 }
175 // process all lexDefinitions
176 Iterator/*<File>*/ fileIterator = filesIt.iterator();
177 while (fileIterator.hasNext()) {
178 File lexDefinition = (File) fileIterator.next();
179 lexDefinition = getAbsolutePath(lexDefinition);
180
181 parseLexDefinition(lexDefinition);
182 }
183 }
184
185 /**
186 * Generate java code of a parser from a lexer file.
187 *
188 * If the {@code lexDefinition} is a directory, process all lexer files
189 * contained within.
190 *
191 * @param lexDefinition
192 * Lexer definiton file or directory to process.
193 * @throws MojoFailureException
194 * if the file is not found.
195 * @throws MojoExecutionException
196 */
197 private void parseLexDefinition(File lexDefinition)
198 throws MojoFailureException, MojoExecutionException {
199
200 if (lexDefinition.isDirectory()) {
201 // recursively process files contained within
202 String[] extensions = { "jflex", "jlex", "lex", "flex" };
203 getLog().debug("Processing lexer files found in "
204 + lexDefinition);
205 Iterator/*<File>*/ fileIterator = FileUtils.iterateFiles(lexDefinition,
206 extensions, true);
207 while (fileIterator.hasNext()) {
208 File lexFile = (File) fileIterator.next();
209 parseLexFile(lexFile);
210 }
211 } else {
212 parseLexFile(lexDefinition);
213 }
214 }
215
216 private void parseLexFile(File lexFile) throws MojoFailureException,
217 MojoExecutionException {
218
219 getLog().debug("Generationg Java code from " + lexFile.getName());
220 ClassInfo classInfo = null;
221 try {
222 classInfo = LexSimpleAnalyzer.guessPackageAndClass(lexFile);
223 } catch (FileNotFoundException e1) {
224 throw new MojoFailureException(e1.getMessage());
225 } catch (IOException e3) {
226 classInfo = new ClassInfo();
227 classInfo.className = LexSimpleAnalyzer.DEFAULT_NAME;
228 classInfo.packageName = null;
229 }
230
231 checkParameters(lexFile);
232
233 /* set destination directory */
234 File generatedFile = new File(outputDirectory,
235 classInfo.getOutputFilename());
236
237 /* Generate only if needs to */
238 if (lexFile.lastModified() - generatedFile.lastModified() <= this.staleMillis) {
239 getLog().info(" " + generatedFile.getName() + " is up to date.");
240 getLog().debug("StaleMillis = "+staleMillis+"ms");
241 return;
242 }
243
244 /*
245 * set options. Very strange that JFlex expects this in a static way.
246 */
247 Options.setDefaults();
248 Options.setDir(generatedFile.getParentFile());
249 Options.dump = verbose;
250 Options.verbose = verbose;
251 Options.dot = dot;
252 if (skeleton != null) {
253 Options.setSkeleton(skeleton);
254 }
255 Options.jlex = jlex;
256
257 Options.no_minimize = !minimize;
258 Options.no_backup = !backup;
259 if ("switch".equals(generationMethod)) {
260 Options.gen_method = Options.SWITCH;
261 } else if ("table".equals(generationMethod)) {
262 Options.gen_method = Options.TABLE;
263 } else if ("pack".equals(generationMethod)) {
264 Options.gen_method = Options.PACK;
265 } else {
266 throw new MojoExecutionException("Illegal generation method: "
267 + generationMethod);
268 }
269
270 try {
271 Main.generate(lexFile);
272 getLog().info(" generated " + generatedFile);
273 } catch (Exception e) {
274 throw new MojoExecutionException(e.getMessage());
275 }
276 }
277
278 /**
279 * Check parameter lexFile.
280 *
281 * Must not be null and file must exist.
282 *
283 * @param lexFile
284 * input file to check.
285 * @throws MojoExecutionException
286 * in case of error
287 */
288 private void checkParameters(File lexFile) throws MojoExecutionException {
289 if (lexFile == null) {
290 throw new MojoExecutionException(
291 "<lexDefinition> is empty. Please define input file with <lexDefinition>input.jflex</lexDefinition>");
292 }
293 if (!lexFile.isFile()) {
294 throw new MojoExecutionException("Input file does not exist: "
295 + lexFile);
296 }
297 }
298
299 /**
300 * Converts the specified path argument into an absolute path. If the path
301 * is relative like "src/main/jflex", it is resolved against the base
302 * directory of the project (in constrast, File.getAbsoluteFile() would
303 * resolve against the current directory which may be different, especially
304 * during a reactor build).
305 *
306 * @param path
307 * The path argument to convert, may be {@code null}.
308 * @return The absolute path corresponding to the input argument.
309 */
310 protected File getAbsolutePath(File path) {
311 if (path == null || path.isAbsolute()) {
312 return path;
313 }
314 return new File(this.project.getBasedir().getAbsolutePath(), path.getPath());
315 }
316
317 }