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