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