fix(lsp/java): impl optimizations in JavaJarModelBuilder

- avoid allocating temporary lists
- avoid multiple string operations

Apart from this, the commit fixes some compilation errors.
This commit is contained in:
Akash Yadav
2024-05-27 14:22:19 +05:30
parent d605cb21be
commit 7bea046fa0
13 changed files with 174 additions and 111 deletions
@@ -17,15 +17,18 @@
package com.itsaky.androidide.utils
import android.app.Application
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/**
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
@Config(application = Application::class)
class JdkUtilsTest {
companion object {
@@ -154,7 +157,8 @@ OpenJDK 64-Bit Server VM (build 21.0.1-internal-adhoc.root.src, mixed mode)
assertThat(dist).isNotNull()
assertThat(dist!!.javaVersion).isEqualTo("17-internal")
assertThat(dist.javaHome).isEqualTo(
"/data/data/com.itsaky.androidide/files/usr/opt/openjdk-17.0")
"/data/data/com.itsaky.androidide/files/usr/opt/openjdk-17.0"
)
}
@Test
@@ -163,7 +167,8 @@ OpenJDK 64-Bit Server VM (build 21.0.1-internal-adhoc.root.src, mixed mode)
assertThat(dist).isNotNull()
assertThat(dist!!.javaVersion).isEqualTo("21.0.1-internal")
assertThat(dist.javaHome).isEqualTo(
"/data/data/com.itsaky.androidide/files/usr/opt/openjdk-21.0.1")
"/data/data/com.itsaky.androidide/files/usr/opt/openjdk-21.0.1"
)
}
@Test
@@ -17,19 +17,22 @@
package com.itsaky.androidide.utils
import android.app.Application
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/**
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
@Config(application = Application::class)
class ShiftedArrayTest {
@Test
fun `test positive shift` () {
fun `test positive shift`() {
val backing = LongArray(10) { it.toLong() }
val array = MutableShiftedLongArray(backing)
for (s in 0..<array.size) {
@@ -45,7 +48,7 @@ class ShiftedArrayTest {
}
@Test
fun `test negative shift` () {
fun `test negative shift`() {
val backing = LongArray(10) { it.toLong() }
val array = MutableShiftedLongArray(backing)
array.shift(-1)
@@ -62,7 +65,7 @@ class ShiftedArrayTest {
}
@Test
fun `test negative shift - 2` () {
fun `test negative shift - 2`() {
val backing = LongArray(10) { it.toLong() }
val array = MutableShiftedLongArray(backing)
array.shift(-2)
@@ -64,9 +64,7 @@ import java.util.zip.ZipFile
*
* @author Akash Yadav
*/
class JavaJarModelBuilder(
private val jar: File
) {
class JavaJarModelBuilder(private val jar: File) {
// for testing purposes
// prefer using consumeTypes(Function)
@@ -78,15 +76,15 @@ class JavaJarModelBuilder(
fun consumeTypes(consumer: (IJavaType<*, *>) -> Unit) {
ZipFile(jar).use { jar ->
for (entry in jar.entries()) {
if (!entry.name.endsWith(".class")) {
continue
}
jar.getInputStream(entry).use { inputStream ->
jar.entries().asSequence().filter { it.name.endsWith(".class") }.forEach { entry ->
jar.getInputStream(entry).buffered().use { inputStream ->
val classFile = ClassFile.read(inputStream)!!
val type = buildType(classFile)
consumer(type)
// build model only if the class is not an anonymous or local class
if (classFile.getAttribute(Attribute.EnclosingMethod) == null) {
val type = buildType(classFile)
consumer(type)
}
}
}
}
@@ -95,27 +93,69 @@ class JavaJarModelBuilder(
private fun buildType(classFile: ClassFile): IJavaType<*, *> {
val classInfo = classFile.constant_pool.getClassInfo(classFile.this_class)
val fqn = classInfo.baseName
val name = simpleName(fqn)
val pck = fqn.substring(0, fqn.length - name.length - 1)
return buildType(fqn, name, pck, classFile)
val (pck, name, isInner) = splitFqn(fqn)
return buildType(fqn, pck, name, isInner, classFile)
}
/**
* Split the given fully qualified name of a class and return the package name and the simple name
* of the class, along with whether the class is an inner class.
*/
private fun splitFqn(fqn: String): Triple<String, String, Boolean> {
val dolIdx = fqn.lastIndexOf('$')
// start looking for '/' from the index of the last '$'
// a '/' will always appear before a '$'
val slaIdx = fqn.lastIndexOf('/', startIndex = if (dolIdx == -1) fqn.lastIndex else dolIdx - 1)
if (slaIdx == -1 && dolIdx != -1) {
// fqn == SomeClass$InnerClass (inner class of a class in the root package)
// pck is empty, but fqn is not the class's simple name
return Triple("", fqn.substring(dolIdx + 1), true)
}
if (slaIdx == -1) {
// fqn == SomeClass (class in the root package)
// pck is empty and fqn is class's simple name
return Triple("", fqn, false)
}
if (dolIdx == -1) {
// fqn == com/example/SomeClass
return Triple(fqn.substring(0, slaIdx), fqn.substring(slaIdx + 1), false)
}
// fqn == com/example/SomeClass$InnerClass
return Triple(fqn.substring(0, slaIdx), fqn.substring(dolIdx + 1), true)
}
private fun buildType(
fqn: String, name: String, pck: String, classFile: ClassFile
fqn: String,
pck: String,
name: String,
isInner: Boolean,
classFile: ClassFile
): IJavaType<*, *> {
if (classFile.access_flags.`is`(AccessFlags.ACC_ENUM)) {
return buildEnum(fqn, name, pck, classFile)
}
return when {
classFile.access_flags.`is`(AccessFlags.ACC_ENUM) -> buildEnum(fqn, name, pck, classFile)
classFile.access_flags.`is`(AccessFlags.ACC_ANNOTATION) -> buildAnnotation(
fqn,
name,
pck,
classFile
)
if (classFile.access_flags.`is`(AccessFlags.ACC_ANNOTATION)) {
return buildAnnotation(fqn, name, pck, classFile)
}
classFile.access_flags.`is`(AccessFlags.ACC_INTERFACE) -> buildInterface(
fqn,
name,
pck,
classFile
)
if (classFile.access_flags.`is`(AccessFlags.ACC_INTERFACE)) {
return buildInterface(fqn, name, pck, classFile)
else -> buildClass(fqn, name, pck, classFile)
}.also {
it.isInner = isInner
}
return buildClass(fqn, name, pck, classFile)
}
private fun buildClass(fqn: String, name: String, pck: String, classFile: ClassFile): JavaClass {
@@ -182,7 +222,7 @@ class JavaJarModelBuilder(
private fun IJavaType<*, JavaMethod>.setMethods(classFile: ClassFile) {
methods = RealmList<JavaMethod>().apply {
addAll(classFile.methods.map { method ->
classFile.methods.forEach { method ->
val descriptor = classFile.constant_pool.getUTF8Value(method.descriptor.index)
val methodName = method.getName(classFile.constant_pool)
val methodType = DescriptorUtils.returnType(descriptor)
@@ -190,18 +230,19 @@ class JavaJarModelBuilder(
addAll(DescriptorUtils.paramTypes(descriptor))
}
JavaMethod.newInstance(methodName, paramTypes, methodType, method.access_flags.flags)
})
add(JavaMethod.newInstance(methodName, paramTypes, methodType, method.access_flags.flags))
}
}
}
private fun IJavaType<JavaField, *>.setFields(classFile: ClassFile) {
fields = RealmList<JavaField>().apply {
addAll(classFile.fields.map { field ->
classFile.fields.forEach { field ->
val descriptor = classFile.constant_pool.getUTF8Value(field.descriptor.index)
val fieldName = field.getName(classFile.constant_pool)
val fieldType = DescriptorUtils.type(descriptor)
JavaField.newField(fieldName, fieldType, field.access_flags.flags).apply {
add(JavaField.newField(fieldName, fieldType, field.access_flags.flags).apply {
field.attributes.get(Attribute.ConstantValue)?.also { constAttr ->
val constantValue = constAttr as ConstantValue_attribute
val constant = classFile.constant_pool.get(constantValue.constantvalue_index)
@@ -214,19 +255,19 @@ class JavaJarModelBuilder(
this.constantValue = javaConstant
}
}
})
})
}
}
}
private fun JavaAnnotation.setAnnotationElements(classFile: ClassFile) {
methods = RealmList<AnnotationElement>().apply {
addAll(classFile.methods.map { method ->
classFile.methods.forEach { method ->
val descriptor = classFile.constant_pool.getUTF8Value(method.descriptor.index)
val methodName = method.getName(classFile.constant_pool)
val methodType = DescriptorUtils.returnType(descriptor)
this@setAnnotationElements.newAnnotationElement(
add(this@setAnnotationElements.newAnnotationElement(
methodName, methodType, method.access_flags.flags
).apply {
val annotationDefault =
@@ -236,8 +277,8 @@ class JavaJarModelBuilder(
val value = toAnnotationElementValue(defaultValue, classFile)
this.defaultValue = value.let(RealmAny::valueOf) ?: RealmAny.nullValue()
}
}
})
})
}
}
}
@@ -259,18 +300,8 @@ class JavaJarModelBuilder(
JavaType.KIND_LONG,
JavaType.KIND_FLOAT,
JavaType.KIND_DOUBLE -> PrimitiveAnnotationElementValue.newInstance(
kind = when (kind) {
JavaType.KIND_BOOLEAN -> IAnnotationElementValue.KIND_BOOLEAN
JavaType.KIND_BYTE -> IAnnotationElementValue.KIND_BYTE
JavaType.KIND_CHAR -> IAnnotationElementValue.KIND_CHAR
JavaType.KIND_SHORT -> IAnnotationElementValue.KIND_SHORT
JavaType.KIND_INT -> IAnnotationElementValue.KIND_INT
JavaType.KIND_LONG -> IAnnotationElementValue.KIND_LONG
JavaType.KIND_FLOAT -> IAnnotationElementValue.KIND_FLOAT
JavaType.KIND_DOUBLE -> IAnnotationElementValue.KIND_DOUBLE
else -> throw IllegalArgumentException("Unknown value tag: $char")
},
// annotation value kinds are same as JavaType.KIND_*
kind = kind,
value = toPrimitiveConstant(
classFile.constant_pool, kind, char, defaultValue as Primitive_element_value
)
@@ -300,15 +331,13 @@ class JavaJarModelBuilder(
}
'c' -> {
val clazz = defaultValue as Class_element_value
// even though the name is given as "class_info_index", the constant at that index
// is actually a CONSTANT_Utf8_info representing a return type descriptor (JLS $4.3.3)
// this has been clearly mentioned in the Java Language Specification
ClassAnnotationElementValue.newInstance(
DescriptorUtils.type(
classFile.constant_pool.getUTF8Value(
clazz.class_info_index
(defaultValue as Class_element_value).class_info_index
)
)
)
@@ -332,13 +361,12 @@ class JavaJarModelBuilder(
}
'[' -> {
val arr = defaultValue as Array_element_value
ArrayAnnotationElementValue.newInstance(values = RealmList<RealmAny>().apply {
addAll(arr.values.map { element ->
toAnnotationElementValue(element, classFile).let { elementValue ->
(defaultValue as Array_element_value).values.forEach { element ->
add(toAnnotationElementValue(element, classFile).let { elementValue ->
RealmAny.valueOf(elementValue)
}
})
})
}
})
}
@@ -349,7 +377,7 @@ class JavaJarModelBuilder(
}
private fun toPrimitiveConstant(
constantPool: ConstantPool, kind: Short, char: Char, prim: Primitive_element_value
constantPool: ConstantPool, kind: Int, char: Char, prim: Primitive_element_value
): JavaConstant {
if (kind == JavaType.KIND_REF && char != 's') {
throw IllegalArgumentException("Unknown value tag: $char")
@@ -360,7 +388,7 @@ class JavaJarModelBuilder(
}
private fun toJavaConstant(
kind: Short, constant: CPInfo, char: Char?
kind: Int, constant: CPInfo, char: Char?
): JavaConstant {
val realmAny = when (kind) {
JavaType.KIND_BOOLEAN,
@@ -390,10 +418,4 @@ class JavaJarModelBuilder(
value = realmAny
)
}
private fun simpleName(fqn: String): String {
var idx = fqn.lastIndexOf('$')
idx = if (idx == -1) fqn.lastIndexOf('/') else idx
return fqn.substring(idx + 1)
}
}
@@ -41,6 +41,7 @@ interface IJavaType<FieldT : IJavaSymbol, MethodT : IJavaSymbol> : IJavaSymbol {
var fqn: String?
var name: String?
var packageName: String?
var isInner: Boolean
/**
* The fully qualified name of the superclass of the type.
@@ -52,6 +52,9 @@ open class JavaAnnotation : IJavaType<JavaField, AnnotationElement> {
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@@ -83,6 +86,7 @@ open class JavaAnnotation : IJavaType<JavaField, AnnotationElement> {
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superInterfacesFqn: RealmList<String>? = null,
apiInfo: ApiInfo? = null,
fields: RealmList<JavaField>? = null,
@@ -92,6 +96,7 @@ open class JavaAnnotation : IJavaType<JavaField, AnnotationElement> {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superClassFqn = superClassFqn
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
@@ -47,6 +47,9 @@ open class JavaClass : IJavaType<JavaField, JavaMethod> {
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@@ -75,6 +78,7 @@ open class JavaClass : IJavaType<JavaField, JavaMethod> {
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superClassFqn: String? = null,
superInterfacesFqn: RealmList<String>? = null,
apiInfo: ApiInfo? = null,
@@ -85,6 +89,7 @@ open class JavaClass : IJavaType<JavaField, JavaMethod> {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superClassFqn = superClassFqn
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
@@ -43,13 +43,15 @@ open class JavaConstant : ISharedJavaIndexable {
override var id: Int? = null
@RealmField("kind")
private var kind: Short = JavaType.KIND_UNKNOWN
var kind: Int = JavaType.KIND_UNKNOWN
private set
@RealmField("value")
private var value: RealmAny? = null
var value: RealmAny? = null
private set
companion object {
fun newInstance(kind: Short, value: RealmAny): JavaConstant {
fun newInstance(kind: Int, value: RealmAny): JavaConstant {
return JavaConstant().apply {
this.kind = kind
this.value = value
@@ -58,7 +60,7 @@ open class JavaConstant : ISharedJavaIndexable {
}
}
fun update(kind: Short = this.kind, value: RealmAny? = this.value) {
fun update(kind: Int = this.kind, value: RealmAny? = this.value) {
this.kind = kind
this.value = value
this.computeId()
@@ -113,7 +115,7 @@ open class JavaConstant : ISharedJavaIndexable {
return safeCast(JavaType.KIND_CHAR) { asInteger()?.toChar() }
}
private inline fun <R> safeCast(kind: Short, action: RealmAny.() -> R): R? {
private inline fun <R> safeCast(kind: Int, action: RealmAny.() -> R): R? {
return value?.let {
if (this.kind == kind) {
try {
@@ -46,6 +46,9 @@ open class JavaEnum : IJavaType<JavaField, JavaMethod> {
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@@ -74,6 +77,7 @@ open class JavaEnum : IJavaType<JavaField, JavaMethod> {
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superClassFqn: String? = null,
superInterfacesFqn: RealmList<String>? = null,
apiInfo: ApiInfo? = null,
@@ -84,6 +88,7 @@ open class JavaEnum : IJavaType<JavaField, JavaMethod> {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superClassFqn = superClassFqn
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
@@ -47,6 +47,9 @@ open class JavaInterface : IJavaType<JavaField, JavaMethod> {
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@@ -75,6 +78,7 @@ open class JavaInterface : IJavaType<JavaField, JavaMethod> {
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superInterfacesFqn: RealmList<String>? = null,
apiInfo: ApiInfo? = null,
fields: RealmList<JavaField>? = null,
@@ -84,6 +88,7 @@ open class JavaInterface : IJavaType<JavaField, JavaMethod> {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
this.apiInfo = apiInfo
@@ -42,7 +42,7 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
var name: String? = null
@RealmField("kind")
var kind: Short = KIND_UNKNOWN
var kind: Int = KIND_UNKNOWN
@RealmField("arrayDims")
var arrayDims: Int = 0
@@ -146,17 +146,25 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
}
companion object {
const val KIND_UNKNOWN: Short = -1
const val KIND_BOOLEAN: Short = 0
const val KIND_BYTE: Short = 1
const val KIND_CHAR: Short = 2
const val KIND_DOUBLE: Short = 3
const val KIND_FLOAT: Short = 4
const val KIND_INT: Short = 5
const val KIND_LONG: Short = 6
const val KIND_SHORT: Short = 7
const val KIND_VOID: Short = 8
const val KIND_REF: Short = 9
const val KIND_UNKNOWN = -1
const val KIND_BOOLEAN = 0
const val KIND_BYTE = 1
const val KIND_CHAR = 2
const val KIND_DOUBLE = 3
const val KIND_FLOAT = 4
const val KIND_INT = 5
const val KIND_LONG = 6
const val KIND_SHORT = 7
const val KIND_VOID = 8
const val KIND_REF = 9
// kind values used in IAnnotationElementValue
// defined here for convenience
internal const val __KIND_STRING = 10
internal const val __KIND_CLASS = 11
internal const val __KIND_ENUM = 12
internal const val __KIND_ANNOTATION = 13
internal const val __KIND_ARRAY = 14
const val TYPE_BOOLEAN = 'Z'
const val TYPE_BYTE = 'B'
@@ -219,7 +227,7 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
}
@JvmStatic
fun typeForKind(kind: Short) = when (kind) {
fun typeForKind(kind: Int) = when (kind) {
KIND_BOOLEAN -> TYPE_BOOLEAN
KIND_BYTE -> TYPE_BYTE
KIND_CHAR -> TYPE_CHAR
@@ -249,7 +257,7 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
}
@JvmStatic
fun newInstance(name: String?, kind: Short, arrayDims: Int = 0): JavaType {
fun newInstance(name: String?, kind: Int, arrayDims: Int = 0): JavaType {
return JavaType().apply {
this.name = name
this.kind = kind
@@ -259,7 +267,7 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
}
@JvmStatic
fun newInstance(name: Char, kind: Short, arrayDims: Int = 0): JavaType {
fun newInstance(name: Char, kind: Int, arrayDims: Int = 0): JavaType {
return newInstance(name.toString(), kind, arrayDims)
}
}
@@ -29,23 +29,24 @@ import io.realm.annotations.RealmField
import io.realm.annotations.Required
sealed interface IAnnotationElementValue : ISharedJavaIndexable {
var kind: Byte
var kind: Int
companion object {
const val KIND_UNKNOWN: Byte = -1
const val KIND_BOOLEAN: Byte = 0
const val KIND_BYTE: Byte = 1
const val KIND_CHAR: Byte = 2
const val KIND_DOUBLE: Byte = 3
const val KIND_FLOAT: Byte = 4
const val KIND_INT: Byte = 5
const val KIND_LONG: Byte = 6
const val KIND_SHORT: Byte = 7
const val KIND_STRING: Byte = 8
const val KIND_CLASS: Byte = 9
const val KIND_ENUM: Byte = 10
const val KIND_ANNOTATION: Byte = 11
const val KIND_ARRAY: Byte = 12
const val KIND_UNKNOWN = JavaType.KIND_UNKNOWN
const val KIND_BOOLEAN = JavaType.KIND_BOOLEAN
const val KIND_BYTE = JavaType.KIND_BYTE
const val KIND_CHAR = JavaType.KIND_CHAR
const val KIND_DOUBLE = JavaType.KIND_DOUBLE
const val KIND_FLOAT = JavaType.KIND_FLOAT
const val KIND_INT = JavaType.KIND_INT
const val KIND_LONG = JavaType.KIND_LONG
const val KIND_SHORT = JavaType.KIND_SHORT
const val KIND_STRING = JavaType.__KIND_STRING
const val KIND_CLASS = JavaType.__KIND_CLASS
const val KIND_ENUM = JavaType.__KIND_ENUM
const val KIND_ANNOTATION = JavaType.__KIND_ANNOTATION
const val KIND_ARRAY = JavaType.__KIND_ARRAY
}
}
@@ -59,7 +60,7 @@ open class PrimitiveAnnotationElementValue : IAnnotationElementValue {
override var id: Int? = null
@RealmField("kind")
override var kind: Byte = 0
override var kind: Int = 0
@RealmField("value")
var value: JavaConstant? = null
@@ -92,7 +93,7 @@ open class PrimitiveAnnotationElementValue : IAnnotationElementValue {
companion object {
@JvmStatic
fun newInstance(kind: Byte, value: JavaConstant): PrimitiveAnnotationElementValue {
fun newInstance(kind: Int, value: JavaConstant): PrimitiveAnnotationElementValue {
return PrimitiveAnnotationElementValue().apply {
this.kind = kind
this.value = value
@@ -111,7 +112,7 @@ open class EnumAnnotationElementValue : IAnnotationElementValue {
override var id: Int? = null
@RealmField("kind")
override var kind: Byte = IAnnotationElementValue.KIND_ENUM
override var kind: Int = IAnnotationElementValue.KIND_ENUM
@RealmField("name")
var name: String? = null
@@ -169,7 +170,7 @@ open class ArrayAnnotationElementValue : IAnnotationElementValue {
override var id: Int? = null
@RealmField("kind")
override var kind: Byte = IAnnotationElementValue.KIND_ARRAY
override var kind: Int = IAnnotationElementValue.KIND_ARRAY
@RealmField("numValues")
var numValues: Int = 0
@@ -230,7 +231,7 @@ open class ClassAnnotationElementValue : IAnnotationElementValue {
override var id: Int? = null
@RealmField("kind")
override var kind: Byte = IAnnotationElementValue.KIND_CLASS
override var kind: Int = IAnnotationElementValue.KIND_CLASS
@RealmField("type")
var type: JavaType? = null
@@ -282,7 +283,7 @@ open class AnnotationAnnotationElementValue : IAnnotationElementValue {
override var id: Int? = null
@RealmField("kind")
override var kind: Byte = IAnnotationElementValue.KIND_ANNOTATION
override var kind: Int = IAnnotationElementValue.KIND_ANNOTATION
@RealmField("type")
var type: JavaType? = null
@@ -22,6 +22,7 @@ import com.itsaky.androidide.lsp.testing.LSPTest
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.projects.util.findAppModule
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
import com.itsaky.androidide.xml.versions.ApiVersions
import com.itsaky.androidide.xml.widgets.WidgetTable
import org.junit.Before
import org.junit.Ignore
@@ -706,7 +706,7 @@ class ResourceEntry(val name : String) {
var allowNew: AllowNew? = null
var overlayable: OverlayableItem? = null
internal val values = mutableListOf<ResourceConfigValue>()
val values = mutableListOf<ResourceConfigValue>()
fun findValue(config: ConfigDescription, product: String = ""): ResourceConfigValue? {
return values.find { it.config == config && it.product == product }