fix: cut down memory usage in ApiVersionsRegistry

This commit adds the following improvements to the ApiVersionsRegistry implementation :

- Use Java StAX parser for memory-efficient XML processing
- Reduce object allocations for representing API version info by reusing ApiVersion instances which represent the same API versions.
- Trim method return types in method signature strings to reduce memory usage by a small amount
- A single model class is now used to represent the API information. Earlier, we had different classes to represent API info for classes, fields and metnods.
This commit is contained in:
Akash Yadav
2024-06-24 12:27:46 +05:30
parent c3e13305d7
commit 52a6117451
32 changed files with 342 additions and 737 deletions
@@ -22,7 +22,7 @@ import androidx.core.content.ContextCompat
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.actions.EditorRelatedAction
import com.itsaky.androidide.models.SaveResult
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.utils.flashError
import com.itsaky.androidide.utils.flashSuccess
@@ -32,7 +32,7 @@ import com.itsaky.androidide.activities.editor.EditorActivityKt
import com.itsaky.androidide.app.EdgeToEdgeIDEActivity
import com.itsaky.androidide.databinding.ActivityMainBinding
import com.itsaky.androidide.preferences.internal.GeneralPreferences
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.templates.ITemplateProvider
import com.itsaky.androidide.utils.DialogUtils
@@ -84,7 +84,7 @@ import com.itsaky.androidide.models.Range
import com.itsaky.androidide.models.SearchResult
import com.itsaky.androidide.preferences.internal.BuildPreferences
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.tasks.cancelIfActive
import com.itsaky.androidide.ui.CodeEditorView
import com.itsaky.androidide.ui.ContentTranslatingDrawerLayout
@@ -51,7 +51,7 @@ import com.itsaky.androidide.models.OpenedFile
import com.itsaky.androidide.models.OpenedFilesCache
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.models.SaveResult
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.ui.CodeEditorView
import com.itsaky.androidide.utils.DialogUtils.newYesNoDialog
@@ -39,7 +39,7 @@ import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.IDELanguageClientImpl
import com.itsaky.androidide.lsp.java.utils.CancelChecker
import com.itsaky.androidide.preferences.internal.GeneralPreferences
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.projects.api.GradleProject
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.services.builder.GradleBuildService
@@ -28,7 +28,7 @@ import com.itsaky.androidide.eventbus.events.editor.OnPauseEvent
import com.itsaky.androidide.eventbus.events.editor.OnResumeEvent
import com.itsaky.androidide.eventbus.events.editor.OnStartEvent
import com.itsaky.androidide.eventbus.events.editor.OnStopEvent
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.projects.util.BootClasspathProvider
import com.itsaky.androidide.utils.EditorActivityActions
import com.itsaky.androidide.utils.EditorSidebarActions
@@ -34,7 +34,7 @@ import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.managers.ToolsManager
import com.itsaky.androidide.preferences.internal.BuildPreferences
import com.itsaky.androidide.preferences.internal.DevOpsPreferences
import com.itsaky.androidide.projects.ProjectManagerImpl
import com.itsaky.androidide.projects.internal.ProjectManagerImpl
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.services.ToolingServerNotStartedException
@@ -1,122 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing.core.platform
import com.google.common.base.Objects
import com.itsaky.androidide.indexing.IIndexable
import com.itsaky.androidide.models.ICloneable
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* @property since The API in which the symbol was added.
* @property deprecatedIn The API in which the symbol was deprecated.
* @property removedIn The API in which the symbol was removed.
* @author Akash Yadav
*/
@RealmClass
open class ApiVersion : IIndexable, ICloneable {
@Required
@PrimaryKey
@RealmField("id")
var id: Int? = null
@RealmField("since")
var since: Int = 1
private set
@RealmField("deprecatedIn")
var deprecatedIn: Int = 0
private set
@RealmField("removedIn")
var removedIn: Int = 0
private set
private fun computeId() {
this.id = Objects.hashCode(this.since, this.removedIn, this.deprecatedIn)
}
/**
* Update the API information from the given [ApiVersion].
*/
fun update(apiVersion: ApiVersion) = apply {
this.since = apiVersion.since
this.deprecatedIn = apiVersion.deprecatedIn
this.removedIn = apiVersion.removedIn
this.computeId()
}
/**
* Update the API information with the given parameters.
*/
fun update(
since: Int = this.since,
deprecatedIn: Int = this.deprecatedIn,
removedIn: Int = this.removedIn
) = apply {
this.since = since
this.deprecatedIn = deprecatedIn
this.removedIn = removedIn
this.computeId()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ApiVersion) return false
if (id != other.id) return false
if (since != other.since) return false
if (deprecatedIn != other.deprecatedIn) return false
if (removedIn != other.removedIn) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + since
result = 31 * result + deprecatedIn
result = 31 * result + removedIn
return result
}
override fun toString(): String {
return "ApiVersion(id=$id, since=$since, deprecatedIn=$deprecatedIn, removedIn=$removedIn)"
}
override fun clone(): ApiVersion {
return newInstance(since, deprecatedIn, removedIn)
}
companion object {
@JvmStatic
fun newInstance(since: Int, deprecatedIn: Int, removedIn: Int): ApiVersion {
return ApiVersion().apply {
this.since = since
this.deprecatedIn = deprecatedIn
this.removedIn = removedIn
this.computeId()
}
}
}
}
@@ -23,9 +23,12 @@ interface ICompletionData
/**
* Information about a class-related completion item.
*
* @property className The fully qualified name of the class.
* @property className The fully qualified name of the class. Example: `pck.outer.inner`.
* @property flatName The flat name of the class. Example: `pck.outer$inner`.
* @property isCompleteData Whether the data provided by this [ClassCompletionData] is complete.
* @property isNested Whether the given class is a nested class or not.
* @property topLevelClass If [isNested] is true, then this must be set to the fully qualified name
* of the top-level class.
* @property simpleName The simple name of the class.
* @property nameWithoutTopLevel The name of this class without the fully qualified name of its top
* level class. For example, the value of this property for class name
@@ -33,20 +36,20 @@ interface ICompletionData
*/
data class ClassCompletionData
@JvmOverloads
constructor(val className: String, val isNested: Boolean = false, val topLevelClass: String = "") :
ICompletionData {
val simpleName: String
get() {
return className.substringAfterLast(delimiter = '.')
}
constructor(
val className: String,
val isCompleteData: Boolean = false,
val flatName: String = className,
val simpleName: String = className.substringAfterLast(delimiter = '.'),
val isNested: Boolean = false,
val topLevelClass: String = ""
) : ICompletionData {
val nameWithoutTopLevel: String
get() {
if (!isNested) {
return className
}
return className.substring(topLevelClass.length + 1)
}
val nameWithoutTopLevel: String = if (isNested) {
className.substring(topLevelClass.length + 1)
} else {
className
}
}
/**
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.projects
package com.itsaky.androidide.projects.internal
import androidx.annotation.RestrictTo
import com.android.builder.model.v2.models.ProjectSyncIssues
@@ -29,6 +29,9 @@ import com.itsaky.androidide.eventbus.events.file.FileEvent
import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.CachingProject
import com.itsaky.androidide.projects.IProjectManager
import com.itsaky.androidide.projects.R
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.ModuleProject
import com.itsaky.androidide.projects.api.Project
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.projects
package com.itsaky.androidide.projects.internal
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.GradleProject
+1
View File
@@ -44,6 +44,7 @@ dependencies {
implementation(libs.androidx.tracing.ktx)
implementation(libs.common.utilcode)
implementation(libs.composite.jdt)
implementation(libs.google.material)
@@ -19,7 +19,6 @@ package com.itsaky.androidide.editor.adapters
import android.content.res.Resources
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.text.TextUtils
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
@@ -28,6 +27,7 @@ import android.widget.TextView
import com.itsaky.androidide.editor.R
import com.itsaky.androidide.editor.databinding.LayoutCompletionItemBinding
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.models.ClassCompletionData
import com.itsaky.androidide.lsp.models.CompletionItemKind.CLASS
import com.itsaky.androidide.lsp.models.CompletionItemKind.CONSTRUCTOR
@@ -49,8 +49,8 @@ import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE.COMPLETION_WND
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.utils.customOrJBMono
import com.itsaky.androidide.xml.versions.ApiVersions
import com.itsaky.androidide.xml.versions.Info
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter
import org.eclipse.jdt.core.Signature
import com.itsaky.androidide.lsp.models.CompletionItem as LspCompletionItem
class CompletionListAdapter : EditorCompletionAdapter() {
@@ -85,8 +85,10 @@ class CompletionListAdapter : EditorCompletionAdapter() {
binding.completionLabel.text = label
binding.completionType.text = type
binding.completionDetail.text = desc
binding.completionIconText.setTypeface(customOrJBMono(EditorPreferences.useCustomFont),
Typeface.BOLD)
binding.completionIconText.setTypeface(
customOrJBMono(EditorPreferences.useCustomFont),
Typeface.BOLD
)
if (desc.isEmpty()) {
binding.completionDetail.visibility = View.GONE
}
@@ -147,45 +149,40 @@ class CompletionListAdapter : EditorCompletionAdapter() {
val data = item.data
val versions =
Lookup.getDefault().lookup(ApiVersions.COMPLETION_LOOKUP_KEY) ?: return@executeAsync null
val className =
val info =
when (data) {
is ClassCompletionData -> data.className
is MemberCompletionData -> data.classInfo.className
is ClassCompletionData -> versions.classInfo(data.className)
is MemberCompletionData -> {
if (data is MethodCompletionData) {
// if the member is a method
// build the method identifier by joining the method name and the erased parameter types
// for method 'int some(String)', the identifier becomes 'some(Ljava/lang/String;)'
// return type of the method is ignored
versions.memberInfo(
data.classInfo.flatName,
methodIdentifier(data.memberName, data.erasedParameterTypes)
)
} else {
versions.memberInfo(data.classInfo.flatName, data.memberName)
}
}
else -> return@executeAsync null
}
val kind = item.completionKind
val clazz = versions.getClass(className) ?: return@executeAsync null
var info: Info? = clazz
if (data is MethodCompletionData) {
if (
kind == METHOD && data.erasedParameterTypes.isNotEmpty() && data.memberName.isNotBlank()
) {
val method = clazz.getMethod(data.memberName, *data.erasedParameterTypes.toTypedArray())
if (method != null) {
info = method
}
} else if (kind == FIELD && data.memberName.isNotBlank()) {
val field = clazz.getField(data.memberName)
if (field != null) {
info = field
}
}
}
val sb = StringBuilder()
if (info!!.since > 1) {
sb.append(textView.context.getString(msg_api_info_since, info.since))
sb.append("\n")
}
if (info.removed > 0) {
sb.append(textView.context.getString(msg_api_info_removed, info.removed))
if (info.removedIn > 0) {
sb.append(textView.context.getString(msg_api_info_removed, info.removedIn))
sb.append("\n")
}
if (info.deprecated > 0) {
sb.append(textView.context.getString(msg_api_info_deprecated, info.deprecated))
if (info.deprecatedIn > 0) {
sb.append(textView.context.getString(msg_api_info_deprecated, info.deprecatedIn))
sb.append("\n")
}
@@ -201,6 +198,21 @@ class CompletionListAdapter : EditorCompletionAdapter() {
}
}
private fun methodIdentifier(memberName: String, erasedParameterTypes: List<String>): String {
val sb = StringBuilder()
sb.append(memberName)
sb.append('(')
for (type in erasedParameterTypes) {
if (type.length == 1 && JavaType.primitiveFor(type[0]) != null) {
sb.append(type)
} else {
sb.append(Signature.createTypeSignature(type, true))
}
}
sb.append(')')
return sb.toString()
}
private fun isValidForApiVersion(item: LspCompletionItem?): Boolean {
if (item == null) {
return false
@@ -209,15 +221,15 @@ class CompletionListAdapter : EditorCompletionAdapter() {
val data = item.data
return if ( // These represent a class type
(type === CLASS ||
type === INTERFACE ||
type === ENUM ||
type === INTERFACE ||
type === ENUM ||
// These represent a method type
type === METHOD ||
type === CONSTRUCTOR ||
// These represent a method type
type === METHOD ||
type === CONSTRUCTOR ||
// A field type
type === FIELD) && data != null
// A field type
type === FIELD) && data != null
) {
val className =
when (data) {
@@ -225,7 +237,7 @@ class CompletionListAdapter : EditorCompletionAdapter() {
is MemberCompletionData -> data.classInfo.className
else -> null
}
!TextUtils.isEmpty(className)
!className.isNullOrBlank()
} else false
}
}
+3 -1
View File
@@ -48,6 +48,8 @@ dependencies {
kapt(projects.annotation.processors)
kapt(libs.google.auto.service)
api(projects.core.indexingApi)
implementation(libs.androidide.ts)
implementation(libs.androidide.ts.java)
implementation(libs.androidx.annotation)
@@ -63,7 +65,6 @@ dependencies {
implementation(projects.core.actions)
implementation(projects.core.common)
implementation(projects.core.indexingApi)
implementation(projects.core.lspApi)
implementation(projects.core.resources)
implementation(projects.editor.api)
@@ -73,6 +74,7 @@ dependencies {
implementation(libs.composite.javapoet)
implementation(libs.composite.jaxp)
implementation(libs.composite.jdkJdeps)
implementation(libs.composite.jdt)
implementation(libs.composite.googleJavaFormat)
implementation(libs.androidx.core.ktx)
@@ -21,6 +21,7 @@ import com.itsaky.androidide.lsp.api.IServerSettings
import com.itsaky.androidide.lsp.java.compiler.CompileTask
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.providers.CompletionProvider
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.models.CompletionResult
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
@@ -50,7 +51,7 @@ class ClassNamesCompletionProvider(
partial: String,
endsWithParen: Boolean,
): CompletionResult {
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
val list = mutableListOf<CompletionItem>()
val packageName = Objects.toString(root.packageName, "")
val uniques: MutableSet<String> = HashSet()
@@ -66,6 +66,7 @@ import jdkx.lang.model.element.ElementKind.TYPE_PARAMETER
import jdkx.lang.model.element.ExecutableElement
import jdkx.lang.model.element.TypeElement
import jdkx.lang.model.element.VariableElement
import jdkx.lang.model.type.TypeKind
import openjdk.source.tree.Tree
import openjdk.source.util.TreePath
import org.slf4j.Logger
@@ -261,6 +262,8 @@ abstract class IJavaCompletionProvider(
item.detail = packageName(className).toString()
item.ideSortText = item.ideLabel
item.matchLevel = matchLevel
// TODO(itsaky): This will result in incorrect flatName if 'className' is a nested or local class
item.data = ClassCompletionData(className)
// If file is not provided, we are probably completing an import path
@@ -331,13 +334,19 @@ abstract class IJavaCompletionProvider(
ENUM -> CompletionItemKind.ENUM
ENUM_CONSTANT -> ENUM_MEMBER
EXCEPTION_PARAMETER,
PARAMETER, -> PROPERTY
PARAMETER,
-> PROPERTY
FIELD -> CompletionItemKind.FIELD
STATIC_INIT,
INSTANCE_INIT, -> FUNCTION
INSTANCE_INIT,
-> FUNCTION
INTERFACE -> CompletionItemKind.INTERFACE
LOCAL_VARIABLE,
RESOURCE_VARIABLE, -> VARIABLE
RESOURCE_VARIABLE,
-> VARIABLE
METHOD -> CompletionItemKind.METHOD
PACKAGE -> MODULE
TYPE_PARAMETER -> CompletionItemKind.TYPE_PARAMETER
@@ -350,8 +359,8 @@ abstract class IJavaCompletionProvider(
abortIfCancelled()
abortCompletionIfCancelled()
return when {
element is TypeElement -> getClassCompletionData(element)
element.kind == FIELD -> getFieldCompletionData(element)
element is TypeElement -> getClassCompletionData(task, element)
element.kind == FIELD -> getFieldCompletionData(task, element)
element is ExecutableElement -> getMethodCompletionData(task, element, overloads)
else -> return null
}
@@ -363,6 +372,7 @@ abstract class IJavaCompletionProvider(
overloads: Int
): MethodCompletionData {
val types = task.task.types
val elements = task.task.elements
val type = element.enclosingElement as TypeElement
val parameterTypes = Array(element.parameters.size) { "" }
val erasedParameterTypes = Array(parameterTypes.size) { "" }
@@ -371,30 +381,47 @@ abstract class IJavaCompletionProvider(
for (i in element.parameters.indices) {
val p = element.parameters[i].asType()
parameterTypes[i] = p.toString()
erasedParameterTypes[i] = types.erasure(p).toString()
if (p.kind == TypeKind.DECLARED) {
erasedParameterTypes[i] =
elements.getBinaryName(types.asElement(p) as TypeElement).toString()
} else {
erasedParameterTypes[i] = types.erasure(p).toString()
}
}
return MethodCompletionData(
element.simpleName.toString(),
getClassCompletionData(type),
getClassCompletionData(task, type),
parameterTypes.toList(),
erasedParameterTypes.toList(),
plusOverloads
)
}
protected open fun getFieldCompletionData(element: Element): FieldCompletionData {
protected open fun getFieldCompletionData(
task: CompileTask,
element: Element
): FieldCompletionData {
val field = element as VariableElement
val type = field.enclosingElement as TypeElement
return FieldCompletionData(field.simpleName.toString(), getClassCompletionData(type))
return FieldCompletionData(field.simpleName.toString(), getClassCompletionData(task, type))
}
protected open fun getClassCompletionData(element: TypeElement) =
ClassCompletionData(
element.qualifiedName.toString(),
element.enclosingElement.kind != PACKAGE,
element.findTopLevelElement().qualifiedName.toString()
protected open fun getClassCompletionData(
task: CompileTask,
element: TypeElement
): ClassCompletionData {
val elements = task.task.elements
return ClassCompletionData(
className = element.qualifiedName.toString(),
isCompleteData = true,
flatName = elements.getBinaryName(element).toString(),
simpleName = element.simpleName.toString(),
isNested = element.enclosingElement.kind != PACKAGE,
topLevelClass = element.findTopLevelElement().qualifiedName.toString()
)
}
protected open fun TypeElement.findTopLevelElement(): TypeElement {
if (enclosingElement.kind == PACKAGE) {
+1
View File
@@ -39,6 +39,7 @@ dependencies {
implementation(libs.common.kotlin)
implementation(libs.composite.jdt)
implementation(libs.composite.jaxp)
implementation(libs.google.auto.service.annotations)
implementation(projects.core.common)
@@ -15,10 +15,10 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing.core.internal.platform
package com.itsaky.androidide.xml.internal.versions
import androidx.collection.mutableIntObjectMapOf
import com.itsaky.androidide.indexing.core.platform.ApiVersion
import com.itsaky.androidide.xml.versions.ApiVersion
import jaxp.xml.namespace.QName
import jaxp.xml.stream.XMLInputFactory
import jaxp.xml.stream.events.Attribute
@@ -37,7 +37,7 @@ open class ApiVersionsParser {
// A cache to store the ApiVersion instances with different values
// For example, if two classes have the same value for `since`,
// 'removed' and 'deprecated', then the same ApiVersion instance
// will be used for the two classes.
// will be used for the two classes. To prevent value corruption, ApiVersion instances are immutable.
// The keys in this map are the packed (bitwise ORed) values of the three version integers.
// The first 8 bits are 0, the next 8 bits represent 'since', the next 8 bits represent 'deprecated'
// and the last 8 bits represent 'removed'.
@@ -47,6 +47,14 @@ open class ApiVersionsParser {
private var currentClass: String? = null
companion object {
internal const val TAG_API = "api"
internal const val TAG_CLASS = "class"
internal const val TAG_FIELD = "field"
internal const val TAG_METHOD = "method"
internal const val ATTR_NAME = "name"
internal const val ATTR_DEPR = "deprecated"
internal const val ATTR_REM = "removed"
internal const val ATTR_SIN = "since"
private val log = LoggerFactory.getLogger(ApiVersionsParser::class.java)
}
@@ -77,14 +85,15 @@ open class ApiVersionsParser {
/**
* Called when the parser is done parsing the `api-versions.xml` file.
*/
protected open fun onFinishParse() {}
protected open fun onFinishParse() {
}
private fun consumeStartElement(event: StartElement) {
when (event.name.localPart) {
"api" -> apiVersion = event.getAttributeByName(QName("version")).value.toInt()
"class" -> consumeClass(event)
"field" -> consumeMember(event, "field")
"method" -> consumeMember(event, "method")
TAG_API -> apiVersion = event.getAttributeByName(QName("version")).value.toInt()
TAG_CLASS -> consumeClass(event)
TAG_FIELD -> consumeMember(event, TAG_FIELD)
TAG_METHOD -> consumeMember(event, TAG_METHOD)
}
}
@@ -161,25 +170,25 @@ open class ApiVersionsParser {
private fun consumeEndElement(element: EndElement) {
when (element.name.localPart) {
"api" -> apiVersion = null
"class" -> currentClass = null
TAG_API -> apiVersion = null
TAG_CLASS -> currentClass = null
}
}
private fun StartElement.parseAttrs(): Pair<String, Int> {
var name: String? = null
var since = 1
var deprecated = 0
var removed = 0
var deprecated = ApiVersion.NONE
var removed = ApiVersion.NONE
attributes.forEach { attribute ->
attribute as Attribute
when (attribute.name.localPart) {
"name" -> name = attribute.value
"since" -> since = attribute.value.toInt()
"deprecated" -> deprecated = attribute.value.toInt()
"removed" -> removed = attribute.value.toInt()
ATTR_NAME -> name = attribute.value
ATTR_SIN -> since = attribute.value.toInt()
ATTR_DEPR -> deprecated = attribute.value.toInt()
ATTR_REM -> removed = attribute.value.toInt()
}
}
@@ -200,6 +209,6 @@ open class ApiVersionsParser {
val since = (versions shr 16) and 0x000000FF
val deprecated = (versions shr 8) and 0x000000FF
val removed = versions and 0x000000FF
return ApiVersion.newInstance(since = since, deprecatedIn = deprecated, removedIn = removed)
return ApiVersion(since = since, deprecatedIn = deprecated, removedIn = removed)
}
}
@@ -17,20 +17,49 @@
package com.itsaky.androidide.xml.internal.versions
import com.itsaky.androidide.xml.versions.ApiVersion
import com.itsaky.androidide.xml.versions.ApiVersions
import com.itsaky.androidide.xml.versions.ClassInfo
import org.eclipse.jdt.core.Signature
import java.util.concurrent.ConcurrentHashMap
/** @author Akash Yadav */
internal class DefaultApiVersions : ApiVersions {
val classes = ConcurrentHashMap<String, ClassInfo>()
val classes =
ConcurrentHashMap<String, Pair<ApiVersion?, ConcurrentHashMap<String, ApiVersion>>>()
override fun getClass(name: String): ClassInfo? {
return classes[name.replace('.', '/')]
private fun String.flatten() = replace('.', '/')
override fun classInfo(name: String): ApiVersion? {
return classes[name.flatten()]?.first
}
internal fun putClass(name: String, info: ClassInfo) {
classes[name] = info
override fun memberInfo(className: String, identifier: String): ApiVersion? {
return classes[className.flatten()]?.second?.get(identifier)
}
fun containsClass(name: String): Boolean {
return classes.containsKey(name.flatten())
}
fun containsClassMember(name: String, member: String): Boolean {
return classes[name.flatten()]?.second?.containsKey(member) ?: false
}
fun putClass(name: String, version: ApiVersion) {
computeClass(name, version)
}
fun putMember(className: String, identifier: String, version: ApiVersion) {
computeClass(className).second[identifier] = version
}
private fun computeClass(
name: String,
version: ApiVersion? = null
): Pair<ApiVersion?, ConcurrentHashMap<String, ApiVersion>> {
return classes.computeIfAbsent(name.flatten()) {
version to ConcurrentHashMap()
}
}
}
@@ -17,16 +17,12 @@
package com.itsaky.androidide.xml.internal.versions
import androidx.annotation.VisibleForTesting
import com.google.auto.service.AutoService
import com.itsaky.androidide.xml.versions.ApiVersion
import com.itsaky.androidide.xml.versions.ApiVersions
import com.itsaky.androidide.xml.versions.ApiVersionsRegistry
import com.itsaky.androidide.xml.versions.ClassInfo
import com.itsaky.androidide.xml.versions.FieldInfo
import com.itsaky.androidide.xml.versions.Info
import com.itsaky.androidide.xml.versions.MethodInfo
import org.slf4j.LoggerFactory
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserFactory
import java.io.File
import java.util.concurrent.ConcurrentHashMap
@@ -36,7 +32,8 @@ import java.util.concurrent.ConcurrentHashMap
* @author Akash Yadav
*/
@AutoService(ApiVersionsRegistry::class)
class DefaultApiVersionsRegistry : ApiVersionsRegistry {
@VisibleForTesting
class DefaultApiVersionsRegistry : ApiVersionsParser(), ApiVersionsRegistry {
private val versions = ConcurrentHashMap<String, ApiVersions>()
@@ -46,6 +43,10 @@ class DefaultApiVersionsRegistry : ApiVersionsRegistry {
override var isLoggingEnabled: Boolean = true
private var _currentApiVersions: DefaultApiVersions? = null
private val currentApiVersions: DefaultApiVersions
get() = checkNotNull(_currentApiVersions)
override fun forPlatformDir(platform: File): ApiVersions? {
var version = versions[platform.path]
if (version != null) {
@@ -67,189 +68,51 @@ class DefaultApiVersionsRegistry : ApiVersionsRegistry {
log.info("Creating API versions table for platform dir: $platform")
}
return versionsFile.bufferedReader().use {
val parser =
XmlPullParserFactory.newInstance().run {
isNamespaceAware = false
return@run newPullParser().run {
setInput(it)
this
}
}
readApiVersions(parser)
}
}
private fun readApiVersions(parser: XmlPullParser): ApiVersions {
val versions = DefaultApiVersions()
var event = parser.eventType
var apiEncountered = false
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
val tag = parser.name
if (tag == "api") {
apiEncountered = true
event = parser.next()
continue
}
if (!apiEncountered) {
throw IllegalStateException("<api> tag not found")
}
val info = readTag(parser)
if (info != null && info is ClassInfo) {
versions.putClass(info.name, info)
}
return versionsFile.inputStream().buffered().use { inputStream ->
check(_currentApiVersions == null)
_currentApiVersions = DefaultApiVersions()
parse(inputStream)
currentApiVersions.also {
_currentApiVersions = null
}
event = parser.next()
}
return versions
}
private fun readTag(parser: XmlPullParser): Info? {
return when (parser.name) {
"class" -> readClassInfo(parser)
"method" -> readMethodInfo(parser)
"field" -> readFieldInfo(parser)
else -> null
}
}
private fun readMethodInfo(parser: XmlPullParser): Info {
val name = parser.readName()
return DefaultMethodInfo(
name = name,
since = parser.readSince(),
removed = parser.readRemoved(),
deprecated = parser.readDeprecated(),
simpleName = name.substringBefore('(')
)
override fun isDuplicateClass(name: String): Boolean {
return currentApiVersions.containsClass(name)
}
private fun readFieldInfo(parser: XmlPullParser): Info {
return DefaultFieldInfo(
name = parser.readName(),
since = parser.readSince(),
removed = parser.readRemoved(),
deprecated = parser.readDeprecated()
)
override fun isDuplicateMember(className: String, memberName: String): Boolean {
return currentApiVersions.containsClassMember(className, memberName)
}
private fun readClassInfo(parser: XmlPullParser): ClassInfo {
return DefaultClassInfo(
name = parser.readName(),
since = parser.readSince(),
removed = parser.readRemoved(),
deprecated = parser.readDeprecated()
)
.apply {
val depth = parser.depth
var event = parser.next()
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.END_TAG && parser.depth == depth) {
break
}
if (event != XmlPullParser.START_TAG) {
event = parser.next()
continue
}
val info = readTag(parser)
if (info == null) {
event = parser.next()
continue
}
when (info) {
is FieldInfo -> this.fields[info.name] = info
is MethodInfo -> {
var methods = this.methods[info.simpleName]
if (methods == null) {
methods = mutableListOf()
}
methods.add(info)
this.methods[info.simpleName] = methods
}
}
event = parser.next()
}
}
}
private fun XmlPullParser.readName(): String {
return readString("name")
}
private fun XmlPullParser.readSince(): Int {
return readInt("since")
}
private fun XmlPullParser.readRemoved(): Int {
return readInt("removed")
}
private fun XmlPullParser.readDeprecated(): Int {
return readInt("deprecated")
}
private fun XmlPullParser.readInt(name: String, default: Int = -1): Int {
return read(this, name) {
if (it.isNullOrBlank()) {
return@read default
}
return@read it.toInt()
override fun consumeClassVersionInfo(name: String, apiVersion: ApiVersion) {
if (apiVersion.isSinceInception()) {
return
}
currentApiVersions.putClass(name, apiVersion)
}
private fun XmlPullParser.readString(name: String, default: String = ""): String {
return read(this, name) {
if (it.isNullOrBlank()) {
return@read default
}
return@read it
override fun consumeMemberVersionInfo(
className: String,
member: String,
memberType: String,
apiVersion: ApiVersion
) {
if (apiVersion.isSinceInception()) {
return
}
}
private fun <T> read(parser: XmlPullParser, name: String, convert: (String?) -> T): T {
val index = parser.attrIndex(name)
if (index != -1) {
return convert(parser.value(index))
var identifier = member
if (memberType == TAG_METHOD) {
// strip return type to save some memory
identifier = member.substring(0, member.lastIndexOf(')') + 1)
}
return convert("")
currentApiVersions.putMember(className, identifier, apiVersion)
}
override fun clear() {
versions.clear()
}
/**
* Find the index of the attribute with the given name.
*
* @param name The name of the attribute to look for.
* @return The index of the attribute or `-1`.
*/
private fun XmlPullParser.attrIndex(name: String): Int {
for (i in 0 until this.attributeCount) {
if (this.getAttributeName(i) == name) {
return i
}
}
return -1
}
/**
* Get the value of the attribute at the given index.
*
* @param index The index of the attribute.
* @return The value of the attribute.
*/
private fun XmlPullParser.value(index: Int): String? {
return getAttributeValue(index)
}
}
@@ -1,53 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.internal.versions
import com.itsaky.androidide.xml.versions.ClassInfo
import com.itsaky.androidide.xml.versions.FieldInfo
import com.itsaky.androidide.xml.versions.MethodInfo
import org.eclipse.jdt.core.Signature
import java.util.concurrent.ConcurrentHashMap
/**
* Default implementation of [ClassInfo]
*
* @author Akash Yadav
*/
internal class DefaultClassInfo(name: String, since: Int, removed: Int, deprecated: Int) :
DefaultInfo(name, since, removed, deprecated), ClassInfo {
internal val fields = ConcurrentHashMap<String, FieldInfo>()
internal val methods = ConcurrentHashMap<String, MutableList<MethodInfo>>()
override fun getField(name: String): FieldInfo? {
return fields[name]
}
override fun getMethod(name: String, vararg params: String): MethodInfo? {
val methods = methods[name] ?: return null
val paramTypes = Array(size = params.size) { "" }
params.forEachIndexed { index, type ->
paramTypes[index] = Signature.createTypeSignature(type.replace('.', '/'), true)
}
return methods.find {
val methodParams = Signature.getParameterTypes(it.name)
methodParams != null && paramTypes.contentDeepEquals(methodParams)
}
}
}
@@ -1,24 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.internal.versions
import com.itsaky.androidide.xml.versions.FieldInfo
/** @author Akash Yadav */
class DefaultFieldInfo(name: String, since: Int, removed: Int, deprecated: Int) :
DefaultInfo(name, since, removed, deprecated), FieldInfo
@@ -1,53 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.internal.versions
import com.itsaky.androidide.xml.versions.Info
/** @author Akash Yadav */
open class DefaultInfo(
override val name: String,
override val since: Int,
override val removed: Int,
override val deprecated: Int
) : Info {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DefaultInfo) return false
if (name != other.name) return false
if (since != other.since) return false
if (removed != other.removed) return false
if (deprecated != other.deprecated) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + since
result = 31 * result + removed
result = 31 * result + deprecated
return result
}
override fun toString(): String {
return "DefaultInfo(name='$name', since=$since, removed=$removed, deprecated=$deprecated)"
}
}
@@ -1,50 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.internal.versions
import com.itsaky.androidide.xml.versions.MethodInfo
/** @author Akash Yadav */
internal class DefaultMethodInfo(
override val simpleName: String,
name: String,
since: Int,
removed: Int,
deprecated: Int
) : DefaultInfo(name, since, removed, deprecated), MethodInfo {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DefaultMethodInfo) return false
if (!super.equals(other)) return false
if (simpleName != other.simpleName) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + simpleName.hashCode()
return result
}
override fun toString(): String {
return "DefaultMethodInfo(simpleName='$simpleName')"
}
}
@@ -18,15 +18,29 @@
package com.itsaky.androidide.xml.versions
/**
* Info about a method.
* A model class to hold the API version information.
*
* **Dev Note**: This class must be immutable as the instances of this class are reused for
* multiple symbols.
*
* @property since The API in which the symbol was added.
* @property deprecatedIn The API in which the symbol was deprecated.
* @property removedIn The API in which the symbol was removed.
* @author Akash Yadav
*/
interface MethodInfo : Info {
data class ApiVersion(
val since: Int,
val deprecatedIn: Int = NONE,
val removedIn: Int = NONE
) {
/**
* In case of a method, the [Info.name] contains signature of the method This field contains the
* actual simple name
* Returns `true` if [since] is 1 and [deprecatedIn] and [removedIn] is [ApiVersion.NONE].
*/
val simpleName: String
fun isSinceInception(): Boolean =
since == 1 && deprecatedIn == NONE && removedIn == NONE
companion object {
internal const val NONE = 0
}
}
@@ -27,13 +27,23 @@ import com.itsaky.androidide.lookup.Lookup
interface ApiVersions {
companion object {
@JvmStatic val COMPLETION_LOOKUP_KEY = Lookup.Key<ApiVersions>()
@JvmStatic
val COMPLETION_LOOKUP_KEY = Lookup.Key<ApiVersions>()
}
/**
* Get the information about the class with the given name.
* Get the API version info about the class with the given name.
*
* @param name The fully qualified name of the class.
* @param name The fully qualified name of the class, in its internal form.
*/
fun getClass(name: String): ClassInfo?
fun classInfo(name: String): ApiVersion?
/**
* Get the API version info about the member of the given class.
*
* @param className The fully qualified name of the class, in its internal form.
* @param identifier The identifier of the member. This is the member name in case the member is a
* field. For method, this is the signature of the method without the return type.
*/
fun memberInfo(className: String, identifier: String): ApiVersion?
}
@@ -1,32 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.versions
/**
* Info about a class.
*
* @author Akash Yadav
*/
interface ClassInfo : Info {
/** Get info about the field with the given name. */
fun getField(name: String): FieldInfo?
/** Get the method with the given [name] and the [parameterTypes]. */
fun getMethod(name: String, vararg params: String) : MethodInfo?
}
@@ -1,25 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.versions
/**
* Info about a field.
*
* @author Akash Yadav
*/
interface FieldInfo : Info
@@ -15,10 +15,10 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing.core.platform
package com.itsaky.androidide.xml.versions
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.testing.common.utils.findAndroidHome
import com.itsaky.androidide.xml.findAndroidHome
import jaxp.xml.stream.XMLStreamException
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -0,0 +1,77 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.versions
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.xml.internal.versions.DefaultApiVersions
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
class ApiVersionsNameReprTest {
private fun someVersion() = ApiVersion(1)
@Test
fun `test class name flattening`() {
val versions = DefaultApiVersions()
versions.putClass("pck/outer", someVersion())
versions.putClass("pck/pck/outer", someVersion())
versions.putClass("pck/outer\$inner", someVersion())
versions.putClass("pck/pck/outer\$inner", someVersion())
assertThat(versions.classInfo("pck.outer")).isNotNull()
assertThat(versions.classInfo("pck.pck.outer")).isNotNull()
assertThat(versions.classInfo("pck/outer")).isNotNull()
assertThat(versions.classInfo("pck/pck/outer")).isNotNull()
assertThat(versions.classInfo("pck.outer\$inner")).isNotNull()
assertThat(versions.classInfo("pck.pck.outer\$inner")).isNotNull()
assertThat(versions.classInfo("pck/outer\$inner")).isNotNull()
assertThat(versions.classInfo("pck/pck/outer\$inner")).isNotNull()
assertThat(versions.classInfo("pck.outer.inner")).isNull()
assertThat(versions.classInfo("pck.pck.outer.inner")).isNull()
assertThat(versions.classInfo("pck/outer/inner")).isNull()
assertThat(versions.classInfo("pck/pck/outer/inner")).isNull()
}
@Test
fun `test method queries with method identifiers`() {
val versions = DefaultApiVersions()
versions.putMember("pck/outer", "some(I)", someVersion())
versions.putMember("pck/outer", "some(II)", someVersion())
versions.putMember("pck/outer", "some(Ljava/lang/String;)", someVersion())
versions.putMember("pck/outer", "some(ILjava/lang/String;)", someVersion())
versions.putMember("pck/outer", "some(Ljava/lang/String;I)", someVersion())
versions.putMember("pck/outer", "some(Lpck/outer\$inner;I)", someVersion())
assertThat(versions.memberInfo("pck/outer", "some(I)")).isNotNull()
assertThat(versions.memberInfo("pck/outer", "some(II)")).isNotNull()
assertThat(versions.memberInfo("pck/outer", "some(Ljava/lang/String;)")).isNotNull()
assertThat(versions.memberInfo("pck/outer", "some(ILjava/lang/String;)")).isNotNull()
assertThat(versions.memberInfo("pck/outer", "some(Ljava/lang/String;I)")).isNotNull()
assertThat(versions.memberInfo("pck/outer", "some(F)")).isNull()
assertThat(versions.memberInfo("pck/outer", "some(III)")).isNull()
assertThat(versions.memberInfo("pck/outer", "some(IILjava/lang/String;)")).isNull()
assertThat(versions.memberInfo("pck/outer", "some(FLjava/lang/String;)")).isNull()
assertThat(versions.memberInfo("pck/outer", "some(BLjava/lang/String;I)")).isNull()
}
}
@@ -1,87 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.xml.versions
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.xml.findAndroidJar
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
class ApiVersionsRegistryTest {
@Test
fun test() {
val androidJar = findAndroidJar()
val registry = ApiVersionsRegistry.getInstance()
val versions = registry.forPlatformDir(androidJar.parentFile!!)
assertThat(versions).isNotNull()
versions!!.getClass("android.Manifest\$permission").apply {
assertThat(this).isNotNull()
this!!.getField("WRITE_EXTERNAL_STORAGE").apply {
assertThat(this).isNotNull()
assertThat(this!!.since).isEqualTo(4)
assertThat(this.removed).isEqualTo(-1)
assertThat(this.deprecated).isEqualTo(-1)
}
this.getField("USE_BIOMETRIC").apply {
assertThat(this).isNotNull()
assertThat(this!!.since).isEqualTo(28)
assertThat(this.removed).isEqualTo(-1)
assertThat(this.deprecated).isEqualTo(-1)
}
this.getField("PERSISTENT_ACTIVITY").apply {
assertThat(this).isNotNull()
assertThat(this!!.since).isEqualTo(-1)
assertThat(this.removed).isEqualTo(-1)
assertThat(this.deprecated).isEqualTo(15)
}
this.getMethod("nonExistentMethod", "i.do.not.exist").apply { assertThat(this).isNull() }
this.getField("nonExistentField").apply { assertThat(this).isNull() }
}
versions.getClass("android.view.View").apply {
assertThat(this).isNotNull()
this!!.getField("SYSTEM_UI_FLAG_FULLSCREEN").apply {
assertThat(this).isNotNull()
assertThat(this!!.since).isEqualTo(16)
assertThat(this.removed).isEqualTo(-1)
assertThat(this.deprecated).isEqualTo(30)
}
this.getMethod(
"setOnSystemUiVisibilityChangeListener",
"android.view.View\$OnSystemUiVisibilityChangeListener"
)
.apply {
assertThat(this).isNotNull()
assertThat(this!!.since).isEqualTo(11)
assertThat(this.removed).isEqualTo(-1)
assertThat(this.deprecated).isEqualTo(30)
}
this.getMethod("nonExistentMethod", "i.do.not.exist").apply { assertThat(this).isNull() }
}
}
}
@@ -15,14 +15,13 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing.core.platform
import com.itsaky.androidide.indexing.platform.ApiVersionsParser
package com.itsaky.androidide.xml.versions
/**
* @author Akash Yadav
*/
class CollectingApiVersionsParser : com.itsaky.androidide.indexing.platform.ApiVersionsParser() {
class CollectingApiVersionsParser :
com.itsaky.androidide.xml.internal.versions.ApiVersionsParser() {
private val apiInfo = mutableMapOf<String, Pair<ApiVersion, MutableMap<String, ApiVersion>>>()