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>>()