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