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