001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.ant;
021
022import java.io.File;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.List;
031import java.util.Locale;
032import java.util.Map;
033import java.util.Objects;
034import java.util.Properties;
035
036import org.apache.tools.ant.BuildException;
037import org.apache.tools.ant.DirectoryScanner;
038import org.apache.tools.ant.FileScanner;
039import org.apache.tools.ant.Project;
040import org.apache.tools.ant.Task;
041import org.apache.tools.ant.taskdefs.LogOutputStream;
042import org.apache.tools.ant.types.EnumeratedAttribute;
043import org.apache.tools.ant.types.FileSet;
044
045import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
046import com.puppycrawl.tools.checkstyle.Checker;
047import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
048import com.puppycrawl.tools.checkstyle.DefaultLogger;
049import com.puppycrawl.tools.checkstyle.ModuleFactory;
050import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
051import com.puppycrawl.tools.checkstyle.PropertiesExpander;
052import com.puppycrawl.tools.checkstyle.SarifLogger;
053import com.puppycrawl.tools.checkstyle.ThreadModeSettings;
054import com.puppycrawl.tools.checkstyle.XMLLogger;
055import com.puppycrawl.tools.checkstyle.api.AuditListener;
056import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
057import com.puppycrawl.tools.checkstyle.api.Configuration;
058import com.puppycrawl.tools.checkstyle.api.RootModule;
059import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
060import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter;
061
062/**
063 * An implementation of an ANT task for calling checkstyle. See the documentation
064 * of the task for usage.
065 */
066public class CheckstyleAntTask extends Task {
067
068    /** Poor man's enum for an xml formatter. */
069    private static final String E_XML = "xml";
070    /** Poor man's enum for a plain formatter. */
071    private static final String E_PLAIN = "plain";
072    /** Poor man's enum for a sarif formatter. */
073    private static final String E_SARIF = "sarif";
074
075    /** Suffix for time string. */
076    private static final String TIME_SUFFIX = " ms.";
077
078    /** Contains the paths to process. */
079    private final List<org.apache.tools.ant.types.Path> paths = new ArrayList<>();
080
081    /** Contains the filesets to process. */
082    private final List<FileSet> fileSets = new ArrayList<>();
083
084    /** Contains the formatters to log to. */
085    private final List<Formatter> formatters = new ArrayList<>();
086
087    /** Contains the Properties to override. */
088    private final List<Property> overrideProps = new ArrayList<>();
089
090    /** Name of file to check. */
091    private String fileName;
092
093    /** Config file containing configuration. */
094    private String config;
095
096    /** Whether to fail build on violations. */
097    private boolean failOnViolation = true;
098
099    /** 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     * 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 org.apache.tools.ant.types.Path createClasspath() {
208        return new org.apache.tools.ant.types.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.toPath();
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 = Objects.toString(
266                    CheckstyleAntTask.class.getPackage().getImplementationVersion(),
267                    "");
268
269            log("checkstyle version " + version, Project.MSG_VERBOSE);
270
271            // Check for no arguments
272            if (fileName == null
273                    && fileSets.isEmpty()
274                    && paths.isEmpty()) {
275                throw new BuildException(
276                        "Must specify at least one of 'file' or nested 'fileset' or 'path'.",
277                        getLocation());
278            }
279            if (config == null) {
280                throw new BuildException("Must specify 'config'.", getLocation());
281            }
282            realExecute(version);
283        }
284        finally {
285            final long endTime = System.currentTimeMillis();
286            log("Total execution took " + (endTime - startTime) + TIME_SUFFIX,
287                Project.MSG_VERBOSE);
288        }
289    }
290
291    /**
292     * Helper implementation to perform execution.
293     *
294     * @param checkstyleVersion Checkstyle compile version.
295     */
296    private void realExecute(String checkstyleVersion) {
297        // Create the root module
298        RootModule rootModule = null;
299        try {
300            rootModule = createRootModule();
301
302            // setup the listeners
303            final AuditListener[] listeners = getListeners();
304            for (AuditListener element : listeners) {
305                rootModule.addListener(element);
306            }
307            final SeverityLevelCounter warningCounter =
308                new SeverityLevelCounter(SeverityLevel.WARNING);
309            rootModule.addListener(warningCounter);
310
311            processFiles(rootModule, warningCounter, checkstyleVersion);
312        }
313        finally {
314            if (rootModule != null) {
315                rootModule.destroy();
316            }
317        }
318    }
319
320    /**
321     * Scans and processes files by means given root module.
322     *
323     * @param rootModule Root module to process files
324     * @param warningCounter Root Module's counter of warnings
325     * @param checkstyleVersion Checkstyle compile version
326     * @throws BuildException if the files could not be processed,
327     *     or if the build failed due to violations.
328     */
329    private void processFiles(RootModule rootModule, final SeverityLevelCounter warningCounter,
330            final String checkstyleVersion) {
331        final long startTime = System.currentTimeMillis();
332        final List<File> files = getFilesToCheck();
333        final long endTime = System.currentTimeMillis();
334        log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX,
335            Project.MSG_VERBOSE);
336
337        log("Running Checkstyle "
338                + checkstyleVersion
339                + " on " + files.size()
340                + " files", Project.MSG_INFO);
341        log("Using configuration " + config, Project.MSG_VERBOSE);
342
343        final int numErrs;
344
345        try {
346            final long processingStartTime = System.currentTimeMillis();
347            numErrs = rootModule.process(files);
348            final long processingEndTime = System.currentTimeMillis();
349            log("To process the files took " + (processingEndTime - processingStartTime)
350                + TIME_SUFFIX, Project.MSG_VERBOSE);
351        }
352        catch (CheckstyleException exc) {
353            throw new BuildException("Unable to process files: " + files, exc);
354        }
355        final int numWarnings = warningCounter.getCount();
356        final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings;
357
358        // Handle the return status
359        if (!okStatus) {
360            final String failureMsg =
361                    "Got " + numErrs + " errors (max allowed: " + maxErrors + ") and "
362                            + numWarnings + " warnings.";
363            if (failureProperty != null) {
364                getProject().setProperty(failureProperty, failureMsg);
365            }
366
367            if (failOnViolation) {
368                throw new BuildException(failureMsg, getLocation());
369            }
370        }
371    }
372
373    /**
374     * Creates new instance of the root module.
375     *
376     * @return new instance of the root module
377     * @throws BuildException if the root module could not be created.
378     */
379    private RootModule createRootModule() {
380        final RootModule rootModule;
381        try {
382            final Properties props = createOverridingProperties();
383            final ThreadModeSettings threadModeSettings =
384                    ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
385            final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
386            if (executeIgnoredModules) {
387                ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
388            }
389            else {
390                ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
391            }
392
393            final Configuration configuration = ConfigurationLoader.loadConfiguration(config,
394                    new PropertiesExpander(props), ignoredModulesOptions, threadModeSettings);
395
396            final ClassLoader moduleClassLoader =
397                Checker.class.getClassLoader();
398
399            final ModuleFactory factory = new PackageObjectFactory(
400                    Checker.class.getPackage().getName() + ".", moduleClassLoader);
401
402            rootModule = (RootModule) factory.createModule(configuration.getName());
403            rootModule.setModuleClassLoader(moduleClassLoader);
404            rootModule.configure(configuration);
405        }
406        catch (final CheckstyleException exc) {
407            throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: "
408                    + "config {%s}.", config), exc);
409        }
410        return rootModule;
411    }
412
413    /**
414     * Create the Properties object based on the arguments specified
415     * to the ANT task.
416     *
417     * @return the properties for property expansion
418     * @throws BuildException if the properties file could not be loaded.
419     */
420    private Properties createOverridingProperties() {
421        final Properties returnValue = new Properties();
422
423        // Load the properties file if specified
424        if (properties != null) {
425            try (InputStream inStream = Files.newInputStream(properties)) {
426                returnValue.load(inStream);
427            }
428            catch (final IOException exc) {
429                throw new BuildException("Error loading Properties file '"
430                        + properties + "'", exc, getLocation());
431            }
432        }
433
434        // override with Ant properties like ${basedir}
435        final Map<String, Object> antProps = getProject().getProperties();
436        for (Map.Entry<String, Object> entry : antProps.entrySet()) {
437            final String value = String.valueOf(entry.getValue());
438            returnValue.setProperty(entry.getKey(), value);
439        }
440
441        // override with properties specified in subelements
442        for (Property p : overrideProps) {
443            returnValue.setProperty(p.getKey(), p.getValue());
444        }
445
446        return returnValue;
447    }
448
449    /**
450     * Return the array of listeners set in this task.
451     *
452     * @return the array of listeners.
453     * @throws BuildException if the listeners could not be created.
454     */
455    private AuditListener[] getListeners() {
456        final int formatterCount = Math.max(1, formatters.size());
457
458        final AuditListener[] listeners = new AuditListener[formatterCount];
459
460        // formatters
461        try {
462            if (formatters.isEmpty()) {
463                final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG);
464                final OutputStream err = new LogOutputStream(this, Project.MSG_ERR);
465                listeners[0] = new DefaultLogger(debug, OutputStreamOptions.CLOSE,
466                        err, OutputStreamOptions.CLOSE);
467            }
468            else {
469                for (int i = 0; i < formatterCount; i++) {
470                    final Formatter formatter = formatters.get(i);
471                    listeners[i] = formatter.createListener(this);
472                }
473            }
474        }
475        catch (IOException exc) {
476            throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: "
477                    + "formatters {%s}.", formatters), exc);
478        }
479        return listeners;
480    }
481
482    /**
483     * Returns the list of files (full path name) to process.
484     *
485     * @return the list of files included via the fileName, filesets and paths.
486     */
487    private List<File> getFilesToCheck() {
488        final List<File> allFiles = new ArrayList<>();
489        if (fileName != null) {
490            // oops, we've got an additional one to process, don't
491            // forget it. No sweat, it's fully resolved via the setter.
492            log("Adding standalone file for audit", Project.MSG_VERBOSE);
493            allFiles.add(Path.of(fileName).toFile());
494        }
495
496        final List<File> filesFromFileSets = scanFileSets();
497        allFiles.addAll(filesFromFileSets);
498
499        final List<Path> filesFromPaths = scanPaths();
500        allFiles.addAll(filesFromPaths.stream()
501            .map(Path::toFile)
502            .toList());
503
504        return allFiles;
505    }
506
507    /**
508     * Retrieves all files from the defined paths.
509     *
510     * @return a list of files defined via paths.
511     */
512    private List<Path> scanPaths() {
513        final List<Path> allFiles = new ArrayList<>();
514
515        for (int i = 0; i < paths.size(); i++) {
516            final org.apache.tools.ant.types.Path currentPath = paths.get(i);
517            final List<Path> pathFiles = scanPath(currentPath, i + 1);
518            allFiles.addAll(pathFiles);
519        }
520
521        return allFiles;
522    }
523
524    /**
525     * Scans the given path and retrieves all files for the given path.
526     *
527     * @param path      A path to scan.
528     * @param pathIndex The index of the given path. Used in log messages only.
529     * @return A list of files, extracted from the given path.
530     */
531    private List<Path> scanPath(org.apache.tools.ant.types.Path path, int pathIndex) {
532        final String[] resources = path.list();
533        log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE);
534        final List<Path> allFiles = new ArrayList<>();
535        int concreteFilesCount = 0;
536
537        for (String resource : resources) {
538            final Path file = Path.of(resource);
539            if (Files.isRegularFile(file)) {
540                concreteFilesCount++;
541                allFiles.add(file);
542            }
543            else {
544                final DirectoryScanner scanner = new DirectoryScanner();
545                scanner.setBasedir(file.toFile());
546                scanner.scan();
547                final List<Path> scannedFiles = retrieveAllScannedFiles(scanner, pathIndex);
548                allFiles.addAll(scannedFiles);
549            }
550        }
551
552        if (concreteFilesCount > 0) {
553            log(String.format(Locale.ROOT, "%d) Adding %d files from path %s",
554                pathIndex, concreteFilesCount, path), Project.MSG_VERBOSE);
555        }
556
557        return allFiles;
558    }
559
560    /**
561     * Returns the list of files (full path name) to process.
562     *
563     * @return the list of files included via the filesets.
564     */
565    protected List<File> scanFileSets() {
566        final List<Path> allFiles = new ArrayList<>();
567
568        for (int i = 0; i < fileSets.size(); i++) {
569            final FileSet fileSet = fileSets.get(i);
570            final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
571            final List<Path> scannedFiles = retrieveAllScannedFiles(scanner, i);
572            allFiles.addAll(scannedFiles);
573        }
574
575        return allFiles.stream()
576            .map(Path::toFile)
577            .toList();
578    }
579
580    /**
581     * Retrieves all matched files from the given scanner.
582     *
583     * @param scanner  A directory scanner. Note, that {@link DirectoryScanner#scan()}
584     *                 must be called before calling this method.
585     * @param logIndex A log entry index. Used only for log messages.
586     * @return A list of files, retrieved from the given scanner.
587     */
588    private List<Path> retrieveAllScannedFiles(FileScanner scanner, int logIndex) {
589        final String[] fileNames = scanner.getIncludedFiles();
590        log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s",
591                logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE);
592
593        return Arrays.stream(fileNames)
594          .map(scanner.getBasedir().toPath()::resolve)
595          .toList();
596    }
597
598    /**
599     * Poor man enumeration for the formatter types.
600     */
601    public static class FormatterType extends EnumeratedAttribute {
602
603        /** My possible values. */
604        private static final String[] VALUES = {E_XML, E_PLAIN, E_SARIF};
605
606        @Override
607        public String[] getValues() {
608            return VALUES.clone();
609        }
610
611    }
612
613    /**
614     * Details about a formatter to be used.
615     */
616    public static class Formatter {
617
618        /** The formatter type. */
619        private FormatterType type;
620        /** The file to output to. */
621        private File toFile;
622        /** Whether or not to write to the named file. */
623        private boolean useFile = true;
624
625        /**
626         * Set the type of the formatter.
627         *
628         * @param type the type
629         */
630        public void setType(FormatterType type) {
631            this.type = type;
632        }
633
634        /**
635         * Set the file to output to.
636         *
637         * @param destination destination the file to output to
638         */
639        public void setTofile(File destination) {
640            toFile = destination;
641        }
642
643        /**
644         * Sets whether or not we write to a file if it is provided.
645         *
646         * @param use whether not to use provided file.
647         */
648        public void setUseFile(boolean use) {
649            useFile = use;
650        }
651
652        /**
653         * Creates a listener for the formatter.
654         *
655         * @param task the task running
656         * @return a listener
657         * @throws IOException if an error occurs
658         */
659        public AuditListener createListener(Task task) throws IOException {
660            final AuditListener listener;
661            if (type != null
662                    && E_XML.equals(type.getValue())) {
663                listener = createXmlLogger(task);
664            }
665            else if (type != null
666                    && E_SARIF.equals(type.getValue())) {
667                listener = createSarifLogger(task);
668            }
669            else {
670                listener = createDefaultLogger(task);
671            }
672            return listener;
673        }
674
675        /**
676         * Creates Sarif logger.
677         *
678         * @param task the task to possibly log to
679         * @return an SarifLogger instance
680         * @throws IOException if an error occurs
681         */
682        private AuditListener createSarifLogger(Task task) throws IOException {
683            final AuditListener sarifLogger;
684            if (toFile == null || !useFile) {
685                sarifLogger = new SarifLogger(new LogOutputStream(task, Project.MSG_INFO),
686                        OutputStreamOptions.CLOSE);
687            }
688            else {
689                sarifLogger = new SarifLogger(Files.newOutputStream(toFile.toPath()),
690                        OutputStreamOptions.CLOSE);
691            }
692            return sarifLogger;
693        }
694
695        /**
696         * Creates default logger.
697         *
698         * @param task the task to possibly log to
699         * @return a DefaultLogger instance
700         * @throws IOException if an error occurs
701         */
702        private AuditListener createDefaultLogger(Task task)
703                throws IOException {
704            final AuditListener defaultLogger;
705            if (toFile == null || !useFile) {
706                defaultLogger = new DefaultLogger(
707                    new LogOutputStream(task, Project.MSG_DEBUG),
708                        OutputStreamOptions.CLOSE,
709                        new LogOutputStream(task, Project.MSG_ERR),
710                        OutputStreamOptions.CLOSE
711                );
712            }
713            else {
714                final OutputStream infoStream = Files.newOutputStream(toFile.toPath());
715                defaultLogger =
716                        new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
717                                infoStream, OutputStreamOptions.NONE);
718            }
719            return defaultLogger;
720        }
721
722        /**
723         * Creates XML logger.
724         *
725         * @param task the task to possibly log to
726         * @return an XMLLogger instance
727         * @throws IOException if an error occurs
728         */
729        private AuditListener createXmlLogger(Task task) throws IOException {
730            final AuditListener xmlLogger;
731            if (toFile == null || !useFile) {
732                xmlLogger = new XMLLogger(new LogOutputStream(task, Project.MSG_INFO),
733                        OutputStreamOptions.CLOSE);
734            }
735            else {
736                xmlLogger = new XMLLogger(Files.newOutputStream(toFile.toPath()),
737                        OutputStreamOptions.CLOSE);
738            }
739            return xmlLogger;
740        }
741
742    }
743
744    /**
745     * Represents a property that consists of a key and value.
746     */
747    public static class Property {
748
749        /** The property key. */
750        private String key;
751        /** The property value. */
752        private String value;
753
754        /**
755         * Gets key.
756         *
757         * @return the property key
758         */
759        public String getKey() {
760            return key;
761        }
762
763        /**
764         * Sets key.
765         *
766         * @param key sets the property key
767         */
768        public void setKey(String key) {
769            this.key = key;
770        }
771
772        /**
773         * Gets value.
774         *
775         * @return the property value
776         */
777        public String getValue() {
778            return value;
779        }
780
781        /**
782         * Sets value.
783         *
784         * @param value set the property value
785         */
786        public void setValue(String value) {
787            this.value = value;
788        }
789
790        /**
791         * Sets the property value from a File.
792         *
793         * @param file set the property value from a File
794         */
795        public void setFile(File file) {
796            value = file.getAbsolutePath();
797        }
798
799    }
800
801}