diff --git a/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassIndex.kt b/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassIndex.kt index 811d13a35..054ca2d0a 100644 --- a/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassIndex.kt +++ b/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassIndex.kt @@ -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() @@ -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 = index.values - fun addVisible(elements: Set) { - elements - .filterIsInstance() - .filter(Element::isVisibleType) - .forEach(::put) - } + fun addVisible(elements: Set) = elements + .filterIsInstance() + .filter(Element::isVisibleType) + .forEach(::put) - private fun packages(): List = index.values.stream() + private fun packages(): List = 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() @@ -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) + } } } } } } - diff --git a/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassInfo.kt b/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassInfo.kt index 582885ba9..ada528bca 100644 --- a/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassInfo.kt +++ b/api-doclet/src/main/kotlin/org/conscrypt/doclet/ClassInfo.kt @@ -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 @@ -25,7 +27,14 @@ data class ClassInfo(val element: TypeElement) : Comparable { 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() + .filter(TypeElement::isType) + .map(classIndex::get) + .sorted() override fun compareTo(other: ClassInfo) = qualifiedName.compareTo(other.qualifiedName) @@ -57,8 +66,13 @@ data class ClassInfo(val element: TypeElement) : Comparable { 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() } @@ -132,5 +146,9 @@ data class ClassInfo(val element: TypeElement) : Comparable { nestedClasses() } } + + private fun relativePath(from: String, to: String) = + Paths.get(from).parent.relativize(Paths.get(to)).toString() + } diff --git a/api-doclet/src/main/kotlin/org/conscrypt/doclet/ElementUtils.kt b/api-doclet/src/main/kotlin/org/conscrypt/doclet/ElementUtils.kt index a9fcd00e3..0688afa9f 100644 --- a/api-doclet/src/main/kotlin/org/conscrypt/doclet/ElementUtils.kt +++ b/api-doclet/src/main/kotlin/org/conscrypt/doclet/ElementUtils.kt @@ -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() @@ -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 } @@ -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 { @@ -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" -> "" diff --git a/api-doclet/src/main/kotlin/org/conscrypt/doclet/FilterDoclet.kt b/api-doclet/src/main/kotlin/org/conscrypt/doclet/FilterDoclet.kt index 77db33fff..316ee748a 100644 --- a/api-doclet/src/main/kotlin/org/conscrypt/doclet/FilterDoclet.kt +++ b/api-doclet/src/main/kotlin/org/conscrypt/doclet/FilterDoclet.kt @@ -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) @@ -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") @@ -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() @@ -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 { return setOf( @@ -151,4 +150,8 @@ class FilterDoclet : Doclet { "Something" ) { noTimestamp = true }) } -} \ No newline at end of file +} + +// Called to determine whether to filter each public API element. +fun Element.isFiltered() = + hasJavadocTag("hide") || hasAnnotation("org.conscrypt.Internal") diff --git a/api-doclet/src/main/kotlin/org/conscrypt/doclet/HtmlBuilder.kt b/api-doclet/src/main/kotlin/org/conscrypt/doclet/HtmlBuilder.kt index 0c2758b2a..0455c44d0 100644 --- a/api-doclet/src/main/kotlin/org/conscrypt/doclet/HtmlBuilder.kt +++ b/api-doclet/src/main/kotlin/org/conscrypt/doclet/HtmlBuilder.kt @@ -43,13 +43,21 @@ class HtmlBuilder { } private fun tagBlock( - tag: String, cssClass: String? = null, colspan: Int? = null, id: String? = null, block: Block) + tag: String, + cssClass: String? = null, + colspan: Int? = null, + id: String? = null, + block: Block, + inline: Boolean? = false) { - content.append("\n<$tag") + content.append("<$tag") cssClass?.let { content.append(""" class="$it"""") } colspan?.let { content.append(""" colspan="$it"""") } id?.let { content.append(""" id="$it"""") } content.append(">") + if(inline == false) { + content.append("\n") + } content.append(block.render()) content.append("\n") } @@ -65,28 +73,35 @@ class HtmlBuilder { fun tr(cssClass: String? = null, id: String? = null, block: Block) = tagBlock("tr", cssClass = cssClass, colspan = null, id, block) fun th(cssClass: String? = null, colspan: Int? = null, id: String? = null, block: Block) = - tagBlock("th", cssClass, colspan, id, block) + tagBlock("th", cssClass, colspan, id, block, true) fun td(cssClass: String? = null, colspan: Int? = null, id: String? = null, block: Block) = - tagBlock("td", cssClass, colspan, id, block) + tagBlock("td", cssClass, colspan, id, block, true) - private fun tagValue(tag: String, value: String, cssClass: String? = null) { + private fun tagValue(tag: String, value: String, cssClass: String? = null, id: String? = null) { val classText = cssClass?.let { """ class="$it"""" } ?: "" - content.append("<$tag$classText>$value\n") + val idText = id?.let { """ id="$it"""" } ?: "" + content.append("<$tag$classText$idText>$value") + } + + private fun tagValueNl(tag: String, value: String, cssClass: String? = null) { + tagValue(tag, value, cssClass) + content.append("\n") } - fun h1(heading: String, cssClass: String? = null) = tagValue("h1", heading, cssClass) + fun h1(heading: String, cssClass: String? = null) = tagValueNl("h1", heading, cssClass) fun h1(cssClass: String? = null, block: Block) = h1(block.render(), cssClass) - fun h2(heading: String, cssClass: String? = null) = tagValue("h2", heading, cssClass) + fun h2(heading: String, cssClass: String? = null) = tagValueNl("h2", heading, cssClass) fun h2(cssClass: String? = null, block: Block) = h2(block.render(), cssClass) - fun h3(heading: String, cssClass: String? = null) = tagValue("h3", heading, cssClass) + fun h3(heading: String, cssClass: String? = null) = tagValueNl("h3", heading, cssClass) fun h3(cssClass: String? = null, block: Block) = h2(block.render(), cssClass) - fun h4(heading: String, cssClass: String? = null) = tagValue("h4", heading, cssClass) + fun h4(heading: String, cssClass: String? = null) = tagValueNl("h4", heading, cssClass) fun h4(cssClass: String? = null, block: Block) = h2(block.render(), cssClass) - fun h5(heading: String, cssClass: String? = null) = tagValue("h5", heading, cssClass) + fun h5(heading: String, cssClass: String? = null) = tagValueNl("h5", heading, cssClass) fun h5(cssClass: String? = null, block: Block) = h2(block.render(), cssClass) - fun p(text: String, cssClass: String? = null) = tagValue("p", text, cssClass) + fun p(text: String, cssClass: String? = null) = tagValueNl("p", text, cssClass) fun p(cssClass: String? = null, block: Block) = p(block.render(), cssClass) + fun b(text: String, cssClass: String? = null) = tagValue("b", text, cssClass) fun b(cssClass: String? = null, block: Block) = b(block.render(), cssClass) fun pre(text: String, cssClass: String? = null) = tagValue("pre", text, cssClass) @@ -103,7 +118,7 @@ class HtmlBuilder { fun a(href: String, block: Block) = a(href, block.render()) fun a(href: String) = a(href, href) - fun li(text: String, cssClass: String? = null) = tagValue("li", text, cssClass) + fun li(text: String, cssClass: String? = null) = tagValueNl("li", text, cssClass) fun li(cssClass: String? = null, block: Block) = li(block.render(), cssClass) fun items(collection: Iterable, cssClass: String? = null, @@ -145,7 +160,7 @@ fun html(block: Block) = block.render() fun exampleSubfunction() = html { h1("Headings from exampleSubfunction") listOf("one", "two", "three").forEach { - h1(it) + h2(it) } } @@ -224,56 +239,56 @@ fun example() = html { text("Item $it") } } - } - val data1 = listOf(1, 2) - val data2 = "3" to "4" - val data3 = listOf( - "tag1" to "Some value", - "tag2" to "Next Value", - "tag3" to "Another value" - ) + val data1 = listOf(1, 2) + val data2 = "3" to "4" + val data3 = listOf( + "key1" to "Some value", + "key2" to "Next Value", + "key3" to "Another value" + ) - table("table-class") { - tr { - th { - text("First column") - } - th { - text("Second column") + table(cssClass = "table-class", id = "tableId") { + tr { + th { + text("First column") + } + th { + text("Second column") + } } - } - tr("tr-class") { - td("td-class") { - text("Data 1") - } - td(colspan = 2, id = "foo") { + tr("tr-class") { + td("td-class") { + text("Data 1") + } + td(colspan = 2, id = "foo") { text("Data 2") + } } - } - tr { - td() { - text("Data 3") - } - } - row(data1, "c1") { - a(href="www.google.com") { text("$it") } - } - row(data2) { p:Pair -> - td { - text(p.first) + tr { + td() { + text("Data 3") + } } - td { - text(p.second) + row(data1, "c1") { + a(href = "www.google.com") { text("$it") } } + row(data2) { p: Pair -> + td { + text(p.first) + } + td { + text(p.second) + } - } - rowGroup(data3, title = "Row Group", colspan=2) { p: Pair -> - td { - text(p.first) } - td { - text(p.second) + rowGroup(data3, title = "Row Group", colspan = 2) { p: Pair -> + td { + text(p.first) + } + td { + text(p.second) + } } } } diff --git a/api-doclet/src/main/resources/styles.css b/api-doclet/src/main/resources/styles.css index 262f64ed8..0a99068bf 100644 --- a/api-doclet/src/main/resources/styles.css +++ b/api-doclet/src/main/resources/styles.css @@ -2,7 +2,6 @@ body { font-family: Arial, sans-serif; line-height: 1.2; color: #333; - /* max-width: 800px; */ margin: 0 auto; padding: 10px; } @@ -75,7 +74,6 @@ body { font-size: 14px; overflow-x: auto; } -/* Index page styles */ .index-container { margin: 0 auto; padding: 20px; @@ -115,7 +113,6 @@ body { color: #2c3e50; margin-bottom: 20px; } - .class-description { margin: 20px 0; padding: 15px; @@ -123,18 +120,15 @@ body { font-size: 16px; line-height: 1.6; } - .class-description p { margin-bottom: 10px; } - .class-description code { background-color: #e9ecef; padding: 2px 4px; border-radius: 4px; font-family: monospace; } - .package-name { font-family: monospace; font-size: 14px;