1 | /* |
2 | * Copyright 2005-2006 The RbUtils Project |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | * |
16 | */ |
17 | |
18 | // $Id: AbstractRbTask.java,v 1.5 2007/07/08 01:15:31 moishi Exp $ |
19 | |
20 | package org.ktc.rbutils.rb; |
21 | |
22 | import java.io.BufferedOutputStream; |
23 | import java.io.File; |
24 | import java.io.FileNotFoundException; |
25 | import java.io.FileOutputStream; |
26 | import java.io.IOException; |
27 | import java.io.OutputStream; |
28 | import java.util.ArrayList; |
29 | import java.util.Arrays; |
30 | import java.util.Iterator; |
31 | import java.util.List; |
32 | import java.util.Locale; |
33 | |
34 | import org.apache.tools.ant.BuildException; |
35 | import org.apache.tools.ant.DirectoryScanner; |
36 | import org.apache.tools.ant.Project; |
37 | import org.apache.tools.ant.Task; |
38 | import org.apache.tools.ant.taskdefs.LogOutputStream; |
39 | import org.apache.tools.ant.types.EnumeratedAttribute; |
40 | import org.apache.tools.ant.types.FileSet; |
41 | import org.ktc.rbutils.api.audit.Logger; |
42 | import org.ktc.rbutils.api.audit.MainProcessor; |
43 | import org.ktc.rbutils.api.i18n.LocaleUtils; |
44 | import org.ktc.rbutils.rb.check.DefaultLogger; |
45 | import org.ktc.rbutils.rb.check.RbCheckerTask; |
46 | import org.ktc.rbutils.rb.check.XmlLogger; |
47 | import org.ktc.rbutils.rb.generation.RbGeneratorTask; |
48 | |
49 | /** |
50 | * Abstract class for RbUtils ant tasks. Provides common options for check and generation. This |
51 | * includes logging system. |
52 | * @since RbUtils 0.9.1 |
53 | * @version $Revision: 1.5 $ |
54 | * @author redfish |
55 | */ |
56 | public abstract class AbstractRbTask extends Task { |
57 | /** Enum value for an xml logger. */ |
58 | private static final String LOGGERTYPE_XML = "xml"; |
59 | /** Enum value for an plain logger. */ |
60 | private static final String LOGGERTYPE_PLAIN = "plain"; |
61 | |
62 | /** Locale for results display. */ |
63 | protected Locale displayLocale; |
64 | /** |
65 | * The maximum number of errors that are tolerated before breaking the build or setting the |
66 | * failure property. |
67 | */ |
68 | protected int maxErrors; |
69 | /** Specifies whether the build will break if errors occur. */ |
70 | protected boolean failOnErrors = true; |
71 | /** The name of the property to set if errors occur. */ |
72 | protected String failureProperty; |
73 | |
74 | /** Contains the loggers to log to. */ |
75 | private final List loggersList = new ArrayList(); |
76 | |
77 | /** Contains the filesets to process. */ |
78 | protected final List fileSets = new ArrayList(); |
79 | /** Root directory used to find properties file. */ |
80 | protected File root; |
81 | /** Extension of properties file. */ |
82 | protected String extension; |
83 | |
84 | /** |
85 | * Processor used by the task to do the job. |
86 | */ |
87 | protected MainProcessor processor; |
88 | |
89 | /** |
90 | * The type of the Task. This must be initialized by subclasses. |
91 | */ |
92 | protected TaskType taskType; |
93 | |
94 | |
95 | /** |
96 | * Instanciates a new AbstractRbTask. |
97 | */ |
98 | public AbstractRbTask() { |
99 | super(); |
100 | } |
101 | |
102 | /** |
103 | * Launches the check. |
104 | */ |
105 | protected void doJob() { |
106 | // TODO Code - validate args in order to get explicit exception (?) |
107 | // MainChecker processor = null; |
108 | try { |
109 | specificTaskinitilization(); |
110 | processor.addLoggers(getLoggers()); |
111 | processor.setMessagesLocale(displayLocale); |
112 | |
113 | // Set the list of files to be inspected |
114 | // If the user specify files with filesets, the checker does not need to look for files |
115 | // in the root directory. So set the filesAlreadyAdded attribute. |
116 | // Otherwise, the checker will look for files by itself. |
117 | // TODO: we should tests if the filesets is empty in the getFilesMethod when the |
118 | // addFiles method will be refactored |
119 | if (!fileSets.isEmpty()) { |
120 | processor.addFiles(getFiles()); |
121 | processor.setFilesAlreadyAdded(true); |
122 | } |
123 | |
124 | // Launch the check |
125 | logTaskStarts(); |
126 | final int numErrors = processor.process(); |
127 | |
128 | // RBUTILS-028: Display the number of processed files |
129 | log("Processed Files: " + processor.getProcessedFilesCount(), Project.MSG_INFO); |
130 | |
131 | handleErrors(numErrors); |
132 | } |
133 | // TODO Code - Exception catching |
134 | catch (final BuildException buildexception) { |
135 | throw buildexception; |
136 | } |
137 | catch (final Exception exception) { |
138 | throw new BuildException(exception, getLocation()); |
139 | } |
140 | finally { |
141 | processor = null; |
142 | } |
143 | } |
144 | |
145 | /** |
146 | * Adds specific Initialization to the task. This let subclasses instanciate the processor and |
147 | * set processor properties . |
148 | * @throws FileNotFoundException if a file is not found on processor instanciation. |
149 | */ |
150 | protected abstract void specificTaskinitilization() throws FileNotFoundException; |
151 | |
152 | /** |
153 | * Display, via the ant loggers, that the task starts. |
154 | */ |
155 | protected void logTaskStarts() { |
156 | log(CommonProps.getRunningTask(taskType), Project.MSG_INFO); |
157 | } |
158 | |
159 | /** |
160 | * Adds an Ant Logger. |
161 | * @param logger the ant logger to be added for logging. |
162 | */ |
163 | public void addLogger(final Log logger) { |
164 | loggersList.add(logger); |
165 | } |
166 | |
167 | /** |
168 | * Returns the list of listeners set in this task. |
169 | * <p> |
170 | * If no loggers are registered, creates a new DefaultLogger using the ant logging system and |
171 | * adds it to the returned list. |
172 | * @return the list of listeners. |
173 | * @throws IOException if an error occurs. |
174 | */ |
175 | protected List getLoggers() throws IOException { |
176 | final List internalLoggerslist = new ArrayList(); |
177 | |
178 | // If no logger is set, create a DefaultLogger which display messges using ant logging |
179 | // system and adds it to the returned list |
180 | if (loggersList.size() == 0) { |
181 | // TODO Code - to be removed because this is already done in the AbstractMainProcessor |
182 | log("no logger registered", Project.MSG_VERBOSE); |
183 | |
184 | final Log antlogger = new Log(); |
185 | final LoggerType loggertype = new LoggerType(); |
186 | loggertype.setValue(LOGGERTYPE_PLAIN); |
187 | antlogger.setType(loggertype); |
188 | |
189 | log("adding a default plain text listener", Project.MSG_VERBOSE); |
190 | internalLoggerslist.add(antlogger.createLogger(this)); |
191 | } |
192 | // Otherwise, create listeners for all registered loggers |
193 | else { |
194 | log("found registered loggers", Project.MSG_VERBOSE); |
195 | |
196 | for (final Iterator iterLogger = loggersList.iterator(); iterLogger.hasNext();) { |
197 | final Log antlogger = (Log) iterLogger.next(); |
198 | log("--found logger : type " + antlogger.getLoggerType() + " with file " |
199 | + antlogger.getOutputFile(), Project.MSG_VERBOSE); |
200 | final Logger logger = antlogger.createLogger(this); |
201 | log("--adding listener " + logger.getClass().getName(), Project.MSG_VERBOSE); |
202 | internalLoggerslist.add(logger); |
203 | } |
204 | log("end of found registered loggers", Project.MSG_VERBOSE); |
205 | } |
206 | return internalLoggerslist; |
207 | } |
208 | |
209 | /** |
210 | * Scans all optional filesets to find files to check and returns the list of found files. |
211 | * <p> |
212 | * The list contains <code>File</code> objects. |
213 | * @return the list of files found with the filesets of the task. |
214 | */ |
215 | protected List getFiles() { |
216 | final List filesList = new ArrayList(); |
217 | |
218 | // Scan all filesets of the task |
219 | for (final Iterator itFSets = fileSets.iterator(); itFSets.hasNext();) { |
220 | // Get all filenames included in this fileset |
221 | final FileSet fs = (FileSet) itFSets.next(); |
222 | final DirectoryScanner ds = fs.getDirectoryScanner(getProject()); |
223 | ds.scan(); |
224 | final File basedir = ds.getBasedir(); |
225 | final String[] names = ds.getIncludedFiles(); |
226 | |
227 | // Add all found files to the files list |
228 | log("Adding " + names.length + " files from directory " + basedir, Project.MSG_VERBOSE); |
229 | for (int i = 0; i < names.length; i++) { |
230 | final String filename = names[i]; |
231 | filesList.add(new File(basedir, filename)); |
232 | log(" " + filename + " added", Project.MSG_VERBOSE); |
233 | } |
234 | } |
235 | |
236 | return filesList; |
237 | } |
238 | // TODO Code - classloader (voir ClasspathUtils.getClassLoaderForPath() |
239 | // /** |
240 | // * Creates and configures an AntClassLoader instance from the |
241 | // * nested classpath element. |
242 | // * |
243 | // * @since Ant 1.6 |
244 | // */ |
245 | // private void createClassLoader() { |
246 | // Path userClasspath = getCommandline().getClasspath(); |
247 | // if (userClasspath != null) { |
248 | // if (reloading || classLoader == null) { |
249 | // Path classpath = (Path) userClasspath.clone(); |
250 | // if (includeAntRuntime) { |
251 | // log("Implicitly adding " + antRuntimeClasses |
252 | // + " to CLASSPATH", Project.MSG_VERBOSE); |
253 | // classpath.append(antRuntimeClasses); |
254 | // } |
255 | // classLoader = getProject().createClassLoader(classpath); |
256 | // if (getClass().getClassLoader() != null |
257 | // && getClass().getClassLoader() != Project.class.getClassLoader()) { |
258 | // classLoader.setParent(getClass().getClassLoader()); |
259 | // } |
260 | // classLoader.setParentFirst(false); |
261 | // classLoader.addJavaLibraries(); |
262 | // log("Using CLASSPATH " + classLoader.getClasspath(), |
263 | // Project.MSG_VERBOSE); |
264 | // // make sure the test will be accepted as a TestCase |
265 | // classLoader.addSystemPackageRoot("junit"); |
266 | // // will cause trouble in JDK 1.1 if omitted |
267 | // classLoader.addSystemPackageRoot("org.apache.tools.ant"); |
268 | // } |
269 | // } |
270 | // } |
271 | |
272 | /** |
273 | * Handles errors. This should be used after the underlying task execution. |
274 | * <p> |
275 | * If this number is greater than maxErrors, sets the failureProperty if asked and throws an |
276 | * BuildException if failOnErrors is sets to <code>true</code>. |
277 | * @param numErrors number of errors to be check against maxErrors. |
278 | */ |
279 | protected void handleErrors(final int numErrors) { |
280 | final boolean ok = numErrors <= maxErrors; |
281 | |
282 | // Display the number of errors |
283 | final int verbosityLevel; |
284 | if (!ok) { |
285 | verbosityLevel = Project.MSG_ERR; |
286 | } |
287 | else { |
288 | verbosityLevel = Project.MSG_INFO; |
289 | } |
290 | log("Errors: " + numErrors, verbosityLevel); |
291 | |
292 | // Handle the return status |
293 | if (!ok && failureProperty != null) { |
294 | getProject().setProperty(failureProperty, "true"); |
295 | } |
296 | |
297 | if (!ok && failOnErrors) { |
298 | throw new BuildException("Got " + numErrors + " errors", getLocation()); |
299 | } |
300 | } |
301 | |
302 | // //////////////////////////////////////////////////////////////////////////////////////////// |
303 | // Setters for ANT specific attributes |
304 | // //////////////////////////////////////////////////////////////////////////////////////////// |
305 | |
306 | /** |
307 | * Sets the locale to be used to generate message used on logging. |
308 | * TODO Javadoc - default? |
309 | * <p> |
310 | * The separator in the <code>String</code> argument must be |
311 | * {@link org.ktc.rbutils.api.i18n.LocaleUtils#LOCALE_SEPARATOR}. |
312 | * @param displayLocale The locale to set. |
313 | */ |
314 | public void setDisplayLocale(final String displayLocale) { |
315 | this.displayLocale = LocaleUtils.toLocale(displayLocale); |
316 | } |
317 | |
318 | /** |
319 | * Tells this task whether to fail if errors occur during the check. Default to |
320 | * <code>true</code>. |
321 | * @param failOnErrors <code>true</code> to fail on errors; <code>false</code> otherwise. |
322 | */ |
323 | public void setFailOnErrors(final boolean failOnErrors) { |
324 | this.failOnErrors = failOnErrors; |
325 | } |
326 | |
327 | /** |
328 | * Tells this task to set the named property to "true" when there is a violation. |
329 | * @param failureProperty the name of the property to set in the event of an failure. |
330 | */ |
331 | public void setFailureProperty(final String failureProperty) { |
332 | this.failureProperty = failureProperty; |
333 | } |
334 | |
335 | /** |
336 | * Sets the maximum number of errors allowed. Default to 0. |
337 | * @param maxErrors the maximum number of errors allowed. |
338 | */ |
339 | public void setMaxErrors(final int maxErrors) { |
340 | this.maxErrors = maxErrors; |
341 | } |
342 | |
343 | // ***************************************** |
344 | // FILESET |
345 | //***************************************** |
346 | /** |
347 | * Adds a set of files (nested fileset attribute). |
348 | * @param fileSet the file set to add. |
349 | */ |
350 | public void addFileset(final FileSet fileSet) { |
351 | fileSets.add(fileSet); |
352 | } |
353 | |
354 | /** |
355 | * Sets the directory where the properties files will be searched for the check. |
356 | * @param root the root directory to set. |
357 | */ |
358 | public void setRoot(final File root) { |
359 | this.root = root; |
360 | } |
361 | |
362 | /** |
363 | * Sets the extension of the properties file used for the check. Default is |
364 | * {@link AbstractMainProcessor#DEFAULT_PROPERTIES_EXTENSION}. |
365 | * @param extension the extension to set. |
366 | */ |
367 | public void setExtension(final String extension) { |
368 | this.extension = extension; |
369 | } |
370 | |
371 | // //////////////////////////////////////////////////////////////////////////////////////////// |
372 | // Inner classes |
373 | // //////////////////////////////////////////////////////////////////////////////////////////// |
374 | /** |
375 | * Logger for the rbUtils ant tasks. |
376 | * <p> |
377 | * The logger is created by the {@link #createLogger(Task)} method. The {@link RbCheckerTask} |
378 | * and {@link RbGeneratorTask} are currently supported. |
379 | * @since RbUtils 0.9.1 |
380 | * @version $Revision: 1.5 $ |
381 | * @author redfish |
382 | */ |
383 | public static class Log { |
384 | /** The logger type. */ |
385 | private LoggerType loggerType; |
386 | /** The file to output to. */ |
387 | private File outputFile; |
388 | /** Header message for unsupported task exception. */ |
389 | private static final String UNSUPPORTEDTASK_MSG = "Unsupported Task "; |
390 | |
391 | /** |
392 | * Instanciates a new Logger. |
393 | */ |
394 | public Log() { |
395 | super(); |
396 | } |
397 | |
398 | /** |
399 | * Set the type of the logger. |
400 | * @param loggertype the type of the logger. |
401 | */ |
402 | public void setType(final LoggerType loggertype) { |
403 | // TODO Code - is this necessary. Create a test case for this |
404 | final String val = loggertype.getValue(); |
405 | // if (!LOGGERTYPE_XML.equals(val) && !LOGGERTYPE_PLAIN.equals(val)) { |
406 | if (!LoggerType.isLoggerTypeValue(val)) { |
407 | throw new BuildException("Invalid formatter type: " + val); |
408 | } |
409 | loggerType = loggertype; |
410 | } |
411 | |
412 | /** |
413 | * Set the file to output to. |
414 | * @param theOutputFile the file to output to. |
415 | */ |
416 | public void setOutputFile(final File theOutputFile) { |
417 | outputFile = theOutputFile; |
418 | } |
419 | |
420 | /** |
421 | * Creates a logger according to a task. |
422 | * TODO Javadoc - to be improved. |
423 | * @param task the task to be associated to the logger. |
424 | * @return a logger according to the task. |
425 | * @throws IOException if an error occurs. |
426 | */ |
427 | public Logger createLogger(final Task task) throws IOException { |
428 | final Logger logger; |
429 | if ((loggerType != null) && LOGGERTYPE_XML.equals(loggerType.getValue())) { |
430 | logger = createXmlLogger(task); |
431 | } |
432 | else { |
433 | logger = createDefaultLogger(task); |
434 | } |
435 | return logger; |
436 | } |
437 | |
438 | /** |
439 | * Creates a default logger according to a task. |
440 | * @return a DefaultLogger instance. |
441 | * @param task the task associated to the logger. |
442 | * @throws IOException if an error occurs on logger creation. |
443 | * @throws BuildException if the task is not supported. |
444 | */ |
445 | private Logger createDefaultLogger(final Task task) throws IOException { |
446 | // Sets the used output stream by this logger |
447 | final OutputStream msgStream; |
448 | final OutputStream errorStream; |
449 | if (outputFile == null) { |
450 | msgStream = new LogOutputStream(task, Project.MSG_DEBUG); |
451 | errorStream = new LogOutputStream(task, Project.MSG_ERR); |
452 | } |
453 | else { |
454 | msgStream = new BufferedOutputStream(new FileOutputStream(outputFile)); |
455 | errorStream = msgStream; |
456 | } |
457 | |
458 | // Instanciates the default logger |
459 | Logger logger = null; |
460 | if (task instanceof RbCheckerTask) { |
461 | logger = new DefaultLogger(msgStream, true, errorStream, true); |
462 | } |
463 | else if (task instanceof RbGeneratorTask) { |
464 | logger = new org.ktc.rbutils.rb.generation.DefaultLogger(msgStream, true, |
465 | errorStream, true); |
466 | } |
467 | else { |
468 | throw new BuildException(UNSUPPORTEDTASK_MSG + task.getTaskName()); |
469 | } |
470 | return logger; |
471 | |
472 | } |
473 | |
474 | /** |
475 | * Creates a xml logger according to the task. |
476 | * @return an XmlLogger instance. |
477 | * @param task the task associated to the logger. |
478 | * @throws IOException if an error occurs on logger creation. |
479 | * @throws BuildException if the outputFile is not set or if the task is not supported. |
480 | */ |
481 | private Logger createXmlLogger(final Task task) throws IOException { |
482 | final Logger logger; |
483 | if (outputFile == null) { |
484 | throw new BuildException("outputFile must not be null", task.getLocation()); |
485 | } |
486 | |
487 | if (task instanceof RbCheckerTask) { |
488 | logger = new XmlLogger(outputFile); |
489 | } |
490 | else if (task instanceof RbGeneratorTask) { |
491 | logger = new org.ktc.rbutils.rb.generation.XmlLogger(outputFile); |
492 | } |
493 | else { |
494 | throw new BuildException(UNSUPPORTEDTASK_MSG + task.getTaskName()); |
495 | } |
496 | return logger; |
497 | } |
498 | |
499 | /** |
500 | * Returns the type of this ant logger. |
501 | * @return the type of the ant logger. |
502 | */ |
503 | public LoggerType getLoggerType() { |
504 | return this.loggerType; |
505 | } |
506 | |
507 | /** |
508 | * Returns the output file of this ant logger. |
509 | * @return the output file of this ant logger. |
510 | */ |
511 | public File getOutputFile() { |
512 | return this.outputFile; |
513 | } |
514 | } |
515 | |
516 | /** |
517 | * Enumerated attribute for ant logger type. |
518 | * @since RbUtils 0.9.1 |
519 | * @version $Revision: 1.5 $ |
520 | * @author redfish |
521 | */ |
522 | public static class LoggerType extends EnumeratedAttribute { |
523 | /** Enum types for the LoggerType as String[].*/ |
524 | private static final String[] LOGGER_TYPES = {LOGGERTYPE_XML, LOGGERTYPE_PLAIN}; |
525 | /** Enum types for the LoggerType as List.*/ |
526 | private static final List LOGGER_TYPES_LIST = Arrays.asList(LOGGER_TYPES); |
527 | |
528 | /** |
529 | * Instanciates a new LoggerType. |
530 | */ |
531 | public LoggerType() { |
532 | super(); |
533 | } |
534 | |
535 | /** |
536 | * {@inheritDoc} |
537 | */ |
538 | public String[] getValues() { |
539 | // return new String[] {LOGGERTYPE_XML, LOGGERTYPE_PLAIN}; |
540 | return LOGGER_TYPES; |
541 | } |
542 | |
543 | /** |
544 | * Indicates if the argument is a logger type value. |
545 | * @param typeValue type value to be checked. |
546 | * @return <code>true</code> if the argument is an ant logger type value; otherwise returns |
547 | * <code>false</code>. |
548 | */ |
549 | public static boolean isLoggerTypeValue(final String typeValue) { |
550 | return LOGGER_TYPES_LIST.contains(typeValue); |
551 | } |
552 | } |
553 | } |