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