View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library 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 GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.ant;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.Properties;
33  import java.util.ResourceBundle;
34  import java.util.stream.Collectors;
35  
36  import org.apache.tools.ant.AntClassLoader;
37  import org.apache.tools.ant.BuildException;
38  import org.apache.tools.ant.DirectoryScanner;
39  import org.apache.tools.ant.Project;
40  import org.apache.tools.ant.Task;
41  import org.apache.tools.ant.taskdefs.LogOutputStream;
42  import org.apache.tools.ant.types.EnumeratedAttribute;
43  import org.apache.tools.ant.types.FileSet;
44  import org.apache.tools.ant.types.Path;
45  import org.apache.tools.ant.types.Reference;
46  
47  import com.google.common.io.Closeables;
48  import com.puppycrawl.tools.checkstyle.Checker;
49  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
50  import com.puppycrawl.tools.checkstyle.DefaultLogger;
51  import com.puppycrawl.tools.checkstyle.ModuleFactory;
52  import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
53  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
54  import com.puppycrawl.tools.checkstyle.ThreadModeSettings;
55  import com.puppycrawl.tools.checkstyle.XMLLogger;
56  import com.puppycrawl.tools.checkstyle.api.AuditListener;
57  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
58  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
59  import com.puppycrawl.tools.checkstyle.api.Configuration;
60  import com.puppycrawl.tools.checkstyle.api.RootModule;
61  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
62  import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter;
63  
64  /**
65   * An implementation of a ANT task for calling checkstyle. See the documentation
66   * of the task for usage.
67   * @author Oliver Burn
68   * @noinspection ClassLoaderInstantiation
69   */
70  public class CheckstyleAntTask extends Task {
71  
72      /** Poor man's enum for an xml formatter. */
73      private static final String E_XML = "xml";
74      /** Poor man's enum for an plain formatter. */
75      private static final String E_PLAIN = "plain";
76  
77      /** Suffix for time string. */
78      private static final String TIME_SUFFIX = " ms.";
79  
80      /** Contains the paths to process. */
81      private final List<Path> paths = new ArrayList<>();
82  
83      /** Contains the filesets to process. */
84      private final List<FileSet> fileSets = new ArrayList<>();
85  
86      /** Contains the formatters to log to. */
87      private final List<Formatter> formatters = new ArrayList<>();
88  
89      /** Contains the Properties to override. */
90      private final List<Property> overrideProps = new ArrayList<>();
91  
92      /** Class path to locate class files. */
93      private Path classpath;
94  
95      /** Name of file to check. */
96      private String fileName;
97  
98      /** Config file containing configuration. */
99      private String config;
100 
101     /** Whether to fail build on violations. */
102     private boolean failOnViolation = true;
103 
104     /** Property to set on violations. */
105     private String failureProperty;
106 
107     /** The name of the properties file. */
108     private File properties;
109 
110     /** The maximum number of errors that are tolerated. */
111     private int maxErrors;
112 
113     /** The maximum number of warnings that are tolerated. */
114     private int maxWarnings = Integer.MAX_VALUE;
115 
116     /**
117      * Whether to execute ignored modules - some modules may log above
118      * their severity depending on their configuration (e.g. WriteTag) so
119      * need to be included
120      */
121     private boolean executeIgnoredModules;
122 
123     ////////////////////////////////////////////////////////////////////////////
124     // Setters for ANT specific attributes
125     ////////////////////////////////////////////////////////////////////////////
126 
127     /**
128      * Tells this task to write failure message to the named property when there
129      * is a violation.
130      * @param propertyName the name of the property to set
131      *                      in the event of an failure.
132      */
133     public void setFailureProperty(String propertyName) {
134         failureProperty = propertyName;
135     }
136 
137     /**
138      * Sets flag - whether to fail if a violation is found.
139      * @param fail whether to fail if a violation is found
140      */
141     public void setFailOnViolation(boolean fail) {
142         failOnViolation = fail;
143     }
144 
145     /**
146      * Sets the maximum number of errors allowed. Default is 0.
147      * @param maxErrors the maximum number of errors allowed.
148      */
149     public void setMaxErrors(int maxErrors) {
150         this.maxErrors = maxErrors;
151     }
152 
153     /**
154      * Sets the maximum number of warnings allowed. Default is
155      * {@link Integer#MAX_VALUE}.
156      * @param maxWarnings the maximum number of warnings allowed.
157      */
158     public void setMaxWarnings(int maxWarnings) {
159         this.maxWarnings = maxWarnings;
160     }
161 
162     /**
163      * Adds a path.
164      * @param path the path to add.
165      */
166     public void addPath(Path path) {
167         paths.add(path);
168     }
169 
170     /**
171      * Adds set of files (nested fileset attribute).
172      * @param fileSet the file set to add
173      */
174     public void addFileset(FileSet fileSet) {
175         fileSets.add(fileSet);
176     }
177 
178     /**
179      * Add a formatter.
180      * @param formatter the formatter to add for logging.
181      */
182     public void addFormatter(Formatter formatter) {
183         formatters.add(formatter);
184     }
185 
186     /**
187      * Add an override property.
188      * @param property the property to add
189      */
190     public void addProperty(Property property) {
191         overrideProps.add(property);
192     }
193 
194     /**
195      * Set the class path.
196      * @param classpath the path to locate classes
197      */
198     public void setClasspath(Path classpath) {
199         if (this.classpath == null) {
200             this.classpath = classpath;
201         }
202         else {
203             this.classpath.append(classpath);
204         }
205     }
206 
207     /**
208      * Set the class path from a reference defined elsewhere.
209      * @param classpathRef the reference to an instance defining the classpath
210      */
211     public void setClasspathRef(Reference classpathRef) {
212         createClasspath().setRefid(classpathRef);
213     }
214 
215     /**
216      * Creates classpath.
217      * @return a created path for locating classes
218      */
219     public Path createClasspath() {
220         if (classpath == null) {
221             classpath = new Path(getProject());
222         }
223         return classpath.createPath();
224     }
225 
226     /**
227      * Sets file to be checked.
228      * @param file the file to be checked
229      */
230     public void setFile(File file) {
231         fileName = file.getAbsolutePath();
232     }
233 
234     /**
235      * Sets configuration file.
236      * @param configuration the configuration file, URL, or resource to use
237      */
238     public void setConfig(String configuration) {
239         if (config != null) {
240             throw new BuildException("Attribute 'config' has already been set");
241         }
242         config = configuration;
243     }
244 
245     /**
246      * Sets flag - whether to execute ignored modules.
247      * @param omit whether to execute ignored modules
248      */
249     public void setExecuteIgnoredModules(boolean omit) {
250         executeIgnoredModules = omit;
251     }
252 
253     ////////////////////////////////////////////////////////////////////////////
254     // Setters for Root Module's configuration attributes
255     ////////////////////////////////////////////////////////////////////////////
256 
257     /**
258      * Sets a properties file for use instead
259      * of individually setting them.
260      * @param props the properties File to use
261      */
262     public void setProperties(File props) {
263         properties = props;
264     }
265 
266     ////////////////////////////////////////////////////////////////////////////
267     // The doers
268     ////////////////////////////////////////////////////////////////////////////
269 
270     @Override
271     public void execute() {
272         final long startTime = System.currentTimeMillis();
273 
274         try {
275             // output version info in debug mode
276             final ResourceBundle compilationProperties = ResourceBundle
277                     .getBundle("checkstylecompilation", Locale.ROOT);
278             final String version = compilationProperties
279                     .getString("checkstyle.compile.version");
280             final String compileTimestamp = compilationProperties
281                     .getString("checkstyle.compile.timestamp");
282             log("checkstyle version " + version, Project.MSG_VERBOSE);
283             log("compiled on " + compileTimestamp, Project.MSG_VERBOSE);
284 
285             // Check for no arguments
286             if (fileName == null
287                     && fileSets.isEmpty()
288                     && paths.isEmpty()) {
289                 throw new BuildException(
290                         "Must specify at least one of 'file' or nested 'fileset' or 'path'.",
291                         getLocation());
292             }
293             if (config == null) {
294                 throw new BuildException("Must specify 'config'.", getLocation());
295             }
296             realExecute(version);
297         }
298         finally {
299             final long endTime = System.currentTimeMillis();
300             log("Total execution took " + (endTime - startTime) + TIME_SUFFIX,
301                 Project.MSG_VERBOSE);
302         }
303     }
304 
305     /**
306      * Helper implementation to perform execution.
307      * @param checkstyleVersion Checkstyle compile version.
308      */
309     private void realExecute(String checkstyleVersion) {
310         // Create the root module
311         RootModule rootModule = null;
312         try {
313             rootModule = createRootModule();
314 
315             // setup the listeners
316             final AuditListener[] listeners = getListeners();
317             for (AuditListener element : listeners) {
318                 rootModule.addListener(element);
319             }
320             final SeverityLevelCounter warningCounter =
321                 new SeverityLevelCounter(SeverityLevel.WARNING);
322             rootModule.addListener(warningCounter);
323 
324             processFiles(rootModule, warningCounter, checkstyleVersion);
325         }
326         finally {
327             destroyRootModule(rootModule);
328         }
329     }
330 
331     /**
332      * Destroy root module. This method exists only due to bug in cobertura library
333      * https://github.com/cobertura/cobertura/issues/170
334      * @param rootModule Root module that was used to process files
335      */
336     private static void destroyRootModule(RootModule rootModule) {
337         if (rootModule != null) {
338             rootModule.destroy();
339         }
340     }
341 
342     /**
343      * Scans and processes files by means given root module.
344      * @param rootModule Root module to process files
345      * @param warningCounter Root Module's counter of warnings
346      * @param checkstyleVersion Checkstyle compile version
347      */
348     private void processFiles(RootModule rootModule, final SeverityLevelCounter warningCounter,
349             final String checkstyleVersion) {
350         final long startTime = System.currentTimeMillis();
351         final List<File> files = getFilesToCheck();
352         final long endTime = System.currentTimeMillis();
353         log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX,
354             Project.MSG_VERBOSE);
355 
356         log("Running Checkstyle " + checkstyleVersion + " on " + files.size()
357                 + " files", Project.MSG_INFO);
358         log("Using configuration " + config, Project.MSG_VERBOSE);
359 
360         final int numErrs;
361 
362         try {
363             final long processingStartTime = System.currentTimeMillis();
364             numErrs = rootModule.process(files);
365             final long processingEndTime = System.currentTimeMillis();
366             log("To process the files took " + (processingEndTime - processingStartTime)
367                 + TIME_SUFFIX, Project.MSG_VERBOSE);
368         }
369         catch (CheckstyleException ex) {
370             throw new BuildException("Unable to process files: " + files, ex);
371         }
372         final int numWarnings = warningCounter.getCount();
373         final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings;
374 
375         // Handle the return status
376         if (!okStatus) {
377             final String failureMsg =
378                     "Got " + numErrs + " errors and " + numWarnings
379                             + " warnings.";
380             if (failureProperty != null) {
381                 getProject().setProperty(failureProperty, failureMsg);
382             }
383 
384             if (failOnViolation) {
385                 throw new BuildException(failureMsg, getLocation());
386             }
387         }
388     }
389 
390     /**
391      * Creates new instance of the root module.
392      * @return new instance of the root module
393      */
394     private RootModule createRootModule() {
395         final RootModule rootModule;
396         try {
397             final Properties props = createOverridingProperties();
398             final ThreadModeSettings threadModeSettings =
399                     ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
400             final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
401             if (executeIgnoredModules) {
402                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
403             }
404             else {
405                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
406             }
407 
408             final Configuration configuration = ConfigurationLoader.loadConfiguration(config,
409                     new PropertiesExpander(props), ignoredModulesOptions, threadModeSettings);
410 
411             final ClassLoader moduleClassLoader =
412                 Checker.class.getClassLoader();
413 
414             final ModuleFactory factory = new PackageObjectFactory(
415                     Checker.class.getPackage().getName() + ".", moduleClassLoader);
416 
417             rootModule = (RootModule) factory.createModule(configuration.getName());
418             rootModule.setModuleClassLoader(moduleClassLoader);
419 
420             if (rootModule instanceof Checker) {
421                 final ClassLoader loader = new AntClassLoader(getProject(),
422                         classpath);
423 
424                 ((Checker) rootModule).setClassLoader(loader);
425             }
426 
427             rootModule.configure(configuration);
428         }
429         catch (final CheckstyleException ex) {
430             throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: "
431                     + "config {%s}, classpath {%s}.", config, classpath), ex);
432         }
433         return rootModule;
434     }
435 
436     /**
437      * Create the Properties object based on the arguments specified
438      * to the ANT task.
439      * @return the properties for property expansion expansion
440      * @throws BuildException if an error occurs
441      */
442     private Properties createOverridingProperties() {
443         final Properties returnValue = new Properties();
444 
445         // Load the properties file if specified
446         if (properties != null) {
447             FileInputStream inStream = null;
448             try {
449                 inStream = new FileInputStream(properties);
450                 returnValue.load(inStream);
451             }
452             catch (final IOException ex) {
453                 throw new BuildException("Error loading Properties file '"
454                         + properties + "'", ex, getLocation());
455             }
456             finally {
457                 Closeables.closeQuietly(inStream);
458             }
459         }
460 
461         // override with Ant properties like ${basedir}
462         final Map<String, Object> antProps = getProject().getProperties();
463         for (Map.Entry<String, Object> entry : antProps.entrySet()) {
464             final String value = String.valueOf(entry.getValue());
465             returnValue.setProperty(entry.getKey(), value);
466         }
467 
468         // override with properties specified in subelements
469         for (Property p : overrideProps) {
470             returnValue.setProperty(p.getKey(), p.getValue());
471         }
472 
473         return returnValue;
474     }
475 
476     /**
477      * Return the list of listeners set in this task.
478      * @return the list of listeners.
479      */
480     private AuditListener[] getListeners() {
481         final int formatterCount = Math.max(1, formatters.size());
482 
483         final AuditListener[] listeners = new AuditListener[formatterCount];
484 
485         // formatters
486         try {
487             if (formatters.isEmpty()) {
488                 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG);
489                 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR);
490                 listeners[0] = new DefaultLogger(debug, AutomaticBean.OutputStreamOptions.CLOSE,
491                         err, AutomaticBean.OutputStreamOptions.CLOSE);
492             }
493             else {
494                 for (int i = 0; i < formatterCount; i++) {
495                     final Formatter formatter = formatters.get(i);
496                     listeners[i] = formatter.createListener(this);
497                 }
498             }
499         }
500         catch (IOException ex) {
501             throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: "
502                     + "formatters {%s}.", formatters), ex);
503         }
504         return listeners;
505     }
506 
507     /**
508      * Returns the list of files (full path name) to process.
509      * @return the list of files included via the fileName, filesets and paths.
510      */
511     private List<File> getFilesToCheck() {
512         final List<File> allFiles = new ArrayList<>();
513         if (fileName != null) {
514             // oops we've got an additional one to process, don't
515             // forget it. No sweat, it's fully resolved via the setter.
516             log("Adding standalone file for audit", Project.MSG_VERBOSE);
517             allFiles.add(new File(fileName));
518         }
519 
520         final List<File> filesFromFileSets = scanFileSets();
521         allFiles.addAll(filesFromFileSets);
522 
523         final List<File> filesFromPaths = scanPaths();
524         allFiles.addAll(filesFromPaths);
525 
526         return allFiles;
527     }
528 
529     /**
530      * Retrieves all files from the defined paths.
531      * @return a list of files defined via paths.
532      */
533     private List<File> scanPaths() {
534         final List<File> allFiles = new ArrayList<>();
535 
536         for (int i = 0; i < paths.size(); i++) {
537             final Path currentPath = paths.get(i);
538             final List<File> pathFiles = scanPath(currentPath, i + 1);
539             allFiles.addAll(pathFiles);
540         }
541 
542         return allFiles;
543     }
544 
545     /**
546      * Scans the given path and retrieves all files for the given path.
547      *
548      * @param path      A path to scan.
549      * @param pathIndex The index of the given path. Used in log messages only.
550      * @return A list of files, extracted from the given path.
551      */
552     private List<File> scanPath(Path path, int pathIndex) {
553         final String[] resources = path.list();
554         log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE);
555         final List<File> allFiles = new ArrayList<>();
556         int concreteFilesCount = 0;
557 
558         for (String resource : resources) {
559             final File file = new File(resource);
560             if (file.isFile()) {
561                 concreteFilesCount++;
562                 allFiles.add(file);
563             }
564             else {
565                 final DirectoryScanner scanner = new DirectoryScanner();
566                 scanner.setBasedir(file);
567                 scanner.scan();
568                 final List<File> scannedFiles = retrieveAllScannedFiles(scanner, pathIndex);
569                 allFiles.addAll(scannedFiles);
570             }
571         }
572 
573         if (concreteFilesCount > 0) {
574             log(String.format(Locale.ROOT, "%d) Adding %d files from path %s",
575                 pathIndex, concreteFilesCount, path), Project.MSG_VERBOSE);
576         }
577 
578         return allFiles;
579     }
580 
581     /**
582      * Returns the list of files (full path name) to process.
583      * @return the list of files included via the filesets.
584      */
585     protected List<File> scanFileSets() {
586         final List<File> allFiles = new ArrayList<>();
587 
588         for (int i = 0; i < fileSets.size(); i++) {
589             final FileSet fileSet = fileSets.get(i);
590             final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
591             final List<File> scannedFiles = retrieveAllScannedFiles(scanner, i);
592             allFiles.addAll(scannedFiles);
593         }
594 
595         return allFiles;
596     }
597 
598     /**
599      * Retrieves all matched files from the given scanner.
600      *
601      * @param scanner  A directory scanner. Note, that {@link DirectoryScanner#scan()}
602      *                 must be called before calling this method.
603      * @param logIndex A log entry index. Used only for log messages.
604      * @return A list of files, retrieved from the given scanner.
605      */
606     private List<File> retrieveAllScannedFiles(DirectoryScanner scanner, int logIndex) {
607         final String[] fileNames = scanner.getIncludedFiles();
608         log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s",
609             logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE);
610 
611         return Arrays.stream(fileNames)
612             .map(name -> scanner.getBasedir() + File.separator + name)
613             .map(File::new)
614             .collect(Collectors.toList());
615     }
616 
617     /**
618      * Poor mans enumeration for the formatter types.
619      * @author Oliver Burn
620      */
621     public static class FormatterType extends EnumeratedAttribute {
622 
623         /** My possible values. */
624         private static final String[] VALUES = {E_XML, E_PLAIN};
625 
626         @Override
627         public String[] getValues() {
628             return VALUES.clone();
629         }
630 
631     }
632 
633     /**
634      * Details about a formatter to be used.
635      * @author Oliver Burn
636      */
637     public static class Formatter {
638 
639         /** The formatter type. */
640         private FormatterType type;
641         /** The file to output to. */
642         private File toFile;
643         /** Whether or not the write to the named file. */
644         private boolean useFile = true;
645 
646         /**
647          * Set the type of the formatter.
648          * @param type the type
649          */
650         public void setType(FormatterType type) {
651             this.type = type;
652         }
653 
654         /**
655          * Set the file to output to.
656          * @param destination destination the file to output to
657          */
658         public void setTofile(File destination) {
659             toFile = destination;
660         }
661 
662         /**
663          * Sets whether or not we write to a file if it is provided.
664          * @param use whether not not to use provided file.
665          */
666         public void setUseFile(boolean use) {
667             useFile = use;
668         }
669 
670         /**
671          * Creates a listener for the formatter.
672          * @param task the task running
673          * @return a listener
674          * @throws IOException if an error occurs
675          */
676         public AuditListener createListener(Task task) throws IOException {
677             final AuditListener listener;
678             if (type != null
679                     && E_XML.equals(type.getValue())) {
680                 listener = createXmlLogger(task);
681             }
682             else {
683                 listener = createDefaultLogger(task);
684             }
685             return listener;
686         }
687 
688         /**
689          * Creates default logger.
690          * @param task the task to possibly log to
691          * @return a DefaultLogger instance
692          * @throws IOException if an error occurs
693          */
694         private AuditListener createDefaultLogger(Task task)
695                 throws IOException {
696             final AuditListener defaultLogger;
697             if (toFile == null || !useFile) {
698                 defaultLogger = new DefaultLogger(
699                     new LogOutputStream(task, Project.MSG_DEBUG),
700                         AutomaticBean.OutputStreamOptions.CLOSE,
701                         new LogOutputStream(task, Project.MSG_ERR),
702                         AutomaticBean.OutputStreamOptions.CLOSE
703                 );
704             }
705             else {
706                 final FileOutputStream infoStream = new FileOutputStream(toFile);
707                 defaultLogger =
708                         new DefaultLogger(infoStream, AutomaticBean.OutputStreamOptions.CLOSE,
709                                 infoStream, AutomaticBean.OutputStreamOptions.NONE);
710             }
711             return defaultLogger;
712         }
713 
714         /**
715          * Creates XML logger.
716          * @param task the task to possibly log to
717          * @return an XMLLogger instance
718          * @throws IOException if an error occurs
719          */
720         private AuditListener createXmlLogger(Task task) throws IOException {
721             final AuditListener xmlLogger;
722             if (toFile == null || !useFile) {
723                 xmlLogger = new XMLLogger(new LogOutputStream(task, Project.MSG_INFO),
724                         AutomaticBean.OutputStreamOptions.CLOSE);
725             }
726             else {
727                 xmlLogger = new XMLLogger(new FileOutputStream(toFile),
728                         AutomaticBean.OutputStreamOptions.CLOSE);
729             }
730             return xmlLogger;
731         }
732 
733     }
734 
735     /**
736      * Represents a property that consists of a key and value.
737      */
738     public static class Property {
739 
740         /** The property key. */
741         private String key;
742         /** The property value. */
743         private String value;
744 
745         /**
746          * Gets key.
747          * @return the property key
748          */
749         public String getKey() {
750             return key;
751         }
752 
753         /**
754          * Sets key.
755          * @param key sets the property key
756          */
757         public void setKey(String key) {
758             this.key = key;
759         }
760 
761         /**
762          * Gets value.
763          * @return the property value
764          */
765         public String getValue() {
766             return value;
767         }
768 
769         /**
770          * Sets value.
771          * @param value set the property value
772          */
773         public void setValue(String value) {
774             this.value = value;
775         }
776 
777         /**
778          * Sets the property value from a File.
779          * @param file set the property value from a File
780          */
781         public void setFile(File file) {
782             value = file.getAbsolutePath();
783         }
784 
785     }
786 
787     /** Represents a custom listener. */
788     public static class Listener {
789 
790         /** Class name of the listener class. */
791         private String className;
792 
793         /**
794          * Gets class name.
795          * @return the class name
796          */
797         public String getClassname() {
798             return className;
799         }
800 
801         /**
802          * Sets class name.
803          * @param name set the class name
804          */
805         public void setClassname(String name) {
806             className = name;
807         }
808 
809     }
810 
811 }