001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.FileInputStream; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.OutputStream; 027import java.net.URL; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Locale; 032import java.util.Map; 033import java.util.Properties; 034import java.util.ResourceBundle; 035import java.util.stream.Collectors; 036 037import org.apache.tools.ant.AntClassLoader; 038import org.apache.tools.ant.BuildException; 039import org.apache.tools.ant.DirectoryScanner; 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; 045import org.apache.tools.ant.types.Path; 046import org.apache.tools.ant.types.Reference; 047 048import com.google.common.io.Closeables; 049import com.puppycrawl.tools.checkstyle.Checker; 050import com.puppycrawl.tools.checkstyle.ConfigurationLoader; 051import com.puppycrawl.tools.checkstyle.DefaultLogger; 052import com.puppycrawl.tools.checkstyle.ModuleFactory; 053import com.puppycrawl.tools.checkstyle.PackageObjectFactory; 054import com.puppycrawl.tools.checkstyle.PropertiesExpander; 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 a ANT task for calling checkstyle. See the documentation 065 * of the task for usage. 066 * @author Oliver Burn 067 */ 068public class CheckstyleAntTask extends Task { 069 /** Poor man's enum for an xml formatter. */ 070 private static final String E_XML = "xml"; 071 /** Poor man's enum for an plain formatter. */ 072 private static final String E_PLAIN = "plain"; 073 074 /** Suffix for time string. */ 075 private static final String TIME_SUFFIX = " ms."; 076 077 /** Contains the paths to process. */ 078 private final List<Path> paths = new ArrayList<>(); 079 080 /** Contains the filesets to process. */ 081 private final List<FileSet> fileSets = new ArrayList<>(); 082 083 /** Contains the formatters to log to. */ 084 private final List<Formatter> formatters = new ArrayList<>(); 085 086 /** Contains the Properties to override. */ 087 private final List<Property> overrideProps = new ArrayList<>(); 088 089 /** Class path to locate class files. */ 090 private Path classpath; 091 092 /** Name of file to check. */ 093 private String fileName; 094 095 /** Config file containing configuration. */ 096 private String configLocation; 097 098 /** Whether to fail build on violations. */ 099 private boolean failOnViolation = true; 100 101 /** Property to set on violations. */ 102 private String failureProperty; 103 104 /** The name of the properties file. */ 105 private File properties; 106 107 /** The maximum number of errors that are tolerated. */ 108 private int maxErrors; 109 110 /** The maximum number of warnings that are tolerated. */ 111 private int maxWarnings = Integer.MAX_VALUE; 112 113 /** 114 * Whether to omit ignored modules - some modules may log tove 115 * their severity depending on their configuration (e.g. WriteTag) so 116 * need to be included 117 */ 118 private boolean omitIgnoredModules = true; 119 120 //////////////////////////////////////////////////////////////////////////// 121 // Setters for ANT specific attributes 122 //////////////////////////////////////////////////////////////////////////// 123 124 /** 125 * Tells this task to write failure message to the named property when there 126 * is a violation. 127 * @param propertyName the name of the property to set 128 * in the event of an 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 * @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 * @param maxErrors the maximum number of errors allowed. 145 */ 146 public void setMaxErrors(int maxErrors) { 147 this.maxErrors = maxErrors; 148 } 149 150 /** 151 * Sets the maximum number of warnings allowed. Default is 152 * {@link Integer#MAX_VALUE}. 153 * @param maxWarnings the maximum number of warnings allowed. 154 */ 155 public void setMaxWarnings(int maxWarnings) { 156 this.maxWarnings = maxWarnings; 157 } 158 159 /** 160 * Adds a path. 161 * @param path the path to add. 162 */ 163 public void addPath(Path path) { 164 paths.add(path); 165 } 166 167 /** 168 * Adds set of files (nested fileset attribute). 169 * @param fileSet the file set to add 170 */ 171 public void addFileset(FileSet fileSet) { 172 fileSets.add(fileSet); 173 } 174 175 /** 176 * Add a formatter. 177 * @param formatter the formatter to add for logging. 178 */ 179 public void addFormatter(Formatter formatter) { 180 formatters.add(formatter); 181 } 182 183 /** 184 * Add an override property. 185 * @param property the property to add 186 */ 187 public void addProperty(Property property) { 188 overrideProps.add(property); 189 } 190 191 /** 192 * Set the class path. 193 * @param classpath the path to locate classes 194 */ 195 public void setClasspath(Path classpath) { 196 if (this.classpath == null) { 197 this.classpath = classpath; 198 } 199 else { 200 this.classpath.append(classpath); 201 } 202 } 203 204 /** 205 * Set the class path from a reference defined elsewhere. 206 * @param classpathRef the reference to an instance defining the classpath 207 */ 208 public void setClasspathRef(Reference classpathRef) { 209 createClasspath().setRefid(classpathRef); 210 } 211 212 /** 213 * Creates classpath. 214 * @return a created path for locating classes 215 */ 216 public Path createClasspath() { 217 if (classpath == null) { 218 classpath = new Path(getProject()); 219 } 220 return classpath.createPath(); 221 } 222 223 /** 224 * Sets file to be checked. 225 * @param file the file to be checked 226 */ 227 public void setFile(File file) { 228 fileName = file.getAbsolutePath(); 229 } 230 231 /** 232 * Sets configuration file. 233 * @param file the configuration file to use 234 */ 235 public void setConfig(File file) { 236 setConfigLocation(file.getAbsolutePath()); 237 } 238 239 /** 240 * Sets URL to the configuration. 241 * @param url the URL of the configuration to use 242 * @deprecated please use setConfigUrl instead 243 */ 244 // -@cs[AbbreviationAsWordInName] Should be removed at 7.0 version, 245 // we keep for some time to avoid braking compatibility. 246 @Deprecated 247 public void setConfigURL(URL url) { 248 setConfigUrl(url); 249 } 250 251 /** 252 * Sets URL to the configuration. 253 * @param url the URL of the configuration to use 254 */ 255 public void setConfigUrl(URL url) { 256 setConfigLocation(url.toExternalForm()); 257 } 258 259 /** 260 * Sets the location of the configuration. 261 * @param location the location, which is either a 262 */ 263 private void setConfigLocation(String location) { 264 if (configLocation != null) { 265 throw new BuildException("Attributes 'config' and 'configURL' " 266 + "must not be set at the same time"); 267 } 268 configLocation = location; 269 } 270 271 /** 272 * Sets flag - whether to omit ignored modules. 273 * @param omit whether to omit ignored modules 274 */ 275 public void setOmitIgnoredModules(boolean omit) { 276 omitIgnoredModules = omit; 277 } 278 279 //////////////////////////////////////////////////////////////////////////// 280 // Setters for Root Module's configuration attributes 281 //////////////////////////////////////////////////////////////////////////// 282 283 /** 284 * Sets a properties file for use instead 285 * of individually setting them. 286 * @param props the properties File to use 287 */ 288 public void setProperties(File props) { 289 properties = props; 290 } 291 292 //////////////////////////////////////////////////////////////////////////// 293 // The doers 294 //////////////////////////////////////////////////////////////////////////// 295 296 @Override 297 public void execute() { 298 final long startTime = System.currentTimeMillis(); 299 300 try { 301 // output version info in debug mode 302 final ResourceBundle compilationProperties = ResourceBundle 303 .getBundle("checkstylecompilation", Locale.ROOT); 304 final String version = compilationProperties 305 .getString("checkstyle.compile.version"); 306 final String compileTimestamp = compilationProperties 307 .getString("checkstyle.compile.timestamp"); 308 log("checkstyle version " + version, Project.MSG_VERBOSE); 309 log("compiled on " + compileTimestamp, Project.MSG_VERBOSE); 310 311 // Check for no arguments 312 if (fileName == null 313 && fileSets.isEmpty() 314 && paths.isEmpty()) { 315 throw new BuildException( 316 "Must specify at least one of 'file' or nested 'fileset' or 'path'.", 317 getLocation()); 318 } 319 if (configLocation == null) { 320 throw new BuildException("Must specify 'config'.", getLocation()); 321 } 322 realExecute(version); 323 } 324 finally { 325 final long endTime = System.currentTimeMillis(); 326 log("Total execution took " + (endTime - startTime) + TIME_SUFFIX, 327 Project.MSG_VERBOSE); 328 } 329 } 330 331 /** 332 * Helper implementation to perform execution. 333 * @param checkstyleVersion Checkstyle compile version. 334 */ 335 private void realExecute(String checkstyleVersion) { 336 // Create the root module 337 RootModule rootModule = null; 338 try { 339 rootModule = createRootModule(); 340 341 // setup the listeners 342 final AuditListener[] listeners = getListeners(); 343 for (AuditListener element : listeners) { 344 rootModule.addListener(element); 345 } 346 final SeverityLevelCounter warningCounter = 347 new SeverityLevelCounter(SeverityLevel.WARNING); 348 rootModule.addListener(warningCounter); 349 350 processFiles(rootModule, warningCounter, checkstyleVersion); 351 } 352 finally { 353 destroyRootModule(rootModule); 354 } 355 } 356 357 /** 358 * Destroy root module. This method exists only due to bug in cobertura library 359 * https://github.com/cobertura/cobertura/issues/170 360 * @param rootModule Root module that was used to process files 361 */ 362 private static void destroyRootModule(RootModule rootModule) { 363 if (rootModule != null) { 364 rootModule.destroy(); 365 } 366 } 367 368 /** 369 * Scans and processes files by means given root module. 370 * @param rootModule Root module to process files 371 * @param warningCounter Root Module's counter of warnings 372 * @param checkstyleVersion Checkstyle compile version 373 */ 374 private void processFiles(RootModule rootModule, final SeverityLevelCounter warningCounter, 375 final String checkstyleVersion) { 376 final long startTime = System.currentTimeMillis(); 377 final List<File> files = getFilesToCheck(); 378 final long endTime = System.currentTimeMillis(); 379 log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX, 380 Project.MSG_VERBOSE); 381 382 log("Running Checkstyle " + checkstyleVersion + " on " + files.size() 383 + " files", Project.MSG_INFO); 384 log("Using configuration " + configLocation, Project.MSG_VERBOSE); 385 386 final int numErrs; 387 388 try { 389 final long processingStartTime = System.currentTimeMillis(); 390 numErrs = rootModule.process(files); 391 final long processingEndTime = System.currentTimeMillis(); 392 log("To process the files took " + (processingEndTime - processingStartTime) 393 + TIME_SUFFIX, Project.MSG_VERBOSE); 394 } 395 catch (CheckstyleException ex) { 396 throw new BuildException("Unable to process files: " + files, ex); 397 } 398 final int numWarnings = warningCounter.getCount(); 399 final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings; 400 401 // Handle the return status 402 if (!okStatus) { 403 final String failureMsg = 404 "Got " + numErrs + " errors and " + numWarnings 405 + " warnings."; 406 if (failureProperty != null) { 407 getProject().setProperty(failureProperty, failureMsg); 408 } 409 410 if (failOnViolation) { 411 throw new BuildException(failureMsg, getLocation()); 412 } 413 } 414 } 415 416 /** 417 * Creates new instance of the root module. 418 * @return new instance of the root module 419 */ 420 private RootModule createRootModule() { 421 final RootModule rootModule; 422 try { 423 final Properties props = createOverridingProperties(); 424 final Configuration config = 425 ConfigurationLoader.loadConfiguration( 426 configLocation, 427 new PropertiesExpander(props), 428 omitIgnoredModules); 429 430 final ClassLoader moduleClassLoader = 431 Checker.class.getClassLoader(); 432 433 final ModuleFactory factory = new PackageObjectFactory( 434 Checker.class.getPackage().getName() + ".", moduleClassLoader); 435 436 rootModule = (RootModule) factory.createModule(config.getName()); 437 rootModule.setModuleClassLoader(moduleClassLoader); 438 439 if (rootModule instanceof Checker) { 440 final ClassLoader loader = new AntClassLoader(getProject(), 441 classpath); 442 443 ((Checker) rootModule).setClassLoader(loader); 444 } 445 446 rootModule.configure(config); 447 } 448 catch (final CheckstyleException ex) { 449 throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: " 450 + "configLocation {%s}, classpath {%s}.", configLocation, classpath), ex); 451 } 452 return rootModule; 453 } 454 455 /** 456 * Create the Properties object based on the arguments specified 457 * to the ANT task. 458 * @return the properties for property expansion expansion 459 * @throws BuildException if an error occurs 460 */ 461 private Properties createOverridingProperties() { 462 final Properties returnValue = new Properties(); 463 464 // Load the properties file if specified 465 if (properties != null) { 466 FileInputStream inStream = null; 467 try { 468 inStream = new FileInputStream(properties); 469 returnValue.load(inStream); 470 } 471 catch (final IOException ex) { 472 throw new BuildException("Error loading Properties file '" 473 + properties + "'", ex, getLocation()); 474 } 475 finally { 476 Closeables.closeQuietly(inStream); 477 } 478 } 479 480 // override with Ant properties like ${basedir} 481 final Map<String, Object> antProps = getProject().getProperties(); 482 for (Map.Entry<String, Object> entry : antProps.entrySet()) { 483 final String value = String.valueOf(entry.getValue()); 484 returnValue.setProperty(entry.getKey(), value); 485 } 486 487 // override with properties specified in subelements 488 for (Property p : overrideProps) { 489 returnValue.setProperty(p.getKey(), p.getValue()); 490 } 491 492 return returnValue; 493 } 494 495 /** 496 * Return the list of listeners set in this task. 497 * @return the list of listeners. 498 */ 499 private AuditListener[] getListeners() { 500 final int formatterCount = Math.max(1, formatters.size()); 501 502 final AuditListener[] listeners = new AuditListener[formatterCount]; 503 504 // formatters 505 try { 506 if (formatters.isEmpty()) { 507 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG); 508 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR); 509 listeners[0] = new DefaultLogger(debug, true, err, true); 510 } 511 else { 512 for (int i = 0; i < formatterCount; i++) { 513 final Formatter formatter = formatters.get(i); 514 listeners[i] = formatter.createListener(this); 515 } 516 } 517 } 518 catch (IOException ex) { 519 throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: " 520 + "formatters {%s}.", formatters), ex); 521 } 522 return listeners; 523 } 524 525 /** 526 * Returns the list of files (full path name) to process. 527 * @return the list of files included via the fileName, filesets and paths. 528 */ 529 protected List<File> getFilesToCheck() { 530 final List<File> allFiles = new ArrayList<>(); 531 if (fileName != null) { 532 // oops we've got an additional one to process, don't 533 // forget it. No sweat, it's fully resolved via the setter. 534 log("Adding standalone file for audit", Project.MSG_VERBOSE); 535 allFiles.add(new File(fileName)); 536 } 537 538 final List<File> filesFromFileSets = scanFileSets(); 539 allFiles.addAll(filesFromFileSets); 540 541 final List<File> filesFromPaths = scanPaths(); 542 allFiles.addAll(filesFromPaths); 543 544 return allFiles; 545 } 546 547 /** 548 * Retrieves all files from the defined paths. 549 * @return a list of files defined via paths. 550 */ 551 private List<File> scanPaths() { 552 final List<File> allFiles = new ArrayList<>(); 553 554 for (int i = 0; i < paths.size(); i++) { 555 final Path currentPath = paths.get(i); 556 final List<File> pathFiles = scanPath(currentPath, i + 1); 557 allFiles.addAll(pathFiles); 558 } 559 560 return allFiles; 561 } 562 563 /** 564 * Scans the given path and retrieves all files for the given path. 565 * 566 * @param path A path to scan. 567 * @param pathIndex The index of the given path. Used in log messages only. 568 * @return A list of files, extracted from the given path. 569 */ 570 private List<File> scanPath(Path path, int pathIndex) { 571 final String[] resources = path.list(); 572 log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE); 573 final List<File> allFiles = new ArrayList<>(); 574 int concreteFilesCount = 0; 575 576 for (String resource : resources) { 577 final File file = new File(resource); 578 if (file.isFile()) { 579 concreteFilesCount++; 580 allFiles.add(file); 581 } 582 else { 583 final DirectoryScanner scanner = new DirectoryScanner(); 584 scanner.setBasedir(file); 585 scanner.scan(); 586 final List<File> scannedFiles = retrieveAllScannedFiles(scanner, pathIndex); 587 allFiles.addAll(scannedFiles); 588 } 589 } 590 591 if (concreteFilesCount > 0) { 592 log(String.format(Locale.ROOT, "%d) Adding %d files from path %s", 593 pathIndex, concreteFilesCount, path), Project.MSG_VERBOSE); 594 } 595 596 return allFiles; 597 } 598 599 /** 600 * Returns the list of files (full path name) to process. 601 * @return the list of files included via the filesets. 602 */ 603 protected List<File> scanFileSets() { 604 final List<File> allFiles = new ArrayList<>(); 605 606 for (int i = 0; i < fileSets.size(); i++) { 607 final FileSet fileSet = fileSets.get(i); 608 final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject()); 609 scanner.scan(); 610 611 final List<File> scannedFiles = retrieveAllScannedFiles(scanner, i); 612 allFiles.addAll(scannedFiles); 613 } 614 615 return allFiles; 616 } 617 618 /** 619 * Retrieves all matched files from the given scanner. 620 * 621 * @param scanner A directory scanner. Note, that {@link DirectoryScanner#scan()} 622 * must be called before calling this method. 623 * @param logIndex A log entry index. Used only for log messages. 624 * @return A list of files, retrieved from the given scanner. 625 */ 626 private List<File> retrieveAllScannedFiles(DirectoryScanner scanner, int logIndex) { 627 final String[] fileNames = scanner.getIncludedFiles(); 628 log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s", 629 logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE); 630 631 return Arrays.stream(fileNames) 632 .map(name -> scanner.getBasedir() + File.separator + name) 633 .map(File::new) 634 .collect(Collectors.toList()); 635 } 636 637 /** 638 * Poor mans enumeration for the formatter types. 639 * @author Oliver Burn 640 */ 641 public static class FormatterType extends EnumeratedAttribute { 642 /** My possible values. */ 643 private static final String[] VALUES = {E_XML, E_PLAIN}; 644 645 @Override 646 public String[] getValues() { 647 return VALUES.clone(); 648 } 649 } 650 651 /** 652 * Details about a formatter to be used. 653 * @author Oliver Burn 654 */ 655 public static class Formatter { 656 /** The formatter type. */ 657 private FormatterType type; 658 /** The file to output to. */ 659 private File toFile; 660 /** Whether or not the write to the named file. */ 661 private boolean useFile = true; 662 663 /** 664 * Set the type of the formatter. 665 * @param type the type 666 */ 667 public void setType(FormatterType type) { 668 this.type = type; 669 } 670 671 /** 672 * Set the file to output to. 673 * @param destination destination the file to output to 674 */ 675 public void setTofile(File destination) { 676 toFile = destination; 677 } 678 679 /** 680 * Sets whether or not we write to a file if it is provided. 681 * @param use whether not not to use provided file. 682 */ 683 public void setUseFile(boolean use) { 684 useFile = use; 685 } 686 687 /** 688 * Creates a listener for the formatter. 689 * @param task the task running 690 * @return a listener 691 * @throws IOException if an error occurs 692 */ 693 public AuditListener createListener(Task task) throws IOException { 694 final AuditListener listener; 695 if (type != null 696 && E_XML.equals(type.getValue())) { 697 listener = createXmlLogger(task); 698 } 699 else { 700 listener = createDefaultLogger(task); 701 } 702 return listener; 703 } 704 705 /** 706 * Creates default logger. 707 * @param task the task to possibly log to 708 * @return a DefaultLogger instance 709 * @throws IOException if an error occurs 710 */ 711 private AuditListener createDefaultLogger(Task task) 712 throws IOException { 713 final AuditListener defaultLogger; 714 if (toFile == null || !useFile) { 715 defaultLogger = new DefaultLogger( 716 new LogOutputStream(task, Project.MSG_DEBUG), 717 true, new LogOutputStream(task, Project.MSG_ERR), true); 718 } 719 else { 720 final FileOutputStream infoStream = new FileOutputStream(toFile); 721 defaultLogger = new DefaultLogger(infoStream, true, infoStream, false); 722 } 723 return defaultLogger; 724 } 725 726 /** 727 * Creates XML logger. 728 * @param task the task to possibly log to 729 * @return an XMLLogger instance 730 * @throws IOException if an error occurs 731 */ 732 private AuditListener createXmlLogger(Task task) throws IOException { 733 final AuditListener xmlLogger; 734 if (toFile == null || !useFile) { 735 xmlLogger = new XMLLogger(new LogOutputStream(task, Project.MSG_INFO), true); 736 } 737 else { 738 xmlLogger = new XMLLogger(new FileOutputStream(toFile), true); 739 } 740 return xmlLogger; 741 } 742 } 743 744 /** 745 * Represents a property that consists of a key and value. 746 */ 747 public static class Property { 748 /** The property key. */ 749 private String key; 750 /** The property value. */ 751 private String value; 752 753 /** 754 * Gets key. 755 * @return the property key 756 */ 757 public String getKey() { 758 return key; 759 } 760 761 /** 762 * Sets key. 763 * @param key sets the property key 764 */ 765 public void setKey(String key) { 766 this.key = key; 767 } 768 769 /** 770 * Gets value. 771 * @return the property value 772 */ 773 public String getValue() { 774 return value; 775 } 776 777 /** 778 * Sets value. 779 * @param value set the property value 780 */ 781 public void setValue(String value) { 782 this.value = value; 783 } 784 785 /** 786 * Sets the property value from a File. 787 * @param file set the property value from a File 788 */ 789 public void setFile(File file) { 790 value = file.getAbsolutePath(); 791 } 792 } 793 794 /** Represents a custom listener. */ 795 public static class Listener { 796 /** Class name of the listener class. */ 797 private String className; 798 799 /** 800 * Gets class name. 801 * @return the class name 802 */ 803 public String getClassname() { 804 return className; 805 } 806 807 /** 808 * Sets class name. 809 * @param name set the class name 810 */ 811 public void setClassname(String name) { 812 className = name; 813 } 814 } 815}