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  
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      * 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 ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
384             if (executeIgnoredModules) {
385                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
386             }
387             else {
388                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
389             }
390 
391             final ThreadModeSettings threadModeSettings =
392                     ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
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 }