001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.ant; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Locale; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Properties; 035 036import org.apache.tools.ant.BuildException; 037import org.apache.tools.ant.DirectoryScanner; 038import org.apache.tools.ant.FileScanner; 039import org.apache.tools.ant.Project; 040import org.apache.tools.ant.Task; 041import org.apache.tools.ant.taskdefs.LogOutputStream; 042import org.apache.tools.ant.types.EnumeratedAttribute; 043import org.apache.tools.ant.types.FileSet; 044 045import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions; 046import com.puppycrawl.tools.checkstyle.Checker; 047import com.puppycrawl.tools.checkstyle.ConfigurationLoader; 048import com.puppycrawl.tools.checkstyle.DefaultLogger; 049import com.puppycrawl.tools.checkstyle.ModuleFactory; 050import com.puppycrawl.tools.checkstyle.PackageObjectFactory; 051import com.puppycrawl.tools.checkstyle.PropertiesExpander; 052import com.puppycrawl.tools.checkstyle.SarifLogger; 053import com.puppycrawl.tools.checkstyle.ThreadModeSettings; 054import com.puppycrawl.tools.checkstyle.XMLLogger; 055import com.puppycrawl.tools.checkstyle.api.AuditListener; 056import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 057import com.puppycrawl.tools.checkstyle.api.Configuration; 058import com.puppycrawl.tools.checkstyle.api.RootModule; 059import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 060import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter; 061 062/** 063 * An implementation of an ANT task for calling checkstyle. See the documentation 064 * of the task for usage. 065 */ 066public class CheckstyleAntTask extends Task { 067 068 /** Poor man's enum for an xml formatter. */ 069 private static final String E_XML = "xml"; 070 /** Poor man's enum for a plain formatter. */ 071 private static final String E_PLAIN = "plain"; 072 /** Poor man's enum for a sarif formatter. */ 073 private static final String E_SARIF = "sarif"; 074 075 /** Suffix for time string. */ 076 private static final String TIME_SUFFIX = " ms."; 077 078 /** Contains the paths to process. */ 079 private final List<org.apache.tools.ant.types.Path> paths = new ArrayList<>(); 080 081 /** Contains the filesets to process. */ 082 private final List<FileSet> fileSets = new ArrayList<>(); 083 084 /** Contains the formatters to log to. */ 085 private final List<Formatter> formatters = new ArrayList<>(); 086 087 /** Contains the Properties to override. */ 088 private final List<Property> overrideProps = new ArrayList<>(); 089 090 /** Name of file to check. */ 091 private String fileName; 092 093 /** Config file containing configuration. */ 094 private String config; 095 096 /** Whether to fail build on violations. */ 097 private boolean failOnViolation = true; 098 099 /** Property to set on violations. */ 100 private String failureProperty; 101 102 /** The name of the properties file. */ 103 private Path properties; 104 105 /** The maximum number of errors that are tolerated. */ 106 private int maxErrors; 107 108 /** The maximum number of warnings that are tolerated. */ 109 private int maxWarnings = Integer.MAX_VALUE; 110 111 /** 112 * Whether to execute ignored modules - some modules may log above 113 * their severity depending on their configuration (e.g. WriteTag) so 114 * need to be included 115 */ 116 private boolean executeIgnoredModules; 117 118 /** 119 * 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}