• 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.text
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.ClassKind
21 import com.android.tools.metalava.model.ClassOrigin
22 import com.android.tools.metalava.model.ClassResolver
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.TypeParameterList
25 import com.android.tools.metalava.model.VisibilityLevel
26 import com.android.tools.metalava.model.createImmutableModifiers
27 import com.android.tools.metalava.model.item.DefaultClassItem
28 import com.android.tools.metalava.model.provider.Capability
29 import com.android.tools.metalava.model.provider.InputFormat
30 import com.android.tools.metalava.model.testing.transformer.CodebaseTransformer
31 import com.android.tools.metalava.model.testsuite.ModelSuiteRunner
32 import com.android.tools.metalava.reporter.FileLocation
33 import com.android.tools.metalava.testing.getAndroidJar
34 import java.io.File
35 import java.net.URLClassLoader
36 
37 // @AutoService(ModelSuiteRunner::class)
38 class TextModelSuiteRunner : ModelSuiteRunner {
39 
40     override val providerName = "text"
41 
42     override val supportedInputFormats = setOf(InputFormat.SIGNATURE)
43 
44     override val capabilities: Set<Capability> = setOf()
45 
createCodebaseAndRunnull46     override fun createCodebaseAndRun(
47         inputs: ModelSuiteRunner.TestInputs,
48         test: (Codebase) -> Unit
49     ) {
50         if (inputs.projectDescription != null) {
51             error("text model does not support project description")
52         }
53 
54         val testFixture = inputs.testFixture
55         val codebaseConfig = testFixture.codebaseConfig
56 
57         val signatureFiles = SignatureFile.forTest(inputs.mainSourceDir.createFiles())
58         val classPath = listOf(getAndroidJar()) + inputs.testFixture.additionalClassPath
59         val resolver = ClassLoaderBasedClassResolver(classPath, codebaseConfig)
60         val codebase =
61             ApiFile.parseApi(
62                 signatureFiles,
63                 codebaseConfig = codebaseConfig,
64                 classResolver = resolver,
65             )
66 
67         // If available, transform the codebase for testing, otherwise use the one provided.
68         val transformedCodebase = CodebaseTransformer.transformIfAvailable(codebase)
69 
70         test(transformedCodebase)
71     }
72 
toStringnull73     override fun toString() = providerName
74 }
75 
76 /**
77  * A [ClassResolver] that is backed by a [URLClassLoader].
78  *
79  * When [resolveClass] is called this will first look in [codebase] to see if the [ClassItem] has
80  * already been loaded, returning it if found. Otherwise, it will look in the [classLoader] to see
81  * if the class exists on the classpath. If it does then it will create a [DefaultClassItem] to
82  * represent it and add it to the [codebase]. Otherwise, it will return `null`.
83  *
84  * The created [DefaultClassItem] is not a complete representation of the class that was found in
85  * the [classLoader]. It is just a placeholder to indicate that it was found, although that may
86  * change in the future.
87  */
88 class ClassLoaderBasedClassResolver(
89     jars: List<File>,
90     codebaseConfig: Codebase.Config = Codebase.Config.NOOP,
91 ) : ClassResolver {
92 
93     private val assembler by
94         lazy(LazyThreadSafetyMode.NONE) {
95             val location = jars.first()
96             TextCodebaseAssembler.createAssembler(
97                 location = location,
98                 description = "Codebase for resolving classes in $location for tests",
99                 codebaseConfig = codebaseConfig,
100                 classResolver = null,
101             )
102         }
103 
104     private val codebase by lazy(LazyThreadSafetyMode.NONE) { assembler.codebase }
105 
106     private val classLoader by
107         lazy(LazyThreadSafetyMode.NONE) {
108             val urls = jars.map { it.toURI().toURL() }.toTypedArray()
109             URLClassLoader(urls, null)
110         }
111 
112     private fun findClassInClassLoader(qualifiedName: String): Class<*>? {
113         var binaryName = qualifiedName
114         do {
115             try {
116                 return classLoader.loadClass(binaryName)
117             } catch (e: ClassNotFoundException) {
118                 // If the class could not be found then maybe it was a nested class so replace the
119                 // last '.' in the name with a $ and try again. If there is no '.' then return.
120                 val lastDot = binaryName.lastIndexOf('.')
121                 if (lastDot == -1) {
122                     return null
123                 } else {
124                     val before = binaryName.substring(0, lastDot)
125                     val after = binaryName.substring(lastDot + 1)
126                     binaryName = "$before\$$after"
127                 }
128             }
129         } while (true)
130     }
131 
132     override fun resolveClass(erasedName: String): ClassItem? {
133         return codebase.findClass(erasedName)
134             ?: run {
135                 val cls = findClassInClassLoader(erasedName) ?: return null
136                 val packageName = cls.`package`.name
137 
138                 val itemFactory = assembler.itemFactory
139 
140                 val packageItem = codebase.findOrCreatePackage(packageName)
141                 itemFactory.createClassItem(
142                     fileLocation = FileLocation.UNKNOWN,
143                     modifiers = createImmutableModifiers(VisibilityLevel.PACKAGE_PRIVATE),
144                     classKind = ClassKind.CLASS,
145                     containingClass = null,
146                     containingPackage = packageItem,
147                     qualifiedName = cls.canonicalName,
148                     typeParameterList = TypeParameterList.NONE,
149                     origin = ClassOrigin.CLASS_PATH,
150                     superClassType = null,
151                     interfaceTypes = emptyList(),
152                 )
153             }
154     }
155 }
156