1 /*
2  * Copyright (C) 2018 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
18 
19 import com.android.tools.metalava.doclava1.ApiFile
20 import com.android.tools.metalava.doclava1.ApiParseException
21 import com.android.tools.metalava.doclava1.Issues
22 import com.android.tools.metalava.doclava1.TextCodebase
23 import com.android.tools.metalava.model.ClassItem
24 import com.android.tools.metalava.model.Codebase
25 import com.android.tools.metalava.model.visitors.ApiVisitor
26 import java.io.File
27 
28 /** Registry of signature files and the corresponding artifact descriptions */
29 class ArtifactTagger {
30     /** Ordered map from signature file to artifact description */
31     private val artifacts = LinkedHashMap<File, String>()
32 
33     /** Registers the given [artifactId] for the APIs found in the given [signatureFile] */
registernull34     fun register(artifactId: String, signatureFile: File) {
35         artifacts[signatureFile] = artifactId
36     }
37 
38     /** Any registered artifacts? */
anynull39     fun any() = artifacts.isNotEmpty()
40 
41     /** Remove all registrations */
42     fun clear() = artifacts.clear()
43 
44     /** Returns the artifacts */
45     private fun getRegistrations(): Collection<Map.Entry<File, String>> = artifacts.entries
46 
47     /**
48      * Applies the artifact registrations in this map to the given [codebase].
49      * If [warnAboutMissing] is true, it will complain if any classes in the API
50      * are found that have not been tagged (e.g. where no artifact signature file
51      * referenced the API.
52      */
53     fun tag(codebase: Codebase, warnAboutMissing: Boolean = true) {
54         if (!any()) {
55             return
56         }
57 
58         // Read through the XML files in order, applying their artifact information
59         // to the Javadoc models.
60         for (artifactSpec in getRegistrations()) {
61             val xmlFile = artifactSpec.key
62             val artifactName = artifactSpec.value
63 
64             val specApi: TextCodebase
65             try {
66                 specApi = ApiFile.parseApi(xmlFile, options.inputKotlinStyleNulls)
67             } catch (e: ApiParseException) {
68                 reporter.report(
69                     Issues.BROKEN_ARTIFACT_FILE, xmlFile,
70                     "Failed to parse $xmlFile for $artifactName artifact data.\n"
71                 )
72                 continue
73             }
74 
75             applyArtifactsFromSpec(artifactName, specApi, codebase)
76         }
77 
78         if (warnAboutMissing) {
79             codebase.accept(object : ApiVisitor() {
80                 override fun visitClass(cls: ClassItem) {
81                     if (cls.artifact == null && cls.isTopLevelClass()) {
82                         reporter.report(
83                             Issues.NO_ARTIFACT_DATA, cls,
84                             "No registered artifact signature file referenced class ${cls.qualifiedName()}"
85                         )
86                     }
87                 }
88             })
89         }
90     }
91 
applyArtifactsFromSpecnull92     private fun applyArtifactsFromSpec(
93         mavenSpec: String,
94         specApi: TextCodebase,
95         codebase: Codebase
96     ) {
97         for (specPkg in specApi.getPackages().packages) {
98             if (!specPkg.emit) {
99                 continue
100             }
101             val pkg = codebase.findPackage(specPkg.qualifiedName()) ?: continue
102             for (cls in pkg.allClasses()) {
103                 if (!cls.emit) {
104                     continue
105                 }
106                 if (cls.artifact == null) {
107                     cls.artifact = mavenSpec
108                 } else {
109                     reporter.report(
110                         Issues.BROKEN_ARTIFACT_FILE, cls,
111                         "Class ${cls.qualifiedName()} belongs to multiple artifacts: ${cls.artifact} and $mavenSpec"
112                     )
113                 }
114             }
115         }
116     }
117 }
118