View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.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.Objects;
33  import java.util.Properties;
34  import java.util.stream.Collectors;
35  
36  import org.apache.tools.ant.BuildException;
37  import org.apache.tools.ant.DirectoryScanner;
38  import org.apache.tools.ant.Project;
39  import org.apache.tools.ant.Task;
40  import org.apache.tools.ant.taskdefs.LogOutputStream;
41  import org.apache.tools.ant.types.EnumeratedAttribute;
42  import org.apache.tools.ant.types.FileSet;
43  import org.apache.tools.ant.types.Path;
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<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 File 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(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      * Creates classpath.
199      *
200      * @return a created path for locating classes
201      * @deprecated left in implementation until #12556 only to allow users to migrate to new gradle
202      *     plugins. This method will be removed in Checkstyle 11.x.x .
203      * @noinspection DeprecatedIsStillUsed
204      * @noinspectionreason DeprecatedIsStillUsed - until #12556
205      */
206     @Deprecated(since = "10.7.0")
207     public Path createClasspath() {
208         return new Path(getProject());
209     }
210 
211     /**
212      * Sets file to be checked.
213      *
214      * @param file the file to be checked
215      */
216     public void setFile(File file) {
217         fileName = file.getAbsolutePath();
218     }
219 
220     /**
221      * Sets configuration file.
222      *
223      * @param configuration the configuration file, URL, or resource to use
224      * @throws BuildException when config was already set
225      */
226     public void setConfig(String configuration) {
227         if (config != null) {
228             throw new BuildException("Attribute 'config' has already been set");
229         }
230         config = configuration;
231     }
232 
233     /**
234      * Sets flag - whether to execute ignored modules.
235      *
236      * @param omit whether to execute ignored modules
237      */
238     public void setExecuteIgnoredModules(boolean omit) {
239         executeIgnoredModules = omit;
240     }
241 
242     ////////////////////////////////////////////////////////////////////////////
243     // Setters for Root Module's configuration attributes
244     ////////////////////////////////////////////////////////////////////////////
245 
246     /**
247      * Sets a properties file for use instead
248      * of individually setting them.
249      *
250      * @param props the properties File to use
251      */
252     public void setProperties(File props) {
253         properties = props;
254     }
255 
256     ////////////////////////////////////////////////////////////////////////////
257     // The doers
258     ////////////////////////////////////////////////////////////////////////////
259 
260     @Override
261     public void execute() {
262         final long startTime = System.currentTimeMillis();
263 
264         try {
265             final String version = CheckstyleAntTask.class.getPackage().getImplementationVersion();
266 
267             log("checkstyle version " + version, Project.MSG_VERBOSE);
268 
269             // Check for no arguments
270             if (fileName == null
271                     && fileSets.isEmpty()
272                     && paths.isEmpty()) {
273                 throw new BuildException(
274                         "Must specify at least one of 'file' or nested 'fileset' or 'path'.",
275                         getLocation());
276             }
277             if (config == null) {
278                 throw new BuildException("Must specify 'config'.", getLocation());
279             }
280             realExecute(version);
281         }
282         finally {
283             final long endTime = System.currentTimeMillis();
284             log("Total execution took " + (endTime - startTime) + TIME_SUFFIX,
285                 Project.MSG_VERBOSE);
286         }
287     }
288 
289     /**
290      * Helper implementation to perform execution.
291      *
292      * @param checkstyleVersion Checkstyle compile version.
293      */
294     private void realExecute(String checkstyleVersion) {
295         // Create the root module
296         RootModule rootModule = null;
297         try {
298             rootModule = createRootModule();
299 
300             // setup the listeners
301             final AuditListener[] listeners = getListeners();
302             for (AuditListener element : listeners) {
303                 rootModule.addListener(element);
304             }
305             final SeverityLevelCounter warningCounter =
306                 new SeverityLevelCounter(SeverityLevel.WARNING);
307             rootModule.addListener(warningCounter);
308 
309             processFiles(rootModule, warningCounter, checkstyleVersion);
310         }
311         finally {
312             if (rootModule != null) {
313                 rootModule.destroy();
314             }
315         }
316     }
317 
318     /**
319      * Scans and processes files by means given root module.
320      *
321      * @param rootModule Root module to process files
322      * @param warningCounter Root Module's counter of warnings
323      * @param checkstyleVersion Checkstyle compile version
324      * @throws BuildException if the files could not be processed,
325      *     or if the build failed due to violations.
326      */
327     private void processFiles(RootModule rootModule, final SeverityLevelCounter warningCounter,
328             final String checkstyleVersion) {
329         final long startTime = System.currentTimeMillis();
330         final List<File> files = getFilesToCheck();
331         final long endTime = System.currentTimeMillis();
332         log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX,
333             Project.MSG_VERBOSE);
334 
335         log("Running Checkstyle "
336                 + Objects.toString(checkstyleVersion, "")
337                 + " on " + files.size()
338                 + " files", Project.MSG_INFO);
339         log("Using configuration " + config, Project.MSG_VERBOSE);
340 
341         final int numErrs;
342 
343         try {
344             final long processingStartTime = System.currentTimeMillis();
345             numErrs = rootModule.process(files);
346             final long processingEndTime = System.currentTimeMillis();
347             log("To process the files took " + (processingEndTime - processingStartTime)
348                 + TIME_SUFFIX, Project.MSG_VERBOSE);
349         }
350         catch (CheckstyleException ex) {
351             throw new BuildException("Unable to process files: " + files, ex);
352         }
353         final int numWarnings = warningCounter.getCount();
354         final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings;
355 
356         // Handle the return status
357         if (!okStatus) {
358             final String failureMsg =
359                     "Got " + numErrs + " errors and " + numWarnings
360                             + " warnings.";
361             if (failureProperty != null) {
362                 getProject().setProperty(failureProperty, failureMsg);
363             }
364 
365             if (failOnViolation) {
366                 throw new BuildException(failureMsg, getLocation());
367             }
368         }
369     }
370 
371     /**
372      * Creates new instance of the root module.
373      *
374      * @return new instance of the root module
375      * @throws BuildException if the root module could not be created.
376      */
377     private RootModule createRootModule() {
378         final RootModule rootModule;
379         try {
380             final Properties props = createOverridingProperties();
381             final ThreadModeSettings threadModeSettings =
382                     ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
383             final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
384             if (executeIgnoredModules) {
385                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
386             }
387             else {
388                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
389             }
390 
391             final Configuration configuration = ConfigurationLoader.loadConfiguration(config,
392                     new PropertiesExpander(props), ignoredModulesOptions, threadModeSettings);
393 
394             final ClassLoader moduleClassLoader =
395                 Checker.class.getClassLoader();
396 
397             final ModuleFactory factory = new PackageObjectFactory(
398                     Checker.class.getPackage().getName() + ".", moduleClassLoader);
399 
400             rootModule = (RootModule) factory.createModule(configuration.getName());
401             rootModule.setModuleClassLoader(moduleClassLoader);
402             rootModule.configure(configuration);
403         }
404         catch (final CheckstyleException ex) {
405             throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: "
406                     + "config {%s}.", config), ex);
407         }
408         return rootModule;
409     }
410 
411     /**
412      * Create the Properties object based on the arguments specified
413      * to the ANT task.
414      *
415      * @return the properties for property expansion
416      * @throws BuildException if the properties file could not be loaded.
417      */
418     private Properties createOverridingProperties() {
419         final Properties returnValue = new Properties();
420 
421         // Load the properties file if specified
422         if (properties != null) {
423             try (InputStream inStream = Files.newInputStream(properties.toPath())) {
424                 returnValue.load(inStream);
425             }
426             catch (final IOException ex) {
427                 throw new BuildException("Error loading Properties file '"
428                         + properties + "'", ex, getLocation());
429             }
430         }
431 
432         // override with Ant properties like ${basedir}
433         final Map<String, Object> antProps = getProject().getProperties();
434         for (Map.Entry<String, Object> entry : antProps.entrySet()) {
435             final String value = String.valueOf(entry.getValue());
436             returnValue.setProperty(entry.getKey(), value);
437         }
438 
439         // override with properties specified in subelements
440         for (Property p : overrideProps) {
441             returnValue.setProperty(p.getKey(), p.getValue());
442         }
443 
444         return returnValue;
445     }
446 
447     /**
448      * Return the array of listeners set in this task.
449      *
450      * @return the array of listeners.
451      * @throws BuildException if the listeners could not be created.
452      */
453     private AuditListener[] getListeners() {
454         final int formatterCount = Math.max(1, formatters.size());
455 
456         final AuditListener[] listeners = new AuditListener[formatterCount];
457 
458         // formatters
459         try {
460             if (formatters.isEmpty()) {
461                 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG);
462                 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR);
463                 listeners[0] = new DefaultLogger(debug, OutputStreamOptions.CLOSE,
464                         err, OutputStreamOptions.CLOSE);
465             }
466             else {
467                 for (int i = 0; i < formatterCount; i++) {
468                     final Formatter formatter = formatters.get(i);
469                     listeners[i] = formatter.createListener(this);
470                 }
471             }
472         }
473         catch (IOException ex) {
474             throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: "
475                     + "formatters {%s}.", formatters), ex);
476         }
477         return listeners;
478     }
479 
480     /**
481      * Returns the list of files (full path name) to process.
482      *
483      * @return the list of files included via the fileName, filesets and paths.
484      */
485     private List<File> getFilesToCheck() {
486         final List<File> allFiles = new ArrayList<>();
487         if (fileName != null) {
488             // oops, we've got an additional one to process, don't
489             // forget it. No sweat, it's fully resolved via the setter.
490             log("Adding standalone file for audit", Project.MSG_VERBOSE);
491             allFiles.add(new File(fileName));
492         }
493 
494         final List<File> filesFromFileSets = scanFileSets();
495         allFiles.addAll(filesFromFileSets);
496 
497         final List<File> filesFromPaths = scanPaths();
498         allFiles.addAll(filesFromPaths);
499 
500         return allFiles;
501     }
502 
503     /**
504      * Retrieves all files from the defined paths.
505      *
506      * @return a list of files defined via paths.
507      */
508     private List<File> scanPaths() {
509         final List<File> allFiles = new ArrayList<>();
510 
511         for (int i = 0; i < paths.size(); i++) {
512             final Path currentPath = paths.get(i);
513             final List<File> pathFiles = scanPath(currentPath, i + 1);
514             allFiles.addAll(pathFiles);
515         }
516 
517         return allFiles;
518     }
519 
520     /**
521      * Scans the given path and retrieves all files for the given path.
522      *
523      * @param path      A path to scan.
524      * @param pathIndex The index of the given path. Used in log messages only.
525      * @return A list of files, extracted from the given path.
526      */
527     private List<File> scanPath(Path path, int pathIndex) {
528         final String[] resources = path.list();
529         log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE);
530         final List<File> allFiles = new ArrayList<>();
531         int concreteFilesCount = 0;
532 
533         for (String resource : resources) {
534             final File file = new File(resource);
535             if (file.isFile()) {
536                 concreteFilesCount++;
537                 allFiles.add(file);
538             }
539             else {
540                 final DirectoryScanner scanner = new DirectoryScanner();
541                 scanner.setBasedir(file);
542                 scanner.scan();
543                 final List<File> scannedFiles = retrieveAllScannedFiles(scanner, pathIndex);
544                 allFiles.addAll(scannedFiles);
545             }
546         }
547 
548         if (concreteFilesCount > 0) {
549             log(String.format(Locale.ROOT, "%d) Adding %d files from path %s",
550                 pathIndex, concreteFilesCount, path), Project.MSG_VERBOSE);
551         }
552 
553         return allFiles;
554     }
555 
556     /**
557      * Returns the list of files (full path name) to process.
558      *
559      * @return the list of files included via the filesets.
560      */
561     protected List<File> scanFileSets() {
562         final List<File> allFiles = new ArrayList<>();
563 
564         for (int i = 0; i < fileSets.size(); i++) {
565             final FileSet fileSet = fileSets.get(i);
566             final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
567             final List<File> scannedFiles = retrieveAllScannedFiles(scanner, i);
568             allFiles.addAll(scannedFiles);
569         }
570 
571         return allFiles;
572     }
573 
574     /**
575      * Retrieves all matched files from the given scanner.
576      *
577      * @param scanner  A directory scanner. Note, that {@link DirectoryScanner#scan()}
578      *                 must be called before calling this method.
579      * @param logIndex A log entry index. Used only for log messages.
580      * @return A list of files, retrieved from the given scanner.
581      */
582     private List<File> retrieveAllScannedFiles(DirectoryScanner scanner, int logIndex) {
583         final String[] fileNames = scanner.getIncludedFiles();
584         log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s",
585             logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE);
586 
587         return Arrays.stream(fileNames)
588             .map(name -> scanner.getBasedir() + File.separator + name)
589             .map(File::new)
590             .collect(Collectors.toUnmodifiableList());
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 }