Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy up filter doclet. #1237

Merged
merged 1 commit into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassIndex.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ 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>()
Expand All @@ -31,27 +30,27 @@ class ClassIndex {
put(ClassInfo(element as TypeElement))
}

fun get(qualifiedName: String) = index[qualifiedName]
fun get(qualifiedName: String) = index[qualifiedName]!!
fun get(typeElement: TypeElement) = get(typeElement.qualifiedName.toString())
fun getParent(typeElement: TypeElement) = get(typeElement.enclosingElement as TypeElement)
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)
}
fun addVisible(elements: Set<Element>) = elements
.filterIsInstance<TypeElement>()
.filter(Element::isVisibleType)
.forEach(::put)

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

private fun classesForPackage(packageName: String) = index.values.stream()
private fun classesForPackage(packageName: String) = index.values
.filter { it.packageName == packageName }
.sorted()
.toList()
Expand All @@ -62,15 +61,30 @@ class ClassIndex {
h2("Package $packageName", "package-name")
ul("class-list") {
classesForPackage(packageName)
.forEach { c ->
li {
a(c.fileName, c.simpleName)
.filter { !it.isInnerClass }
.forEach {
compose {
classAndInners(it)
}
}
}
}
}
}

private fun classAndInners(classInfo: ClassInfo): String = html {
li {
a(classInfo.fileName, classInfo.simpleName)
}
val inners = classInfo.innerClasses()
inners.takeIf { it.isNotEmpty() }.let {
ul("class-list") {
inners.forEach {
compose {
classAndInners(it)
}
}
}
}
}
}

22 changes: 20 additions & 2 deletions api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package org.conscrypt.doclet

import org.conscrypt.doclet.FilterDoclet.Companion.classIndex
import java.nio.file.Paths
import javax.lang.model.element.Element
import javax.lang.model.element.ExecutableElement
import javax.lang.model.element.TypeElement
Expand All @@ -25,7 +27,14 @@ 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"
val fileName = element.baseFileName() + ".html"
val isInnerClass = element.enclosingElement.isType()

fun innerClasses() = element.enclosedElements
.filterIsInstance<TypeElement>()
.filter(TypeElement::isType)
.map(classIndex::get)
.sorted()

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

Expand Down Expand Up @@ -57,8 +66,13 @@ data class ClassInfo(val element: TypeElement) : Comparable<ClassInfo> {
nested.takeIf { it.isNotEmpty() }?.let {
h2("Nested Classes")
nested.forEach { cls ->
val typeElement = cls as TypeElement
val info = classIndex.get(typeElement)
val parent = classIndex.getParent(typeElement)
div("member") {
h4(cls.simpleName.toString())
h4 {
a(relativePath(parent.fileName, info.fileName), info.simpleName)
}
compose {
cls.commentsAndTagTrees()
}
Expand Down Expand Up @@ -132,5 +146,9 @@ data class ClassInfo(val element: TypeElement) : Comparable<ClassInfo> {
nestedClasses()
}
}

private fun relativePath(from: String, to: String) =
Paths.get(from).parent.relativize(Paths.get(to)).toString()

}

16 changes: 10 additions & 6 deletions api-doclet/src/main/kotlin/org/conscrypt/doclet/ElementUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ fun Element.isVisibleConstructor() = isExecutable() && isVisible() && kind == El
fun Element.isVisibleField() = isField() && isVisible()
fun Element.isPublic() = modifiers.contains(Modifier.PUBLIC)
fun Element.isPrivate() = !isPublic() // Ignore protected for now :)
fun Element.isHidden() = isPrivate() || hasHideMarker() || parentIsHidden()
fun Element.isVisible() = !isHidden()
fun Element.hasHideMarker() = hasAnnotation("org.conscrypt.Internal") || hasHideTag()
fun Element.isHidden() = isPrivate() || isFiltered() || parentIsHidden()
fun Element.children(filterFunction: (Element) -> Boolean) = enclosedElements
.filter(filterFunction)
.toList()
Expand All @@ -53,10 +52,9 @@ fun Element.hasAnnotation(annotationName: String): Boolean = annotationMirrors
.map { it.annotationType.toString() }
.any { it == annotationName }


fun Element.hasHideTag(): Boolean {
fun Element.hasJavadocTag(tagName: String): Boolean {
return docTree()?.blockTags?.any {
tag -> tag is UnknownBlockTagTree && tag.tagName == "hide"
tag -> tag is UnknownBlockTagTree && tag.tagName == tagName
} ?: false
}

Expand All @@ -79,7 +77,7 @@ fun ExecutableElement.methodSignature(): String {
val exceptions = thrownTypes
.joinToString(", ")
.prefixIfNotEmpty(" throws ")
return "$modifiers $typeParams$returnType${simpleName}($parameters)$exceptions"
return "$modifiers $typeParams$returnType${name()}($parameters)$exceptions"
}

fun formatType(typeMirror: TypeMirror): String {
Expand All @@ -105,6 +103,12 @@ fun TypeElement.signature(): String {
return "$modifiers $kind $simpleName$superName$interfaces"
}

fun TypeElement.baseFileName(): String =
if (enclosingElement.isType())
(enclosingElement as TypeElement).baseFileName() + "." + simpleName
else
qualifiedName.toString().replace('.', '/')

fun superDisplayName(mirror: TypeMirror): String {
return when (mirror.toString()) {
"none", "java.lang.Object" -> ""
Expand Down
39 changes: 21 additions & 18 deletions api-doclet/src/main/kotlin/org/conscrypt/doclet/FilterDoclet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,42 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.util.Locale
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.util.Elements
import javax.lang.model.util.Types

/**
* A Doclet which can filter out internal APIs in various ways and then render the results
* as HTML.
*
* See also: The Element.isFiltered extension function below to see what is filtered.
*/
class FilterDoclet : Doclet {
companion object {
lateinit var docTrees: DocTrees
lateinit var elementUtils: Elements
lateinit var typeUtils: Types
lateinit var outputPath: Path
lateinit var cssPath: Path
var baseUrl: String = "https://docs.oracle.com/javase/8/docs/api/"
val CSS_FILENAME = "styles.css"
const val CSS_FILENAME = "styles.css"
var outputDir = "."
var docTitle = "DTITLE"
var windowTitle = "WTITLE"
var docTitle = "DOC TITLE"
var windowTitle = "WINDOW TITLE"
var noTimestamp: Boolean = false
val classIndex = ClassIndex()
}

override fun init(locale: Locale?, reporter: Reporter?) = Unit // TODO
override fun getName() = "FilterDoclet"
override fun getSupportedSourceVersion() = SourceVersion.latest()
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()

override fun run(environment: DocletEnvironment): Boolean {
docTrees = environment.docTrees
elementUtils = environment.elementUtils
typeUtils = environment.typeUtils
outputPath = Paths.get(outputDir)
cssPath = outputPath.resolve(CSS_FILENAME)
Files.createDirectories(outputPath)

classIndex.addVisible(environment.includedElements)
Expand All @@ -75,7 +84,7 @@ class FilterDoclet : Doclet {
html {
body(
title = docTitle,
stylesheet = relativePath(indexPath, CSS_FILENAME),
stylesheet = relativePath(indexPath, cssPath),
) {
div("index-container") {
h1(docTitle, "index-title")
Expand All @@ -99,7 +108,7 @@ class FilterDoclet : Doclet {
html {
body(
title = "$simpleName - conscrypt-openjdk API",
stylesheet = relativePath(classFilePath, CSS_FILENAME),
stylesheet = relativePath(classFilePath, cssPath),
) {
compose {
classInfo.generateHtml()
Expand All @@ -112,17 +121,7 @@ class FilterDoclet : Doclet {
}
}

private fun relativePath(from: Path, to: String): String {
val fromDir = from.parent
val toPath = Paths.get(outputDir).resolve(to)

if (fromDir == null) {
return to
}

val relativePath = fromDir.relativize(toPath)
return relativePath.toString().replace('\\', '/')
}
private fun relativePath(from: Path, to: Path) = from.parent.relativize(to).toString()

override fun getSupportedOptions(): Set<Doclet.Option> {
return setOf<Doclet.Option>(
Expand Down Expand Up @@ -151,4 +150,8 @@ class FilterDoclet : Doclet {
"Something"
) { noTimestamp = true })
}
}
}

// Called to determine whether to filter each public API element.
fun Element.isFiltered() =
hasJavadocTag("hide") || hasAnnotation("org.conscrypt.Internal")
Loading
Loading