diff --git a/pom.xml b/pom.xml
index 2c783963..756c162e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,6 +17,7 @@
spring-auto-restdocs-core
spring-auto-restdocs-example
spring-auto-restdocs-docs
+ spring-auto-restdocs-dokka-json
@@ -126,6 +127,8 @@
src/main/java
src/test/java
+ src/main/kotlin
+ src/test/kotlin
diff --git a/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationObjectVisitor.java b/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationObjectVisitor.java
index 020df2b0..b972be04 100644
--- a/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationObjectVisitor.java
+++ b/spring-auto-restdocs-core/src/main/java/capital/scalable/restdocs/jackson/FieldDocumentationObjectVisitor.java
@@ -7,9 +7,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.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.
@@ -51,6 +51,11 @@ public FieldDocumentationObjectVisitor(SerializerProvider provider,
this.typeFactory = typeFactory;
}
+ @Override
+ public void property(BeanProperty prop) throws JsonMappingException {
+ optionalProperty(prop);
+ }
+
@Override
public void optionalProperty(BeanProperty prop) throws JsonMappingException {
String jsonName = prop.getName();
diff --git a/spring-auto-restdocs-dokka-json/.gitignore b/spring-auto-restdocs-dokka-json/.gitignore
new file mode 100644
index 00000000..21765a79
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/.gitignore
@@ -0,0 +1,29 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# IntelliJ
+.idea/
+*.iml
+
+# Genertaed files
+target/
diff --git a/spring-auto-restdocs-dokka-json/README.md b/spring-auto-restdocs-dokka-json/README.md
new file mode 100644
index 00000000..f1feb162
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/README.md
@@ -0,0 +1,42 @@
+# KDoc and Javadoc to JSON converter for Spring Auto REST Docs
+
+This library is a [Dokka](https://github.com/Kotlin/dokka) extension.
+Dokka is a documentation engine for Kotlin, performing the same function as Javadoc for Java.
+Mixed-language Java/Kotlin projects are fully supported.
+Dokka understands standard Javadoc comments in Java files and KDoc comments in Kotlin files.
+The same holds true for this Dokka extension.
+
+## Usage with Maven
+
+This Dokka extension can be used with the standard `dokka-maven-plugin`.
+To avoid any incompatibilities, the Dokka version of the `dokka-maven-plugin` and of this extension should be the same.
+If this extension is included as a dependency of the plugin, the output format `auto-restdocs-json` can be used.
+
+Example usage:
+```
+
+ org.jetbrains.dokka
+ dokka-maven-plugin
+ ${dokka.version}
+
+
+ compile
+
+ dokka
+
+
+
+
+
+ capital.scalable
+ spring-auto-restdocs-dokka-json
+ ${spring-auto-restdocs-dokka-json.version}
+
+
+
+ auto-restdocs-json
+ ${jsonDirectory}
+
+
+```
+
diff --git a/spring-auto-restdocs-dokka-json/pom.xml b/spring-auto-restdocs-dokka-json/pom.xml
new file mode 100644
index 00000000..8ef3404f
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/pom.xml
@@ -0,0 +1,96 @@
+
+ 4.0.0
+
+
+ capital.scalable
+ spring-auto-restdocs-parent
+ 1.0.12-SNAPSHOT
+ ..
+
+
+ spring-auto-restdocs-dokka-json
+ jar
+
+ Spring Auto REST Docs Dokka JSON
+ Dokka extension that produces Spring Auto REST Docs' JSON format
+ https://github.com/scacap/spring-auto-restdocs
+
+
+ UTF-8
+ 1.2.30
+ 4.12
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit
+ ${kotlin.version}
+ test
+
+
+ junit
+ junit
+ ${junit.version}
+ test
+
+
+ org.jetbrains.dokka
+ dokka-fatjar
+ 0.9.16
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.9.4
+
+
+ com.fasterxml.jackson.module
+ jackson-module-kotlin
+ 2.9.4.1
+
+
+
+
+ src/main/kotlin
+ src/test/kotlin
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+
+
+
+
+ jcenter
+ https://jcenter.bintray.com/
+
+
+
+
diff --git a/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFileGenerator.kt b/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFileGenerator.kt
new file mode 100644
index 00000000..eebd8adf
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFileGenerator.kt
@@ -0,0 +1,85 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+package capital.scalable.dokka.json
+
+import com.google.inject.Inject
+import org.jetbrains.dokka.*
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.OutputStreamWriter
+
+class JsonFileGenerator @Inject constructor(val locationService: FileLocationService) : Generator {
+
+ @set:Inject(optional = true)
+ lateinit var formatService: FormatService
+
+ override fun buildPages(nodes: Iterable) {
+ val specificLocationService = locationService.withExtension(formatService.extension)
+
+ for ((_, items) in nodes.groupBy { specificLocationService.location(it) }) {
+ if (items.any { it.kind == NodeKind.Class }) {
+ val location = locationOverride(items.find { it.kind == NodeKind.Class }!!)
+ val file = location.file
+ file.parentFile?.mkdirsOrFail()
+ try {
+ FileOutputStream(file).use {
+ OutputStreamWriter(it, Charsets.UTF_8).use {
+ it.write(formatService.format(location, items))
+ }
+ }
+ } catch (e: Throwable) {
+ println(e)
+ }
+ }
+ buildPages(items.flatMap { it.members })
+ }
+ }
+
+ private fun locationOverride(node: DocumentationNode): FileLocation {
+ val path = node.path
+ // Remove class name. It is appended again below.
+ .dropLast(1)
+ .filter { it.name.isNotEmpty() && it.kind != NodeKind.Module }
+ .joinToString("") {
+ if (it.kind == NodeKind.Class) {
+ // Parent class if the node is a nested class
+ "${it.name}."
+ } else {
+ // Turn package dots into folders
+ "${it.name.replace(".", "/")}/"
+ }
+ }
+ val className = node.name
+ return FileLocation(File(locationService.root.path, "$path$className.json"))
+ }
+
+ override fun buildOutlines(nodes: Iterable) {}
+
+ override fun buildSupportFiles() {}
+
+ override fun buildPackageList(nodes: Iterable) {}
+
+ private fun File.mkdirsOrFail() {
+ if (!mkdirs() && !exists()) {
+ throw IOException("Failed to create directory $this")
+ }
+ }
+}
diff --git a/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFormatDescriptor.kt b/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFormatDescriptor.kt
new file mode 100644
index 00000000..7b39a487
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFormatDescriptor.kt
@@ -0,0 +1,40 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+package capital.scalable.dokka.json
+
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Formats.FormatDescriptor
+import org.jetbrains.dokka.Kotlin.KotlinDescriptorSignatureProvider
+import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
+import org.jetbrains.dokka.Samples.SampleProcessingService
+import kotlin.reflect.KClass
+
+class JsonFormatDescriptor : FormatDescriptor {
+ override val formatServiceClass = JsonFormatService::class
+
+ override val packageDocumentationBuilderClass = KotlinPackageDocumentationBuilder::class
+ override val javaDocumentationBuilderClass = KotlinJavaDocumentationBuilder::class
+
+ override val generatorServiceClass = JsonFileGenerator::class
+ override val outlineServiceClass: KClass? = null
+ override val sampleProcessingService: KClass = DefaultSampleProcessingService::class
+ override val packageListServiceClass: KClass? = DefaultPackageListService::class
+ override val descriptorSignatureProvider = KotlinDescriptorSignatureProvider::class
+}
diff --git a/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFormatService.kt b/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFormatService.kt
new file mode 100644
index 00000000..27713bf3
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/main/kotlin/capital/scalable/dokka/json/JsonFormatService.kt
@@ -0,0 +1,131 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+package capital.scalable.dokka.json
+
+import com.fasterxml.jackson.databind.SerializationFeature
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.google.inject.Inject
+import org.jetbrains.dokka.*
+
+data class ClassDocumentation(
+ val comment: String,
+ val fields: Map,
+ val methods: Map)
+
+data class MethodDocumentation(
+ val comment: String,
+ val parameters: Map,
+ val tags: Map)
+
+data class FieldDocumentation(
+ val comment: String,
+ val tags: Map)
+
+open class JsonOutputBuilder(
+ private val to: StringBuilder,
+ private val logger: DokkaLogger) : FormattedOutputBuilder {
+
+ private val mapper = jacksonObjectMapper()
+ .configure(SerializationFeature.INDENT_OUTPUT, true)
+
+ override fun appendNodes(nodes: Iterable) {
+ val singleNode = nodes.singleOrNull()
+ when (singleNode?.kind) {
+ NodeKind.AllTypes -> Unit
+ NodeKind.GroupNode -> Unit
+ null -> Unit
+ else -> appendClassDocumentation(singleNode)
+ }
+ }
+
+ private fun appendClassDocumentation(node: DocumentationNode) {
+ val fields = node.references(RefKind.Member)
+ .filter { it.to.kind == NodeKind.Property || it.to.kind == NodeKind.Field }
+ .map { propertyDocumentation(it.to) }
+ .toMap()
+ val methods = node.references(RefKind.Member)
+ .filter { it.to.kind == NodeKind.Function }
+ .map { functionDocumentation(it.to) }
+ .toMap()
+ val classDocumentation = ClassDocumentation(
+ comment = extractContent(node),
+ fields = fields,
+ methods = methods)
+ to.append(mapper.writeValueAsString(classDocumentation))
+ }
+
+ private fun propertyDocumentation(node: DocumentationNode): Pair {
+ return Pair(node.name, FieldDocumentation(
+ comment = extractContent(node),
+ tags = tags(node)))
+ }
+
+ private fun functionDocumentation(node: DocumentationNode): Pair {
+ val parameterComments = node.content.sections
+ .filter { it.subjectName != null }
+ .map {
+ Pair(it.subjectName!!, extractContent(it))
+ }.toMap()
+ return Pair(node.name, MethodDocumentation(
+ comment = extractContent(node),
+ parameters = parameterComments,
+ tags = tags(node)))
+ }
+
+ private fun JsonOutputBuilder.tags(node: DocumentationNode): Map {
+ return node.content.sections
+ .map { Pair(tagName(it.tag), extractContent(it.children)) }
+ .toMap()
+ }
+
+ private fun tagName(tag: String): String {
+ return when (tag) {
+ ContentTags.SeeAlso -> "see"
+ else -> tag.toLowerCase()
+ }
+ }
+
+ private fun extractContent(node: DocumentationNode): String {
+ return extractContent(node.content.children)
+ }
+
+ private fun extractContent(content: List): String {
+ return content.joinToString("") { extractContent(it) }
+ }
+
+ private fun extractContent(content: ContentNode): String {
+ when (content) {
+ is ContentText -> return content.text
+ is ContentBlock -> return content.children.joinToString("") { extractContent(it) }
+ is ContentNodeLink -> return content.node?.let { extractContent(it) } ?: ""
+ is ContentEmpty -> return ""
+ else -> logger.warn("Unhandled content node: $content")
+ }
+ return ""
+ }
+}
+
+open class JsonFormatService @Inject constructor(private val logger: DokkaLogger) : FormatService {
+
+ override val extension: String = "json"
+
+ override fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder =
+ JsonOutputBuilder(to, logger)
+}
diff --git a/spring-auto-restdocs-dokka-json/src/main/resources/dokka/format/auto-restdocs-json.properties b/spring-auto-restdocs-dokka-json/src/main/resources/dokka/format/auto-restdocs-json.properties
new file mode 100644
index 00000000..ce509e04
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/main/resources/dokka/format/auto-restdocs-json.properties
@@ -0,0 +1,2 @@
+class=capital.scalable.dokka.json.JsonFormatDescriptor
+description=Turns KDoc and Javadoc into the Spring Auto REST Docs JSON format
\ No newline at end of file
diff --git a/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/FileComparisonFailure.kt b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/FileComparisonFailure.kt
new file mode 100644
index 00000000..42dacc37
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/FileComparisonFailure.kt
@@ -0,0 +1,54 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+package capital.scalable.dokka.json
+
+import junit.framework.ComparisonFailure
+
+class FileComparisonFailure(
+ message: String,
+ expected: String?,
+ actual: String?,
+ expectedFilePath: String
+) : ComparisonFailure(message, expected, actual) {
+
+ private val myExpected: String
+ private val myActual: String
+ val filePath: String
+
+ init {
+ when {
+ expected == null -> throw NullPointerException("'expected' must not be null")
+ actual == null -> throw NullPointerException("'actual' must not be null")
+ else -> {
+ this.myExpected = expected
+ this.myActual = actual
+ this.filePath = expectedFilePath
+ }
+ }
+ }
+
+ override fun getExpected(): String {
+ return this.myExpected
+ }
+
+ override fun getActual(): String {
+ return this.myActual
+ }
+}
diff --git a/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/JsonFileGeneratorTest.kt b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/JsonFileGeneratorTest.kt
new file mode 100644
index 00000000..3be8a953
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/JsonFileGeneratorTest.kt
@@ -0,0 +1,78 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+package capital.scalable.dokka.json
+
+import com.intellij.openapi.util.io.FileUtil
+import org.jetbrains.dokka.DocumentationModule
+import org.jetbrains.dokka.DokkaConsoleLogger
+import org.jetbrains.dokka.SingleFolderLocationService
+import org.jetbrains.dokka.contentRootFromPath
+import org.junit.Test
+import java.io.File
+import kotlin.test.assertTrue
+
+class JsonFileGeneratorTest {
+
+ @Test
+ fun `buildPages should generate JSON files for Kotlin classes and nested classes`() {
+ val inputFile = "KotlinDataClass.kt"
+ val expectedFiles = listOf("testdata/KotlinDataClass", "testdata/KotlinDataClass.NestedClass")
+ verifyOutput(inputFile, verifier(expectedFiles))
+ }
+
+ @Test
+ fun `buildPages should generate JSON files for Java classes and nested classes`() {
+ val inputFile = "JavaClass.java"
+ val expectedFiles = listOf("JavaClass", "JavaClass.NestedJavaClass")
+ verifyJavaOutput(inputFile, verifier(expectedFiles))
+ }
+
+ private fun verifyOutput(inputFile: String, verifier: (DocumentationModule) -> Unit) {
+ verifyModel(
+ contentRootFromPath("testdata/$inputFile"),
+ verifier = verifier)
+ }
+
+ private fun verifyJavaOutput(inputFile: String, verifier: (DocumentationModule) -> Unit) {
+ verifyJavaModel(
+ "testdata/$inputFile",
+ verifier = verifier)
+ }
+
+ private fun verifier(expectedFiles: List): (DocumentationModule) -> Unit {
+ val fileGenerator = initFileGenerator()
+ val rootPath = File(fileGenerator.locationService.root.path)
+ return {
+ fileGenerator.buildPages(listOf(it))
+ expectedFiles.forEach {
+ val file = rootPath.resolve("$it.json")
+ assertTrue("Expected file $it.json was not generated") { file.exists() }
+ }
+ }
+ }
+
+ private fun initFileGenerator(): JsonFileGenerator {
+ val tempDir = FileUtil.createTempDirectory("dokka-json", "file-generator-test")
+ val fileLocationService = SingleFolderLocationService(tempDir, "json")
+ val fileGenerator = JsonFileGenerator(fileLocationService)
+ fileGenerator.formatService = JsonFormatService(DokkaConsoleLogger)
+ return fileGenerator
+ }
+}
diff --git a/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/JsonFormatServiceTest.kt b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/JsonFormatServiceTest.kt
new file mode 100644
index 00000000..44ae0ae0
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/JsonFormatServiceTest.kt
@@ -0,0 +1,81 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+package capital.scalable.dokka.json
+
+import org.jetbrains.dokka.DocumentationModule
+import org.jetbrains.dokka.DocumentationNode
+import org.jetbrains.dokka.DokkaConsoleLogger
+import org.jetbrains.dokka.NodeKind
+import org.junit.Test
+
+private val tempLocation = InMemoryLocation("")
+
+class JsonFormatServiceTest {
+
+ private val jsonFormatService = JsonFormatService(DokkaConsoleLogger)
+
+ @Test
+ fun `Correct JSON file for a Kotlin data class should be created`() {
+ verifyJsonNode("KotlinDataClass")
+ }
+
+ @Test
+ fun `Correct JSON file for a nested Kotlin data class should be created`() {
+ verifyNestedJsonNode("KotlinDataClass", ".NestedClass.json")
+ }
+
+ @Test
+ fun `Correct JSON file for a Java class should be created`() {
+ verifyJavaJsonNode("JavaClass")
+ }
+
+ @Test
+ fun `Correct JSON file for a nested Java class should be created`() {
+ verifyNestedJavaJsonNode("JavaClass", ".NestedJavaClass.json")
+ }
+
+ private fun verifyJsonNode(fileName: String) {
+ verifyJsonNodes(fileName) { model -> model.members.single().members }
+ }
+
+ private fun verifyNestedJsonNode(fileName: String, outputExtension: String) {
+ verifyJsonNodes(fileName, outputExtension) { model -> model.members.single().members.single().members(NodeKind.Class) }
+ }
+
+ private fun verifyJsonNodes(fileName: String, outputExtension: String = ".json", nodeFilter: (DocumentationModule) -> List) {
+ verifyOutput("testdata/$fileName.kt", outputExtension) { model, output ->
+ jsonFormatService.createOutputBuilder(output, tempLocation).appendNodes(nodeFilter(model))
+ }
+ }
+
+ private fun verifyJavaJsonNode(fileName: String) {
+ verifyJavaJsonNodes(fileName) { model -> model.members.single().members }
+ }
+
+ private fun verifyNestedJavaJsonNode(fileName: String, outputExtension: String) {
+ verifyJavaJsonNodes(fileName, outputExtension) { model -> model.members.single().members.single().members(NodeKind.Class) }
+ }
+
+ private fun verifyJavaJsonNodes(fileName: String, outputExtension: String = ".json", nodeFilter: (DocumentationModule) -> List) {
+ verifyJavaOutput("testdata/$fileName.java", outputExtension) { model, output ->
+ jsonFormatService.createOutputBuilder(output, tempLocation).appendNodes(nodeFilter(model))
+ }
+ }
+}
diff --git a/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/TestAPI.kt b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/TestAPI.kt
new file mode 100644
index 00000000..51dbfd7a
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/src/test/kotlin/capital/scalable/dokka/json/TestAPI.kt
@@ -0,0 +1,261 @@
+/*-
+ * #%L
+ * Spring Auto REST Docs Dokka JSON
+ * %%
+ * Copyright (C) 2015 - 2018 Scalable Capital GmbH
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.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.
+ * #L%
+ */
+// Initially copied from https://github.com/Kotlin/dokka/blob/master/core/src/test/kotlin/TestAPI.kt
+package capital.scalable.dokka.json
+
+import com.google.inject.Guice
+import com.intellij.openapi.application.PathManager
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.util.io.FileUtil
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Utilities.DokkaAnalysisModule
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
+import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
+import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
+import org.jetbrains.kotlin.config.ContentRoot
+import org.jetbrains.kotlin.config.KotlinSourceRoot
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.junit.Assert
+import java.io.File
+
+fun verifyModel(vararg roots: ContentRoot,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ format: String = "auto-restdocs-json",
+ includeNonPublic: Boolean = true,
+ perPackageOptions: List = emptyList(),
+ verifier: (DocumentationModule) -> Unit) {
+ val documentation = DocumentationModule("test")
+
+ val options = DocumentationOptions(
+ "",
+ format,
+ includeNonPublic = includeNonPublic,
+ skipEmptyPackages = false,
+ includeRootPackage = true,
+ sourceLinks = listOf(),
+ perPackageOptions = perPackageOptions,
+ generateIndexPages = false,
+ noStdlibLink = true,
+ cacheRoot = "default",
+ languageVersion = null,
+ apiVersion = null
+ )
+
+ appendDocumentation(documentation, *roots,
+ withJdk = withJdk,
+ withKotlinRuntime = withKotlinRuntime,
+ options = options)
+ documentation.prepareForGeneration(options)
+
+ verifier(documentation)
+}
+
+fun appendDocumentation(documentation: DocumentationModule,
+ vararg roots: ContentRoot,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ options: DocumentationOptions,
+ defaultPlatforms: List = emptyList()) {
+ val messageCollector = object : MessageCollector {
+ override fun clear() {
+
+ }
+
+ override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
+ when (severity) {
+ CompilerMessageSeverity.STRONG_WARNING,
+ CompilerMessageSeverity.WARNING,
+ CompilerMessageSeverity.LOGGING,
+ CompilerMessageSeverity.OUTPUT,
+ CompilerMessageSeverity.INFO,
+ CompilerMessageSeverity.ERROR -> {
+ println("$severity: $message at $location")
+ }
+ CompilerMessageSeverity.EXCEPTION -> {
+ Assert.fail("$severity: $message at $location")
+ }
+ }
+ }
+
+ override fun hasErrors() = false
+ }
+
+ val environment = AnalysisEnvironment(messageCollector)
+ environment.apply {
+ if (withJdk || withKotlinRuntime) {
+ val stringRoot = PathManager.getResourceRoot(String::class.java, "/java/lang/String.class")
+ addClasspath(File(stringRoot))
+ }
+ if (withKotlinRuntime) {
+ val kotlinStrictfpRoot = PathManager.getResourceRoot(Strictfp::class.java, "/kotlin/jvm/Strictfp.class")
+ addClasspath(File(kotlinStrictfpRoot))
+ }
+ addRoots(roots.toList())
+
+ loadLanguageVersionSettings(options.languageVersion, options.apiVersion)
+ }
+ val defaultPlatformsProvider = object : DefaultPlatformsProvider {
+ override fun getDefaultPlatforms(descriptor: DeclarationDescriptor) = defaultPlatforms
+ }
+ val injector = Guice.createInjector(
+ DokkaAnalysisModule(environment, options, defaultPlatformsProvider, documentation.nodeRefGraph, DokkaConsoleLogger))
+ buildDocumentationModule(injector, documentation)
+ Disposer.dispose(environment)
+}
+
+fun verifyJavaModel(source: String,
+ withKotlinRuntime: Boolean = false,
+ verifier: (DocumentationModule) -> Unit) {
+ val tempDir = FileUtil.createTempDirectory("dokka-json", "")
+ try {
+ val sourceFile = File(source)
+ FileUtil.copy(sourceFile, File(tempDir, sourceFile.name))
+ verifyModel(JavaSourceRoot(tempDir, null), withJdk = true, withKotlinRuntime = withKotlinRuntime, verifier = verifier)
+ }
+ finally {
+ FileUtil.delete(tempDir)
+ }
+}
+
+fun verifyOutput(roots: Array,
+ outputExtension: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ format: String = "auto-restdocs-json",
+ includeNonPublic: Boolean = true,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ verifyModel(
+ *roots,
+ withJdk = withJdk,
+ withKotlinRuntime = withKotlinRuntime,
+ format = format,
+ includeNonPublic = includeNonPublic
+ ) {
+ verifyModelOutput(it, outputExtension, roots.first().path, outputGenerator)
+ }
+}
+
+fun verifyModelOutput(it: DocumentationModule,
+ outputExtension: String,
+ sourcePath: String,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ val output = StringBuilder()
+ outputGenerator(it, output)
+ val ext = outputExtension.removePrefix(".")
+ val expectedFile = File(sourcePath.replaceAfterLast(".", ext, "$sourcePath.$ext"))
+ assertEqualsIgnoringSeparators(expectedFile, output.toString())
+}
+
+fun verifyOutput(path: String,
+ outputExtension: String,
+ withJdk: Boolean = false,
+ withKotlinRuntime: Boolean = false,
+ format: String = "auto-restdocs-json",
+ includeNonPublic: Boolean = true,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ verifyOutput(
+ arrayOf(contentRootFromPath(path)),
+ outputExtension,
+ withJdk,
+ withKotlinRuntime,
+ format,
+ includeNonPublic,
+ outputGenerator
+ )
+}
+
+fun verifyJavaOutput(path: String,
+ outputExtension: String,
+ withKotlinRuntime: Boolean = false,
+ outputGenerator: (DocumentationModule, StringBuilder) -> Unit) {
+ verifyJavaModel(path, withKotlinRuntime) { model ->
+ verifyModelOutput(model, outputExtension, path, outputGenerator)
+ }
+}
+
+fun assertEqualsIgnoringSeparators(expectedFile: File, output: String) {
+ if (!expectedFile.exists()) expectedFile.createNewFile()
+ val expectedText = expectedFile.readText().replace("\r\n", "\n")
+ val actualText = output.replace("\r\n", "\n")
+
+ if(expectedText != actualText)
+ throw FileComparisonFailure("", expectedText, actualText, expectedFile.canonicalPath)
+}
+
+fun StringBuilder.appendChildren(node: ContentBlock): StringBuilder {
+ for (child in node.children) {
+ val childText = child.toTestString()
+ append(childText)
+ }
+ return this
+}
+
+fun StringBuilder.appendNode(node: ContentNode): StringBuilder {
+ when (node) {
+ is ContentText -> {
+ append(node.text)
+ }
+ is ContentEmphasis -> append("*").appendChildren(node).append("*")
+ is ContentBlockCode -> {
+ if (node.language.isNotBlank())
+ appendln("[code lang=${node.language}]")
+ else
+ appendln("[code]")
+ appendChildren(node)
+ appendln()
+ appendln("[/code]")
+ }
+ is ContentNodeLink -> {
+ append("[")
+ appendChildren(node)
+ append(" -> ")
+ append(node.node.toString())
+ append("]")
+ }
+ is ContentBlock -> {
+ appendChildren(node)
+ }
+ is ContentEmpty -> { /* nothing */ }
+ else -> throw IllegalStateException("Don't know how to format node $node")
+ }
+ return this
+}
+
+fun ContentNode.toTestString(): String {
+ val node = this
+ return StringBuilder().apply {
+ appendNode(node)
+ }.toString()
+}
+
+class InMemoryLocation(override val path: String): Location {
+ override fun relativePathTo(other: Location, anchor: String?): String =
+ if (anchor != null) other.path + "#" + anchor else other.path
+}
+
+val ContentRoot.path: String
+ get() = when(this) {
+ is KotlinSourceRoot -> path
+ is JavaSourceRoot -> file.path
+ else -> throw UnsupportedOperationException()
+ }
+
diff --git a/spring-auto-restdocs-dokka-json/testdata/JavaClass.NestedJavaClass.json b/spring-auto-restdocs-dokka-json/testdata/JavaClass.NestedJavaClass.json
new file mode 100644
index 00000000..8c87cd43
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/testdata/JavaClass.NestedJavaClass.json
@@ -0,0 +1,5 @@
+{
+ "comment" : "A nested Java class",
+ "fields" : { },
+ "methods" : { }
+}
\ No newline at end of file
diff --git a/spring-auto-restdocs-dokka-json/testdata/JavaClass.java b/spring-auto-restdocs-dokka-json/testdata/JavaClass.java
new file mode 100644
index 00000000..19bd6b80
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/testdata/JavaClass.java
@@ -0,0 +1,36 @@
+import java.math.BigDecimal;
+
+/**
+ * Javadoc on a Java class
+ */
+public class JavaClass {
+
+ /**
+ * A Java field
+ *
+ * @see JavaClass
+ * @deprecated Use something else
+ * @title Custom tag
+ */
+ public String someField;
+
+ /**
+ * Method add
+ *
+ * @param a First param a
+ * @param b Second param b
+ * @see JavaClass
+ * @deprecated Use something else
+ * @title Custom tag
+ */
+ public BigDecimal add(BigDecimal a, BigDecimal b) {
+ return a.add(b);
+ }
+
+ /**
+ * A nested Java class
+ */
+ public static class NestedJavaClass {
+
+ }
+}
\ No newline at end of file
diff --git a/spring-auto-restdocs-dokka-json/testdata/JavaClass.json b/spring-auto-restdocs-dokka-json/testdata/JavaClass.json
new file mode 100644
index 00000000..d8943f71
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/testdata/JavaClass.json
@@ -0,0 +1,26 @@
+{
+ "comment" : "Javadoc on a Java class",
+ "fields" : {
+ "someField" : {
+ "comment" : "A Java field",
+ "tags" : {
+ "see" : "JavaClass",
+ "title" : "Custom tag"
+ }
+ }
+ },
+ "methods" : {
+ "add" : {
+ "comment" : "Method add",
+ "parameters" : {
+ "a" : "First param a",
+ "b" : "Second param b"
+ },
+ "tags" : {
+ "parameters" : "Second param b",
+ "see" : "JavaClass",
+ "title" : "Custom tag"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.NestedClass.json b/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.NestedClass.json
new file mode 100644
index 00000000..6956b720
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.NestedClass.json
@@ -0,0 +1,10 @@
+{
+ "comment" : "A nested class",
+ "fields" : {
+ "someField" : {
+ "comment" : "Field on a nested class",
+ "tags" : { }
+ }
+ },
+ "methods" : { }
+}
\ No newline at end of file
diff --git a/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.json b/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.json
new file mode 100644
index 00000000..118f3f15
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.json
@@ -0,0 +1,32 @@
+{
+ "comment" : "Class documentation",
+ "fields" : {
+ "text" : {
+ "comment" : "Documentation for text property",
+ "tags" : {
+ "see" : "KotlinDataClass",
+ "deprecated" : "Use something else",
+ "title" : "Custom tag"
+ }
+ },
+ "number" : {
+ "comment" : "Documentation for number property",
+ "tags" : { }
+ }
+ },
+ "methods" : {
+ "add" : {
+ "comment" : "Function add",
+ "parameters" : {
+ "a" : "First param a",
+ "b" : "Second param b"
+ },
+ "tags" : {
+ "parameters" : "Second param b",
+ "see" : "KotlinDataClass",
+ "deprecated" : "Use something else",
+ "title" : "Custom tag"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.kt b/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.kt
new file mode 100644
index 00000000..6b7efe57
--- /dev/null
+++ b/spring-auto-restdocs-dokka-json/testdata/KotlinDataClass.kt
@@ -0,0 +1,41 @@
+package testdata
+
+import java.math.BigDecimal
+
+/**
+ * Class documentation
+ */
+data class KotlinDataClass(
+ /**
+ * Documentation for text property
+ *
+ * @see KotlinDataClass
+ * @deprecated Use something else
+ * @title Custom tag
+ */
+ val text: String,
+ /**
+ * Documentation for number property
+ */
+ val number: BigDecimal) {
+
+ /**
+ * Function add
+ *
+ * @param a First param a
+ * @param b Second param b
+ * @see KotlinDataClass
+ * @deprecated Use something else
+ * @title Custom tag
+ */
+ fun add(a: BigDecimal, b: BigDecimal): BigDecimal = a + b
+
+ /**
+ * A nested class
+ */
+ data class NestedClass(
+ /**
+ * Field on a nested class
+ */
+ val someField: String)
+}
\ No newline at end of file