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.ActionData
|
||||||
import com.itsaky.androidide.actions.EditorRelatedAction
|
import com.itsaky.androidide.actions.EditorRelatedAction
|
||||||
import com.itsaky.androidide.models.SaveResult
|
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.resources.R
|
||||||
import com.itsaky.androidide.utils.flashError
|
import com.itsaky.androidide.utils.flashError
|
||||||
import com.itsaky.androidide.utils.flashSuccess
|
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.app.EdgeToEdgeIDEActivity
|
||||||
import com.itsaky.androidide.databinding.ActivityMainBinding
|
import com.itsaky.androidide.databinding.ActivityMainBinding
|
||||||
import com.itsaky.androidide.preferences.internal.GeneralPreferences
|
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.resources.R.string
|
||||||
import com.itsaky.androidide.templates.ITemplateProvider
|
import com.itsaky.androidide.templates.ITemplateProvider
|
||||||
import com.itsaky.androidide.utils.DialogUtils
|
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.models.SearchResult
|
||||||
import com.itsaky.androidide.preferences.internal.BuildPreferences
|
import com.itsaky.androidide.preferences.internal.BuildPreferences
|
||||||
import com.itsaky.androidide.projects.IProjectManager
|
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.tasks.cancelIfActive
|
||||||
import com.itsaky.androidide.ui.CodeEditorView
|
import com.itsaky.androidide.ui.CodeEditorView
|
||||||
import com.itsaky.androidide.ui.ContentTranslatingDrawerLayout
|
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.OpenedFilesCache
|
||||||
import com.itsaky.androidide.models.Range
|
import com.itsaky.androidide.models.Range
|
||||||
import com.itsaky.androidide.models.SaveResult
|
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.tasks.executeAsync
|
||||||
import com.itsaky.androidide.ui.CodeEditorView
|
import com.itsaky.androidide.ui.CodeEditorView
|
||||||
import com.itsaky.androidide.utils.DialogUtils.newYesNoDialog
|
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.IDELanguageClientImpl
|
||||||
import com.itsaky.androidide.lsp.java.utils.CancelChecker
|
import com.itsaky.androidide.lsp.java.utils.CancelChecker
|
||||||
import com.itsaky.androidide.preferences.internal.GeneralPreferences
|
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.api.GradleProject
|
||||||
import com.itsaky.androidide.projects.builder.BuildService
|
import com.itsaky.androidide.projects.builder.BuildService
|
||||||
import com.itsaky.androidide.services.builder.GradleBuildService
|
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.OnResumeEvent
|
||||||
import com.itsaky.androidide.eventbus.events.editor.OnStartEvent
|
import com.itsaky.androidide.eventbus.events.editor.OnStartEvent
|
||||||
import com.itsaky.androidide.eventbus.events.editor.OnStopEvent
|
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.projects.util.BootClasspathProvider
|
||||||
import com.itsaky.androidide.utils.EditorActivityActions
|
import com.itsaky.androidide.utils.EditorActivityActions
|
||||||
import com.itsaky.androidide.utils.EditorSidebarActions
|
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.managers.ToolsManager
|
||||||
import com.itsaky.androidide.preferences.internal.BuildPreferences
|
import com.itsaky.androidide.preferences.internal.BuildPreferences
|
||||||
import com.itsaky.androidide.preferences.internal.DevOpsPreferences
|
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.projects.builder.BuildService
|
||||||
import com.itsaky.androidide.resources.R
|
import com.itsaky.androidide.resources.R
|
||||||
import com.itsaky.androidide.services.ToolingServerNotStartedException
|
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.
|
* 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 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
|
* @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 simpleName The simple name of the class.
|
||||||
* @property nameWithoutTopLevel The name of this class without the fully qualified name of its top
|
* @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
|
* level class. For example, the value of this property for class name
|
||||||
@@ -33,20 +36,20 @@ interface ICompletionData
|
|||||||
*/
|
*/
|
||||||
data class ClassCompletionData
|
data class ClassCompletionData
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(val className: String, val isNested: Boolean = false, val topLevelClass: String = "") :
|
constructor(
|
||||||
ICompletionData {
|
val className: String,
|
||||||
val simpleName: String
|
val isCompleteData: Boolean = false,
|
||||||
get() {
|
val flatName: String = className,
|
||||||
return className.substringAfterLast(delimiter = '.')
|
val simpleName: String = className.substringAfterLast(delimiter = '.'),
|
||||||
}
|
val isNested: Boolean = false,
|
||||||
|
val topLevelClass: String = ""
|
||||||
|
) : ICompletionData {
|
||||||
|
|
||||||
val nameWithoutTopLevel: String
|
val nameWithoutTopLevel: String = if (isNested) {
|
||||||
get() {
|
className.substring(topLevelClass.length + 1)
|
||||||
if (!isNested) {
|
} else {
|
||||||
return className
|
className
|
||||||
}
|
}
|
||||||
return className.substring(topLevelClass.length + 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-1
@@ -15,7 +15,7 @@
|
|||||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
* 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 androidx.annotation.RestrictTo
|
||||||
import com.android.builder.model.v2.models.ProjectSyncIssues
|
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.file.FileRenameEvent
|
||||||
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent
|
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent
|
||||||
import com.itsaky.androidide.lookup.Lookup
|
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.AndroidModule
|
||||||
import com.itsaky.androidide.projects.api.ModuleProject
|
import com.itsaky.androidide.projects.api.ModuleProject
|
||||||
import com.itsaky.androidide.projects.api.Project
|
import com.itsaky.androidide.projects.api.Project
|
||||||
+1
-1
@@ -15,7 +15,7 @@
|
|||||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
* 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.AndroidModule
|
||||||
import com.itsaky.androidide.projects.api.GradleProject
|
import com.itsaky.androidide.projects.api.GradleProject
|
||||||
@@ -44,6 +44,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.tracing.ktx)
|
implementation(libs.androidx.tracing.ktx)
|
||||||
|
|
||||||
implementation(libs.common.utilcode)
|
implementation(libs.common.utilcode)
|
||||||
|
implementation(libs.composite.jdt)
|
||||||
|
|
||||||
implementation(libs.google.material)
|
implementation(libs.google.material)
|
||||||
|
|
||||||
|
|||||||
+50
-38
@@ -19,7 +19,6 @@ package com.itsaky.androidide.editor.adapters
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -28,6 +27,7 @@ import android.widget.TextView
|
|||||||
import com.itsaky.androidide.editor.R
|
import com.itsaky.androidide.editor.R
|
||||||
import com.itsaky.androidide.editor.databinding.LayoutCompletionItemBinding
|
import com.itsaky.androidide.editor.databinding.LayoutCompletionItemBinding
|
||||||
import com.itsaky.androidide.lookup.Lookup
|
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.ClassCompletionData
|
||||||
import com.itsaky.androidide.lsp.models.CompletionItemKind.CLASS
|
import com.itsaky.androidide.lsp.models.CompletionItemKind.CLASS
|
||||||
import com.itsaky.androidide.lsp.models.CompletionItemKind.CONSTRUCTOR
|
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.tasks.executeAsync
|
||||||
import com.itsaky.androidide.utils.customOrJBMono
|
import com.itsaky.androidide.utils.customOrJBMono
|
||||||
import com.itsaky.androidide.xml.versions.ApiVersions
|
import com.itsaky.androidide.xml.versions.ApiVersions
|
||||||
import com.itsaky.androidide.xml.versions.Info
|
|
||||||
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter
|
import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter
|
||||||
|
import org.eclipse.jdt.core.Signature
|
||||||
import com.itsaky.androidide.lsp.models.CompletionItem as LspCompletionItem
|
import com.itsaky.androidide.lsp.models.CompletionItem as LspCompletionItem
|
||||||
|
|
||||||
class CompletionListAdapter : EditorCompletionAdapter() {
|
class CompletionListAdapter : EditorCompletionAdapter() {
|
||||||
@@ -85,8 +85,10 @@ class CompletionListAdapter : EditorCompletionAdapter() {
|
|||||||
binding.completionLabel.text = label
|
binding.completionLabel.text = label
|
||||||
binding.completionType.text = type
|
binding.completionType.text = type
|
||||||
binding.completionDetail.text = desc
|
binding.completionDetail.text = desc
|
||||||
binding.completionIconText.setTypeface(customOrJBMono(EditorPreferences.useCustomFont),
|
binding.completionIconText.setTypeface(
|
||||||
Typeface.BOLD)
|
customOrJBMono(EditorPreferences.useCustomFont),
|
||||||
|
Typeface.BOLD
|
||||||
|
)
|
||||||
if (desc.isEmpty()) {
|
if (desc.isEmpty()) {
|
||||||
binding.completionDetail.visibility = View.GONE
|
binding.completionDetail.visibility = View.GONE
|
||||||
}
|
}
|
||||||
@@ -147,45 +149,40 @@ class CompletionListAdapter : EditorCompletionAdapter() {
|
|||||||
val data = item.data
|
val data = item.data
|
||||||
val versions =
|
val versions =
|
||||||
Lookup.getDefault().lookup(ApiVersions.COMPLETION_LOOKUP_KEY) ?: return@executeAsync null
|
Lookup.getDefault().lookup(ApiVersions.COMPLETION_LOOKUP_KEY) ?: return@executeAsync null
|
||||||
val className =
|
val info =
|
||||||
when (data) {
|
when (data) {
|
||||||
is ClassCompletionData -> data.className
|
is ClassCompletionData -> versions.classInfo(data.className)
|
||||||
is MemberCompletionData -> data.classInfo.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
|
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()
|
val sb = StringBuilder()
|
||||||
if (info!!.since > 1) {
|
if (info!!.since > 1) {
|
||||||
sb.append(textView.context.getString(msg_api_info_since, info.since))
|
sb.append(textView.context.getString(msg_api_info_since, info.since))
|
||||||
sb.append("\n")
|
sb.append("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.removed > 0) {
|
if (info.removedIn > 0) {
|
||||||
sb.append(textView.context.getString(msg_api_info_removed, info.removed))
|
sb.append(textView.context.getString(msg_api_info_removed, info.removedIn))
|
||||||
sb.append("\n")
|
sb.append("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.deprecated > 0) {
|
if (info.deprecatedIn > 0) {
|
||||||
sb.append(textView.context.getString(msg_api_info_deprecated, info.deprecated))
|
sb.append(textView.context.getString(msg_api_info_deprecated, info.deprecatedIn))
|
||||||
sb.append("\n")
|
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 {
|
private fun isValidForApiVersion(item: LspCompletionItem?): Boolean {
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
return false
|
return false
|
||||||
@@ -209,15 +221,15 @@ class CompletionListAdapter : EditorCompletionAdapter() {
|
|||||||
val data = item.data
|
val data = item.data
|
||||||
return if ( // These represent a class type
|
return if ( // These represent a class type
|
||||||
(type === CLASS ||
|
(type === CLASS ||
|
||||||
type === INTERFACE ||
|
type === INTERFACE ||
|
||||||
type === ENUM ||
|
type === ENUM ||
|
||||||
|
|
||||||
// These represent a method type
|
// These represent a method type
|
||||||
type === METHOD ||
|
type === METHOD ||
|
||||||
type === CONSTRUCTOR ||
|
type === CONSTRUCTOR ||
|
||||||
|
|
||||||
// A field type
|
// A field type
|
||||||
type === FIELD) && data != null
|
type === FIELD) && data != null
|
||||||
) {
|
) {
|
||||||
val className =
|
val className =
|
||||||
when (data) {
|
when (data) {
|
||||||
@@ -225,7 +237,7 @@ class CompletionListAdapter : EditorCompletionAdapter() {
|
|||||||
is MemberCompletionData -> data.classInfo.className
|
is MemberCompletionData -> data.classInfo.className
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
!TextUtils.isEmpty(className)
|
!className.isNullOrBlank()
|
||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ dependencies {
|
|||||||
kapt(projects.annotation.processors)
|
kapt(projects.annotation.processors)
|
||||||
kapt(libs.google.auto.service)
|
kapt(libs.google.auto.service)
|
||||||
|
|
||||||
|
api(projects.core.indexingApi)
|
||||||
|
|
||||||
implementation(libs.androidide.ts)
|
implementation(libs.androidide.ts)
|
||||||
implementation(libs.androidide.ts.java)
|
implementation(libs.androidide.ts.java)
|
||||||
implementation(libs.androidx.annotation)
|
implementation(libs.androidx.annotation)
|
||||||
@@ -63,7 +65,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation(projects.core.actions)
|
implementation(projects.core.actions)
|
||||||
implementation(projects.core.common)
|
implementation(projects.core.common)
|
||||||
implementation(projects.core.indexingApi)
|
|
||||||
implementation(projects.core.lspApi)
|
implementation(projects.core.lspApi)
|
||||||
implementation(projects.core.resources)
|
implementation(projects.core.resources)
|
||||||
implementation(projects.editor.api)
|
implementation(projects.editor.api)
|
||||||
@@ -73,6 +74,7 @@ dependencies {
|
|||||||
implementation(libs.composite.javapoet)
|
implementation(libs.composite.javapoet)
|
||||||
implementation(libs.composite.jaxp)
|
implementation(libs.composite.jaxp)
|
||||||
implementation(libs.composite.jdkJdeps)
|
implementation(libs.composite.jdkJdeps)
|
||||||
|
implementation(libs.composite.jdt)
|
||||||
implementation(libs.composite.googleJavaFormat)
|
implementation(libs.composite.googleJavaFormat)
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
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.CompileTask
|
||||||
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
|
||||||
import com.itsaky.androidide.lsp.java.providers.CompletionProvider
|
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.CompletionResult
|
||||||
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
|
import com.itsaky.androidide.lsp.models.MatchLevel.NO_MATCH
|
||||||
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
|
import com.itsaky.androidide.progress.ProgressManager.Companion.abortIfCancelled
|
||||||
@@ -50,7 +51,7 @@ class ClassNamesCompletionProvider(
|
|||||||
partial: String,
|
partial: String,
|
||||||
endsWithParen: Boolean,
|
endsWithParen: Boolean,
|
||||||
): CompletionResult {
|
): CompletionResult {
|
||||||
val list = mutableListOf<com.itsaky.androidide.lsp.models.CompletionItem>()
|
val list = mutableListOf<CompletionItem>()
|
||||||
val packageName = Objects.toString(root.packageName, "")
|
val packageName = Objects.toString(root.packageName, "")
|
||||||
val uniques: MutableSet<String> = HashSet()
|
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.ExecutableElement
|
||||||
import jdkx.lang.model.element.TypeElement
|
import jdkx.lang.model.element.TypeElement
|
||||||
import jdkx.lang.model.element.VariableElement
|
import jdkx.lang.model.element.VariableElement
|
||||||
|
import jdkx.lang.model.type.TypeKind
|
||||||
import openjdk.source.tree.Tree
|
import openjdk.source.tree.Tree
|
||||||
import openjdk.source.util.TreePath
|
import openjdk.source.util.TreePath
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
@@ -85,7 +86,7 @@ abstract class IJavaCompletionProvider(
|
|||||||
) : BaseJavaServiceProvider(completingFile, compiler, settings) {
|
) : BaseJavaServiceProvider(completingFile, compiler, settings) {
|
||||||
protected lateinit var filePackage: String
|
protected lateinit var filePackage: String
|
||||||
protected lateinit var fileImports: Set<String>
|
protected lateinit var fileImports: Set<String>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
protected val log: Logger = LoggerFactory.getLogger(IJavaCompletionProvider::class.java)
|
protected val log: Logger = LoggerFactory.getLogger(IJavaCompletionProvider::class.java)
|
||||||
@@ -261,6 +262,8 @@ abstract class IJavaCompletionProvider(
|
|||||||
item.detail = packageName(className).toString()
|
item.detail = packageName(className).toString()
|
||||||
item.ideSortText = item.ideLabel
|
item.ideSortText = item.ideLabel
|
||||||
item.matchLevel = matchLevel
|
item.matchLevel = matchLevel
|
||||||
|
|
||||||
|
// TODO(itsaky): This will result in incorrect flatName if 'className' is a nested or local class
|
||||||
item.data = ClassCompletionData(className)
|
item.data = ClassCompletionData(className)
|
||||||
|
|
||||||
// If file is not provided, we are probably completing an import path
|
// If file is not provided, we are probably completing an import path
|
||||||
@@ -331,13 +334,19 @@ abstract class IJavaCompletionProvider(
|
|||||||
ENUM -> CompletionItemKind.ENUM
|
ENUM -> CompletionItemKind.ENUM
|
||||||
ENUM_CONSTANT -> ENUM_MEMBER
|
ENUM_CONSTANT -> ENUM_MEMBER
|
||||||
EXCEPTION_PARAMETER,
|
EXCEPTION_PARAMETER,
|
||||||
PARAMETER, -> PROPERTY
|
PARAMETER,
|
||||||
|
-> PROPERTY
|
||||||
|
|
||||||
FIELD -> CompletionItemKind.FIELD
|
FIELD -> CompletionItemKind.FIELD
|
||||||
STATIC_INIT,
|
STATIC_INIT,
|
||||||
INSTANCE_INIT, -> FUNCTION
|
INSTANCE_INIT,
|
||||||
|
-> FUNCTION
|
||||||
|
|
||||||
INTERFACE -> CompletionItemKind.INTERFACE
|
INTERFACE -> CompletionItemKind.INTERFACE
|
||||||
LOCAL_VARIABLE,
|
LOCAL_VARIABLE,
|
||||||
RESOURCE_VARIABLE, -> VARIABLE
|
RESOURCE_VARIABLE,
|
||||||
|
-> VARIABLE
|
||||||
|
|
||||||
METHOD -> CompletionItemKind.METHOD
|
METHOD -> CompletionItemKind.METHOD
|
||||||
PACKAGE -> MODULE
|
PACKAGE -> MODULE
|
||||||
TYPE_PARAMETER -> CompletionItemKind.TYPE_PARAMETER
|
TYPE_PARAMETER -> CompletionItemKind.TYPE_PARAMETER
|
||||||
@@ -350,8 +359,8 @@ abstract class IJavaCompletionProvider(
|
|||||||
abortIfCancelled()
|
abortIfCancelled()
|
||||||
abortCompletionIfCancelled()
|
abortCompletionIfCancelled()
|
||||||
return when {
|
return when {
|
||||||
element is TypeElement -> getClassCompletionData(element)
|
element is TypeElement -> getClassCompletionData(task, element)
|
||||||
element.kind == FIELD -> getFieldCompletionData(element)
|
element.kind == FIELD -> getFieldCompletionData(task, element)
|
||||||
element is ExecutableElement -> getMethodCompletionData(task, element, overloads)
|
element is ExecutableElement -> getMethodCompletionData(task, element, overloads)
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
@@ -363,6 +372,7 @@ abstract class IJavaCompletionProvider(
|
|||||||
overloads: Int
|
overloads: Int
|
||||||
): MethodCompletionData {
|
): MethodCompletionData {
|
||||||
val types = task.task.types
|
val types = task.task.types
|
||||||
|
val elements = task.task.elements
|
||||||
val type = element.enclosingElement as TypeElement
|
val type = element.enclosingElement as TypeElement
|
||||||
val parameterTypes = Array(element.parameters.size) { "" }
|
val parameterTypes = Array(element.parameters.size) { "" }
|
||||||
val erasedParameterTypes = Array(parameterTypes.size) { "" }
|
val erasedParameterTypes = Array(parameterTypes.size) { "" }
|
||||||
@@ -371,30 +381,47 @@ abstract class IJavaCompletionProvider(
|
|||||||
for (i in element.parameters.indices) {
|
for (i in element.parameters.indices) {
|
||||||
val p = element.parameters[i].asType()
|
val p = element.parameters[i].asType()
|
||||||
parameterTypes[i] = p.toString()
|
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(
|
return MethodCompletionData(
|
||||||
element.simpleName.toString(),
|
element.simpleName.toString(),
|
||||||
getClassCompletionData(type),
|
getClassCompletionData(task, type),
|
||||||
parameterTypes.toList(),
|
parameterTypes.toList(),
|
||||||
erasedParameterTypes.toList(),
|
erasedParameterTypes.toList(),
|
||||||
plusOverloads
|
plusOverloads
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getFieldCompletionData(element: Element): FieldCompletionData {
|
protected open fun getFieldCompletionData(
|
||||||
|
task: CompileTask,
|
||||||
|
element: Element
|
||||||
|
): FieldCompletionData {
|
||||||
val field = element as VariableElement
|
val field = element as VariableElement
|
||||||
val type = field.enclosingElement as TypeElement
|
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) =
|
protected open fun getClassCompletionData(
|
||||||
ClassCompletionData(
|
task: CompileTask,
|
||||||
element.qualifiedName.toString(),
|
element: TypeElement
|
||||||
element.enclosingElement.kind != PACKAGE,
|
): ClassCompletionData {
|
||||||
element.findTopLevelElement().qualifiedName.toString()
|
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 {
|
protected open fun TypeElement.findTopLevelElement(): TypeElement {
|
||||||
if (enclosingElement.kind == PACKAGE) {
|
if (enclosingElement.kind == PACKAGE) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.common.kotlin)
|
implementation(libs.common.kotlin)
|
||||||
implementation(libs.composite.jdt)
|
implementation(libs.composite.jdt)
|
||||||
|
implementation(libs.composite.jaxp)
|
||||||
implementation(libs.google.auto.service.annotations)
|
implementation(libs.google.auto.service.annotations)
|
||||||
|
|
||||||
implementation(projects.core.common)
|
implementation(projects.core.common)
|
||||||
|
|||||||
+26
-17
@@ -15,10 +15,10 @@
|
|||||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
* 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 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.namespace.QName
|
||||||
import jaxp.xml.stream.XMLInputFactory
|
import jaxp.xml.stream.XMLInputFactory
|
||||||
import jaxp.xml.stream.events.Attribute
|
import jaxp.xml.stream.events.Attribute
|
||||||
@@ -37,7 +37,7 @@ open class ApiVersionsParser {
|
|||||||
// A cache to store the ApiVersion instances with different values
|
// A cache to store the ApiVersion instances with different values
|
||||||
// For example, if two classes have the same value for `since`,
|
// For example, if two classes have the same value for `since`,
|
||||||
// 'removed' and 'deprecated', then the same ApiVersion instance
|
// '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 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'
|
// 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'.
|
// and the last 8 bits represent 'removed'.
|
||||||
@@ -47,6 +47,14 @@ open class ApiVersionsParser {
|
|||||||
private var currentClass: String? = null
|
private var currentClass: String? = null
|
||||||
|
|
||||||
companion object {
|
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)
|
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.
|
* 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) {
|
private fun consumeStartElement(event: StartElement) {
|
||||||
when (event.name.localPart) {
|
when (event.name.localPart) {
|
||||||
"api" -> apiVersion = event.getAttributeByName(QName("version")).value.toInt()
|
TAG_API -> apiVersion = event.getAttributeByName(QName("version")).value.toInt()
|
||||||
"class" -> consumeClass(event)
|
TAG_CLASS -> consumeClass(event)
|
||||||
"field" -> consumeMember(event, "field")
|
TAG_FIELD -> consumeMember(event, TAG_FIELD)
|
||||||
"method" -> consumeMember(event, "method")
|
TAG_METHOD -> consumeMember(event, TAG_METHOD)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,25 +170,25 @@ open class ApiVersionsParser {
|
|||||||
|
|
||||||
private fun consumeEndElement(element: EndElement) {
|
private fun consumeEndElement(element: EndElement) {
|
||||||
when (element.name.localPart) {
|
when (element.name.localPart) {
|
||||||
"api" -> apiVersion = null
|
TAG_API -> apiVersion = null
|
||||||
"class" -> currentClass = null
|
TAG_CLASS -> currentClass = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun StartElement.parseAttrs(): Pair<String, Int> {
|
private fun StartElement.parseAttrs(): Pair<String, Int> {
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
var since = 1
|
var since = 1
|
||||||
var deprecated = 0
|
var deprecated = ApiVersion.NONE
|
||||||
var removed = 0
|
var removed = ApiVersion.NONE
|
||||||
|
|
||||||
attributes.forEach { attribute ->
|
attributes.forEach { attribute ->
|
||||||
attribute as Attribute
|
attribute as Attribute
|
||||||
|
|
||||||
when (attribute.name.localPart) {
|
when (attribute.name.localPart) {
|
||||||
"name" -> name = attribute.value
|
ATTR_NAME -> name = attribute.value
|
||||||
"since" -> since = attribute.value.toInt()
|
ATTR_SIN -> since = attribute.value.toInt()
|
||||||
"deprecated" -> deprecated = attribute.value.toInt()
|
ATTR_DEPR -> deprecated = attribute.value.toInt()
|
||||||
"removed" -> removed = attribute.value.toInt()
|
ATTR_REM -> removed = attribute.value.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +209,6 @@ open class ApiVersionsParser {
|
|||||||
val since = (versions shr 16) and 0x000000FF
|
val since = (versions shr 16) and 0x000000FF
|
||||||
val deprecated = (versions shr 8) and 0x000000FF
|
val deprecated = (versions shr 8) and 0x000000FF
|
||||||
val removed = versions 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
|
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.ApiVersions
|
||||||
import com.itsaky.androidide.xml.versions.ClassInfo
|
import org.eclipse.jdt.core.Signature
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
/** @author Akash Yadav */
|
/** @author Akash Yadav */
|
||||||
internal class DefaultApiVersions : ApiVersions {
|
internal class DefaultApiVersions : ApiVersions {
|
||||||
|
|
||||||
val classes = ConcurrentHashMap<String, ClassInfo>()
|
val classes =
|
||||||
|
ConcurrentHashMap<String, Pair<ApiVersion?, ConcurrentHashMap<String, ApiVersion>>>()
|
||||||
|
|
||||||
override fun getClass(name: String): ClassInfo? {
|
private fun String.flatten() = replace('.', '/')
|
||||||
return classes[name.replace('.', '/')]
|
|
||||||
|
override fun classInfo(name: String): ApiVersion? {
|
||||||
|
return classes[name.flatten()]?.first
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun putClass(name: String, info: ClassInfo) {
|
override fun memberInfo(className: String, identifier: String): ApiVersion? {
|
||||||
classes[name] = info
|
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
|
package com.itsaky.androidide.xml.internal.versions
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.google.auto.service.AutoService
|
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.ApiVersions
|
||||||
import com.itsaky.androidide.xml.versions.ApiVersionsRegistry
|
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.slf4j.LoggerFactory
|
||||||
import org.xmlpull.v1.XmlPullParser
|
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@@ -36,7 +32,8 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
* @author Akash Yadav
|
* @author Akash Yadav
|
||||||
*/
|
*/
|
||||||
@AutoService(ApiVersionsRegistry::class)
|
@AutoService(ApiVersionsRegistry::class)
|
||||||
class DefaultApiVersionsRegistry : ApiVersionsRegistry {
|
@VisibleForTesting
|
||||||
|
class DefaultApiVersionsRegistry : ApiVersionsParser(), ApiVersionsRegistry {
|
||||||
|
|
||||||
private val versions = ConcurrentHashMap<String, ApiVersions>()
|
private val versions = ConcurrentHashMap<String, ApiVersions>()
|
||||||
|
|
||||||
@@ -46,6 +43,10 @@ class DefaultApiVersionsRegistry : ApiVersionsRegistry {
|
|||||||
|
|
||||||
override var isLoggingEnabled: Boolean = true
|
override var isLoggingEnabled: Boolean = true
|
||||||
|
|
||||||
|
private var _currentApiVersions: DefaultApiVersions? = null
|
||||||
|
private val currentApiVersions: DefaultApiVersions
|
||||||
|
get() = checkNotNull(_currentApiVersions)
|
||||||
|
|
||||||
override fun forPlatformDir(platform: File): ApiVersions? {
|
override fun forPlatformDir(platform: File): ApiVersions? {
|
||||||
var version = versions[platform.path]
|
var version = versions[platform.path]
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
@@ -67,189 +68,51 @@ class DefaultApiVersionsRegistry : ApiVersionsRegistry {
|
|||||||
log.info("Creating API versions table for platform dir: $platform")
|
log.info("Creating API versions table for platform dir: $platform")
|
||||||
}
|
}
|
||||||
|
|
||||||
return versionsFile.bufferedReader().use {
|
return versionsFile.inputStream().buffered().use { inputStream ->
|
||||||
val parser =
|
check(_currentApiVersions == null)
|
||||||
XmlPullParserFactory.newInstance().run {
|
_currentApiVersions = DefaultApiVersions()
|
||||||
isNamespaceAware = false
|
parse(inputStream)
|
||||||
return@run newPullParser().run {
|
currentApiVersions.also {
|
||||||
setInput(it)
|
_currentApiVersions = null
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 {
|
override fun isDuplicateClass(name: String): Boolean {
|
||||||
val name = parser.readName()
|
return currentApiVersions.containsClass(name)
|
||||||
return DefaultMethodInfo(
|
|
||||||
name = name,
|
|
||||||
since = parser.readSince(),
|
|
||||||
removed = parser.readRemoved(),
|
|
||||||
deprecated = parser.readDeprecated(),
|
|
||||||
simpleName = name.substringBefore('(')
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readFieldInfo(parser: XmlPullParser): Info {
|
override fun isDuplicateMember(className: String, memberName: String): Boolean {
|
||||||
return DefaultFieldInfo(
|
return currentApiVersions.containsClassMember(className, memberName)
|
||||||
name = parser.readName(),
|
|
||||||
since = parser.readSince(),
|
|
||||||
removed = parser.readRemoved(),
|
|
||||||
deprecated = parser.readDeprecated()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readClassInfo(parser: XmlPullParser): ClassInfo {
|
override fun consumeClassVersionInfo(name: String, apiVersion: ApiVersion) {
|
||||||
return DefaultClassInfo(
|
if (apiVersion.isSinceInception()) {
|
||||||
name = parser.readName(),
|
return
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
currentApiVersions.putClass(name, apiVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun XmlPullParser.readString(name: String, default: String = ""): String {
|
override fun consumeMemberVersionInfo(
|
||||||
return read(this, name) {
|
className: String,
|
||||||
if (it.isNullOrBlank()) {
|
member: String,
|
||||||
return@read default
|
memberType: String,
|
||||||
}
|
apiVersion: ApiVersion
|
||||||
|
) {
|
||||||
return@read it
|
if (apiVersion.isSinceInception()) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> read(parser: XmlPullParser, name: String, convert: (String?) -> T): T {
|
var identifier = member
|
||||||
val index = parser.attrIndex(name)
|
if (memberType == TAG_METHOD) {
|
||||||
if (index != -1) {
|
// strip return type to save some memory
|
||||||
return convert(parser.value(index))
|
identifier = member.substring(0, member.lastIndexOf(')') + 1)
|
||||||
}
|
}
|
||||||
return convert("")
|
|
||||||
|
currentApiVersions.putMember(className, identifier, apiVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
versions.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
|
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
|
* @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
|
* Returns `true` if [since] is 1 and [deprecatedIn] and [removedIn] is [ApiVersion.NONE].
|
||||||
* actual simple name
|
|
||||||
*/
|
*/
|
||||||
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 {
|
interface ApiVersions {
|
||||||
|
|
||||||
companion object {
|
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/>.
|
* 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.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 jaxp.xml.stream.XMLStreamException
|
||||||
import org.junit.Assert.assertThrows
|
import org.junit.Assert.assertThrows
|
||||||
import org.junit.Before
|
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/>.
|
* 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.itsaky.androidide.indexing.platform.ApiVersionsParser
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Akash Yadav
|
* @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>>>()
|
private val apiInfo = mutableMapOf<String, Pair<ApiVersion, MutableMap<String, ApiVersion>>>()
|
||||||
|
|
||||||
Reference in New Issue
Block a user