Skip to content

Commit

Permalink
Add a filter doclet which works with the Java 11 doclet API. (#1216)
Browse files Browse the repository at this point in the history
* Add a filter doclet which works with the Java 11 doclet API.

MVP with some KIs but I ran out of weekend but allows us to
bump the compiler version everywhere.

Pros:
* Kotlin
* Has potential to be project independent without being as
heavyweight as doclava
* Nifty HTML DSL :)

KI:
* HTML DSL incomplete
* Nested class paths are wrong
* No links on types in signatures

* Minor bugfixes.

* Another minor fix.
  • Loading branch information
prbprbprb authored Sep 9, 2024
1 parent d3d9e77 commit 0538a58
Show file tree
Hide file tree
Showing 14 changed files with 1,140 additions and 191 deletions.
25 changes: 11 additions & 14 deletions api-doclet/build.gradle
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '2.0.0'
}

description = 'Conscrypt: API Doclet'

kotlin {
jvmToolchain(11)
}

java {
toolchain {
// Force Java 8 for the doclet.
languageVersion = JavaLanguageVersion.of(8)
}
// Java 8 doclets depend on the JDK's tools.jar
def compilerMetadata = javaToolchains.compilerFor(toolchain).get().metadata
def jdkHome = compilerMetadata.getInstallationPath()
def toolsJar = jdkHome.file("lib/tools.jar")
dependencies {
implementation files(toolsJar)
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

tasks.withType(Javadoc) {
// TODO(prb): Update doclet to Java 11.
tasks.withType(Javadoc).configureEach {
// No need to javadoc the Doclet....
enabled = false
}
151 changes: 0 additions & 151 deletions api-doclet/src/main/java/org/conscrypt/doclet/FilterDoclet.java

This file was deleted.

76 changes: 76 additions & 0 deletions api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* 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.
*/

package org.conscrypt.doclet

import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
import kotlin.streams.toList

class ClassIndex {
private val index = mutableMapOf<String, ClassInfo>()

private fun put(classInfo: ClassInfo) {
index[classInfo.qualifiedName] = classInfo
}

fun put(element: Element) {
put(ClassInfo(element as TypeElement))
}

fun get(qualifiedName: String) = index[qualifiedName]
fun contains(qualifiedName: String) = index.containsKey(qualifiedName)
fun find(name: String) = if (contains(name)) get(name) else findSimple(name)
private fun findSimple(name: String) = classes().firstOrNull { it.simpleName == name } // XXX dups

fun classes(): Collection<ClassInfo> = index.values

fun addVisible(elements: Set<Element>) {
elements
.filterIsInstance<TypeElement>()
.filter(Element::isVisibleType)
.forEach(::put)
}

private fun packages(): List<String> = index.values.stream()
.map { it.packageName }
.distinct()
.sorted()
.toList()

private fun classesForPackage(packageName: String) = index.values.stream()
.filter { it.packageName == packageName }
.sorted()
.toList()

fun generateHtml():String = html {
packages().forEach { packageName ->
div("package-section") {
h2("Package $packageName", "package-name")
ul("class-list") {
classesForPackage(packageName)
.forEach { c ->
li {
a(c.fileName, c.simpleName)
}
}

}
}
}
}
}

134 changes: 134 additions & 0 deletions api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* 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.
*/

package org.conscrypt.doclet

import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement


data class ClassInfo(val element: TypeElement) : Comparable<ClassInfo> {
val simpleName = element.simpleName.toString()
val qualifiedName = element.qualifiedName.toString()
val packageName = FilterDoclet.elementUtils.getPackageOf(element).qualifiedName.toString()
val fileName = qualifiedName.replace('.', '/') + ".html"

override fun compareTo(other: ClassInfo) = qualifiedName.compareTo(other.qualifiedName)

private fun description() = html {
div("class-description") {
compose { element.commentTree() + element.tagTree() }
}
}

private fun fields() = html {
val fields = element.children(Element::isVisibleField)
if (fields.isNotEmpty()) {
h2("Fields")
fields.forEach { field ->
div("member") {
h4(field.simpleName.toString())
compose {
field.commentTree() + field.tagTree()
}
}
}
}
}

private fun nestedClasses() = html {
val nested = element.children(Element::isVisibleType)
nested.takeIf { it.isNotEmpty() }?.let {
h2("Nested Classes")
nested.forEach { cls ->
div("member") {
h4(cls.simpleName.toString())
compose {
cls.commentTree() + cls.tagTree()
}
}
}
}
}

private fun method(method: ExecutableElement) = html {
div("member") {
h4(method.simpleName.toString())
pre(method.methodSignature(), "method-signature")
div("description") {
compose {
method.commentTree()
}
val params = method.paramTags()
val throwns = method.throwTags()
val returns = if (method.isConstructor())
emptyList()
else
method.returnTag(method.returnType)

if(params.size + returns.size + throwns.size > 0) {
div("params") {
table("params-table") {
rowGroup(params, title = "Parameters", colspan = 2) {
td {text(it.first)}
td {text(it.second)}
}
rowGroup(returns, title = "Returns", colspan = 2) {
td {text(it.first)}
td {text(it.second)}
}
rowGroup(throwns, title = "Throws", colspan = 2) {
td {text(it.first)}
td {text(it.second)}
}
}
}
}
}
}
}

private fun executables(title: String, filter: (Element) -> Boolean) = html {
val methods = element.children(filter)
if (methods.isNotEmpty()) {
h2(title)
methods.forEach {
compose {
method(it as ExecutableElement)
}
}
}
}

private fun constructors() = executables("Constructors", Element::isVisibleConstructor)
private fun methods() = executables("Public Methods", Element::isVisibleMethod)

fun generateHtml() = html {
div("package-name") { text("Package: $packageName") }
h1(simpleName)
pre(element.signature(), "class-signature")

compose {
description() +
fields() +
constructors() +
methods() +
nestedClasses()
}
}
}

Loading

0 comments on commit 0538a58

Please sign in to comment.