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