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