diff --git a/pom.xml b/pom.xml index cb4a35f..c94e6d4 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,12 @@ system ${java.home}/../lib/tools.jar + + org.projectlombok + lombok + 1.16.20 + provided + diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/CoverageDoclet.java b/src/main/java/com/manoelcampos/javadoc/coverage/CoverageDoclet.java index 0d5739a..4042569 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/CoverageDoclet.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/CoverageDoclet.java @@ -15,16 +15,10 @@ */ package com.manoelcampos.javadoc.coverage; -import com.manoelcampos.javadoc.coverage.exporter.ConsoleExporter; -import com.manoelcampos.javadoc.coverage.exporter.DataExporter; -import com.manoelcampos.javadoc.coverage.exporter.HtmlExporter; -import com.sun.javadoc.DocErrorReporter; -import com.sun.javadoc.Doclet; -import com.sun.javadoc.LanguageVersion; -import com.sun.javadoc.RootDoc; -import com.sun.tools.doclets.standard.Standard; - -import java.io.*; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; +import com.manoelcampos.javadoc.coverage.exporter.*; +import com.manoelcampos.javadoc.coverage.stats.JavaDocsStats; +import com.sun.javadoc.*; /** * A {@link Doclet} that computes coverage of JavaDoc documentation. @@ -42,13 +36,6 @@ * @since 1.0.0 */ public class CoverageDoclet { - /** - * A command line parameter to enable defining the name of the coverage report. - * The first value is the long version of the parameter name and the second - * is the short one. - */ - public static final String OUTPUT_NAME_OPTION[] = {"-outputName", "-o"}; - /** * The {@link DataExporter} object to export the coverage report to a file * in a specific format. @@ -57,8 +44,8 @@ public class CoverageDoclet { private final RootDoc rootDoc; /** - * Starts the actual parsing or JavaDoc documentation and generation of the coverage report. - * This is the entry point for the JavaDoc tool to start the Doclet. + * Starts the actual parsing or JavaDoc documentation and generation of the coverage report. This is the entry point + * for the JavaDoc tool to start the Doclet. * * @param rootDoc root element which enables reading JavaDoc documentation * @return true if the Doclet was started successfully, false otherwise @@ -75,51 +62,12 @@ public static boolean start(final RootDoc rootDoc) { */ public CoverageDoclet(final RootDoc rootDoc) { this.rootDoc = rootDoc; - this.exporter = new HtmlExporter(this); - } + Configuration config = new Configuration(rootDoc.options()); - /** - * Checks if a given parameter is a valid custom parameter accepted by this doclet. - * @param paramName the name of the parameter to check - * @return true if it's a valid custom parameter, false otherwise - */ - private static boolean isCustomParameter(final String paramName) { - return isParameter(paramName, OUTPUT_NAME_OPTION); - } + JavaDocsStats stats = new JavaDocsStats(rootDoc, config); - /** - * Checks if the name of a given parameter corresponds to either its long or short form. - * - * @param paramName the name of the parameter to check - * @param validNames the list of accepted names for that parameter - * @return true if the given name corresponds to one of the valid names, false otherwise - */ - private static boolean isParameter(final String paramName, final String[] validNames) { - for (String validName : validNames) { - if (validName.equalsIgnoreCase(paramName)) { - return true; - } - } - - return false; - } - - /** - * Validates command line options. - * - * @param options the array of given options - * @param errorReporter an object that allows printing error messages for invalid options - * @return true if the options are valid, false otherwise - * @see Doclet#validOptions(String[][], DocErrorReporter) - */ - public static boolean validOptions(final String[][] options, final DocErrorReporter errorReporter) { - for (final String[] opt : options) { - if (isCustomParameter(opt[0])) { - return true; - } - } - - return Standard.validOptions(options, errorReporter); + // this needs to be the last part as it already accesses some stuff from the doclet + this.exporter = new HtmlExporter(config, stats); } /** @@ -130,32 +78,19 @@ public static boolean validOptions(final String[][] options, final DocErrorRepor * @see Doclet#optionLength(String) */ public static int optionLength(final String option) { - /*The custom outputName parameter accepts one argument. - * The name of the param counts as the one argument.*/ - if (isCustomParameter(option)) { - return 2; - } - - return Standard.optionLength(option); + return Configuration.getOptionLength(option); } /** - * Gets the values associated to a given command line option. + * Checks that all given option are valid * - * @param optionNames an array containing the valid names for the command line option to get its associated values. - * This array may include the long and short versions of the option name, - * for instance {@code {-outputName, -o}}. - * @return the values associated to the option, where the 0th element is the option itself; - * or an empty array if the option is invalid. + * @param options the options to be checked on validity + * @param errorReporter + * @return true if the options are valid + * @see Doclet#validOptions(String[][], DocErrorReporter) */ - public String[] getOptionValues(final String[] optionNames) { - for (final String[] optionValues : rootDoc.options()) { - if (isParameter(optionValues[0], optionNames)) { - return optionValues; - } - } - - return new String[]{}; + public static boolean validOptions(final String[][] options, final DocErrorReporter errorReporter) { + return Configuration.areValidOptions(options, errorReporter); } /** @@ -186,44 +121,4 @@ private boolean render() { public RootDoc getRootDoc() { return rootDoc; } - - /** - * Gets a {@link PrintWriter} used by the {@link #exporter} to write - * the coverage report to. - * - * @param file the file to which the coverage report will be saved to - */ - public PrintWriter getWriter(final File file) throws FileNotFoundException { - return new PrintWriter(new OutputStreamWriter(new FileOutputStream(file))); - } - - /** - * Gets a {@link File} object from a given file name. - * - * @param fileName the name of the file to get a {@link File} object. - * @return the {@link File} object - */ - public File getOutputFile(final String fileName) { - final File dir = new File(getOutputDir()); - if (!dir.exists() && !dir.mkdirs()) { - throw new RuntimeException("The directory '" + getOutputDir() + "' was not created due to unknown reason."); - } - - return new File(dir, fileName); - } - - /** - * Gets the output directory passed as a command line argument to javadoc tool. - * - * @return the output directory to export the JavaDocs - */ - private String getOutputDir() { - for (final String[] option : rootDoc.options()) { - if (option.length == 2 && option[0].equals("-d")) { - return Utils.includeTrailingDirSeparator(option[1]); - } - } - - return ""; - } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/configuration/Configuration.java b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/Configuration.java new file mode 100644 index 0000000..0a22d71 --- /dev/null +++ b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/Configuration.java @@ -0,0 +1,124 @@ +package com.manoelcampos.javadoc.coverage.configuration; + +import java.util.*; + +import com.manoelcampos.javadoc.coverage.Utils; +import com.sun.javadoc.DocErrorReporter; +import com.sun.tools.doclets.standard.Standard; + +public class Configuration { + + private static final NoValueOption computePublicCoverageOnly = new NoValueOption("-po", "-publicOnly", + "Indicates if coverage should be computed only for the public parts of a project."); + private static final StringOption coverageFileName = new StringOption("-o", "-outputName", + "Set the filename for the created coverage file. If nothing is given, the default is \"javadoc-coverage\".", + Optional.of("javadoc-coverage")); + + private static final List> ALL_OPTIONS = new ArrayList<>(); + + /** + * Static constructor for initializing the options list + */ + static { + ALL_OPTIONS.add(computePublicCoverageOnly); + ALL_OPTIONS.add(coverageFileName); + } + + /** + * The raw options which need to be parsed to our configuration + */ + private final String[][] rawOptions; + + public Configuration(String[][] rawOptions) { + this.rawOptions = rawOptions; + } + + public boolean computePublicCoverageOnly() { + return isOptionContained(computePublicCoverageOnly); + } + + public String getCoverageFileName() { + if (isOptionContained(coverageFileName)) { + return getOptionValue(coverageFileName); + } + return coverageFileName.getDefaultValue(); + } + + /** + * Gets the output directory passed as a command line argument to javadoc tool. + * + * @return the output directory to export the JavaDocs + */ + public String getOutputDirectory() { + // This is no option of the doclet, but instead of the javadoc tool + // so we don't declare it as an option here directly) + + for (final String[] option : rawOptions) { + if (option.length == 2 && option[0].equals("-d")) { + return Utils.includeTrailingDirSeparator(option[1]); + } + } + + return ""; + } + + private boolean isOptionContained(Option option) { + for (final String[] opt : rawOptions) { + if (option.isOption(opt[0])) { + return true; + } + } + return false; + } + + private T getOptionValue(Option option) { + if (!isOptionContained(option) && !option.hasDefaultValue()) { + throw new IllegalStateException("Option is not contained and has no default value"); + } + + if (!isOptionContained(option)) { + return option.getDefaultValue(); + } + + for (final String[] opt: rawOptions) { + if (option.isOption(opt[0])) { + return option.parseValue(opt); + } + } + + throw new IllegalStateException("no valid option found although it should be there"); + } + + /** + * This method is necessary for validating the doclet parameters, which happens before instantiating the + * CoverageDoclet and the configuration container. + * + * @param option + * @return the length of the option + */ + public static int getOptionLength(final String option) { + Optional> opt = ALL_OPTIONS.stream().filter(o -> o.isOption(option)).findFirst(); + if (opt.isPresent()) { + return opt.get().getLength(); + } + return Standard.optionLength(option); + } + + /** + * This method is necessary for validating the doclet parameters, which happens before instantiating the + * CoverageDoclet and the configuration container. + * + * @param options + * @param errorReporter + * @return indicates if the given options are valid + */ + public static boolean areValidOptions(final String[][] options, final DocErrorReporter errorReporter) { + for (final String[] opt : options) { + if (ALL_OPTIONS.stream().anyMatch(o -> o.isOption(opt[0]))) { + return true; + } + } + + return Standard.validOptions(options, errorReporter); + } +} diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/configuration/NoValueOption.java b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/NoValueOption.java new file mode 100644 index 0000000..f615411 --- /dev/null +++ b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/NoValueOption.java @@ -0,0 +1,20 @@ +package com.manoelcampos.javadoc.coverage.configuration; + +import lombok.NonNull; + +class NoValueOption extends Option { + + public NoValueOption(@NonNull String shortName, @NonNull String longName, @NonNull String description) { + super(shortName, longName, description, 1); + } + + @Override + public Void parseValue(String[] value) { + throw new UnsupportedOperationException("NoValueOption does not have a value."); + } + + @Override + public Void getDefaultValue() { + throw new UnsupportedOperationException("NoValueOption does not have a default value."); + } +} diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/configuration/Option.java b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/Option.java new file mode 100644 index 0000000..817f7c5 --- /dev/null +++ b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/Option.java @@ -0,0 +1,32 @@ +package com.manoelcampos.javadoc.coverage.configuration; + +import lombok.*; + +@ToString +@Getter +@AllArgsConstructor +abstract class Option { + @NonNull + private final String shortName; + @NonNull + private final String longName; + @NonNull + private final String description; + private final int length; + + public abstract T parseValue(String[] opt); + + public final boolean isOption(String name) { + return shortName.equals(name) || longName.equals(name); + } + + public boolean hasDefaultValue() { + return false; + } + + public abstract T getDefaultValue(); + + public int getLength() { + return length; + } +} diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/configuration/StringOption.java b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/StringOption.java new file mode 100644 index 0000000..8967ab3 --- /dev/null +++ b/src/main/java/com/manoelcampos/javadoc/coverage/configuration/StringOption.java @@ -0,0 +1,34 @@ +package com.manoelcampos.javadoc.coverage.configuration; + +import java.util.Optional; + +import lombok.NonNull; + +class StringOption extends Option { + + private final Optional defaultValue; + + public StringOption(@NonNull String shortName, @NonNull String longName, @NonNull String description, + @NonNull Optional defaultValue) { + super(shortName, longName, description, 2); + this.defaultValue = defaultValue; + } + + @Override + public String parseValue(String[] value) { + if (value.length != 2) { + throw new IllegalStateException("Wrong option line passed, string options do only have 2 values"); + } + return value[1]; + } + + @Override + public boolean hasDefaultValue() { + return defaultValue.isPresent(); + } + + @Override + public String getDefaultValue() { + return defaultValue.get(); + } +} diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/AbstractDataExporter.java b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/AbstractDataExporter.java index 91a9e45..8579c80 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/AbstractDataExporter.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/AbstractDataExporter.java @@ -15,13 +15,13 @@ */ package com.manoelcampos.javadoc.coverage.exporter; -import com.manoelcampos.javadoc.coverage.CoverageDoclet; +import java.io.*; + import com.manoelcampos.javadoc.coverage.Utils; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; import com.manoelcampos.javadoc.coverage.stats.JavaDocsStats; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; +import lombok.NonNull; /** * Abstract class to implement JavaDoc Coverage reports in different formats. @@ -34,17 +34,21 @@ public abstract class AbstractDataExporter implements DataExporter { private final JavaDocsStats stats; private final PrintWriter writer; private File file; - private final CoverageDoclet doclet; + private final Configuration config; private final String reportFileName; /** * Instantiates a DataExporter object to generate JavaDoc coverage report. * - * @param doclet the {@link CoverageDoclet} which computes teh JavaDoc coverage statistics. - * @param fileExtension the extension to the report file. If empty, the report will be printed to the standard output. + * @param config the configuration of the doclet + * @param stats the javadoc statistics + * @param fileExtension the extension to the report file. If empty, the report will be printed to the standard + * output. */ - protected AbstractDataExporter(final CoverageDoclet doclet, final String fileExtension) { - this.doclet = doclet; + protected AbstractDataExporter(@NonNull final Configuration config, @NonNull final JavaDocsStats stats, + final String fileExtension) { + this.stats = stats; + this.config = config; if (Utils.isStringEmpty(fileExtension)) { writer = new PrintWriter(System.out); @@ -52,54 +56,54 @@ protected AbstractDataExporter(final CoverageDoclet doclet, final String fileExt } else { this.reportFileName = generateReportFileName(fileExtension); try { - this.writer = doclet.getWriter(file); + this.writer = getWriter(file); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } - - this.stats = new JavaDocsStats(doclet.getRootDoc()); } /** * Instantiates a DataExporter object that generates JavaDoc coverage report to the standard output. * - * @param doclet the {@link CoverageDoclet} which computes teh JavaDoc coverage statistics. + * @param config the configuration of the doclet + * @param stats the javadoc statistics */ - protected AbstractDataExporter(final CoverageDoclet doclet) { - this(doclet, ""); - } - - private String generateReportFileName(final String fileExtension) { - String fileName = getFileNameFromCommandLine(); - fileName = fileName + fileExtensionToAdd(fileName, fileExtension); - this.file = doclet.getOutputFile(fileName); - return fileName; + protected AbstractDataExporter(@NonNull final Configuration config, @NonNull final JavaDocsStats stats) { + this(config, stats, ""); } /** - * Gets the JavaDoc Coverage Report file name from command line options - * or the default name if no option is given. + * Gets a {@link PrintWriter} used by the {@link #exporter} to write the coverage report to. * - * @return - * @see CoverageDoclet#OUTPUT_NAME_OPTION + * @param file the file to which the coverage report will be saved to */ - private String getFileNameFromCommandLine() { - final String[] outputNameOption = doclet.getOptionValues(CoverageDoclet.OUTPUT_NAME_OPTION); - return outputNameOption.length > 1 ? outputNameOption[1] : DEFAULT_OUTPUT_NAME; + private PrintWriter getWriter(final File file) throws FileNotFoundException { + return new PrintWriter(new OutputStreamWriter(new FileOutputStream(file))); + } + + private String generateReportFileName(final String fileExtension) { + String fileName = config.getCoverageFileName(); + if (Utils.getFileExtension(fileName).isEmpty()) { + fileName += getFileExtensionStartingWithDot(fileExtension); + } + this.file = getOutputFile(fileName); + return fileName; } /** - * Gets the extension to add to a given file if it doesn't have one. + * Gets a {@link File} object for the output file * - * @param fileName the file name to try getting and extension to add - * @param defaultFileExtension the default file extension to return if the file doesn't have one - * @return the file extension to add to the file it it doesn't have one or an empty string - * if it already has. + * @return the {@link File} object */ - private String fileExtensionToAdd(final String fileName, final String defaultFileExtension) { - return Utils.getFileExtension(fileName).isEmpty() ? - getFileExtensionStartingWithDot(defaultFileExtension) : ""; + private File getOutputFile(String fileName) { + String outputDirectory = config.getOutputDirectory(); + final File dir = new File(outputDirectory); + if (!dir.exists() && !dir.mkdirs()) { + throw new RuntimeException("The directory '" + outputDirectory + "' was not created due to unknown reason."); + } + + return new File(dir, fileName); } /** @@ -121,7 +125,6 @@ public String getReportFileName() { public boolean build() { try { header(); - exportClassesDocStats(); exportPackagesDocStats(); exportProjectDocumentationCoverageSummary(); footer(); @@ -154,6 +157,4 @@ public File getFile() { protected abstract void afterBuild(); protected abstract void exportPackagesDocStats(); - - protected abstract void exportClassesDocStats(); } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/ConsoleExporter.java b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/ConsoleExporter.java index 1f60fc3..f5d950f 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/ConsoleExporter.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/ConsoleExporter.java @@ -15,13 +15,14 @@ */ package com.manoelcampos.javadoc.coverage.exporter; -import com.manoelcampos.javadoc.coverage.CoverageDoclet; +import java.io.PrintWriter; +import java.util.List; + import com.manoelcampos.javadoc.coverage.Utils; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; import com.manoelcampos.javadoc.coverage.stats.*; -import com.sun.javadoc.PackageDoc; -import java.io.PrintWriter; -import java.util.List; +import lombok.NonNull; /** * Prints the JavaDoc coverage report to the console (standard output). @@ -31,8 +32,8 @@ */ public class ConsoleExporter extends AbstractDataExporter { - public ConsoleExporter(final CoverageDoclet doclet) { - super(doclet); + public ConsoleExporter(@NonNull Configuration config, @NonNull JavaDocsStats stats) { + super(config, stats); } @Override @@ -51,33 +52,20 @@ protected void exportProjectDocumentationCoverageSummary() { @Override protected void exportPackagesDocStats() { - final PackagesDocStats packagesDocStats = getStats().getPackagesDocStats(); - exportPkgsOrClassesDocStats(packagesDocStats); - packagesDocStats.getPackagesDoc().forEach(this::exportPackageDocStats); + for (PackageDocStats packageDoc : getStats().getPackagesDocStats()) { + getWriter().printf("%-26s: \t%11d Undocumented: %6d Documented: %6d (%.2f%%)\n", + packageDoc.getType() + " " + packageDoc.getName(), + packageDoc.getNumberOfDocumentableMembers(), packageDoc.getUndocumentedMembersOfElement(), + packageDoc.getNumberOfDocumentedMembers(), packageDoc.getDocumentedMembersPercent()); + exportClassesDocStats(packageDoc); + } getWriter().println(); } - private void exportPkgsOrClassesDocStats(MembersDocStats packagesDocStats) { - getWriter().printf("%-26s: \t%11d Undocumented: %6d Documented: %6d (%.2f%%)\n", - packagesDocStats.getType(), packagesDocStats.getMembersNumber(), packagesDocStats.getUndocumentedMembers(), - packagesDocStats.getDocumentedMembers(), packagesDocStats.getDocumentedMembersPercent()); - } - - /** - * Exports the statistics about JavaDoc coverage of a given package. - * @param doc the object containing the JavaDoc coverage data - * - */ - private void exportPackageDocStats(final PackageDoc doc) { - getWriter().printf("\tPackage %s. Documented: %s\n", doc.name(), Utils.isNotStringEmpty(doc.commentText())); - } - - @Override - protected void exportClassesDocStats() { - final ClassesDocStats classesDocStats = getStats().getClassesDocStats(); - exportPkgsOrClassesDocStats(classesDocStats); - - for (final ClassDocStats classStats : getStats().getClassesDocStats().getClassesList()) { + private void exportClassesDocStats(PackageDocStats packageWithClasses) { + for (final ClassDocStats classStats : packageWithClasses.getClassDocs()) { + getWriter().printf("\t%s: %s Package: %s Documented: %s (%.2f%%)\n", classStats.getType(), classStats.getName(), + packageWithClasses.getName(), classStats.isDocumented(), classStats.getDocumentedMembersPercent()); exportClassDocStats(classStats); } getWriter().println(); @@ -89,14 +77,11 @@ protected void exportClassesDocStats() { * */ private void exportClassDocStats(final ClassDocStats classStats) { - getWriter().printf("\t%s: %s Package: %s Documented: %s (%.2f%%)\n", - classStats.getType(), classStats.getName(), classStats.getPackageName(), - classStats.isDocumented(), classStats.getDocumentedMembersPercent()); - exportMembersDocStats(getWriter(), classStats.getFieldsStats()); + exportMembersDocStats(getWriter(), classStats.getEnumsStats()); + exportMembersDocStats(getWriter(), classStats.getAnnotationsStats()); exportMethodsDocStats(getWriter(), classStats.getConstructorsStats()); exportMethodsDocStats(getWriter(), classStats.getMethodsStats()); - exportMembersDocStats(getWriter(), classStats.getEnumsStats()); getWriter().flush(); } @@ -108,7 +93,7 @@ private void exportMethodsDocStats(final PrintWriter writer, final List 0) { + if (methodStats.getThrownExceptionsStats().getNumberOfDocumentableMembers() > 0) { exportMembersDocStats(writer, methodStats.getThrownExceptionsStats(), memberTypeFormat); } } @@ -119,16 +104,16 @@ private void exportMembersDocStats(final PrintWriter writer, final MembersDocSta } private void exportMembersDocStats(final PrintWriter writer, final MembersDocStats membersDocStats, final String memberTypeFormat) { - if (membersDocStats.getMembersNumber() == 0 && !membersDocStats.isPrintIfNoMembers()) { + if (membersDocStats.getNumberOfDocumentableMembers() == 0 && !membersDocStats.isPrintIfNoMembers()) { return; } final String format = (Utils.isStringEmpty(memberTypeFormat) ? "\t\t%-20s" : memberTypeFormat) + - " %6d Undocumented: %6d Documented: %6d (%.2f%%) \n"; + " %6d Undocumented: %6d Documented: %6d (%.2f%%) \n"; writer.printf(format, - membersDocStats.getType()+":", membersDocStats.getMembersNumber(), - membersDocStats.getUndocumentedMembers(), - membersDocStats.getDocumentedMembers(), membersDocStats.getDocumentedMembersPercent()); + membersDocStats.getType() + ":", membersDocStats.getNumberOfDocumentableMembers(), + membersDocStats.getUndocumentedMembersOfElement(), + membersDocStats.getNumberOfDocumentedMembers(), membersDocStats.getDocumentedMembersPercent()); } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/DataExporter.java b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/DataExporter.java index 4100b24..264ac32 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/DataExporter.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/DataExporter.java @@ -15,8 +15,6 @@ */ package com.manoelcampos.javadoc.coverage.exporter; -import com.manoelcampos.javadoc.coverage.CoverageDoclet; - /** * An interface to implement JavaDoc Coverage reports in different formats such as HTML, CSV, JSON, etc. * @@ -24,14 +22,6 @@ * @since 1.0.0 */ public interface DataExporter { - /** - * The name to be used as default for the JavaDoc Coverage report if - * a specific name is not given. - * - * @see CoverageDoclet#OUTPUT_NAME_OPTION - */ - String DEFAULT_OUTPUT_NAME = "javadoc-coverage"; - /** * Gets the actual name to be used for the JavaDoc Coverage Report, * whether the default or given one. diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/HtmlExporter.java b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/HtmlExporter.java index dd12c80..bfa2cb6 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/exporter/HtmlExporter.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/exporter/HtmlExporter.java @@ -15,14 +15,13 @@ */ package com.manoelcampos.javadoc.coverage.exporter; -import com.manoelcampos.javadoc.coverage.CoverageDoclet; +import java.util.List; + import com.manoelcampos.javadoc.coverage.Utils; -import com.manoelcampos.javadoc.coverage.stats.ClassDocStats; -import com.manoelcampos.javadoc.coverage.stats.MembersDocStats; -import com.manoelcampos.javadoc.coverage.stats.MethodDocStats; -import com.sun.javadoc.PackageDoc; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; +import com.manoelcampos.javadoc.coverage.stats.*; -import java.util.List; +import lombok.NonNull; /** * Exports the JavaDoc coverage report to an HTML file. @@ -33,13 +32,16 @@ public class HtmlExporter extends AbstractDataExporter { public static final String COLUMNS = "%s%s%s%s%s%s%.2f%%\n"; - public HtmlExporter(final CoverageDoclet doclet) { - super(doclet, ".html"); + public HtmlExporter(@NonNull final Configuration config, @NonNull JavaDocsStats stats) { + super(config, stats, ".html"); } @Override protected void exportProjectDocumentationCoverageSummary() { - getWriter().printf("" + COLUMNS + "", "Project Documentation Coverage", "", "", "", "", "", getStats().getDocumentedMembersPercent()); + JavaDocsStats stats = getStats(); + getWriter().printf("" + COLUMNS + "", "Project Documentation Coverage", "", "", + stats.getNumberOfDocumentableMembers(), stats.getUndocumentedMembersOfElement(), + stats.getNumberOfDocumentedMembers(), stats.getDocumentedMembersPercent()); } @Override @@ -81,21 +83,27 @@ protected void footer() { @Override protected void exportPackagesDocStats() { - exportMembersDocStatsSummary(getStats().getPackagesDocStats()); - for (final PackageDoc doc : getStats().getPackagesDocStats().getPackagesDoc()) { + for (final PackageDocStats doc : getStats().getPackagesDocStats()) { getWriter().println(""); - final Boolean documented = Utils.isNotStringEmpty(doc.getRawCommentText()); - final double coverage = Utils.boolToInt(documented)*100; - exportLine(2, "Package", doc.name(), "", "", "", documented.toString(), coverage); + exportLine(0, "Package", doc.getName(), "", doc.getNumberOfDocumentableMembers(), doc.getUndocumentedMembersOfElement(), + doc.getNumberOfDocumentedMembers(), doc.getDocumentedMembersPercent()); + int isPackageDocumented = Utils.boolToInt(doc.isDocumented()); + exportLine(2, "package-info.java", "", "", 1L, 1L - isPackageDocumented, (long) isPackageDocumented, + isPackageDocumented * 100.0); + exportClassesDocStats(doc); } } - @Override - protected void exportClassesDocStats() { - exportMembersDocStatsSummary(getStats().getClassesDocStats()); - for (final ClassDocStats classDocStats : getStats().getClassesDocStats().getClassesList()) { - exportMembersDocStatsSummary(classDocStats, 2, classDocStats.getName(), classDocStats.getPackageName()); + + private void exportClassesDocStats(PackageDocStats pkg) { + for (final ClassDocStats classDocStats : pkg.getClassDocs()) { + exportMembersDocStatsSummary(classDocStats, 2, classDocStats.getName(), pkg.getName()); + int isClassDocumented = Utils.boolToInt(classDocStats.isDocumented()); + exportLine(3, classDocStats.getType() + " Doc", "", "", 1L, 1L - isClassDocumented, (long) isClassDocumented, + isClassDocumented * 100.0); + exportMembersDocStatsSummary(classDocStats.getEnumsStats(), 3); exportMembersDocStatsSummary(classDocStats.getFieldsStats(), 3); + exportMembersDocStatsSummary(classDocStats.getAnnotationsStats(), 3); exportMethodsDocStats(classDocStats.getConstructorsStats()); exportMethodsDocStats(classDocStats.getMethodsStats()); } @@ -104,6 +112,13 @@ protected void exportClassesDocStats() { private void exportMethodsDocStats(final List methods) { for (MethodDocStats m : methods) { exportMembersDocStatsSummary(m, 4, m.getMethodName(), ""); + int isMethodDocumented = Utils.boolToInt(m.isDocumented()); + exportLine(5, m.getType() + " Doc", "", "", 1L, 1L - isMethodDocumented, (long) isMethodDocumented, + isMethodDocumented * 100.0); + if (!m.isVoidMethodOrConstructor()) { + int isReturnDocumented = Utils.boolToInt(m.isReturnDocumented()); + exportLine(5, "Return Value", "", "", 1L, 1L - isReturnDocumented, (long) isReturnDocumented, isReturnDocumented * 100.0); + } exportMembersDocStatsSummary(m.getParamsStats(), 5); exportMembersDocStatsSummary(m.getThrownExceptionsStats(), 5); } @@ -113,20 +128,16 @@ private void exportMembersDocStatsSummary(final MembersDocStats membersDocStats, exportMembersDocStatsSummary(membersDocStats, indentLevel, "",""); } - private void exportMembersDocStatsSummary(final MembersDocStats membersDocStats) { - exportMembersDocStatsSummary(membersDocStats, 1, "",""); - } - private void exportMembersDocStatsSummary(final MembersDocStats membersDocStats, final int indentLevel, final String name, final String pkg) { - if(!membersDocStats.isPrintIfNoMembers() && membersDocStats.getMembersNumber() == 0){ + if (!membersDocStats.isPrintIfNoMembers() && membersDocStats.getNumberOfDocumentableMembers() == 0) { return; } exportLine( indentLevel, membersDocStats.getType(), name, pkg, - membersDocStats.getMembersNumber(), - membersDocStats.getUndocumentedMembers(), - membersDocStats.getDocumentedMembers(), + membersDocStats.getNumberOfDocumentableMembers(), + membersDocStats.getUndocumentedMembersOfElement(), + membersDocStats.getNumberOfDocumentedMembers(), membersDocStats.getDocumentedMembersPercent()); } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassDocStats.java index 1a6147d..7e2bf23 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassDocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassDocStats.java @@ -15,13 +15,12 @@ */ package com.manoelcampos.javadoc.coverage.stats; +import java.util.*; + import com.manoelcampos.javadoc.coverage.Utils; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; import com.sun.javadoc.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - /** * Computes statistics about the JavaDocs of a class, inner class, interface or enum * and its members, such as: fields, methods, constructors and annotations. @@ -33,8 +32,6 @@ public class ClassDocStats extends MembersDocStats { /** * This value is added to the number of elements in order to count the class itself as an element * which can be documented. - * - * @see #getMembersNumber() */ private static final int CLASS_DOC = 1; @@ -46,82 +43,51 @@ public class ClassDocStats extends MembersDocStats { private List methodsStats; private List constructorsStats; - public ClassDocStats(final ClassDoc doc) { + public ClassDocStats(final ClassDoc doc, Configuration config) { this.doc = doc; - fieldsStats = new ClassMembersDocStats(doc.fields(false), "Fields"); - enumsStats = new ClassMembersDocStats(doc.enumConstants(), "Enum Consts"); - processMethodsDocsStats(doc); - processConstructorsDocsStats(doc); - processAnnotationsDocsStats(doc); + fieldsStats = new ClassMembersDocStats(doc.fields(false), "Fields", config); + enumsStats = new ClassMembersDocStats(doc.enumConstants(), "Enum Consts", config); + processMethodsDocsStats(doc, config.computePublicCoverageOnly()); + processConstructorsDocsStats(doc, config.computePublicCoverageOnly()); + processAnnotationsDocsStats(doc, config); } - private void processAnnotationsDocsStats(ClassDoc doc) { + private void processAnnotationsDocsStats(ClassDoc doc, Configuration config) { if (doc instanceof AnnotationTypeDoc) { - annotationsStats = new ClassMembersDocStats(((AnnotationTypeDoc) doc).elements(), "Annotations"); - } else annotationsStats = new ClassMembersDocStats(new AnnotationTypeElementDoc[0], "Annotations"); + annotationsStats = new ClassMembersDocStats(((AnnotationTypeDoc) doc).elements(), "Annotations", config); + } else { + annotationsStats = new ClassMembersDocStats(new AnnotationTypeElementDoc[0], "Annotations", config); + } } - private void processConstructorsDocsStats(ClassDoc doc) { + private void processConstructorsDocsStats(ClassDoc doc, boolean computeOnlyForPublic) { final ConstructorDoc[] constructors = doc.constructors(false); - constructorsStats = new ArrayList<>(constructors.length); + constructorsStats = new ArrayList<>(); for (final ConstructorDoc constructor : constructors) { - constructorsStats.add(new MethodDocStats(constructor)); + if (!computeOnlyForPublic || constructor.isPublic()) { + constructorsStats.add(new MethodDocStats(constructor)); + } } } - private void processMethodsDocsStats(ClassDoc doc) { + private void processMethodsDocsStats(ClassDoc doc, boolean computeOnlyForPublic) { final MethodDoc[] methods = doc.methods(false); - methodsStats = new ArrayList<>(methods.length); + methodsStats = new ArrayList<>(); for (final MethodDoc method : methods) { - methodsStats.add(new MethodDocStats(method)); + if ((!computeOnlyForPublic || method.isPublic()) && isNoPredefinedEnumMethod(doc, method)) { + methodsStats.add(new MethodDocStats(method)); + } } } - @Override - public long getDocumentedMembers() { - return - Utils.boolToInt(isDocumented()) + - fieldsStats.getDocumentedMembers() + - enumsStats.getDocumentedMembers() + - getDocumentedMethodMembers(methodsStats) + - getDocumentedMethodMembers(constructorsStats) + - annotationsStats.getDocumentedMembers(); - } - - @Override - public long getMembersNumber() { - return CLASS_DOC + - fieldsStats.getMembersNumber() + - enumsStats.getMembersNumber() + - getMethodMembers(methodsStats) + - getMethodMembers(constructorsStats) + - annotationsStats.getMembersNumber(); - } - - private long getDocumentedMethodMembers(final List methodOrConstructor) { - return methodOrConstructor.stream().filter(MethodDocStats::isDocumented).count() + - methodOrConstructor.stream().mapToLong(MethodDocStats::getDocumentedMembers).sum(); - } - - /** - * Gets the amount of documentable members from a given list of methods/constructors. - * - * @param methodOrConstructor a list containing the methods and constructors to get their number of members - * @return the total number of members for the given list of methods/constructors - * @see MethodDocStats#getMembersNumber() - */ - private long getMethodMembers(final List methodOrConstructor) { - return methodOrConstructor.stream().mapToLong(MethodDocStats::getMembersNumber).sum(); + private boolean isNoPredefinedEnumMethod(ClassDoc doc, final MethodDoc method) { + return !(doc.isEnum() && (method.name().equals("values") || method.name().equals("valueOf"))); } public String getName() { return doc.name(); } - public String getPackageName() { - return doc.containingPackage().name(); - } - @Override public String getType() { return doc.isInterface() ? "Interface" : doc.isEnum() ? "Enum" : "Class"; @@ -147,12 +113,32 @@ public List getConstructorsStats() { return Collections.unmodifiableList(constructorsStats); } - public ClassDoc getDoc() { - return doc; - } - @Override public boolean isDocumented() { return Utils.isElementDocumented(doc.getRawCommentText()); } + + @Override + public long getNumberOfDocumentedMembers() { + // @formatter:off + return constructorsStats.stream().mapToLong(DocStats::getNumberOfDocumentedMembers).sum() + + methodsStats.stream().mapToLong(DocStats::getNumberOfDocumentedMembers).sum() + + fieldsStats.getNumberOfDocumentedMembers() + + enumsStats.getNumberOfDocumentedMembers() + + annotationsStats.getNumberOfDocumentedMembers() + + (isDocumented() ? CLASS_DOC : 0); + // @formatter:on + } + + @Override + public long getNumberOfDocumentableMembers() { + // @formatter:off + return constructorsStats.stream().mapToLong(DocStats::getNumberOfDocumentableMembers).sum() + + methodsStats.stream().mapToLong(DocStats::getNumberOfDocumentableMembers).sum() + + fieldsStats.getNumberOfDocumentableMembers() + + enumsStats.getNumberOfDocumentableMembers() + + annotationsStats.getNumberOfDocumentableMembers() + + CLASS_DOC; + // @formatter:on + } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassMembersDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassMembersDocStats.java index 311f7e3..caa05cd 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassMembersDocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassMembersDocStats.java @@ -15,12 +15,13 @@ */ package com.manoelcampos.javadoc.coverage.stats; -import com.manoelcampos.javadoc.coverage.Utils; -import com.sun.javadoc.Doc; - import java.util.Arrays; import java.util.Objects; +import com.manoelcampos.javadoc.coverage.Utils; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; +import com.sun.javadoc.*; + /** * Computes JavaDoc coverage statistics for specific type of members belonging to an owner. * An owner can be a class, interface or enum. @@ -35,59 +36,75 @@ public class ClassMembersDocStats extends MembersDocStats { */ private final Doc[] membersDocs; private final String membersType; + private final Configuration config; /** - * Instantiates an object to compute JavaDoc coverage statistics - * for the members of a class, interface or enum. + * Instantiates an object to compute JavaDoc coverage statistics for the members of a class, interface or enum. * * @param membersDocs the JavaDoc documentation for the members of the owner. * @param membersType the type of the members of the owner to compute JavaDoc coverage statistics. + * @param config the coverage configuration + * */ - ClassMembersDocStats(final Doc[] membersDocs, final String membersType) { + ClassMembersDocStats(final Doc[] membersDocs, final String membersType, Configuration config) { this.membersDocs = membersDocs; this.membersType = membersType; + this.config = config; + } + + @Override + public String getType() { + return membersType; } /** - * Gets the number of members which are explicitly declared into the source code, - * from a list of given members. + * A set of class members doesn't have documentation, only each individual member may have. * - * The length of the given array cannot be used to this purpose - * because some elements such as default no-args constructors are not directly declared - * into the source class but are counted as a member. - * This way, it may count as a non-documented element - * while it doesn't even exist into the source code. + * @return always false */ @Override - public long getMembersNumber() { - /* - * @todo the method is not working as expected. It always returns the length of the array. - * The side-effect is that default no-args constructors (which aren't directly declared into - * a class source code) will be computed as undocumented. - */ - return Arrays.stream(membersDocs) - .map(Doc::position) - .filter(Objects::nonNull) - .count(); + public boolean isDocumented() { + return false; } + @Override - public long getDocumentedMembers() { - return Arrays.stream(membersDocs).map(Doc::getRawCommentText).filter(Utils::isNotStringEmpty).count(); + public long getNumberOfDocumentedMembers() { + // @formatter:off + return Arrays.stream(membersDocs) + .filter(this::filterPublicIfNecessary) + .map(Doc::getRawCommentText) + .filter(Utils::isNotStringEmpty) + .count(); + // @formatter:on } @Override - public String getType() { - return membersType; + public long getNumberOfDocumentableMembers() { + /* + * default constructors are also counted here, this is correct, they will also appear in the generated javadoc, and have no + * dedicated information + */ + // @formatter:off + return Arrays.stream(membersDocs) + .filter(this::filterPublicIfNecessary) + .map(Doc::position) + .filter(Objects::nonNull) + .count(); + // @formatter:on } - /** - * A set of class members doesn't have documentation, - * only each individual member may have. - * @return - */ - @Override - public boolean isDocumented() { - return false; + private boolean filterPublicIfNecessary(Doc m) { + boolean computeOnlyForPublic = config.computePublicCoverageOnly(); + if (computeOnlyForPublic) { + if (m instanceof ClassDoc) { + return !computeOnlyForPublic || ((ClassDoc) m).isPublic(); + } else if (m instanceof ProgramElementDoc) { + return !computeOnlyForPublic || ((ProgramElementDoc) m).isPublic(); + } else { + throw new UnsupportedOperationException("unimplemented for type " + m.getClass()); + } + } + return true; } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassesDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassesDocStats.java deleted file mode 100644 index 201e4c4..0000000 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/ClassesDocStats.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2017-2017 Manoel Campos da Silva Filho - * - * Licensed under the General Public License Version 3 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.gnu.org/licenses/gpl-3.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.manoelcampos.javadoc.coverage.stats; - -import com.manoelcampos.javadoc.coverage.Utils; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.Doc; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Computes JavaDoc coverage statistics for a list of classes. - * - * @author Manoel Campos da Silva Filho - * @since 1.0.0 - */ -public class ClassesDocStats extends MembersDocStats { - private final List classesDocStats; - - /** - * Instantiates an object to compute JavaDoc coverage statistics for a list of classes. - * - * @param docs an array of elements which enables reading the classes' JavaDoc documentation - */ - public ClassesDocStats(final ClassDoc[] docs){ - classesDocStats = new ArrayList<>(docs.length); - for (final ClassDoc doc : docs) { - classesDocStats.add(new ClassDocStats(doc)); - } - } - - @Override - public long getMembersNumber() { - return classesDocStats.size(); - } - - @Override - public String getType() { - return "Classes/Interfaces/Enums"; - } - - @Override - public long getDocumentedMembers() { - return classesDocStats.stream().map(ClassDocStats::getDoc).map(Doc::getRawCommentText).filter(Utils::isNotStringEmpty).count(); - } - - /** - * Gets a List where each element represents the individual JavaDoc coverage statistics for a specific class. - * - * @return a List of class's JavaDoc coverage statistics - */ - public List getClassesList() { - return Collections.unmodifiableList(classesDocStats); - } - - /** - * A set of classes doesn't have documentation, - * only each individual class may have. - * @return - */ - @Override - public boolean isDocumented() { - return false; - } -} diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/DocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/DocStats.java index 694bf5f..11d789b 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/DocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/DocStats.java @@ -38,7 +38,15 @@ public interface DocStats { * * @return the number of members having JavaDoc documentation */ - long getDocumentedMembers(); + long getNumberOfDocumentedMembers(); + + /** + * Gets the total number of members contained into the object for which the JavaDoc coverage statistics is being computed. + * + * @return the total number of members + */ + long getNumberOfDocumentableMembers(); + /** * Gets the number of undocumented members contained into @@ -46,8 +54,8 @@ public interface DocStats { * * @return the number of members not having JavaDoc documentation */ - default long getUndocumentedMembers() { - return getMembersNumber() - getDocumentedMembers(); + default long getUndocumentedMembersOfElement() { + return getNumberOfDocumentableMembers() - getNumberOfDocumentedMembers(); } /** @@ -57,14 +65,7 @@ default long getUndocumentedMembers() { * @return the percentage of documented members, in scale from 0 to 100. */ default double getDocumentedMembersPercent(){ - return Utils.computePercentage(getDocumentedMembers(), getMembersNumber()); + return Utils.computePercentage(getNumberOfDocumentedMembers(), getNumberOfDocumentableMembers()); } - /** - * Gets the total number of members contained into - * the object for which the JavaDoc coverage statistics is being computed. - * - * @return the total number of members - */ - long getMembersNumber(); } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/JavaDocsStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/JavaDocsStats.java index 3fd1f6a..58533d6 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/JavaDocsStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/JavaDocsStats.java @@ -15,9 +15,10 @@ */ package com.manoelcampos.javadoc.coverage.stats; -import com.manoelcampos.javadoc.coverage.Utils; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.RootDoc; +import java.util.*; + +import com.manoelcampos.javadoc.coverage.configuration.Configuration; +import com.sun.javadoc.*; /** * Computes JavaDoc coverage statistics for Java files received by the JavaDoc tool. @@ -31,74 +32,55 @@ * @since 1.0.0 */ public class JavaDocsStats implements DocStats { - /** - * Stores the root of the program structure information. - */ - private final RootDoc rootDoc; - private final PackagesDocStats packagesDocStats; - private final ClassesDocStats classesDocStats; + private final List packagesDocStats; /** - * Instantiates an object to compute JavaDoc coverage statistics for all Java files - * received by the JavaDoc tool. + * Instantiates an object to compute JavaDoc coverage statistics for all Java files received by the JavaDoc tool. * * @param rootDoc root element which enables reading JavaDoc documentation + * @param config the coverage configuration */ - public JavaDocsStats(final RootDoc rootDoc) { - this.rootDoc = rootDoc; - this.classesDocStats = new ClassesDocStats(rootDoc.classes()); - this.packagesDocStats = computePackagesDocsStats(); - } + public JavaDocsStats(final RootDoc rootDoc, Configuration config) { + this.packagesDocStats = new ArrayList<>(); - /** - * Computes JavaDoc coverage statistics for detected packages. - * - * @return packages' JavaDoc coverage statistics - */ - private PackagesDocStats computePackagesDocsStats() { - final PackagesDocStats stats = new PackagesDocStats(); + Map tmp = new HashMap<>(); for (final ClassDoc doc : rootDoc.classes()) { - stats.addPackageDoc(doc.containingPackage()); - } + // add all packages regardless of public/whatever classes in it + if (!tmp.containsKey(doc.containingPackage())) { + PackageDocStats pkgDoc = new PackageDocStats(doc.containingPackage(), config); + tmp.put(doc.containingPackage(), pkgDoc); + } - return stats; + // only add necessary classes depending on options + if (!config.computePublicCoverageOnly() || doc.isPublic()) { + tmp.get(doc.containingPackage()).addClass(doc); + } + } + packagesDocStats.addAll(tmp.values()); } /** * Gets the object containing JavaDoc coverage statistics for detected packages. * * @return packages' JavaDoc coverage statistics - * @see #computePackagesDocsStats() */ - public PackagesDocStats getPackagesDocStats() { + public List getPackagesDocStats() { return packagesDocStats; } - /** - * Gets the object containing JavaDoc coverage statistics for detected classes. - * - * @return classes' JavaDoc coverage statistics - */ - public ClassesDocStats getClassesDocStats() { return classesDocStats; } - @Override public String getType() { return "Project JavaDoc Statistics"; } @Override - public long getDocumentedMembers() { - return packagesDocStats.getDocumentedMembers() + classesDocStats.getDocumentedMembers(); - } - - @Override - public double getDocumentedMembersPercent() { - return Utils.mean(packagesDocStats.getDocumentedMembersPercent(), classesDocStats.getDocumentedMembersPercent()); + public long getNumberOfDocumentedMembers() { + return packagesDocStats.stream().mapToLong(DocStats::getNumberOfDocumentedMembers).sum(); } @Override - public long getMembersNumber() { - return classesDocStats.getMembersNumber() + packagesDocStats.getMembersNumber(); + public long getNumberOfDocumentableMembers() { + return packagesDocStats.stream().mapToLong(DocStats::getNumberOfDocumentableMembers).sum(); } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodDocStats.java index 952f595..ea48caf 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodDocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodDocStats.java @@ -15,13 +15,16 @@ */ package com.manoelcampos.javadoc.coverage.stats; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + import com.manoelcampos.javadoc.coverage.Utils; +import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.Tag; -import java.util.Arrays; - /** * Computes JavaDoc coverage statistics for a method/constructor * and its members, namely parameters and thrown exceptions. @@ -34,7 +37,7 @@ public class MethodDocStats extends MembersDocStats { * This value is added to the number of elements in order to count the method itself as an element * which can be documented. * - * @see #getMembersNumber() + * @see #getMembersOfElement() * @see #getDocumentedMembersPercent() */ private static final int METHOD_DOC = 1; @@ -82,36 +85,50 @@ public MethodExceptionsDocStats getThrownExceptionsStats() { return thrownExceptionsStats; } - @Override - public long getDocumentedMembers() { - final int returnCount = (!isVoidMethodOrConstructor() && isReturnDocumented()) ? 1 : 0; - final long documentedMembers = Utils.boolToInt(isDocumented()) + - paramsStats.getDocumentedMembers() + - thrownExceptionsStats.getDocumentedMembers() + - returnCount; - - /* If an overridden method isn't documented at all, it doesn't matter because - * its documentation is optional. The superclass is accountable to document - * the method. This way, the method is counted as completely documented. - */ - if (isOverridden() && documentedMembers == 0) { - return getMembersNumber(); - } - - return documentedMembers; - } - - private boolean isReturnDocumented() { + public boolean isReturnDocumented() { return Arrays.stream(doc.tags()).filter(t -> t.name().equals("@return")).map(Tag::text).anyMatch(Utils::isNotStringEmpty); } - private boolean isOverridden() { - return doc.isMethod() && ((MethodDoc) doc).overriddenMethod() != null; + private boolean isOverriddenDocumented() { + if (doc.isMethod()) { + MethodDoc curMethod = (MethodDoc) doc; + Set allClassesToCheck = new HashSet<>(); + + // really use all interfaces, there might be interfaces extending interfaces + // such that this is needed + for (ClassDoc ifc : curMethod.containingClass().interfaces()) { + allClassesToCheck.add(ifc); + allClassesToCheck.addAll(getAllInterfaces(ifc)); + } + ClassDoc superclass = curMethod.containingClass().superclass(); + while (superclass != null) { + allClassesToCheck.add(superclass); + superclass = superclass.superclass(); + } + + for (ClassDoc potOverridden : allClassesToCheck) { + for (MethodDoc method : potOverridden.methods()) { + // only if overridden method is documented completely + // we assume that the overriding method doesn't need to be documented + MethodDocStats methodStats = new MethodDocStats(method); + if (curMethod.overrides(method) + && methodStats.getNumberOfDocumentedMembers() == methodStats.getNumberOfDocumentableMembers()) { + return true; + } + } + } + } + + return false; } - @Override - public double getDocumentedMembersPercent() { - return Utils.computePercentage(getDocumentedMembers(), getMembersNumber()); + private Set getAllInterfaces(ClassDoc doc) { + Set ifcs = new HashSet<>(); + for (ClassDoc ifc : doc.interfaces()) { + ifcs.add(ifc); + ifcs.addAll(getAllInterfaces(ifc)); + } + return ifcs; } /** @@ -120,7 +137,7 @@ public double getDocumentedMembersPercent() { * * @return true if the method is void or is a constructor, false otherwise */ - private boolean isVoidMethodOrConstructor() { + public boolean isVoidMethodOrConstructor() { return isVoidMethod() || doc.isConstructor(); } @@ -129,17 +146,41 @@ private boolean isVoidMethod() { } @Override - public long getMembersNumber() { - final int returnCount = isVoidMethodOrConstructor() ? 0 : 1; + public boolean isDocumented() { + return Utils.isElementDocumented(doc.getRawCommentText()); + } - return METHOD_DOC + - paramsStats.getMembersNumber() + - thrownExceptionsStats.getMembersNumber() + - returnCount; + @Override + public long getNumberOfDocumentedMembers() { + final int returnCount = (!isVoidMethodOrConstructor() && isReturnDocumented()) ? 1 : 0; + + // @formatter:off + final long documentedMembers = Utils.boolToInt(isDocumented()) + + paramsStats.getNumberOfDocumentedMembers() + + thrownExceptionsStats.getNumberOfDocumentedMembers() + + returnCount; + // @formatter:on + + /* + * If an overridden method isn't documented at all, it doesn't matter because its documentation is optional. The superclass is + * accountable to document the method. This way, the method is counted as completely documented. + */ + if (isOverriddenDocumented() && documentedMembers == 0) { + return getNumberOfDocumentableMembers(); + } + + return documentedMembers; } @Override - public boolean isDocumented() { - return Utils.isElementDocumented(doc.getRawCommentText()); + public long getNumberOfDocumentableMembers() { + final int returnCount = isVoidMethodOrConstructor() ? 0 : 1; + + // @formatter:off + return METHOD_DOC + + paramsStats.getNumberOfDocumentableMembers() + + thrownExceptionsStats.getNumberOfDocumentableMembers() + + returnCount; + // @formatter:on } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodExceptionsDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodExceptionsDocStats.java index 7104203..df23846 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodExceptionsDocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodExceptionsDocStats.java @@ -15,15 +15,15 @@ */ package com.manoelcampos.javadoc.coverage.stats; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.ExecutableMemberDoc; -import com.sun.javadoc.Tag; - import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.ExecutableMemberDoc; +import com.sun.javadoc.Tag; + /** * Computes JavaDoc coverage statistics for the exceptions thrown by a specific method. * @@ -61,10 +61,10 @@ public String getType() { * @return the total number of exceptions */ @Override - public long getMembersNumber() { + public long getNumberOfDocumentableMembers() { return getDeclaredButNotDocumentedExceptionsNumber() + - getDocumentedButNotDeclaredExceptionsNumber() + - getDeclaredAndDocumentedExceptionsNumber(); + getDocumentedButNotDeclaredExceptionsNumber() + + getDeclaredAndDocumentedExceptionsNumber(); } /** @@ -80,7 +80,7 @@ public long getMembersNumber() { */ private long getDocumentedButNotDeclaredExceptionsNumber() { return - getDocumentedTagStream() + getDocumentedTagStream() .filter(tag -> getDeclaredExceptionsStream().noneMatch(ex -> isExceptionEqualsToJavaDocTag(ex, tag))) .count(); } @@ -128,7 +128,7 @@ private boolean isExceptionEqualsToJavaDocTag(final ClassDoc exception, final Ta even if the package is not included in the throws clause. On the other hand, the JavaDoc tag used by a developer to document the exception usually isn't prefixed with its package. - */ + */ return exception.toString().endsWith(getExceptionClassFromTag(tag)); } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodParamsDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodParamsDocStats.java index b139059..1cde9de 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodParamsDocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodParamsDocStats.java @@ -35,11 +35,6 @@ public class MethodParamsDocStats extends MethodTagsDocStats { super(doc); } - @Override - public long getMembersNumber() { - return getDoc().parameters().length; - } - @Override public String getTagName() { return "@param"; @@ -49,4 +44,9 @@ public String getTagName() { public String getType() { return "Params"; } + + @Override + public long getNumberOfDocumentableMembers() { + return getDoc().parameters().length; + } } diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodTagsDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodTagsDocStats.java index 9146917..8600bb4 100644 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodTagsDocStats.java +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/MethodTagsDocStats.java @@ -15,13 +15,13 @@ */ package com.manoelcampos.javadoc.coverage.stats; +import java.util.Arrays; +import java.util.stream.Stream; + import com.manoelcampos.javadoc.coverage.Utils; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.Tag; -import java.util.Arrays; -import java.util.stream.Stream; - /** * An abstract class to compute JavaDoc statistics for a set of tags * associated to a method/constructor, such as the {@code @param} and {@code @throws} tags. @@ -50,16 +50,15 @@ public abstract class MethodTagsDocStats extends MembersDocStats { } /** - * Gets the name of the tag associated to this object for which - * JavaDoc coverage statistics will be computed, for instance, - * "param" or "throws" tags. + * Gets the name of the tag associated to this object for which JavaDoc coverage statistics will be computed, for instance, "param" or + * "throws" tags. * - * @return + * @return the tag name */ public abstract String getTagName(); @Override - public long getDocumentedMembers() { + public long getNumberOfDocumentedMembers() { return getDocumentedTagStream().count(); } @@ -68,9 +67,11 @@ public long getDocumentedMembers() { * @return the documented Tag Stream */ protected Stream getDocumentedTagStream() { + // @formatter:off return Arrays.stream(getDoc().tags()) .filter(tag -> getTagName().equals(tag.name())) .filter(tag -> Utils.isNotStringEmpty(tag.text())); + // @formatter:on } /** @@ -83,10 +84,9 @@ protected ExecutableMemberDoc getDoc() { } /** - * A set of tags doesn't have documentation. - * Only each individual tag may have. + * A set of tags doesn't have documentation. Only each individual tag may have. * - * @return + * @return always false */ @Override public boolean isDocumented() { diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/PackageDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/PackageDocStats.java new file mode 100644 index 0000000..2f80199 --- /dev/null +++ b/src/main/java/com/manoelcampos/javadoc/coverage/stats/PackageDocStats.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017-2017 Manoel Campos da Silva Filho + * + * Licensed under the General Public License Version 3 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.gnu.org/licenses/gpl-3.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.manoelcampos.javadoc.coverage.stats; + +import java.util.*; + +import com.manoelcampos.javadoc.coverage.Utils; +import com.manoelcampos.javadoc.coverage.configuration.Configuration; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.PackageDoc; + +/** + * Computes JavaDoc statistics for a set of packages. + * + * @author Manoel Campos da Silva Filho + * @since 1.0.0 + */ +public class PackageDocStats extends MembersDocStats { + private final PackageDoc packageDoc; + private final Configuration config; + private final List classDocs = new ArrayList<>(); + + public PackageDocStats(final PackageDoc doc, Configuration config) { + this.packageDoc = doc; + this.config = config; + } + + /** + * Adds an element to the Set of elements containing packages' JavaDocs. + * + * @param doc the package's JavaDoc element to add to the Set + */ + public void addClass(final ClassDoc doc) { + if (!packageDoc.equals(doc.containingPackage())) { + throw new IllegalArgumentException("Class is not in the correct package"); + } + classDocs.add(new ClassDocStats(doc, config)); + } + + public List getClassDocs() { + return Collections.unmodifiableList(classDocs); + } + + @Override + public String getType() { + return "Package"; + } + + public String getName() { + return packageDoc.name(); + } + + @Override + public long getNumberOfDocumentedMembers() { + return classDocs.stream().mapToLong(DocStats::getNumberOfDocumentedMembers).sum() + Utils.boolToInt(isDocumented()); + } + + @Override + public long getNumberOfDocumentableMembers() { + // +1 for the package-info documentation + return classDocs.stream().mapToLong(DocStats::getNumberOfDocumentableMembers).sum() + 1; + } + + @Override + public boolean isDocumented() { + return Utils.isNotStringEmpty(packageDoc.getRawCommentText()); + } + +} diff --git a/src/main/java/com/manoelcampos/javadoc/coverage/stats/PackagesDocStats.java b/src/main/java/com/manoelcampos/javadoc/coverage/stats/PackagesDocStats.java deleted file mode 100644 index 7dc4d57..0000000 --- a/src/main/java/com/manoelcampos/javadoc/coverage/stats/PackagesDocStats.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2017-2017 Manoel Campos da Silva Filho - * - * Licensed under the General Public License Version 3 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.gnu.org/licenses/gpl-3.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.manoelcampos.javadoc.coverage.stats; - -import com.manoelcampos.javadoc.coverage.Utils; -import com.sun.javadoc.PackageDoc; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * Computes JavaDoc statistics for a set of packages. - * - * @author Manoel Campos da Silva Filho - * @since 1.0.0 - */ -public class PackagesDocStats extends MembersDocStats { - private final Set packagesDoc; - - public PackagesDocStats(){ - this.packagesDoc = new HashSet<>(); - } - - /** - * Adds an element to the Set of elements containing packages' JavaDocs. - * - * @param doc the package's JavaDoc element to add to the Set - */ - public void addPackageDoc(final PackageDoc doc){ - packagesDoc.add(doc); - } - - @Override - public String getType() { - return "Packages"; - } - - @Override - public long getMembersNumber() { - return packagesDoc.size(); - } - - @Override - public long getDocumentedMembers() { - return packagesDoc.stream().map(PackageDoc::getRawCommentText).filter(Utils::isNotStringEmpty).count(); - } - - /** - * Gets a Set of elements containing packages' JavaDocs. - * - * @return the Set of packages' JavaDocs - */ - public Set getPackagesDoc() { - return Collections.unmodifiableSet(packagesDoc); - } - - /** - * A set of packages doesn't have documentation, - * only each individual package may have. - * @return - */ - @Override - public boolean isDocumented() { - return false; - } -}