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