mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
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:
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
-122
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
@@ -44,6 +44,7 @@ dependencies {
|
||||
implementation(libs.androidx.tracing.ktx)
|
||||
|
||||
implementation(libs.common.utilcode)
|
||||
implementation(libs.composite.jdt)
|
||||
|
||||
implementation(libs.google.material)
|
||||
|
||||
|
||||
+50
-38
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-1
@@ -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()
|
||||
|
||||
|
||||
+42
-15
@@ -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
|
||||
@@ -85,7 +86,7 @@ abstract class IJavaCompletionProvider(
|
||||
) : BaseJavaServiceProvider(completingFile, compiler, settings) {
|
||||
protected lateinit var filePackage: String
|
||||
protected lateinit var fileImports: Set<String>
|
||||
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
protected val log: Logger = LoggerFactory.getLogger(IJavaCompletionProvider::class.java)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
+26
-17
@@ -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)
|
||||
}
|
||||
}
|
||||
+36
-7
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-173
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
-53
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
-24
@@ -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)"
|
||||
}
|
||||
}
|
||||
-50
@@ -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')"
|
||||
}
|
||||
}
|
||||
+20
-6
@@ -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
|
||||
+2
-2
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -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>>>()
|
||||
|
||||
Reference in New Issue
Block a user