1 /*
<lambda>null2  * Copyright (C) 2023 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.text
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.ClassResolver
21 import com.android.tools.metalava.model.Codebase
22 import com.android.tools.metalava.model.DefaultModifierList
23 import com.android.tools.metalava.model.noOpAnnotationManager
24 import com.android.tools.metalava.model.provider.Capability
25 import com.android.tools.metalava.model.provider.InputFormat
26 import com.android.tools.metalava.model.testsuite.ModelSuiteRunner
27 import com.android.tools.metalava.reporter.FileLocation
28 import com.android.tools.metalava.testing.getAndroidJar
29 import java.io.File
30 import java.net.URLClassLoader
31 
32 // @AutoService(ModelSuiteRunner::class)
33 class TextModelSuiteRunner : ModelSuiteRunner {
34 
35     override val providerName = "text"
36 
37     override val supportedInputFormats = setOf(InputFormat.SIGNATURE)
38 
39     override val capabilities: Set<Capability> = setOf()
40 
41     override fun createCodebaseAndRun(
42         inputs: ModelSuiteRunner.TestInputs,
43         test: (Codebase) -> Unit
44     ) {
45         if (inputs.commonSourceDir != null) {
46             error("text model does not support common sources")
47         }
48 
49         val signatureFiles = SignatureFile.fromFiles(inputs.mainSourceDir.createFiles())
50         val resolver = ClassLoaderBasedClassResolver(getAndroidJar())
51         val codebase = ApiFile.parseApi(signatureFiles, classResolver = resolver)
52         test(codebase)
53     }
54 
55     override fun toString() = providerName
56 }
57 
58 /**
59  * A [ClassResolver] that is backed by a [URLClassLoader].
60  *
61  * When [resolveClass] is called this will first look in [codebase] to see if the [ClassItem] has
62  * already been loaded, returning it if found. Otherwise, it will look in the [classLoader] to see
63  * if the class exists on the classpath. If it does then it will create a [TextClassItem] to
64  * represent it and add it to the [codebase]. Otherwise, it will return `null`.
65  *
66  * The created [TextClassItem] is not a complete representation of the class that was found in the
67  * [classLoader]. It is just a placeholder to indicate that it was found, although that may change
68  * in the future.
69  */
70 internal class ClassLoaderBasedClassResolver(jar: File) : ClassResolver {
71 
<lambda>null72     private val codebase by lazy {
73         TextCodebase(
74             location = jar,
75             annotationManager = noOpAnnotationManager,
76             classResolver = null,
77         )
78     }
79 
<lambda>null80     private val classLoader by lazy { URLClassLoader(arrayOf(jar.toURI().toURL()), null) }
81 
findClassInClassLoadernull82     private fun findClassInClassLoader(qualifiedName: String): Class<*>? {
83         var binaryName = qualifiedName
84         do {
85             try {
86                 return classLoader.loadClass(binaryName)
87             } catch (e: ClassNotFoundException) {
88                 // If the class could not be found then maybe it was an inner class so replace the
89                 // last '.' in the name with a $ and try again. If there is no '.' then return.
90                 val lastDot = binaryName.lastIndexOf('.')
91                 if (lastDot == -1) {
92                     return null
93                 } else {
94                     val before = binaryName.substring(0, lastDot)
95                     val after = binaryName.substring(lastDot + 1)
96                     binaryName = "$before\$$after"
97                 }
98             }
99         } while (true)
100     }
101 
resolveClassnull102     override fun resolveClass(erasedName: String): ClassItem? {
103         return codebase.findClass(erasedName)
104             ?: run {
105                 val cls = findClassInClassLoader(erasedName) ?: return null
106                 val packageName = cls.`package`.name
107 
108                 val packageItem =
109                     codebase.findPackage(packageName)
110                         ?: TextPackageItem(
111                                 codebase = codebase,
112                                 name = packageName,
113                                 modifiers = DefaultModifierList(codebase),
114                                 fileLocation = FileLocation.UNKNOWN,
115                             )
116                             .also { newPackageItem -> codebase.addPackage(newPackageItem) }
117 
118                 TextClassItem(
119                         codebase = codebase,
120                         modifiers = DefaultModifierList(codebase),
121                         qualifiedName = cls.canonicalName,
122                     )
123                     .also { newClassItem ->
124                         codebase.registerClass(newClassItem)
125                         packageItem.addClass(newClassItem)
126                     }
127             }
128     }
129 }
130