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