View Javadoc

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 }