xref: /aosp_15_r20/external/conscrypt/api-doclet/src/main/kotlin/org/conscrypt/doclet/FilterDoclet.kt (revision cd0cc2e34ba52cdf454361820a14d744e4bd531d)
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.conscrypt.doclet
18 
19 import com.sun.source.util.DocTrees
20 import jdk.javadoc.doclet.Doclet
21 import jdk.javadoc.doclet.DocletEnvironment
22 import jdk.javadoc.doclet.Reporter
23 import java.nio.file.Files
24 import java.nio.file.Path
25 import java.nio.file.Paths
26 import java.util.Locale
27 import javax.lang.model.SourceVersion
28 import javax.lang.model.element.Element
29 import javax.lang.model.util.Elements
30 import javax.lang.model.util.Types
31 
32 /**
33  * A Doclet which can filter out internal APIs in various ways and then render the results
34  * as HTML.
35  *
36  * See also: The Element.isFiltered extension function below to see what is filtered.
37  */
38 class FilterDoclet : Doclet {
39     companion object {
40         lateinit var docTrees: DocTrees
41         lateinit var elementUtils: Elements
42         lateinit var typeUtils: Types
43         lateinit var outputPath: Path
44         lateinit var cssPath: Path
45         var baseUrl: String = "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/"
46         const val CSS_FILENAME = "styles.css"
47         var outputDir = "."
48         var docTitle = "DOC TITLE"
49         var windowTitle = "WINDOW TITLE"
50         var noTimestamp: Boolean = false
51         val classIndex = ClassIndex()
52     }
53 
54     override fun init(locale: Locale?, reporter: Reporter?) = Unit // TODO
55     override fun getName() = "FilterDoclet"
56     override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest()
57 
58     override fun run(environment: DocletEnvironment): Boolean {
59         docTrees = environment.docTrees
60         elementUtils = environment.elementUtils
61         typeUtils = environment.typeUtils
62         outputPath = Paths.get(outputDir)
63         cssPath = outputPath.resolve(CSS_FILENAME)
64         Files.createDirectories(outputPath)
65 
66         classIndex.addVisible(environment.includedElements)
67 
68         try {
69             generateClassFiles()
70             generateIndex()
71             return true
72         } catch (e: Exception) {
73             System.err.println("Error generating documentation: " + e.message)
74             e.printStackTrace()
75             return false
76         }
77     }
78 
79     private fun generateClassFiles() = classIndex.classes().forEach(::generateClassFile)
80 
81     private fun generateIndex() {
82         val indexPath = outputPath.resolve("index.html")
83 
84         html {
85             body(
86                 title = docTitle,
87                 stylesheet = relativePath(indexPath, cssPath),
88             ) {
89                 div("index-container") {
90                     h1(docTitle, "index-title")
91                     compose {
92                         classIndex.generateHtml()
93                     }
94                 }
95             }
96         }.let {
97             Files.newBufferedWriter(indexPath).use { writer ->
98                 writer.write(it)
99             }
100         }
101     }
102 
103     private fun generateClassFile(classInfo: ClassInfo) {
104         val classFilePath = outputPath.resolve(classInfo.fileName)
105         Files.createDirectories(classFilePath.parent)
106         val name = classInfo.innerName()
107 
108         html {
109             body(
110                 title = "$name - Conscrypt API",
111                 stylesheet = relativePath(classFilePath, cssPath),
112             ) {
113                 compose {
114                     classInfo.generateHtml()
115                 }
116             }
117         }.let {
118             Files.newBufferedWriter(classFilePath).use { writer ->
119                 writer.write(it)
120             }
121         }
122     }
123 
124     private fun relativePath(from: Path, to: Path) = from.parent.relativize(to).toString()
125 
126     override fun getSupportedOptions(): Set<Doclet.Option> {
127         return setOf<Doclet.Option>(
128             StringOption(
129                 "-d",
130                 "<directory>",
131                 "Destination directory for output files"
132             ) { d: String -> outputDir = d },
133             StringOption(
134                 "-doctitle",
135                 "<title>",
136                 "Document title"
137             ) { t: String -> docTitle = t },
138             StringOption(
139                 "-windowtitle",
140                 "<title>",
141                 "Window title"
142             ) { w: String -> windowTitle = w },
143             StringOption(
144                 "-link",
145                 "<link>",
146                 "Link"
147             ) { l: String -> baseUrl = l },
148             BooleanOption(
149                 "-notimestamp",
150                 "Something"
151             ) { noTimestamp = true })
152     }
153 }
154 
155 // Called to determine whether to filter each public API element.
Elementnull156 fun Element.isFiltered() =
157         hasJavadocTag("hide") || hasAnnotation("org.conscrypt.Internal")
158