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