• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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 com.android.tools.metalava.model.source.utils
18 
19 import com.android.tools.metalava.model.ItemDocumentation.Companion.toItemDocumentationFactory
20 import com.android.tools.metalava.model.item.MutablePackageDoc
21 import com.android.tools.metalava.model.item.PackageDocs
22 import com.android.tools.metalava.model.item.ResourceFile
23 import com.android.tools.metalava.model.source.SourceSet
24 import com.android.tools.metalava.reporter.FileLocation
25 import com.android.tools.metalava.reporter.Issues
26 import com.android.tools.metalava.reporter.Reporter
27 import java.io.File
28 
29 /** The kinds of package documentation file. */
30 private enum class PackageDocumentationKind {
31     PACKAGE {
updatenull32         override fun update(packageDoc: MutablePackageDoc, file: File) {
33             val contents = file.readText(Charsets.UTF_8)
34             packageDoc.commentFactory = packageHtmlToJavadoc(contents).toItemDocumentationFactory()
35             packageDoc.fileLocation = FileLocation.forFile(file)
36         }
37     },
38     OVERVIEW {
updatenull39         override fun update(packageDoc: MutablePackageDoc, file: File) {
40             packageDoc.overview = ResourceFile(file)
41         }
42     };
43 
44     /** Update kind appropriate property in [packageDoc] with [contents]. */
updatenull45     abstract fun update(packageDoc: MutablePackageDoc, file: File)
46 }
47 
48 /**
49  * Gather javadoc related to packages from the [sourceSet] and a list of model specific
50  * [packageInfoFiles].
51  *
52  * This will look for `package.html` and `overview.html` files within the source set and then map
53  * that back to a package. It will first check to see if there is a java class in the same directory
54  * and if so then extract the package name from that otherwise it will construct one from the
55  * directory, which may be wrong.
56  *
57  * If a `package.html` and `package-info.java` are provided for the same package then it will be
58  * reported as an error and the comment from the latter will win.
59  *
60  * @param P the model specific `package-info.java` file type.
61  * @param packageNameFilter a lambda that given a package name will return `true` if it is a valid
62  *   package and `false` otherwise. This is used to filter out any packages incorrectly inferred
63  *   from `package.html` files.
64  * @param packageInfoFiles a collection of model specific `package-info.java` files.
65  * @param packageInfoDocExtractor get a [MutablePackageDoc] from a model specific
66  *   `package-info.java` file.
67  */
68 fun <P> gatherPackageJavadoc(
69     reporter: Reporter,
70     sourceSet: SourceSet,
71     packageNameFilter: (String) -> Boolean,
72     packageInfoFiles: Collection<P>,
73     packageInfoDocExtractor: (P) -> MutablePackageDoc?,
74 ): PackageDocs {
75     val packages = mutableMapOf<String, MutablePackageDoc>()
76     val sortedSourceRoots = sourceSet.sourcePath.sortedBy { -it.name.length }
77     for (file in sourceSet.sources) {
78         val documentationFile =
79             when (file.name) {
80                 PACKAGE_HTML -> {
81                     PackageDocumentationKind.PACKAGE
82                 }
83                 OVERVIEW_HTML -> {
84                     PackageDocumentationKind.OVERVIEW
85                 }
86                 else -> continue
87             }
88 
89         // Figure out the package: if there is a java file in the same directory, get the package
90         // name from the java file. Otherwise, guess from the directory path + source roots.
91         // NOTE: This causes metalava to read files other than the ones explicitly passed to it.
92         var pkg =
93             file.parentFile
94                 ?.listFiles()
95                 ?.filter { it.name.endsWith(DOT_JAVA) }
96                 ?.asSequence()
97                 ?.mapNotNull { findPackage(it) }
98                 ?.firstOrNull()
99         if (pkg == null) {
100             // Strip the longest prefix source root.
101             val prefix = sortedSourceRoots.firstOrNull { file.startsWith(it) }?.path ?: ""
102             pkg = file.parentFile.path.substring(prefix.length).trim('/').replace("/", ".")
103         }
104 
105         // If the package name is invalid then skip it.
106         if (!packageNameFilter(pkg)) continue
107 
108         val packageDoc = packages.computeIfAbsent(pkg, ::MutablePackageDoc)
109 
110         documentationFile.update(packageDoc, file)
111     }
112 
113     // Merge package-info.java documentation.
114     for (packageInfoFile in packageInfoFiles) {
115         val (packageName, fileLocation, modifiers, comment, _) =
116             packageInfoDocExtractor(packageInfoFile) ?: continue
117 
118         val packageDoc = packages.computeIfAbsent(packageName, ::MutablePackageDoc)
119         if (packageDoc.commentFactory != null) {
120             reporter.report(
121                 Issues.BOTH_PACKAGE_INFO_AND_HTML,
122                 null,
123                 "It is illegal to provide both a package-info.java file and " +
124                     "a package.html file for the same package",
125                 fileLocation,
126             )
127         }
128 
129         // Always set this as package-info.java is preferred over package.html.
130         packageDoc.fileLocation = fileLocation
131         packageDoc.modifiers = modifiers
132         packageDoc.commentFactory = comment
133     }
134 
135     return PackageDocs(packages)
136 }
137