Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 8e8a041

Browse files
authored
#432 Adds support for Java 14 records (#437)
1 parent 9333bcc commit 8e8a041

File tree

6 files changed

+287
-47
lines changed

6 files changed

+287
-47
lines changed

spring-auto-restdocs-json-doclet-jdk9/pom.xml

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,24 @@
1616
<description>Doclet exporting JavaDoc to JSON for Spring Auto REST Docs</description>
1717

1818
<properties>
19+
<maven.javadoc.skip>true</maven.javadoc.skip>
1920
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2021
</properties>
2122

2223
<dependencies>
2324
<dependency>
2425
<groupId>com.fasterxml.jackson.core</groupId>
2526
<artifactId>jackson-databind</artifactId>
27+
<!--
28+
| Jackson Databind 2.12.0 has a breaking API change that is corrected in 2.12.1
29+
| If you are using Java 14 records, you must use version 2.12.1 or later.
30+
| Otherwise, you can use any version other than 2.12.0.
31+
|
32+
| NOTE:
33+
| This version range may be removed once the parent project specifies a range
34+
| greater than 2.12.0.
35+
+-->
36+
<version>[2.11.2,2.12.0),[2.12.1,2.99)</version>
2637
</dependency>
2738
<dependency>
2839
<groupId>org.apache.commons</groupId>
@@ -54,35 +65,70 @@
5465
<build>
5566
<plugins>
5667
<plugin>
68+
<groupId>org.apache.maven.plugins</groupId>
5769
<artifactId>maven-compiler-plugin</artifactId>
5870
<configuration>
5971
<release>9</release>
6072
</configuration>
6173
</plugin>
62-
<plugin>
63-
<artifactId>maven-javadoc-plugin</artifactId>
64-
<extensions>true</extensions>
65-
<executions>
66-
<execution>
67-
<id>generate-javadoc-json</id>
68-
<phase>compile</phase>
69-
<goals>
70-
<goal>test-javadoc-no-fork</goal>
71-
</goals>
72-
<configuration>
73-
<doclet>capital.scalable.restdocs.jsondoclet.ExtractDocumentationAsJsonDoclet</doclet>
74-
<docletArtifact>
75-
<groupId>capital.scalable</groupId>
76-
<artifactId>spring-auto-restdocs-json-doclet-jdk9</artifactId>
77-
<version>${project.parent.version}</version>
78-
</docletArtifact>
79-
<reportOutputDirectory>${project.build.directory}</reportOutputDirectory>
80-
<useStandardDocletOptions>false</useStandardDocletOptions>
81-
<show>package</show>
82-
</configuration>
83-
</execution>
84-
</executions>
85-
</plugin>
8674
</plugins>
8775
</build>
76+
77+
<profiles>
78+
79+
<profile>
80+
<id>java14</id>
81+
<activation>
82+
<jdk>[14,17)</jdk>
83+
</activation>
84+
<build>
85+
86+
<pluginManagement>
87+
<plugins>
88+
<plugin>
89+
<groupId>org.apache.maven.plugins</groupId>
90+
<artifactId>maven-surefire-plugin</artifactId>
91+
<configuration>
92+
<argLine>
93+
--enable-preview
94+
--illegal-access=permit
95+
</argLine>
96+
<!-- Force alphabetical order to have a reproducible build -->
97+
<runOrder>alphabetical</runOrder>
98+
</configuration>
99+
</plugin>
100+
</plugins>
101+
</pluginManagement>
102+
103+
<plugins>
104+
<plugin>
105+
<groupId>org.apache.maven.plugins</groupId>
106+
<artifactId>maven-compiler-plugin</artifactId>
107+
<executions>
108+
<execution>
109+
<id>test-compile-java-14</id>
110+
<phase>test-compile</phase>
111+
<goals>
112+
<goal>testCompile</goal>
113+
</goals>
114+
<configuration>
115+
<release>${java.specification.version}</release>
116+
<multiReleaseOutput>true</multiReleaseOutput>
117+
<compilerArgs>
118+
<!-- Enables use of record and text blocks -->
119+
<compilerArg>--enable-preview</compilerArg>
120+
</compilerArgs>
121+
<compileSourceRoots>
122+
<compileSourceRoot>${project.basedir}/src/test/java14</compileSourceRoot>
123+
</compileSourceRoots>
124+
</configuration>
125+
</execution>
126+
</executions>
127+
</plugin>
128+
</plugins>
129+
</build>
130+
</profile>
131+
132+
</profiles>
133+
88134
</project>

spring-auto-restdocs-json-doclet-jdk9/src/main/java/capital/scalable/restdocs/jsondoclet/ClassDocumentation.java

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@
1919
*/
2020
package capital.scalable.restdocs.jsondoclet;
2121

22+
import static java.util.Optional.ofNullable;
2223
import static capital.scalable.restdocs.jsondoclet.DocletUtils.cleanupDocComment;
2324

25+
import com.sun.source.doctree.DocCommentTree;
26+
import com.sun.source.doctree.ParamTree;
27+
2428
import javax.lang.model.element.Element;
25-
import javax.lang.model.element.ElementKind;
2629
import java.util.HashMap;
30+
import java.util.List;
2731
import java.util.Map;
32+
import java.util.stream.Collectors;
2833

2934
import jdk.javadoc.doclet.DocletEnvironment;
3035

@@ -42,14 +47,41 @@ public static ClassDocumentation fromClassDoc(DocletEnvironment docEnv,
4247
ClassDocumentation cd = new ClassDocumentation();
4348
cd.setComment(cleanupDocComment(docEnv.getElementUtils().getDocComment(element)));
4449

45-
element.getEnclosedElements().forEach(fieldOrMethod -> {
46-
if (fieldOrMethod.getKind().equals(ElementKind.FIELD)) {
47-
cd.addField(docEnv, fieldOrMethod);
48-
} else if (fieldOrMethod.getKind().equals(ElementKind.METHOD)
49-
|| fieldOrMethod.getKind().equals(ElementKind.CONSTRUCTOR)) {
50-
cd.addMethod(docEnv, fieldOrMethod);
51-
}
52-
});
50+
if ("RECORD".equals(element.getKind().name())) {
51+
ofNullable(docEnv.getDocTrees().getDocCommentTree(element))
52+
.stream()
53+
.map(DocCommentTree::getBlockTags)
54+
.flatMap(List::stream)
55+
.filter(ParamTree.class::isInstance)
56+
.map(ParamTree.class::cast)
57+
.filter(p -> !p.isTypeParameter())
58+
.forEach(p -> {
59+
String name = p.getName().getName().toString();
60+
String desc = p.getDescription()
61+
.stream()
62+
.map(Object::toString)
63+
.collect(Collectors.joining(" "))
64+
;
65+
66+
cd.fields.put(name, FieldDocumentation.fromString(desc));
67+
})
68+
;
69+
} else {
70+
element.getEnclosedElements().forEach(fieldOrMethod -> {
71+
switch (fieldOrMethod.getKind()) {
72+
case FIELD:
73+
cd.addField(docEnv, fieldOrMethod);
74+
break;
75+
case METHOD:
76+
case CONSTRUCTOR:
77+
cd.addMethod(docEnv, fieldOrMethod);
78+
break;
79+
default:
80+
// Ignored
81+
break;
82+
}
83+
});
84+
}
5385

5486
return cd;
5587
}
@@ -67,4 +99,5 @@ private void addMethod(DocletEnvironment docEnv, Element element) {
6799
this.methods.put(element.getSimpleName().toString(),
68100
MethodDocumentation.fromMethodDoc(docEnv, element));
69101
}
102+
70103
}

spring-auto-restdocs-json-doclet-jdk9/src/main/java/capital/scalable/restdocs/jsondoclet/FieldDocumentation.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public class FieldDocumentation {
3737
private String comment;
3838
private final Map<String, String> tags = new HashMap<>();
3939

40+
private FieldDocumentation(String comment) {
41+
this.comment = comment;
42+
}
43+
4044
private void addTag(DocTree tag) {
4145
if (tag instanceof BlockTagTree) {
4246
tags.put(
@@ -47,13 +51,17 @@ private void addTag(DocTree tag) {
4751

4852
public static FieldDocumentation fromFieldDoc(DocletEnvironment docEnv,
4953
Element fieldElement) {
50-
FieldDocumentation fd = new FieldDocumentation();
51-
fd.comment = cleanupDocComment(docEnv.getElementUtils().getDocComment(fieldElement));
54+
FieldDocumentation fd = fromString(
55+
cleanupDocComment(docEnv.getElementUtils().getDocComment(fieldElement)));
5256

5357
Optional.ofNullable(docEnv.getDocTrees().getDocCommentTree(fieldElement))
5458
.ifPresent(docCommentTree -> docCommentTree.getBlockTags()
5559
.forEach(tag -> fd.addTag(tag)));
5660

5761
return fd;
5862
}
63+
64+
public static FieldDocumentation fromString(String comment) {
65+
return new FieldDocumentation(comment);
66+
}
5967
}

spring-auto-restdocs-json-doclet-jdk9/src/test/java/capital/scalable/restdocs/jsondoclet/ExtractDocumentationAsJsonDocletTest.java

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,89 @@
1919
*/
2020
package capital.scalable.restdocs.jsondoclet;
2121

22-
import static java.nio.charset.StandardCharsets.UTF_8;
23-
24-
import java.io.File;
22+
import java.nio.file.FileSystems;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
2525
import java.io.FileInputStream;
2626
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.net.URI;
29+
import java.util.List;
30+
31+
import javax.tools.DocumentationTool.DocumentationTask;
32+
import javax.tools.JavaFileObject;
33+
import javax.tools.SimpleJavaFileObject;
34+
import javax.tools.ToolProvider;
35+
import static org.junit.Assert.assertTrue;
36+
37+
import static java.nio.charset.StandardCharsets.UTF_8;
2738

2839
import org.apache.commons.io.IOUtils;
2940
import org.json.JSONException;
41+
import org.junit.Before;
3042
import org.junit.Test;
3143
import org.skyscreamer.jsonassert.JSONAssert;
3244

3345
public class ExtractDocumentationAsJsonDocletTest {
3446

47+
private static final Path SRC_PATH = FileSystems.getDefault().getPath("src/test/resources").toAbsolutePath();
48+
private static final Path TGT_PATH = FileSystems.getDefault().getPath("target/test/generated-javadoc-json").toAbsolutePath();
49+
3550
private static final String JSON_PATH =
3651
"capital/scalable/restdocs/jsondoclet/DocumentedClass.json";
3752

38-
/**
39-
* The test requires that the Doclet is executed before. This is ensured by
40-
* the Maven configuration, but not when the test is executed on its own.
41-
*/
53+
private static final List<String> args = List.of(
54+
"--release", "9",
55+
"-private",
56+
"-d", TGT_PATH.toString()
57+
);
58+
59+
@Before
60+
public void setup() throws IOException {
61+
Files.createDirectories(TGT_PATH);
62+
}
63+
4264
@Test
4365
public void testDocumentedClass() throws IOException, JSONException {
44-
String generated = IOUtils.toString(
45-
new FileInputStream(new File("target/generated-javadoc-json/" + JSON_PATH)), UTF_8);
46-
String expected = IOUtils.toString(
47-
this.getClass().getClassLoader().getResourceAsStream(JSON_PATH), UTF_8);
48-
JSONAssert.assertEquals(expected, generated, false);
66+
Path source = SRC_PATH.resolve("capital/scalable/restdocs/jsondoclet/DocumentedClass.java");
67+
Iterable<JavaFileObject> compilationUnits = compilationUnits(source);
68+
69+
DocumentationTask task = ToolProvider.getSystemDocumentationTool().getTask(null, null, null, ExtractDocumentationAsJsonDoclet.class, args, compilationUnits);
70+
71+
boolean result = task.call();
72+
assertTrue(result);
73+
74+
try (
75+
InputStream expectedStream = new FileInputStream(SRC_PATH.resolve(JSON_PATH).toFile());
76+
InputStream generatedStream = new FileInputStream(TGT_PATH.resolve(JSON_PATH).toFile());
77+
) {
78+
String expected = IOUtils.toString(expectedStream, UTF_8);
79+
String generated = IOUtils.toString(generatedStream, UTF_8);
80+
JSONAssert.assertEquals(expected, generated, false);
81+
}
82+
}
83+
84+
Iterable<JavaFileObject> compilationUnits(Path path) throws IOException {
85+
try (InputStream in = new FileInputStream(path.toFile())) {
86+
String content = IOUtils.toString(in, UTF_8);
87+
return List.of(new InMemoryJavaFileObject(path.toUri(), content));
88+
}
89+
}
90+
91+
static class InMemoryJavaFileObject extends SimpleJavaFileObject {
92+
93+
private final String source;
94+
95+
public InMemoryJavaFileObject(URI uri, String sourceCode) {
96+
super(uri, Kind.SOURCE);
97+
this.source = sourceCode;
98+
}
99+
100+
@Override
101+
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
102+
return source;
103+
}
104+
49105
}
106+
50107
}

0 commit comments

Comments
 (0)