Repository: mockito/mockito
Issue: Tidy-up javadoc generation
Labels: docs

I investigated the need for Javadoc doclet rule that currently is kept in buildSrc.

- currently, some javadoc links from public API lead to internal API, which gives 404 error (search for ReturnEmptyValues in public Mockito.html, click the link, 404).
- excluding files at the Javadoc task level does not prevent Javadoc from being generated for the internal classes. However, it does turn links to private API into non-clickable \<code\>\</code\> links.

I'm tempted to just start generating Javadoc for internals, and be very explicit that internal API is not intended for public use and can be changed at any time. Thoughts?

Issue comments:
Was fixed in #763

Pull request review comments:
This change does not work, see https://github.com/mockito/mockito/pull/760#issuecomment-261229080


Patch that resolved the issue:
diff --git a/build.gradle b/build.gradle
index ada4849ffb..4a6588f14f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
 
     dependencies {
         classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' //publishing to bintray
-        classpath'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0' // later versions don't work on JDK6
+        classpath 'nl.javadude.gradle.plugins:license-gradle-plugin:0.11.0' // later versions don't work on JDK6
 
         //Needed so that release notes and release workflow plugin can be applied in separate gradle/*.gradle build files
         classpath project.'dependencies.mockito-release-tools'
@@ -103,7 +103,7 @@ task javadocJar(type: Jar) {
     baseName = 'mockito-core'
     classifier = "javadoc"
     with licenseFiles
-    from mockitoJavadoc
+    from javadoc
 }
 
 task coverageReport(dependsOn: ["test", "mockitoCoverage"]) {}
@@ -165,4 +165,4 @@ compileTestJava.finalizedBy removeTestResources
 buildScan {
     licenseAgreementUrl = 'https://gradle.com/terms-of-service'
     licenseAgree = 'yes'
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/java/org/mockito/javadoc/JavadocExclude.java b/buildSrc/src/main/java/org/mockito/javadoc/JavadocExclude.java
deleted file mode 100644
index 3464f0b426..0000000000
--- a/buildSrc/src/main/java/org/mockito/javadoc/JavadocExclude.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.mockito.javadoc;
-
-import com.sun.javadoc.*;
-import com.sun.tools.doclets.formats.html.HtmlDoclet;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Doclet built to exclude mockito internal packages from the outputted Javadoc.
- * All methods except the start method are copied from {@link com.sun.tools.doclets.standard.Standard}.
- * Inspired by https://gist.github.com/benjchristensen/1410681
- */
-public class JavadocExclude {
-
-    public static int optionLength(String var0) {
-        return HtmlDoclet.optionLength(var0);
-    }
-
-    public static boolean start(RootDoc root) {
-        Class clz = root.getClass();
-        return HtmlDoclet.start((RootDoc) Proxy.newProxyInstance(clz.getClassLoader(), clz.getInterfaces(), new ExcludeHandler(root)));
-    }
-
-    public static boolean validOptions(String[][] var0, DocErrorReporter var1) {
-        return HtmlDoclet.validOptions(var0, var1);
-    }
-
-    public static LanguageVersion languageVersion() {
-        return HtmlDoclet.languageVersion();
-    }
-
-    /**
-     * Proxy which filters the {@link RootDoc#classes()} method to filter {@link ClassDoc} based on their package name.
-     */
-    private static class ExcludeHandler implements InvocationHandler {
-        private final RootDoc root;
-
-        private ExcludeHandler(RootDoc root) {
-            this.root = root;
-        }
-
-        @Override
-        public Object invoke(Object o, Method method, Object[] args) throws Throwable {
-            Object result = method.invoke(root, args);
-            if (result instanceof Object[]) {
-                List<ClassDoc> filteredDocs = new ArrayList<ClassDoc>();
-                Object[] array = (Object[]) result;
-                for (Object entry : array) {
-                    if (entry instanceof ClassDoc) {
-                        ClassDoc doc = (ClassDoc) entry;
-                        if (!doc.containingPackage().name().startsWith("org.mockito.internal")) {
-                            filteredDocs.add(doc);
-                        }
-                    }
-                }
-                // If no ClassDoc were found in the original array,
-                // since PackageDoc area also included in the classes array.
-                if (filteredDocs.size() > 0) {
-                    ClassDoc[] docArray = new ClassDoc[filteredDocs.size()];
-                    return filteredDocs.toArray(docArray);
-                }
-            }
-            return result;
-        }
-    }
-}
diff --git a/gradle/javadoc.gradle b/gradle/javadoc.gradle
index 27aee3d2e6..5bb5203299 100644
--- a/gradle/javadoc.gradle
+++ b/gradle/javadoc.gradle
@@ -1,9 +1,7 @@
-//vanilla javadoc task does not work for us and it actually fails on java8
-tasks.javadoc.enabled = false
-
-task mockitoJavadoc(type: Javadoc) {
+//It seems the gradle javadoc task works file by file and as such disable some features of javadoc tool
+//such as link to packages, https://groups.google.com/d/msg/gradle-dev/R83dy_6PHMc/bgw0cUTMFAAJ
+javadoc {
     description "Creates javadoc html for Mockito API."
-    tasks.javadoc.dependsOn it //so that when the user invokes 'javadoc' right things happen
 
     // For more details on the format
     // see https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javadoc.html
@@ -14,12 +12,26 @@ task mockitoJavadoc(type: Javadoc) {
     title = "Mockito ${project.version} API"
     def javadocJdk6Hack = "jdk6-project-version-insert.min.js"
 
-    options.doclet "org.mockito.javadoc.JavadocExclude"
-    options.docletpath = [rootProject.file('./buildSrc/build/classes/main/')]
+    // The way *gradle* javadoc task works and due to *gradle exclusion* the log output triggers javadoc warning and
+    // errors on internal classes that are not found, non-exhaustive list :
+    //
+    // * import org.mockito.internal.Internal <- error: package org.mockito.internal does not exists
+    // * import org.mockito.internal.Internal.util <- error: static import only from classes and interfaces
+    // * class Public extends Internal <- cannot find symbol
+    // * warning - Tag @linkplain: reference not found: org.mockito.internal.Internal internal stuff
+    // * warning - Tag @link: reference not found: org.mockito.stubbing
+    //
+    // While there's errors the javadoc is still generated without those symbols or links ;
+    // for this reason the javadoc output is suppressed and can be reactivated with --info option.
+    exclude 'org/mockito/internal'
+    logging.captureStandardError LogLevel.INFO
+    logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message
+
     options.docTitle = """<h1><a href="org/mockito/Mockito.html">Click to see examples</a>. Mockito ${project.version} API.</h1>"""
     options.windowTitle = "Mockito ${project.version} API"
     options.group("Main package", ["org.mockito"])
     options.memberLevel = JavadocMemberLevel.PROTECTED
+    options.outputLevel = JavadocOutputLevel.QUIET
     options.charSet = 'UTF-8'
     options.encoding = 'UTF-8'
     options.docEncoding = 'UTF-8'
@@ -30,8 +42,11 @@ task mockitoJavadoc(type: Javadoc) {
     options.noIndex = false
     options.noNavBar = false
     options.noTree = false
-    options.links('http://docs.oracle.com/javase/8/docs/api/')
-    options.header("""<script>var l = window.location; if (l.protocol === "http" && l.origin.indexOf("javadoc.io") > -1) { l.protocol = "https"; }</script><em id="mockito-version-header-javadoc7-header"><strong>Mockito ${project.version} API</strong></em>""")
+    options.links = ['https://docs.oracle.com/javase/6/docs/api/',
+                     'http://junit.org/junit4/javadoc/4.12/',
+                     'http://hamcrest.org/JavaHamcrest/javadoc/1.3/',
+                     'http://testng.org/javadocs/']
+    options.header("""<em id="mockito-version-header-javadoc7-header"><strong>Mockito ${project.version} API</strong></em>""")
     options.footer("""<em id="mockito-version-header-javadoc7-footer"><strong>Mockito ${project.version} API</strong></em>""")
     options.bottom("""
         <script type="text/javascript" src="{@docRoot}/js/${javadocJdk6Hack}"></script>
@@ -40,9 +55,15 @@ task mockitoJavadoc(type: Javadoc) {
 
         <link rel="stylesheet" type="text/css" href="{@docRoot}/js/highlight-8.6-java/styles/obsidian.css"/>
         <script type="text/javascript" src="{@docRoot}/js/index.js" async defer></script>
-    """.replaceAll(/\r|\n/, ""))
-    options.stylesheetFile file("src/javadoc/stylesheet.css")
-//    options.addStringOption('top', 'some html')
+    """.replaceAll(/\r|\n|[ ]{8}/, ""))
+    options.stylesheetFile rootProject.file("src/javadoc/stylesheet.css")
+    options.addStringOption('top', """<script type="text/javascript" >
+        var l = window.location; 
+        if (l.protocol === "http" && l.origin.indexOf("javadoc.io") > -1) { 
+            l.protocol = "https"; 
+        }
+        </script>
+    """.replaceAll(/\r|\n|[ ]{8}/, ""))
     if (JavaVersion.current().isJava8Compatible()) {
         options.addStringOption('Xdoclint:none', '-quiet')
     }
diff --git a/src/main/java/org/mockito/AdditionalAnswers.java b/src/main/java/org/mockito/AdditionalAnswers.java
index ade1ce30cf..348743da04 100644
--- a/src/main/java/org/mockito/AdditionalAnswers.java
+++ b/src/main/java/org/mockito/AdditionalAnswers.java
@@ -4,7 +4,6 @@
  */
 package org.mockito;
 
-import static org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces.toAnswer;
 import java.util.Collection;
 import org.mockito.internal.stubbing.answers.ReturnsArgumentAt;
 import org.mockito.internal.stubbing.answers.ReturnsElementsOf;
@@ -21,12 +20,14 @@
 import org.mockito.stubbing.VoidAnswer4;
 import org.mockito.stubbing.VoidAnswer5;
 
+import static org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces.toAnswer;
+
 /**
  * Additional answers provides factory methods for answers.
  *
  * <p>Currently offer answers that can return the parameter of an invocation at a certain position,
- * along with answers that draw on a strongly typed interface from {@link org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces}
- * to provide a neater way to write custom answers that either return a value or are void.
+ * along with answers that draw on a strongly typed interface to provide a neater way to write custom answers
+ * that either return a value or are void (see answer interfaces in {@link org.mockito.stubbing}).
  *
  * <p>See factory methods for more information : {@link #returnsFirstArg}, {@link #returnsSecondArg},
  * {@link #returnsLastArg}, {@link #returnsArgAt}, {@link #answer} and {@link #answerVoid}
diff --git a/src/main/java/org/mockito/Mockito.java b/src/main/java/org/mockito/Mockito.java
index 4d759e0ddc..45f6558843 100644
--- a/src/main/java/org/mockito/Mockito.java
+++ b/src/main/java/org/mockito/Mockito.java
@@ -8,17 +8,21 @@
 import org.mockito.internal.creation.MockSettingsImpl;
 import org.mockito.internal.debugging.MockitoDebuggerImpl;
 import org.mockito.internal.framework.DefaultMockitoFramework;
-import org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues;
-import org.mockito.internal.stubbing.defaultanswers.ReturnsMoreEmptyValues;
 import org.mockito.internal.verification.VerificationModeFactory;
 import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.junit.MockitoRule;
 import org.mockito.mock.SerializableMode;
-import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Answer1;
 import org.mockito.stubbing.OngoingStubbing;
 import org.mockito.stubbing.Stubber;
-import org.mockito.verification.*;
+import org.mockito.stubbing.VoidAnswer1;
+import org.mockito.verification.After;
+import org.mockito.verification.Timeout;
+import org.mockito.verification.VerificationAfterDelay;
+import org.mockito.verification.VerificationMode;
+import org.mockito.verification.VerificationWithTimeout;
 
 /**
  * <p align="left"><img src="logo.png" srcset="logo@2x.png 2x" alt="Mockito logo"/></p>
@@ -1154,9 +1158,9 @@
  * as Java 8 lambdas. Even in Java 7 and lower these custom answers based on a typed interface can reduce boilerplate.
  * In particular, this approach will make it easier to test functions which use callbacks.
  *
- * The functions answer and answerVoid can be found in {@link AdditionalAnswers} to create the answer object
- * using the interfaces in {@link org.mockito.internal.stubbing.answers.AnswerFunctionalInterfaces} support is provided
- * for functions with up to 5 parameters
+ * The methods {@link AdditionalAnswers#answer(Answer1) answer} and {@link AdditionalAnswers#answerVoid(VoidAnswer1) answerVoid}
+ * can be used to create the answer. They rely on the related answer interfaces in {@link org.mockito.stubbing} that
+ * support answers up to 5 parameters.
  *
  * <p>
  * Examples:
@@ -1187,7 +1191,7 @@
  *     .when(mock).execute(anyString(), any(Callback.class));
  *
  * // Java 7
- * doAnswer(answerVoid(new AnswerFunctionalInterfaces.VoidAnswer2<String, Callback>() {
+ * doAnswer(answerVoid(new VoidAnswer2<String, Callback>() {
  *     public void answer(String operation, Callback callback) {
  *         callback.receive("dummy");
  *     }})).when(mock).execute(anyString(), any(Callback.class));
@@ -1203,7 +1207,7 @@
  *     .when(mock).execute(anyString(), anyString());
  *
  * // Java 7
- * doAnswer(answer(new AnswerFunctionalInterfaces.Answer2<String, String, String>() {
+ * doAnswer(answer(new Answer2<String, String, String>() {
  *     public String answer(String input1, String input2) {
  *         return input1 + input2;
  *     }})).when(mock).execute(anyString(), anyString());
@@ -1287,12 +1291,13 @@ public class Mockito extends ArgumentMatchers {
 
     /**
      * The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
+     *
      * Typically it just returns some empty value.
      * <p>
      * {@link Answer} can be used to define the return values of unstubbed invocations.
      * <p>
-     * This implementation first tries the global configuration.
-     * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
+     * This implementation first tries the global configuration and if there is no global configuration then
+     * it will use a default answer that returns zeros, empty collections, nulls, etc.
      */
     public static final Answer<Object> RETURNS_DEFAULTS = Answers.RETURNS_DEFAULTS;
 
@@ -1306,8 +1311,8 @@ public class Mockito extends ArgumentMatchers {
      * This implementation of Answer <b>returns SmartNull instead of null</b>.
      * <code>SmartNull</code> gives nicer exception message than NPE because it points out the line where unstubbed method was called. You just click on the stack trace.
      * <p>
-     * <code>ReturnsSmartNulls</code> first tries to return ordinary return values (see {@link ReturnsMoreEmptyValues})
-     * then it tries to return SmartNull. If the return type is final then plain null is returned.
+     * <code>ReturnsSmartNulls</code> first tries to return ordinary values (zeros, empty collections, empty string, etc.)
+     * then it tries to return SmartNull. If the return type is final then plain <code>null</code> is returned.
      * <p>
      * <code>ReturnsSmartNulls</code> will be probably the default return values strategy in Mockito 3.0.0
      * <p>
@@ -1335,8 +1340,8 @@ public class Mockito extends ArgumentMatchers {
      * <p>
      * This implementation can be helpful when working with legacy code.
      * <p>
-     * ReturnsMocks first tries to return ordinary return values (see {@link ReturnsMoreEmptyValues})
-     * then it tries to return mocks. If the return type cannot be mocked (e.g. is final) then plain null is returned.
+     * ReturnsMocks first tries to return ordinary values (zeros, empty collections, empty string, etc.)
+     * then it tries to return mocks. If the return type cannot be mocked (e.g. is final) then plain <code>null</code> is returned.
      * <p>
      */
     public static final Answer<Object> RETURNS_MOCKS = Answers.RETURNS_MOCKS;
diff --git a/src/main/java/org/mockito/configuration/IMockitoConfiguration.java b/src/main/java/org/mockito/configuration/IMockitoConfiguration.java
index cebd25f366..2ab19671e8 100644
--- a/src/main/java/org/mockito/configuration/IMockitoConfiguration.java
+++ b/src/main/java/org/mockito/configuration/IMockitoConfiguration.java
@@ -4,26 +4,36 @@
  */
 package org.mockito.configuration;
 
-import org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues;
 import org.mockito.stubbing.Answer;
 
 /**
  * Use it to configure Mockito. For now there are not many configuration options but it may change in future.
+ *
  * <p>
- * In most cases you don't really need to configure Mockito. For example in case of working with legacy code, 
+ * In most cases you don't really need to configure Mockito. For example in case of working with legacy code,
  * when you might want to have different 'mocking style' this interface might be helpful. 
- * A reason of configuring Mockito might be if you disagree with the {@link ReturnsEmptyValues} unstubbed mocks return.
+ * A reason of configuring Mockito might be if you disagree with the {@link org.mockito.Answers#RETURNS_DEFAULTS}
+ * unstubbed mocks return.
+ *
  * <p>
- * To configure Mockito create exactly <b>org.mockito.configuration.MockitoConfiguration</b> class that implements this interface.
+ * To configure Mockito create exactly <b>org.mockito.configuration.MockitoConfiguration</b> class
+ * that implements this interface.
+ *
  * <p>
- * Configuring Mockito is completely <b>optional</b> - nothing happens if there isn't any <b>org.mockito.configuration.MockitoConfiguration</b> on the classpath. 
+ * Configuring Mockito is completely <b>optional</b> - nothing happens if there isn't any
+ * <b>org.mockito.configuration.MockitoConfiguration</b> on the classpath.
+ *
  * <p>
- * <b>org.mockito.configuration.MockitoConfiguration</b> must implement IMockitoConfiguration or extend {@link DefaultMockitoConfiguration}
+ * <b>org.mockito.configuration.MockitoConfiguration</b> must implement <code>IMockitoConfiguration</code> or
+ * extend {@link DefaultMockitoConfiguration}
+ *
  * <p>
- * Mockito will store single instance of org.mockito.configuration.MockitoConfiguration per thread (using ThreadLocal). 
+ * Mockito will store single instance of <code>org.mockito.configuration.MockitoConfiguration</code>
+ * per thread (using <code>ThreadLocal</code>).
  * For sanity of your tests, don't make the implementation stateful.
+ *
  * <p>
- * If you have comments on Mockito configuration feature don't hesitate to write to mockito@googlegroups.com
+ * If you have comments on Mockito configuration feature don't hesitate to write to mockito@googlegroups.com.
  */
 public interface IMockitoConfiguration {
 
@@ -57,4 +67,4 @@ public interface IMockitoConfiguration {
      * are dynamically reloaded, you can disable this to avoid classcast exceptions.
      */
     boolean enableClassCache();
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java b/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java
index af44a63914..a3229a8c80 100644
--- a/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java
+++ b/src/main/java/org/mockito/exceptions/stacktrace/StackTraceCleaner.java
@@ -9,14 +9,12 @@
  * Mockito stack trace filtering mechanism uses this information.
  * <p>
  * Excluding an element will make it not show in the cleaned stack trace.
- * Not-excluding an element does not guarantee it will be shown
- * (e.g. it depends on the implementation of
- * Mockito internal {@link org.mockito.internal.exceptions.stacktrace.StackTraceFilter}).
+ * Not-excluding an element does not guarantee it will be shown (e.g. it depends on the implementation of
+ * {@linkplain org.mockito.internal.exceptions.stacktrace.StackTraceFilter Mockito internal cleaner}).
  * <p>
- * The implementations are required to be thread safe. For example, make them stateless.
+ * The implementations are required to be thread safe ; for example, make them stateless.
  * <p>
- * See the default implementation: {@link org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleaner}.
- *
+ * See also the {@linkplain org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleaner Mockito default implementation}
  */
 public interface StackTraceCleaner {
 
diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMoreEmptyValues.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMoreEmptyValues.java
index 2c20525057..179f7ddc8b 100644
--- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMoreEmptyValues.java
+++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsMoreEmptyValues.java
@@ -5,13 +5,12 @@
 
 package org.mockito.internal.stubbing.defaultanswers;
 
+import java.io.Serializable;
+import java.lang.reflect.Array;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.io.Serializable;
-import java.lang.reflect.Array;
-
 /**
  * It's likely this implementation will be used by default by every Mockito 3.0.0 mock.
  * <p>
@@ -67,9 +66,9 @@ Object returnValueFor(Class<?> type) {
         if (type == String.class) {
             return "";
         }  else if (type.isArray()) {
-            Class<?> componenetType = type.getComponentType();
-            return Array.newInstance(componenetType, 0);
+            Class<?> componentType = type.getComponentType();
+            return Array.newInstance(componentType, 0);
         }
         return null;
     }
-}
\ No newline at end of file
+}


Test patch:
diff --git a/subprojects/testng/testng.gradle b/subprojects/testng/testng.gradle
index 42f2c1a17c..ac90dac0eb 100644
--- a/subprojects/testng/testng.gradle
+++ b/subprojects/testng/testng.gradle
@@ -35,6 +35,8 @@ artifacts {
     archives sourcesJar
 }
 
+tasks.javadoc.enabled = false
+
 uploadArchives {
     repositories {
         mavenDeployer {
@@ -53,4 +55,4 @@ test {
     useTestNG() {
         suites file('src/test/resources/mockito-testng.xml')
     }
-}
\ No newline at end of file
+}