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