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