dnault / therapi-runtime-javadoc Goto Github PK
View Code? Open in Web Editor NEWRead Javadoc comments at run time.
License: Apache License 2.0
Read Javadoc comments at run time.
License: Apache License 2.0
JDK 18 delivered the @snippet
tag for javadoc: https://openjdk.java.net/jeps/413
Java 18 Snippet documentation: https://docs.oracle.com/en/java/javase/18/code-snippet/index.html
The JEP 413 supports a wide range of options adding code snippets to the javadoc.
Supporting everything would be overkill:
local.zip=94123 # @highlight regex="[0-9]+"
System.out.println("Hello World!"); // @link substring="System.out" target="System#out"
System.out.println("Hello World!"); // @replace regex='".*"' replacement="..."
{@snippet file="ShowOptional.java" region="example"}
I think even a basic support would be a great addition.
@snippet
{@snippet :
if (v.isPresent()) {
System.out.println("v: " + v.get());
}
}
@snippet
(as-is, without region
; both file
and class
) {@snippet file=HelloWorld.java}
{@snippet class=HelloWorld}
{@snippet file=config.properties}
class — a class containing the content for the snippet
file — a file containing the content for the snippet
id — an identifier for the snippet, to identify the snippet in the generated documentation
lang — the language or format for the snippet
No support for region
The content of an inline snippet is the text between the newline after the initial colon (:) and the final right curly bracket (}). Incidental white space is removed from the content in the same way as with String.stripIndent. This means you can control the amount of indentation in the generated output by adjusting the indentation of the final right bracket.
Implement the features suggested by @xeals in #52:
GENERATED
or RECORD
flags set on the method (which I think is specified, but I can't find the exact mention in the JVM spec)@param
tags from the primary constructor could be applied to the fields and/or methodsCurrently, there is no way to directly get the doc for a method, and this is not a trivial thing to do with the current API, because you have to:
ClassJavadoc
of the declaring class of the methodMethodJavadoc
s for that classThe last bit is really not trivial, because the method name is not sufficient to find the matching methods due to overloading, and MethodJavadoc.getSignature()
always returns ""
.
The only way I can think of is to first find methods with matching names, and then try to match parameter names, but this is invalid because overloads are distinguished by parameter types, not names.
Also, parameter names are absent at runtime (unless special compilation flags are used I imagine), so even this almost-working hack wouldn't work.
It would be nice to somehow provide the list of parameter types in the MethodJavadoc
in order to allow this kind of method matching.
springdoc/springdoc-openapi#1548
Add the ability to read JavaDoc from the implementation, where the JavaDoc is actually placed at the interface.
As discussed in #31 . The advantage of this pattern is that it removes the need for using instanceof within the CommentFormatters.
The code that searches for inherited method documentation was bailing out if the subclass has abosolutely no Javadoc at all.
It looks like 0.5.0 were not synced between JCenter and Maven Central, because I can only access 0.4.0 on Maven Central, while JCenter has the last version.
It looks like your bintray plugin config in Gradle is set up with disabled sync to Maven Central. It would probably make things easier to deploy if you just activated the sync from there.
So I found that using an @see tag will fail to match the the linkPattern in the LinkParser class if it contains a period at the end.
This may not necessarily be a bug since I don't believe that a comment type tag should end in a period, though the documentation is somewhat sparse and other tag types do not fail but I'm pointing this out for good measure.
Exception in thread "main" java.lang.AssertionError: SeeAlso not recognized as string literal, HTML link or Javadoc link
at com.github.therapi.runtimejavadoc.internal.parser.SeeAlsoParser.parseSeeAlso(SeeAlsoParser.java:27)
at com.github.therapi.runtimejavadoc.internal.parser.JavadocParser.parseMethodJavadoc(JavadocParser.java:66)
at com.github.therapi.runtimejavadoc.internal.JsonJavadocReader.readMethodDoc(JsonJavadocReader.java:67)
at com.github.therapi.runtimejavadoc.internal.JsonJavadocReader.readMethodDocs(JsonJavadocReader.java:57)
at com.github.therapi.runtimejavadoc.internal.JsonJavadocReader.readClassJavadoc(JsonJavadocReader.java:28)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.parseJavadocResource(RuntimeJavadoc.java:102)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:85)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:37)
at example.JavadocReader.printClassJavadocClass(JavadocReader.java:16)
at example.JavadocReader.main(JavadocReader.java:12)
/**
* This is a test inner class level javadoc. // this shouldn't throw an assertion error
*/
public static class InnerTestClass {
/**
* This is a test inner class method level javadoc.
*
* @param string a string input. // this will not throw assertion error
*
* @return the length of the string. // this will not throw assertion error
*/
public int stringLength(String string) {
return string.length();
}
/**
* @see InnerTestClass#stringLength(String). // this WILL throw assertion error
*/
public void testSeeWithPeriod() {}
}
"add javadoc from interface to implementation" feature which mentioned in issue #61 is very useful.
I just realized we couldn't access the Javadoc of a class's fields, or did I just miss it somewhere?
Anyway, I believe this feature would be very useful, and not too hard to add given the existing structure I believe.
It would be nice to have 2 separate artifacts for the annotation processor used at compile time VS the library used at runtime to access the javadoc.
Gradle 5.0 will disable annotation processor that are on the compile classpath, and thus prevent the use of this library if not split (or force the user to include it twice).
therapi-runtime-javadoc/build.gradle
Line 20 in 584558d
Hello! I just wonder if it is possible to remove dependency at all and have nice pure Java library.
Version 0.11.0 introduces bug.
I'm printing the usage of a command, which works fine up until 0.10.0, but breaks in 0.11.0:
CommandLine.usage(new CommandLine(command).setUsageHelpWidth(textWidth), out, COLOR_SCHEME);
Which results in:
java.util.MissingFormatArgumentException: Format specifier '%s'
Please let me know if you need anything else. It's not trivial though to show you how the command is produced in my code, which is all automatic with reflection.
I would expect ClassJavadoc#getName()
to return the class name, just like MethodJavadoc#getName()
returns the method name.
But currently it always returns the string "whatever".
It seems Javadoc and/or Sources jars are not included in the release to Maven Central. Because of this, when inspecting classes and methods in IntelliJ, all documentation is missing.
I don't know how it works in Gradle, but in maven the following would solve the issue:
<project>
<build>
(..)
<plugins>
(..)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<!--<additionalparam>-Xdoclint:none</additionalparam>-->
<additionalparam>-Xdoclint:all</additionalparam>
<additionalparam>-Xdoclint:-missing</additionalparam>
<show>public</show>
<nohelp>true</nohelp>
<excludePackageNames>*.internal.*</excludePackageNames>
</configuration>
</execution>
</executions>
<configuration>
<failOnError>false</failOnError>
</configuration>
</plugin>
</plugins>
</build>
This causes maven to also generate standard jars for javadoc and sources which are automatically included in the deploy plugin. IDE's like IntelliJ look for these for any dependency they download.
In the annotation processor we erase all the type parameters so the param types of methods are limited to their bounds which can be seen on the generic method in Documented Class in #63. However the javadoc tool actually preserves the generic type and bounds.
Do we want to preserve the generic type and bounds in the param types of the retained javadoc?
I'm on 0.9.0 and since I'm heavily relying on all the api and quirks from that version I'm not ready to upgrade until I'm on Java 8.
However, the following simply stops returning data, Everything works fine when compiled with Java 1.7, but is completely empty when compiled with Java 1.8 (and not just params, but everything):
You can easily reproduce this problem as follows:
<java.version>1.8</java.version>
mvn clean test
(I'm running with Maven 3.5.0 and jdk1.8.0_241)It fails on the cli-module's unit tests. I get the same error in IntelliJ. Any idea what might cause this?
For now, the class name provided in Link
elements is taken directly from the Javadoc's text. This is barely usable to actually find the corresponding class/method.
It would be nice to find a way to actually read the corresponding fully qualified class name from the imports, so that we can provide the actual Class<?>
/Method
object at runtime.
I noticed when running the formatter over a doc with slightly different code blocks that some appear to get formatted with <code>
tags and others do not.
The driver:
import com.github.therapi.runtimejavadoc.ClassJavadoc;
import com.github.therapi.runtimejavadoc.CommentFormatter;
import com.github.therapi.runtimejavadoc.MethodJavadoc;
import com.github.therapi.runtimejavadoc.RuntimeJavadoc;
public class Driver {
public static void main(String[] args) {
ClassJavadoc javadoc = RuntimeJavadoc.getJavadoc(Test.class);
MethodJavadoc methodJavadoc = javadoc.getMethods().get(0);
String format = new CommentFormatter().format(methodJavadoc.getComment());
System.out.println(format);
}
}
The Test file:
public class Test {
/**
* <pre>{@code
* <Class>
* <Load>ask</Load>
* <ConstructArg>
* <ArgName>facilities</ArgName>
* <ArgType>java.lang.String</ArgType>
* <ArgValue>ZDC,ZOB,ZNY</ArgValue>
* </ConstructArg>
* </Class>
* }</pre>
*
* <pre>{@code
* echo 'net.ipv4.igmp_max_memberships=1024' > /usr/lib/sysctl.d/51-adsb.conf
* }</pre>
*/
public void testMethod() {}
}
The output:
<pre>{@code
<Class>
<Load>ask</Load>
<ConstructArg>
<ArgName>facilities</ArgName>
<ArgType>java.lang.String</ArgType>
<ArgValue>ZDC,ZOB,ZNY</ArgValue>
</ConstructArg>
</Class>
}</pre>
<pre><code>echo 'net.ipv4.igmp_max_memberships=1024' > /usr/lib/sysctl.d/51-adsb.conf
</code></pre>
If a class doesn't have any Javadoc, there's not much point in generating a companion class.
Similarly to {@link}, {@value} can reference a member implicitly (#field) or through explicit Class reference (ClassName#field). At the same time, when Class reference is missing, it should default to the 'owning' class just like how {@link}
is processed.
The following regex demonstrates the valid scenario's: https://regex101.com/r/KhEo62/3
Inner classes in the default package as the JavaReader fails to the return a valid comment from the javadoc.
Attaching a stand alone example
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>JavadocRuntimeReader</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc-scribe</artifactId>
<version>0.9.0</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Annotation processor -->
<!-- <dependency>-->
<!-- <groupId>com.github.therapi</groupId>-->
<!-- <artifactId>therapi-runtime-javadoc-scribe</artifactId>-->
<!-- <version>0.9.0</version>-->
<!-- <scope>provided</scope>-->
<!-- </dependency>-->
<!-- Runtime library -->
<dependency>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
</project>
Example class reader
import com.github.therapi.runtimejavadoc.ClassJavadoc;
import com.github.therapi.runtimejavadoc.CommentFormatter;
import com.github.therapi.runtimejavadoc.MethodJavadoc;
import com.github.therapi.runtimejavadoc.RuntimeJavadoc;
public class JavadocReader {
private static final CommentFormatter FORMATTER = new CommentFormatter();
public static void main(String[] args) {
printClassJavadocClass(TestClass.class);
}
private static void printClassJavadocClass(Class<?> clazz) {
ClassJavadoc classDoc = RuntimeJavadoc.getJavadoc(clazz);
if (classDoc.isEmpty()) {
System.out.println("Failed to return javadoc");
return;
}
System.out.println(FORMATTER.format(classDoc.getComment()));
StringBuilder sb = new StringBuilder();
for (MethodJavadoc methodDoc : classDoc.getMethods()) {
sb.append(methodDoc.getName())
.append(methodDoc.getParamTypes())
.append(FORMATTER.format(methodDoc.getComment()))
.append(" returns ")
.append(FORMATTER.format(methodDoc.getReturns()))
.append(System.lineSeparator());
}
System.out.println("\t" + sb.toString());
}
/**
* This is a test class level javadoc.
*/
static class TestClass {
/**
* This is a test method level javadoc.
*/
public void testMethod() {}
}
}
Originally posted by @jhameier in #34 (comment)
Annotating every class with @RetainJavadoc
can be really cumbersome depending on the use case, especially when there are a lot of classes involved.
In particular, it is impractical when we want to keep the Javadoc for all classes in a package because of the semantics of the package, and not of each individual class (e.g. model classes in the model package). If we often create new classes there, we need to remember to add the annotation each time.
It would be nice to be able to provide a way to use a package whitelist (maybe via a resource file) in addition to the annotation mechanism. If the class is in a white-listed package OR is annotated with @RetainJavadoc
, then we keep the Javadoc.
The processor could, for instance, read a resource file with a specific name like /runtime-javadoc/package-whitelist.txt
, which would contain a list of package prefixes. Then we could add features like the use of wildcards etc, but even a plain list of package prefixes would be great.
I could help if you're interested, what do you think?
Parameter lists (ie {@link ClassName#someMethod(String, Integer) label}
), are not parsed properly, resulting in a member of "someMethod(String, " and a label of ", Integer) label" or sometinhg similar.
Member parameter lists should be parsed properly. At the same time, it would be good if the parameter list was stored on Link as an array instead of a single string.
It looks like the Comment
and CommentFormatter
classes have been designed to support the multiple elements we can find in javadoc comments.
However, all I can find is a single CommentText
node containing the whole Javadoc comment.
It would be nice to finish this feature so that users can make use of the CommentFormatter
.
So things have been working fairly well and of course like most development work there is feature creep. I've been asked to run the javadoc reader as a maven plugin. I thought okay shouldn't be that big of a deal, moved the reader and markdown generation code to a new project and included the primary jar with the have the generated json files). So far so good.
Then I realized that I'm getting a json file for every class within the primary jar. With 6000+ classes at 2K each that is quite a bit more data that is not really needed in our jar at runtime since the document is getting created and that is what I need not the javadoc itself. Yea I know its an edge case.
What I really need is a way to specify only the classes that are either marked with the @RetainJavadcoc (which is currently about 100 classes in dozens of packages) or a way to specify a different Annotation to pass to a filter for the annotation processor to use. It doesn't seem right to pass it to the PackageFilter as the name implies its to filter packages.
Looking through the code I see that if I use the -A when compiling that I can specify specific packages but by default it includes everything.
Is there a way to specify NO packages (this way at worse case I add the @RetainJavadoc annotation to the classes I want to retain.
Is there a way (or could there be) a way to specify different annotations for retention (that would allow me to use existing annotations as a kind of filter for which classes I would generate for). This one is a long shot but have to ask anyway.
Thanks in advance!
During testing, I noticed that I could retrieve the constructor java docs for
/**
* abc
*/
public static class A {
/**
* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
public A() {}
}
but not for
public static class A {
/**
* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
*/
public A() {}
}
The only difference in the two classes was whether they had javadocs at the class level. Running a debugger I can see that within RuntimeJavadoc.getJavadoc(Constructor); ClassJavadoc.getConstructors() returns an empty list.
While it is unusual for a constructor to have documentation and the class to not, it can still happen.
The problem appears to be located here, notice that it looks like the constructor check is .isArray() and will always return true.
I just thought you should know that I've just released Simple Java Mail 6.0.0-rc1, which includes CLI support based on your library. How? Because Simple Java Mail generates CLI options (using the excellent picocli) using the embedded Javadoc as usage documentation!
Pretty sweet right, I have my builder api and respective documentation in one place and it's exactly the same code compared to using just the Java api.
I couldn't have done it without your excellent library (and your cooperation when I backported it to JDK7+), so a big thank you to you!
It looks like the purpose of the shadow plugin is to build a fat jar. I believe this is only desirable when packaging an end-user application that needs runtime dependencies.
A library, on the other hand, is used within a build system, and its dependencies will be fetched via standard transitive deps resolution.
Is there a particular reason why you're using this plugin for your lib?
minimal-json gets bundled into both the runtime library and the annotation processor.
In both cases, it's relocated to the same package. This leads to duplicate classes on the class path, which is no good.
See if the annotation processor can just use the version in the runtime library. If we can't make that work, then relocate to different locations.
For classes, methods, and constructors the @see javadoc tag gets separated out into BaseJavadoc.seeAlso; however with fields it gets left in 'other' and 'seeAlso' remains empty.
As titled, I'm trying to get therapi-runtime-javadoc working in a Gradle multiproject with JPMS
The problematic module is scijava-discovery-therapi
I added the dependencies as shown on the readme
// Annotation processor (prior to Gradle 4.6, use `compileOnly` instead)
annotationProcessor("com.github.therapi:therapi-runtime-javadoc-scribe:0.12.0")
// Runtime library
implementation("com.github.therapi:therapi-runtime-javadoc:0.12.0")
But at sync I keep getting
/home/elect/IdeaProjects/incubator/scijava/scijava-discovery-therapi/src/main/java/module-info.java:8: error: module not found: therapi.runtime.javadoc
requires therapi.runtime.javadoc;
The module-info.java
is the following
module org.scijava.discovery.therapi {
exports org.scijava.discovery.therapi;
opens org.scijava.discovery.therapi to therapi.runtime.javadoc;
requires org.scijava.discovery;
requires transitive org.scijava.parse2;
requires therapi.runtime.javadoc; // this is the problematic line
}
If I click on the therapi.runtime.javadoc
I do end up in the right dependency
All the Default
packages have annotation processing enable as follow
While the Gradle imported has a custom one:
Which I think is fine, but I still cant understand why it cannot find this module..
MethodJavadoc constructor always received empty lists of "exceptions" and "seeAlso"
please complete
com.github.therapi.runtimejavadoc.internal.parser.JavadocParser#parseMethodJavadoc
method or give a way to substitute with custom implementation
thanx
The method name for a constructor should always be <init>
, not the class name.
I see that the new artifact for the annotation processor is not linked to JCenter. Just pointing that out in case you didn't already request the inclusion.
Thanks a lot for the very quick response regarding the artifact separation by the way!
I'm trying to locate the actual Method object referenced by the Link.
Is it possible when creating Link objects to include the package relevant to the classname being referenced? Either the package is included in the class name itself, or the package comes from an import. It would take some trickery for sure, but the information could be very useful in runtime.
public class Link {
private final String label;
private final String referencedPackage;
private final String referencedClassName;
private final String referencedMemberName;
(..)
}
Alternatively, you could add a list of all imports to ClassJavadoc so I can match it later manually. I imagine this to be a lot easier to implement in the short term.
public class ClassJavadoc extends BaseJavadoc {
private final List<Import> imports;
private final List<FieldJavadoc> fields;
private final List<FieldJavadoc> enumConstants;
private final List<MethodJavadoc> methods;
(..)
}
Create smaller JARs by writing the Javadoc as JSON class path resources.
Affected version: 0.14.0
Class `javasource.foo.DocumentedClass.NestedWithoutJavadoc` does not match class doc for `javasource.foo.DocumentedClass$NestedWithoutJavadoc`
java.lang.IllegalArgumentException: Class `javasource.foo.DocumentedClass.NestedWithoutJavadoc` does not match class doc for `javasource.foo.DocumentedClass$NestedWithoutJavadoc`
at com.github.therapi.runtimejavadoc.ClassJavadoc.createEnhancedClassJavadoc(ClassJavadoc.java:102)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:55)
...
Hello @dnault
I am running into this error below. It parses my class and parses it into json the docs, methods, even enums, but the input stream does not read in my constructors which causes this error. Would it be possible to modify the readMethodDocs function to ignore if the methods JsonValue methodsValue == null or something like that?
java.lang.NullPointerException
at com.github.therapi.runtimejavadoc.internal.JsonJavadocReader.readMethodDocs(JsonJavadocReader.java:56)
at com.github.therapi.runtimejavadoc.internal.JsonJavadocReader.readClassJavadoc(JsonJavadocReader.java:30)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.parseJavadocResource(RuntimeJavadoc.java:103)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:86)
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:38)
at org.web.util.JavadocUtil.printJavadoc(JavadocUtil.java:20)
at org.web.util.JavadocUtilTest.testJavaDoc(JavadocUtilTest.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Here is the stacktrace I got when using Livedoc v4.3.2 which uses therapi-runtime-javadoc 0.4.0:
java.lang.NullPointerException: null
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:41) ~[therapi-runtime-javadoc-0.4.0.jar:na]
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:32) ~[therapi-runtime-javadoc-0.4.0.jar:na]
at com.github.therapi.runtimejavadoc.RuntimeJavadoc.getJavadoc(RuntimeJavadoc.java:75) ~[therapi-runtime-javadoc-0.4.0.jar:na]
It looks like RuntimeJavadoc
crashes at line 41:
try (InputStream is = classLoader.getResourceAsStream(resourceName)) {
My bet is that the classloader is null, which is possible as the doc states:
Returns the class loader for the class. Some implementations may use
null to represent the bootstrap class loader. This method will return
null in such implementations if this class was loaded by the bootstrap
class loader.
One solution would be to directly use the getResourceAsStream()
method on the class itself rather than on its ClassLoader. I understand that the getJavadoc(String, ClassLoader)
overload is needed for tests, but it should be easy to just extract the code properly to maximize code reuse while avoiding using the classloader overload in production code.
Currently if a method exists with no javadoc that overrides a method in a base class that has javadoc no java doc is returned when specifying the overriding method.
It would be nice if the overriden method inherited the javadoc from the base method when none exists on itself. As this is the behavior of the javadoc tools provided by oracle.
Hi @dnault,
What an innovative and interesting way of including Javadoc in runtime! Reminds me of Lombok.
I would love to use this library in my own open source library Simple Java Mail, but I'm restricted to Java 7 and your library is out of reach because of that. Is it possible to make this library available to Java 7? I know what a pain it is to not be able to use Java 8 for my own library, but since Java 7 is still is used by a significant audience I have no choice if I want to service them.
I would love to try and make this library work for my own use case, which is that I'm using Java reflection to generate CLI support and I would like to use therapi-runtime-javadoc to dynamically include the Javadoc as documentation. Otherwise I would have to maintain the documentation both in Javadoc as well as annotations. You library would reach a lot of people if I could use it somehow.
Record classes are unmodifiable data class introduced in Java 16 and included in the recent LTS version Java 17. Since records are a new syntax for Java, they generate an ElementKind.RECORD
AST element instead of ElementKind.CLASS
and are not supported by the current visitor.
Javadoc for a record is the same as a regular class, except it may include @param
fields referring to record components:
/**
* Example class.
*
* @param a first component input
* @param b second input
*/
public class Foo(boolean a, String b) {
/** Alternate constructor */
public Foo(boolean a) {
this(a, null);
}
}
General thoughts:
Depending on how an implementation might balance representation vs. intent:
GENERATED
or RECORD
flags set on the method (which I think is specified, but I can't find the exact mention in the JVM spec)@param
tags from the primary constructor could be applied to the fields and/or methodsFound that there is no way to pull the javadoc from a constructor.
Test Class:
/**
* This is the class level javadoc.
*/
public class TestClass {
/**
* This is the ctor javadoc.
*/
public TestClass() {}
/**
* This is the method javadoc.
*/
public void testMethod() {}
}
Driver:
import com.github.therapi.runtimejavadoc.ClassJavadoc;
import com.github.therapi.runtimejavadoc.RuntimeJavadoc;
public class Test {
public static void main(String[] args) {
ClassJavadoc classJavadoc = RuntimeJavadoc.getJavadoc(TestClass.class);
System.out.println(classJavadoc);
}
}
Output
ClassJavadoc {
name='TestClass',
comment=This is the class level javadoc.,
fields=[],
methods=[MethodJavadoc {
name='testMethod',
paramTypes='[]',
comment=This is the method javadoc.,
params=[],
exceptions=[],
other=[],
returns=,
seeAlso=[]
}],
seeAlso=[],
other=[]
}
I went through the code quickly, but is my understanding correct that @see
parsing is not implemented for Method Javadoc? In runtime I do see the tag under other with name "see" and value CommentText rather than InlineLink.
This is unfortunate, since the rules for @see
works exactly the same as @link
except it can also be string literal or an HTML link (<a href>
).
Currently it is impossible to perform context-based formatting, because while formatting a CommentElement, we don't have access to the entire comment structure. For example, in formatLink(..), I would like to perform different kind of formatting based on the preceding CommentElement, which is now impossible.
This can be solved in two ways:
private final CommentElement previousCommentElement;
which is passed in constructor.formatLink(..)
and all other formatting methods so you have the information needed.Personally, I find the first solution the cleanest. Only the constructor call is expanded as opposed to expanding all the formatting methods. And 3rd parties creating a custom CommentFormatter can decide where to use this new API if at all.
If you agree this would be a useful addition, please let me know which approach you prefer and I'll get it sorted.
@bbottema I'd like to drop support for Java 1.7 so we can take advantage of Gradle toolchains and bump a couple of dependency versions. Are you ready for this?
`package com.ps.apps.utility.docgenerator;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.github.therapi.runtimejavadoc.ClassJavadoc;
import com.github.therapi.runtimejavadoc.CommentFormatter;
import com.github.therapi.runtimejavadoc.MethodJavadoc;
import com.github.therapi.runtimejavadoc.OtherJavadoc;
import com.github.therapi.runtimejavadoc.RuntimeJavadoc;
import com.github.therapi.runtimejavadoc.SeeAlsoJavadoc;
import com.github.therapi.runtimejavadoc.ThrowsJavadoc;
import com.github.therapi.runtimejavadoc.*;
import com.github.therapi.runtimejavadoc.ParamJavadoc;
public class DocExtractorUtil {
// formatters are reusable and thread-safe
private static final CommentFormatter formatter = new CommentFormatter();
public static DocumentationHolder getDocumentation(String fullyQualifiedClassName) throws IOException {
DocumentationHolder documentationHolder = new DocumentationHolder();
ClassJavadoc classDoc = RuntimeJavadoc.getJavadoc(fullyQualifiedClassName);
documentationHolder.setClassName(classDoc.getName());
documentationHolder.setClassDocumenation(format(classDoc.getComment()));
List<String> classTags = Collections.emptyList();
// @see tags
for (SeeAlsoJavadoc see : classDoc.getSeeAlso()) {
classTags.add("See also: " + see.getLink());
}
documentationHolder.setTags(classTags);
Map<String, String> classMiscellaneousJavadoc = Collections.emptyMap();
// miscellaneous and custom javadoc tags (@author, etc.)
for (OtherJavadoc other : classDoc.getOther()) {
classMiscellaneousJavadoc.put(other.getName() , format(other.getComment()));
}
documentationHolder.setMiscellaneousJavadoc(classMiscellaneousJavadoc);
List<MethodDocumentations> methods = Collections.emptyList();
for (MethodJavadoc methodDoc : classDoc.getMethods()) {
MethodDocumentations methodDocumentations = new MethodDocumentations();
methodDocumentations.setName(methodDoc.getName());
methodDocumentations.setParamTypes(methodDoc.getParamTypes());
methodDocumentations.setComment(format(methodDoc.getComment()));
methodDocumentations.setReturnType(format(methodDoc.getReturns()));
List<String> tags = Collections.emptyList();
for (SeeAlsoJavadoc see : methodDoc.getSeeAlso()) {
tags.add(" See also: " + see.getLink());
}
methodDocumentations.setTags(tags);
Map<String, String> miscellaneousJavadoc = Collections.emptyMap();
for (OtherJavadoc other : methodDoc.getOther()) {
miscellaneousJavadoc.put(other.getName(), format(other.getComment()));
}
methodDocumentations.setMiscellaneousJavadoc(miscellaneousJavadoc);
Map<String, String> paramJavaDoc = Collections.emptyMap();
for (ParamJavadoc paramDoc : methodDoc.getParams()) {
paramJavaDoc.put(paramDoc.getName(), format(paramDoc.getComment()));
}
methodDocumentations.setParams(paramJavaDoc);
Map<String, String> throwsDocMap = Collections.emptyMap();
for (ThrowsJavadoc throwsDoc : methodDoc.getThrows()) {
paramJavaDoc.put(throwsDoc.getName(), format(throwsDoc.getComment()));
}
methodDocumentations.setThrowsDoc(throwsDocMap);
methods.add(methodDocumentations);
}
documentationHolder.setMethods(methods);
return documentationHolder;
}
private static String format(Comment c) {
return formatter.format(c);
}
}`
I debugged the internal code and seems like i am missing "__Javadoc.json" file which supposed to be genrated on it's own or do i have to perform any specific task ? I am using maven. I do understand that 'therapi-runtime-javadoc-scribe' is used for baking docs but it's not working?
I want to know, If i should configure something in pom.xml or do i need to use any annotation on class that need bake _Javadoc.json file
The following link is not recognized and made part of the (preceding) CommentText instead:
/**
* Delegates to {@link #withReturnReceiptTo(Recipient)} with a new blah blah
*/
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.