diff --git a/core/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt b/core/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt
index 5b9900c79a..be08d3c862 100644
--- a/core/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/actions/file/SaveFileAction.kt
@@ -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
diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
index 1721900929..0c3422ec5f 100755
--- a/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/activities/MainActivity.kt
@@ -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
diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
index a2138a4852..9223edc0ce 100644
--- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/BaseEditorActivity.kt
@@ -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
diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
index 4b21c87147..58dd1ff8fe 100644
--- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/EditorHandlerActivity.kt
@@ -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
diff --git a/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt b/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt
index fec8eea108..10c82e6083 100644
--- a/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/activities/editor/ProjectHandlerActivity.kt
@@ -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
diff --git a/core/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityLifecyclerObserver.kt b/core/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityLifecyclerObserver.kt
index 72b5db455e..61649a8296 100644
--- a/core/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityLifecyclerObserver.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/handlers/EditorActivityLifecyclerObserver.kt
@@ -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
diff --git a/core/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt b/core/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt
index 827be7e6df..b7e0fd18dc 100644
--- a/core/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt
+++ b/core/app/src/main/java/com/itsaky/androidide/services/builder/GradleBuildService.kt
@@ -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
diff --git a/core/indexing-core/src/main/java/com/itsaky/androidide/indexing/core/platform/ApiVersion.kt b/core/indexing-core/src/main/java/com/itsaky/androidide/indexing/core/platform/ApiVersion.kt
deleted file mode 100644
index 0ccbe07146..0000000000
--- a/core/indexing-core/src/main/java/com/itsaky/androidide/indexing/core/platform/ApiVersion.kt
+++ /dev/null
@@ -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 .
- */
-
-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()
- }
- }
- }
-}
diff --git a/core/lsp-models/src/main/java/com/itsaky/androidide/lsp/models/CompletionData.kt b/core/lsp-models/src/main/java/com/itsaky/androidide/lsp/models/CompletionData.kt
index a90a310ea3..6ca9ad86cc 100644
--- a/core/lsp-models/src/main/java/com/itsaky/androidide/lsp/models/CompletionData.kt
+++ b/core/lsp-models/src/main/java/com/itsaky/androidide/lsp/models/CompletionData.kt
@@ -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
+ }
}
/**
diff --git a/core/app/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt
similarity index 98%
rename from core/app/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt
rename to core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt
index 492a151c63..9f70e04045 100644
--- a/core/app/src/main/java/com/itsaky/androidide/projects/ProjectManagerImpl.kt
+++ b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectManagerImpl.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-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
diff --git a/core/app/src/main/java/com/itsaky/androidide/projects/ProjectTransformer.kt b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectTransformer.kt
similarity index 99%
rename from core/app/src/main/java/com/itsaky/androidide/projects/ProjectTransformer.kt
rename to core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectTransformer.kt
index 7c808fba6a..04dea1b969 100644
--- a/core/app/src/main/java/com/itsaky/androidide/projects/ProjectTransformer.kt
+++ b/core/projects/src/main/java/com/itsaky/androidide/projects/internal/ProjectTransformer.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-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
diff --git a/editor/impl/build.gradle.kts b/editor/impl/build.gradle.kts
index 095a5c73d1..30ec47e917 100644
--- a/editor/impl/build.gradle.kts
+++ b/editor/impl/build.gradle.kts
@@ -44,6 +44,7 @@ dependencies {
implementation(libs.androidx.tracing.ktx)
implementation(libs.common.utilcode)
+ implementation(libs.composite.jdt)
implementation(libs.google.material)
diff --git a/editor/impl/src/main/java/com/itsaky/androidide/editor/adapters/CompletionListAdapter.kt b/editor/impl/src/main/java/com/itsaky/androidide/editor/adapters/CompletionListAdapter.kt
index a43471c8cc..282b6940a3 100755
--- a/editor/impl/src/main/java/com/itsaky/androidide/editor/adapters/CompletionListAdapter.kt
+++ b/editor/impl/src/main/java/com/itsaky/androidide/editor/adapters/CompletionListAdapter.kt
@@ -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
- 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
+ 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 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 {
+ 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
}
}
diff --git a/java/lsp/build.gradle.kts b/java/lsp/build.gradle.kts
index d68b77d751..f10078ad9f 100644
--- a/java/lsp/build.gradle.kts
+++ b/java/lsp/build.gradle.kts
@@ -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)
diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ClassNamesCompletionProvider.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ClassNamesCompletionProvider.kt
index 25dd6b33bd..3d1a21a704 100644
--- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ClassNamesCompletionProvider.kt
+++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/ClassNamesCompletionProvider.kt
@@ -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()
+ val list = mutableListOf()
val packageName = Objects.toString(root.packageName, "")
val uniques: MutableSet = HashSet()
diff --git a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
index 3af1445718..35884fc7b7 100644
--- a/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
+++ b/java/lsp/src/main/java/com/itsaky/androidide/lsp/java/providers/completion/IJavaCompletionProvider.kt
@@ -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
-
+
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) {
diff --git a/xml/utils/build.gradle.kts b/xml/utils/build.gradle.kts
index 38588bfa7b..6701097d58 100644
--- a/xml/utils/build.gradle.kts
+++ b/xml/utils/build.gradle.kts
@@ -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)
diff --git a/core/indexing-core/src/main/java/com/itsaky/androidide/indexing/core/internal/platform/ApiVersionsParser.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/ApiVersionsParser.kt
similarity index 81%
rename from core/indexing-core/src/main/java/com/itsaky/androidide/indexing/core/internal/platform/ApiVersionsParser.kt
rename to xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/ApiVersionsParser.kt
index ecae1a6f50..c8743db048 100644
--- a/core/indexing-core/src/main/java/com/itsaky/androidide/indexing/core/internal/platform/ApiVersionsParser.kt
+++ b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/ApiVersionsParser.kt
@@ -15,10 +15,10 @@
* along with AndroidIDE. If not, see .
*/
-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 {
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)
}
}
\ No newline at end of file
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersions.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersions.kt
index b3047c694f..346c112215 100644
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersions.kt
+++ b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersions.kt
@@ -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()
+ val classes =
+ ConcurrentHashMap>>()
- 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
+ }
+
+ 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)
}
-
- internal fun putClass(name: String, info: ClassInfo) {
- classes[name] = info
+
+ fun putMember(className: String, identifier: String, version: ApiVersion) {
+ computeClass(className).second[identifier] = version
+ }
+
+ private fun computeClass(
+ name: String,
+ version: ApiVersion? = null
+ ): Pair> {
+ return classes.computeIfAbsent(name.flatten()) {
+ version to ConcurrentHashMap()
+ }
}
}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersionsRegistry.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersionsRegistry.kt
index acb6406242..3285d43dda 100644
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersionsRegistry.kt
+++ b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultApiVersionsRegistry.kt
@@ -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()
@@ -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(" 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('(')
- )
- }
-
- private fun readFieldInfo(parser: XmlPullParser): Info {
- return DefaultFieldInfo(
- name = parser.readName(),
- since = parser.readSince(),
- removed = parser.readRemoved(),
- deprecated = parser.readDeprecated()
- )
- }
-
- 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")
+ override fun isDuplicateClass(name: String): Boolean {
+ return currentApiVersions.containsClass(name)
}
- private fun XmlPullParser.readDeprecated(): Int {
- return readInt("deprecated")
+ override fun isDuplicateMember(className: String, memberName: String): Boolean {
+ return currentApiVersions.containsClassMember(className, memberName)
}
- 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 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)
- }
}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultClassInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultClassInfo.kt
deleted file mode 100644
index ea97fdeb00..0000000000
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultClassInfo.kt
+++ /dev/null
@@ -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 .
- */
-
-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()
- internal val methods = ConcurrentHashMap>()
-
- 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)
- }
- }
-}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultFieldInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultFieldInfo.kt
deleted file mode 100644
index f6531aed76..0000000000
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultFieldInfo.kt
+++ /dev/null
@@ -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 .
- */
-
-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
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultInfo.kt
deleted file mode 100644
index d38553d286..0000000000
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultInfo.kt
+++ /dev/null
@@ -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 .
- */
-
-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)"
- }
-}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultMethodInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultMethodInfo.kt
deleted file mode 100644
index 8127e35aec..0000000000
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/internal/versions/DefaultMethodInfo.kt
+++ /dev/null
@@ -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 .
- */
-
-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')"
- }
-}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/MethodInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersion.kt
similarity index 52%
rename from xml/utils/src/main/java/com/itsaky/androidide/xml/versions/MethodInfo.kt
rename to xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersion.kt
index 05778e5f75..1966ec3706 100644
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/MethodInfo.kt
+++ b/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersion.kt
@@ -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
+ }
}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt
index 87e71bf65e..a8054f8ba6 100644
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt
+++ b/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ApiVersions.kt
@@ -27,13 +27,23 @@ import com.itsaky.androidide.lookup.Lookup
interface ApiVersions {
companion object {
- @JvmStatic val COMPLETION_LOOKUP_KEY = Lookup.Key()
+ @JvmStatic
+ val COMPLETION_LOOKUP_KEY = Lookup.Key()
}
/**
- * 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?
}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ClassInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ClassInfo.kt
deleted file mode 100644
index c8c58fa3b4..0000000000
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/ClassInfo.kt
+++ /dev/null
@@ -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 .
- */
-
-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?
-}
diff --git a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/FieldInfo.kt b/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/FieldInfo.kt
deleted file mode 100644
index aaa71f55bd..0000000000
--- a/xml/utils/src/main/java/com/itsaky/androidide/xml/versions/FieldInfo.kt
+++ /dev/null
@@ -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 .
- */
-
-package com.itsaky.androidide.xml.versions
-
-/**
- * Info about a field.
- *
- * @author Akash Yadav
- */
-interface FieldInfo : Info
\ No newline at end of file
diff --git a/core/indexing-core/src/test/java/com/itsaky/androidide/indexing/core/platform/ApiVersionParserTest.kt b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionParserTest.kt
similarity index 98%
rename from core/indexing-core/src/test/java/com/itsaky/androidide/indexing/core/platform/ApiVersionParserTest.kt
rename to xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionParserTest.kt
index 8f2d8a5076..b5ced72ca8 100644
--- a/core/indexing-core/src/test/java/com/itsaky/androidide/indexing/core/platform/ApiVersionParserTest.kt
+++ b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionParserTest.kt
@@ -15,10 +15,10 @@
* along with AndroidIDE. If not, see .
*/
-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
diff --git a/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionsNameReprTest.kt b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionsNameReprTest.kt
new file mode 100644
index 0000000000..e795506d2e
--- /dev/null
+++ b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionsNameReprTest.kt
@@ -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 .
+ */
+
+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()
+ }
+}
diff --git a/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionsRegistryTest.kt b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionsRegistryTest.kt
deleted file mode 100644
index 0083f4b5fd..0000000000
--- a/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/ApiVersionsRegistryTest.kt
+++ /dev/null
@@ -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 .
- */
-
-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() }
- }
- }
-}
diff --git a/core/indexing-core/src/test/java/com/itsaky/androidide/indexing/core/platform/CollectingApiVersionsParser.kt b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/CollectingApiVersionsParser.kt
similarity index 91%
rename from core/indexing-core/src/test/java/com/itsaky/androidide/indexing/core/platform/CollectingApiVersionsParser.kt
rename to xml/utils/src/test/java/com/itsaky/androidide/xml/versions/CollectingApiVersionsParser.kt
index 54e0ec378b..4055b2bd33 100644
--- a/core/indexing-core/src/test/java/com/itsaky/androidide/indexing/core/platform/CollectingApiVersionsParser.kt
+++ b/xml/utils/src/test/java/com/itsaky/androidide/xml/versions/CollectingApiVersionsParser.kt
@@ -15,14 +15,13 @@
* along with AndroidIDE. If not, see .
*/
-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>>()