fix: remove Realm DB

This commit is contained in:
Akash Yadav
2024-07-20 20:16:47 +05:30
parent 1d68499189
commit f837b9369e
51 changed files with 15 additions and 3383 deletions
+2
View File
@@ -116,6 +116,7 @@
<module name="AndroidIDE.colorschemes" target="11" />
<module name="AndroidIDE.colorschemes.main" target="11" />
<module name="AndroidIDE.common" target="17" />
<module name="AndroidIDE.core.database" target="17" />
<module name="AndroidIDE.core.di" target="17" />
<module name="AndroidIDE.core.indexing" target="17" />
<module name="AndroidIDE.editor-api" target="17" />
@@ -272,6 +273,7 @@
<module name="AndroidIDE.uidesigner" target="17" />
<module name="AndroidIDE.xml-inflater" target="17" />
<module name="AndroidIDE.xml.resources-api.test" target="11" />
<module name="AndroidIDE.xml.resources-indexing" target="17" />
<module name="ATS.android-tree-sitter" target="17" />
<module name="ATS.app" target="17" />
<module name="ATS.tree-sitter-aidl" target="17" />
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.22" />
<option name="version" value="1.9.24" />
</component>
</project>
-1
View File
@@ -42,7 +42,6 @@ buildscript {
dependencies {
classpath(libs.kotlin.gradle.plugin)
classpath(libs.nav.safe.args.gradle.plugin)
classpath(libs.realm.gradle.plugin)
}
}
-1
View File
@@ -28,7 +28,6 @@ plugins {
id("kotlin-android")
id("kotlin-kapt")
id("kotlin-parcelize")
id("realm-android")
id("androidx.navigation.safeargs.kotlin")
id("com.itsaky.androidide.desugaring")
}
@@ -58,7 +58,6 @@ import com.itsaky.androidide.utils.flashError
import com.termux.app.TermuxApplication
import com.termux.shared.reflection.ReflectionUtils
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
import io.realm.Realm
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -110,8 +109,6 @@ class IDEApplication : TermuxApplication() {
EventBus.getDefault().register(this)
Realm.init(this)
AppCompatDelegate.setDefaultNightMode(GeneralPreferences.uiMode)
if (IThemeManager.getInstance().getCurrentTheme() == IDETheme.MATERIAL_YOU) {
-1
View File
@@ -1 +0,0 @@
/build
-41
View File
@@ -1,41 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
import com.itsaky.androidide.build.config.BuildConfig
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("realm-android")
}
android {
namespace = "${BuildConfig.packageName}.database"
}
dependencies {
kapt(libs.google.auto.service)
implementation(libs.google.auto.service.annotations)
implementation(projects.core.common)
implementation(projects.logging.logger)
implementation(projects.utilities.shared)
}
View File
-21
View File
@@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -1,91 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.db
import com.itsaky.androidide.utils.ServiceLoader
import io.realm.Realm
import io.realm.RealmConfiguration
/**
* An [IRealmProvider] provides the instance of [Realm].
*
* @author Akash Yadav
*/
interface IRealmProvider {
/**
* Get the [Realm] instance.
*
* @param path The path of the database.
*/
fun get(path: String): Realm {
return get(path, null)
}
/**
* Get the [Realm] instance.
*
* @param path The path of the database.
* @param config An optional function to configure the [RealmConfiguration] builder.
*/
fun get(path: String, config: (RealmConfiguration.Builder.() -> Unit)?): Realm
companion object {
private lateinit var sInstance: IRealmProvider
/**
* The separator used to separate segments in an index path.
*/
const val PATH_SEPARATOR = '/'
/**
* Create the path for the given segments.
*
* @param first The first (root) path segment.
* @param rest The rest of the path segments.
* @return The created path string.
*/
@JvmStatic
fun createPath(first: String, vararg rest: String): String {
val sb = StringBuilder()
sb.append(PATH_SEPARATOR)
sb.append(first)
for (segment in rest) {
sb.append(PATH_SEPARATOR)
sb.append(segment)
}
return sb.toString()
}
/**
* Get the [IRealmProvider] instance, finding the implementation if necessary.
*/
@JvmStatic
fun instance(): IRealmProvider {
if (!Companion::sInstance.isInitialized) {
val clazz = IRealmProvider::class.java
sInstance = ServiceLoader.load(clazz, clazz.classLoader).findFirstOrThrow()
}
return sInstance
}
}
}
@@ -1,66 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.db.internal
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* An entity to store information about all registered box stores.
*
* @author Akash Yadav
*/
open class DatabaseEntity(
@Required
@PrimaryKey
@RealmField("path")
var path: String = "",
@Required
@RealmField("name")
var name: String = "",
@Required
@RealmField("directory")
var directory: String = "",
) : RealmObject() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DatabaseEntity) return false
if (path != other.path) return false
if (name != other.name) return false
if (directory != other.directory) return false
return true
}
override fun hashCode(): Int {
var result = path.hashCode()
result = 31 * result + name.hashCode()
result = 31 * result + directory.hashCode()
return result
}
override fun toString(): String {
return "DatabaseEntity(path='$path', name='$name', directory='$directory')"
}
}
@@ -1,28 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.db.internal
import io.realm.annotations.RealmModule
/**
* [RealmModule] for the internal database entitites.
*
* @author Akash Yadav
*/
@RealmModule(library = true, classes = [DatabaseEntity::class])
internal class InternalDBRealmModule
@@ -1,115 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.db.internal
import com.google.auto.service.AutoService
import com.itsaky.androidide.db.IRealmProvider
import com.itsaky.androidide.utils.Environment
import io.realm.Realm
import io.realm.RealmConfiguration
import org.slf4j.LoggerFactory
/**
* Default implementation of [IRealmProvider].
*
* @author Akash Yadav
*/
@AutoService(IRealmProvider::class)
@Suppress("unused")
internal class RealmProviderImpl : IRealmProvider {
companion object {
private val log = LoggerFactory.getLogger(RealmProviderImpl::class.java)
// must be a relative path, with no special characters except '_' '-' and '/' (path sep)
private val dbNameRegex = Regex("^[a-zA-Z0-9_\\-/]+$")
private const val MASTER_DB_NAME = "master"
private const val MASTER_DB_PATH = "/$MASTER_DB_NAME"
}
private val masterDb: Realm by lazy {
createDb(MASTER_DB_PATH) {
modules(InternalDBRealmModule())
}
}
override fun get(path: String, config: (RealmConfiguration.Builder.() -> Unit)?): Realm {
require(path != MASTER_DB_PATH) {
"Master DB is internal to AndroidIDE"
}
return createDb(path, config)
}
private fun createDb(path: String, confFunc: (RealmConfiguration.Builder.() -> Unit)?): Realm {
val (parentPath, name) = validateDbPath(path)
val configBuilder = RealmConfiguration.Builder()
confFunc?.also { configure -> configBuilder.configure() }
var dbDir = Environment.REALM_DB_DIR
if (parentPath.isNotEmpty()) {
dbDir = dbDir.resolve(parentPath)
}
val dbName = "${name}.realm"
val config = configBuilder
.name(dbName)
.directory(dbDir)
.allowQueriesOnUiThread(false)
.allowWritesOnUiThread(false)
.compactOnLaunch()
.build()
log.info("Creating DB at $dbDir/$dbName")
if (parentPath.isEmpty() && name != MASTER_DB_NAME) {
masterDb.executeTransactionAsync { realm ->
realm.insertOrUpdate(DatabaseEntity(name = name, path = path, directory = dbDir.absolutePath))
}
}
return Realm.getInstance(config)
}
private fun validateDbPath(path: String): Pair<String, String> {
val idx = path.indexOfLast { it == IRealmProvider.PATH_SEPARATOR }
require(
path.matches(dbNameRegex)
&& path[0] == IRealmProvider.PATH_SEPARATOR
&& idx >= 0
&& path.indexOf("//") == -1
) {
"Invalid DB path: $path"
}
val parentPath = if (idx > 1) {
path.substring(1, idx)
} else ""
val name = path.substring(idx + 1)
if (name == MASTER_DB_NAME) {
require(parentPath.isEmpty()) {
"Master DB must not have any parent path"
}
}
return parentPath to name
}
}
@@ -1,60 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.db.utils
import io.realm.RealmDictionary
/**
* Check if two RealmDictionaries are equal.
*/
fun <T> RealmDictionary<out T>?.isEqualTo(other: Any?): Boolean {
// implementation taken from java/util/AbstractMap from JDK 21
if (other == this) return true
if (other == null || this == null) return false
if (other !is RealmDictionary<*>) return false
if (this.size != other.size) return false
try {
for (entry in this.entries) {
val key = entry.key
val value = entry.value
if (value == null) {
if (!(other[key] == null && other.containsKey(key))) {
return false
}
} else {
if (value != other[key]) {
return false
}
}
}
} catch (e: ClassCastException) {
return false
} catch (e: NullPointerException) {
return false
}
return true
}
/**
* Generate the hash code for a RealmDictionary.
*/
fun <T> RealmDictionary<T>.hash(): Int {
return this.entries.sumOf { it.hashCode() }
}
-4
View File
@@ -22,11 +22,8 @@ plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("realm-android")
}
android {
namespace = "${BuildConfig.packageName}.indexing.api"
}
@@ -36,7 +33,6 @@ dependencies {
implementation(libs.google.auto.service.annotations)
api(projects.core.common)
api(projects.core.database)
api(projects.core.projects)
api(projects.logging.logger)
api(projects.utilities.shared)
@@ -1,59 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing
import io.realm.Realm
/**
* Abstract implementation of [IIndex] for indexing data in the database.
*
* @property params The index parameters.
* @author Akash Yadav
*/
abstract class AbstractDBIndex<T : IIndexable>(
protected val params: IIndexParams?
) : IDatabaseIndex<T> {
protected abstract val realm: Realm
override fun index(symbol: T) {
realm.executeTransaction {
it.insertOrUpdate(symbol)
}
}
override fun indexAsync(symbol: T) {
realm.executeTransactionAsync {
it.insertOrUpdate(symbol)
}
}
override fun indexAll(symbols: Collection<T>) {
realm.executeTransaction {
it.insertOrUpdate(symbols)
}
}
override fun indexAllAsync(symbols: Collection<T>) {
realm.executeTransactionAsync { it.insertOrUpdate(symbols) }
}
override fun delete() {
realm.deleteAll()
}
}
@@ -1,33 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing
import io.realm.RealmQuery
/**
* An index which is stored in the database.
*
* @author Akash Yadav
*/
interface IDatabaseIndex<IndexableT : IIndexable> : IIndex<IndexableT> {
/**
* Perform a query on the Realm database.
*/
fun query(): RealmQuery<IndexableT>
}
@@ -17,8 +17,6 @@
package com.itsaky.androidide.indexing
import com.itsaky.androidide.db.IRealmProvider
/**
* An index of symbols of type [IndexableT].
*
@@ -28,6 +26,8 @@ interface IIndex<IndexableT : IIndexable> {
companion object {
const val PATH_SEPARATOR = '/'
/**
* The default batch size for indexing.
*/
@@ -44,7 +44,7 @@ interface IIndex<IndexableT : IIndexable> {
* Create an index path.
*/
fun createIndexPath(parentPath: String, name: String): String =
"${parentPath}${IRealmProvider.PATH_SEPARATOR}${name}"
"${parentPath}${PATH_SEPARATOR}${name}"
}
/**
@@ -17,11 +17,9 @@
package com.itsaky.androidide.indexing
import io.realm.RealmModel
/**
* An indexable object.
*
* @author Akash Yadav
*/
interface IIndexable : RealmModel
interface IIndexable
-3
View File
@@ -22,11 +22,8 @@ plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("realm-android")
}
android {
namespace = "${BuildConfig.packageName}.indexing.core"
}
@@ -1,26 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing.core
import io.realm.annotations.RealmModule
/**
* @author Akash Yadav
*/
@RealmModule(classes = [])
class IndexingCoreRealmModule
@@ -27,7 +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.java.utils.JavaType
import com.itsaky.androidide.lsp.models.ClassCompletionData
import com.itsaky.androidide.lsp.models.CompletionItemKind.CLASS
import com.itsaky.androidide.lsp.models.CompletionItemKind.CONSTRUCTOR
-2
View File
@@ -161,8 +161,6 @@ tooling-builderModel = { module = "com.android.tools.build:builder-model", versi
tooling-gradleApi = { module = "com.itsaky.androidide.gradle:gradle-tooling-api", version.ref = "gradle-tooling" }
tooling-slf4j = { module = "org.slf4j:slf4j-api", version = "2.0.12" }
realm-gradle-plugin = { module = "io.realm:realm-gradle-plugin", version.ref = "realm" }
# Classpaths
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
-3
View File
@@ -22,11 +22,8 @@ plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("realm-android")
}
android {
namespace = "${BuildConfig.packageName}.lsp.java"
@@ -20,7 +20,7 @@ package com.itsaky.androidide.lsp.java.indexing
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.utils.JavaType
import com.itsaky.androidide.testing.android.rules.RealmDBTestRule
import io.realm.RealmList
import io.realm.RealmObject
@@ -20,7 +20,7 @@ package com.itsaky.androidide.lsp.java.indexing
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaField
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaMethod
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.utils.JavaType
import io.realm.RealmList
import openjdk.tools.classfile.AccessFlags
@@ -25,7 +25,7 @@ import com.itsaky.androidide.lsp.java.indexing.classfile.ClassAnnotationElementV
import com.itsaky.androidide.lsp.java.indexing.classfile.EnumAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.IAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaConstant
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.utils.JavaType
import com.itsaky.androidide.lsp.java.indexing.classfile.PrimitiveAnnotationElementValue
import com.itsaky.androidide.testing.android.rules.RealmDBTestRule
import io.realm.RealmAny
@@ -1,25 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import com.itsaky.androidide.indexing.IIndexable
/**
* @author Akash Yadav
*/
interface IJavaIndexable : IIndexable
@@ -1,29 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
/**
* A java symbol.
*
* @property accessFlags Access flags of the symbol.
* @property apiInfo Info about the APIs in which this symbol was added, deprecated or removed in.
* @author Akash Yadav
*/
interface IJavaSymbol : IJavaIndexable {
var accessFlags: Int
}
@@ -1,73 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import io.realm.RealmList
import io.realm.annotations.Required
/**
* A Java user type. May be a class, interface, enum, or annotation. Qualified name of a type or a
* package name is always represented in the (partially) internal form. For example, the fully qualified
* of `java.lang.Object` is `java/lang/Object` and the package name is `java/lang`.
*
* @property fqn The fully qualified name of the type.
* @property name The simple name of the type.
* @property packageName The package name of the type.
* @property superInterfacesFqn The fully qualified names of the interfaces implemented by the type.
* @property fields The fields of the type.
* @property methods The methods of the type.
* @property isClass Whether the type is a class.
* @property isInterface Whether the type is an interface.
* @property isAnnotation Whether the type is an annotation.
* @property isEnum Whether the type is an enum.
* @author Akash Yadav
*/
interface IJavaType<FieldT : IJavaSymbol, MethodT : IJavaSymbol> : IJavaSymbol {
var fqn: String?
var name: String?
var packageName: String?
var isInner: Boolean
/**
* The fully qualified name of the superclass of the type.
*
* Info about fields and methods are availabe in the class file itself. Hhowever, a class may
* extend a class (or implement an interface) which may be defined in other jar files. As a result,
* we only store the fully qualifed names of the classes and interfaces then add an index based on
* these fully qualifed names in the database.
*
* This property is not [Required] because we also index `java/lang/Object`, which does not have
* a superclass.
*/
var superClassFqn: String?
var superInterfacesFqn: RealmList<String>?
var fields: RealmList<FieldT>?
var methods: RealmList<MethodT>?
val isClass: Boolean
get() = false
val isInterface: Boolean
get() = false
val isAnnotation: Boolean
get() = false
val isEnum: Boolean
get() = false
}
@@ -1,36 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
/**
* An [IJavaIndexable] model which is shared between other models.
*
* @author Akash Yadav
*/
interface ISharedJavaIndexable : IJavaIndexable {
/**
* A unique ID for the indexable.
*/
var id: Int?
/**
* Compute and update the ID of this indexable.
*/
fun computeId()
}
@@ -1,59 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement
import com.itsaky.androidide.lsp.java.indexing.classfile.ArrayAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.ClassAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.EnumAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaAnnotation
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaConstant
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaEnum
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaField
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaInterface
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaMethod
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.indexing.classfile.PrimitiveAnnotationElementValue
import io.realm.annotations.RealmModule
/**
* [RealmModule] for the indexing databased for Java.
*
* @author Akash Yadav
*/
@RealmModule(
library = true, classes = [
AnnotationElement::class,
AnnotationAnnotationElementValue::class,
ArrayAnnotationElementValue::class,
ClassAnnotationElementValue::class,
EnumAnnotationElementValue::class,
PrimitiveAnnotationElementValue::class,
JavaAnnotation::class,
JavaClass::class,
JavaConstant::class,
JavaEnum::class,
JavaField::class,
JavaInterface::class,
JavaMethod::class,
JavaType::class,
]
)
internal class JavaIndexingRealmModule
@@ -1,424 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import androidx.annotation.VisibleForTesting
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement.Companion.newAnnotationElement
import com.itsaky.androidide.lsp.java.indexing.classfile.ArrayAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.ClassAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.EnumAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.IAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaAnnotation
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaConstant
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaEnum
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaField
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaInterface
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaMethod
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.indexing.classfile.PrimitiveAnnotationElementValue
import com.itsaky.androidide.lsp.java.utils.DescriptorUtils
import io.realm.RealmAny
import io.realm.RealmDictionary
import io.realm.RealmList
import openjdk.tools.classfile.AccessFlags
import openjdk.tools.classfile.Annotation.Annotation_element_value
import openjdk.tools.classfile.Annotation.Array_element_value
import openjdk.tools.classfile.Annotation.Class_element_value
import openjdk.tools.classfile.Annotation.Enum_element_value
import openjdk.tools.classfile.Annotation.Primitive_element_value
import openjdk.tools.classfile.Annotation.element_value
import openjdk.tools.classfile.AnnotationDefault_attribute
import openjdk.tools.classfile.Attribute
import openjdk.tools.classfile.ClassFile
import openjdk.tools.classfile.ConstantPool
import openjdk.tools.classfile.ConstantPool.CONSTANT_Double_info
import openjdk.tools.classfile.ConstantPool.CONSTANT_Float_info
import openjdk.tools.classfile.ConstantPool.CONSTANT_Integer_info
import openjdk.tools.classfile.ConstantPool.CONSTANT_Long_info
import openjdk.tools.classfile.ConstantPool.CONSTANT_String_info
import openjdk.tools.classfile.ConstantPool.CONSTANT_Utf8_info
import openjdk.tools.classfile.ConstantPool.CPInfo
import openjdk.tools.classfile.ConstantValue_attribute
import java.io.File
import java.util.zip.ZipFile
/**
* [JavaJarModelBuilder] builds models for Java classes in a JAR file.
*
* @author Akash Yadav
*/
class JavaJarModelBuilder(private val jar: File) {
// for testing purposes
// prefer using consumeTypes(Function)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun buildTypes(): List<IJavaType<*, *>> {
val types = mutableListOf<IJavaType<*, *>>()
consumeTypes { types.add(it) }
return types
}
fun consumeTypes(consumer: (IJavaType<*, *>) -> Unit) {
ZipFile(jar).use { jar ->
jar.entries().asSequence().filter { it.name.endsWith(".class") }.forEach { entry ->
jar.getInputStream(entry).buffered().use { inputStream ->
val classFile = ClassFile.read(inputStream)!!
// build model only if the class is not an anonymous or local class
if (classFile.getAttribute(Attribute.EnclosingMethod) == null) {
val type = buildType(classFile)
consumer(type)
}
}
}
}
}
private fun buildType(classFile: ClassFile): IJavaType<*, *> {
val classInfo = classFile.constant_pool.getClassInfo(classFile.this_class)
val fqn = classInfo.baseName
val (pck, name, isInner) = splitFqn(fqn)
return buildType(fqn, pck, name, isInner, classFile)
}
/**
* Split the given fully qualified name of a class and return the package name and the simple name
* of the class, along with whether the class is an inner class.
*/
private fun splitFqn(fqn: String): Triple<String, String, Boolean> {
val dolIdx = fqn.lastIndexOf('$')
// start looking for '/' from the index of the last '$'
// a '/' will always appear before a '$'
val slaIdx = fqn.lastIndexOf('/', startIndex = if (dolIdx == -1) fqn.lastIndex else dolIdx - 1)
if (slaIdx == -1 && dolIdx != -1) {
// fqn == SomeClass$InnerClass (inner class of a class in the root package)
// pck is empty, but fqn is not the class's simple name
return Triple("", fqn.substring(dolIdx + 1), true)
}
if (slaIdx == -1) {
// fqn == SomeClass (class in the root package)
// pck is empty and fqn is class's simple name
return Triple("", fqn, false)
}
if (dolIdx == -1) {
// fqn == com/example/SomeClass
return Triple(fqn.substring(0, slaIdx), fqn.substring(slaIdx + 1), false)
}
// fqn == com/example/SomeClass$InnerClass
return Triple(fqn.substring(0, slaIdx), fqn.substring(dolIdx + 1), true)
}
private fun buildType(
fqn: String,
pck: String,
name: String,
isInner: Boolean,
classFile: ClassFile
): IJavaType<*, *> {
return when {
classFile.access_flags.`is`(AccessFlags.ACC_ENUM) -> buildEnum(fqn, name, pck, classFile)
classFile.access_flags.`is`(AccessFlags.ACC_ANNOTATION) -> buildAnnotation(
fqn,
name,
pck,
classFile
)
classFile.access_flags.`is`(AccessFlags.ACC_INTERFACE) -> buildInterface(
fqn,
name,
pck,
classFile
)
else -> buildClass(fqn, name, pck, classFile)
}.also {
it.isInner = isInner
}
}
private fun buildClass(fqn: String, name: String, pck: String, classFile: ClassFile): JavaClass {
return JavaClass.newInstance(fqn, name, pck, classFile.access_flags.flags).apply {
setSuperClass(classFile)
setSuperInterfaces(classFile)
setFields(classFile)
setMethods(classFile)
}
}
private fun buildInterface(
fqn: String, name: String, pck: String, classFile: ClassFile
): JavaInterface {
return JavaInterface.newInstance(fqn, name, pck, classFile.access_flags.flags).apply {
setSuperClass(classFile)
setSuperInterfaces(classFile)
setFields(classFile)
setMethods(classFile)
}
}
private fun buildAnnotation(
fqn: String, name: String, pck: String, classFile: ClassFile
): JavaAnnotation {
return JavaAnnotation.newInstance(fqn, name, pck, classFile.access_flags.flags).apply {
setSuperClass(classFile)
setSuperInterfaces(classFile)
setFields(classFile)
setAnnotationElements(classFile)
}
}
private fun buildEnum(fqn: String, name: String, pck: String, classFile: ClassFile): JavaEnum {
return JavaEnum.newInstance(fqn, name, pck, classFile.access_flags.flags).apply {
setSuperClass(classFile)
setSuperInterfaces(classFile)
setFields(classFile)
setMethods(classFile)
}
}
private fun IJavaType<*, *>.setSuperInterfaces(classFile: ClassFile) {
val interfaces = RealmList<String>()
for (iface in classFile.interfaces) {
val ifaceInfo = classFile.constant_pool.getClassInfo(iface)
interfaces.add(ifaceInfo.baseName!!)
}
superInterfacesFqn = interfaces
}
private fun IJavaType<*, *>.setSuperClass(classFile: ClassFile) {
if (classFile.super_class == 0) {
// From https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.5
// If the value of the super_class field is 0, then this class represents the java/lang/Object class itself
// and hence, doesn't have a super class.
return
}
val superclassInfo = classFile.constant_pool.getClassInfo(classFile.super_class)
superClassFqn = superclassInfo.baseName!!
}
private fun IJavaType<*, JavaMethod>.setMethods(classFile: ClassFile) {
methods = RealmList<JavaMethod>().apply {
classFile.methods.forEach { method ->
val descriptor = classFile.constant_pool.getUTF8Value(method.descriptor.index)
val methodName = method.getName(classFile.constant_pool)
val methodType = DescriptorUtils.returnType(descriptor)
val paramTypes = RealmList<JavaType>().apply {
addAll(DescriptorUtils.paramTypes(descriptor))
}
add(JavaMethod.newInstance(methodName, paramTypes, methodType, method.access_flags.flags))
}
}
}
private fun IJavaType<JavaField, *>.setFields(classFile: ClassFile) {
fields = RealmList<JavaField>().apply {
classFile.fields.forEach { field ->
val descriptor = classFile.constant_pool.getUTF8Value(field.descriptor.index)
val fieldName = field.getName(classFile.constant_pool)
val fieldType = DescriptorUtils.type(descriptor)
add(JavaField.newField(fieldName, fieldType, field.access_flags.flags).apply {
field.attributes.get(Attribute.ConstantValue)?.also { constAttr ->
val constantValue = constAttr as ConstantValue_attribute
val constant = classFile.constant_pool.get(constantValue.constantvalue_index)
val isStringType = fieldType.isStringType()
val javaConstant = if (fieldType.isPrimitive() || isStringType) {
toJavaConstant(fieldType.kind, constant, if (isStringType) 's' else null)
} else {
throw IllegalArgumentException("Invalid field type for constant value: $fieldType")
}
this.constantValue = javaConstant
}
})
}
}
}
private fun JavaAnnotation.setAnnotationElements(classFile: ClassFile) {
methods = RealmList<AnnotationElement>().apply {
classFile.methods.forEach { method ->
val descriptor = classFile.constant_pool.getUTF8Value(method.descriptor.index)
val methodName = method.getName(classFile.constant_pool)
val methodType = DescriptorUtils.returnType(descriptor)
add(
newAnnotationElement(
methodName, methodType, method.access_flags.flags
).apply {
val annotationDefault =
method.attributes.get(Attribute.AnnotationDefault) as? AnnotationDefault_attribute?
if (annotationDefault != null) {
val defaultValue = annotationDefault.default_value
val value = toAnnotationElementValue(defaultValue, classFile)
this.defaultValue = value.let(RealmAny::valueOf) ?: RealmAny.nullValue()
}
})
}
}
}
private fun toAnnotationElementValue(
defaultValue: element_value, classFile: ClassFile
): IAnnotationElementValue {
val char = defaultValue.tag.toChar()
var kind = JavaType.kindForType(char)
if (char == 's') {
kind = JavaType.KIND_REF
}
var value: IAnnotationElementValue? = when (kind) {
JavaType.KIND_BOOLEAN,
JavaType.KIND_BYTE,
JavaType.KIND_CHAR,
JavaType.KIND_SHORT,
JavaType.KIND_INT,
JavaType.KIND_LONG,
JavaType.KIND_FLOAT,
JavaType.KIND_DOUBLE -> PrimitiveAnnotationElementValue.newInstance(
// annotation value kinds are same as JavaType.KIND_*
kind = kind,
value = toPrimitiveConstant(
classFile.constant_pool, kind, char, defaultValue as Primitive_element_value
)
)
else -> null
}
if (value == null) {
value = when (char) {
's' -> PrimitiveAnnotationElementValue.newInstance(
kind = IAnnotationElementValue.KIND_STRING,
value = toPrimitiveConstant(
classFile.constant_pool,
JavaType.KIND_REF,
char,
defaultValue as Primitive_element_value
)
)
'e' -> {
val enum = defaultValue as Enum_element_value
EnumAnnotationElementValue.newInstance(
name = classFile.constant_pool.getUTF8Value(enum.const_name_index),
type = DescriptorUtils.type(classFile.constant_pool.getUTF8Value(enum.type_name_index))
)
}
'c' -> {
// even though the name is given as "class_info_index", the constant at that index
// is actually a CONSTANT_Utf8_info representing a return type descriptor (JLS $4.3.3)
// this has been clearly mentioned in the Java Language Specification
ClassAnnotationElementValue.newInstance(
DescriptorUtils.type(
classFile.constant_pool.getUTF8Value(
(defaultValue as Class_element_value).class_info_index
)
)
)
}
'@' -> {
val annotation = (defaultValue as Annotation_element_value).annotation_value
AnnotationAnnotationElementValue.newInstance(type = DescriptorUtils.type(
classFile.constant_pool.getUTF8Value(
annotation.type_index
)
),
values = RealmDictionary<RealmAny>().apply {
for (pair in annotation.element_value_pairs) {
this[classFile.constant_pool.getUTF8Value(pair.element_name_index)] =
toAnnotationElementValue(pair.value, classFile).let { elementValue ->
RealmAny.valueOf(elementValue)
}
}
})
}
'[' -> {
ArrayAnnotationElementValue.newInstance(values = RealmList<RealmAny>().apply {
(defaultValue as Array_element_value).values.forEach { element ->
add(toAnnotationElementValue(element, classFile).let { elementValue ->
RealmAny.valueOf(elementValue)
})
}
})
}
else -> throw IllegalArgumentException("Unknown value tag: ${defaultValue.tag}")
}
}
return value
}
private fun toPrimitiveConstant(
constantPool: ConstantPool, kind: Int, char: Char, prim: Primitive_element_value
): JavaConstant {
if (kind == JavaType.KIND_REF && char != 's') {
throw IllegalArgumentException("Unknown value tag: $char")
}
val constant = constantPool.get(prim.const_value_index)
return toJavaConstant(kind, constant, char)
}
private fun toJavaConstant(
kind: Int, constant: CPInfo, char: Char?
): JavaConstant {
val realmAny = when (kind) {
JavaType.KIND_BOOLEAN,
JavaType.KIND_BYTE,
JavaType.KIND_CHAR,
JavaType.KIND_SHORT,
JavaType.KIND_INT -> (constant as CONSTANT_Integer_info).value.let {
RealmAny.valueOf(it)
}
JavaType.KIND_LONG -> (constant as CONSTANT_Long_info).value.let { RealmAny.valueOf(it) }
JavaType.KIND_FLOAT -> (constant as CONSTANT_Float_info).value.let { RealmAny.valueOf(it) }
JavaType.KIND_DOUBLE -> (constant as CONSTANT_Double_info).value.let { RealmAny.valueOf(it) }
else -> if (char == 's') {
if (constant is CONSTANT_String_info) {
constant.string
} else {
(constant as CONSTANT_Utf8_info).value
}.let { RealmAny.valueOf(it) }
} else {
throw IllegalArgumentException("Unknown value tag: $char")
}
}
return JavaConstant.newInstance(
kind = kind,
value = realmAny
)
}
}
@@ -1,91 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaSymbol
import io.realm.RealmAny
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* A method defined in an annotation type.
*
* @property defaultValue The default value of the annotation element.
* @author Akash Yadav
*/
@RealmClass(embedded = true)
open class AnnotationElement : IJavaSymbol {
@Index
@Required
@RealmField("name")
var name: String? = null
@RealmField("type")
var type: JavaType? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@LinkingObjects("methods")
val ofClass: RealmResults<JavaAnnotation>? = null
/**
* The default value of the annotation element. One of the following:
* - [PrimitiveAnnotationElementValue]
* - [EnumAnnotationElementValue]
* - [ArrayAnnotationElementValue]
* - [ClassAnnotationElementValue]
* - [AnnotationAnnotationElementValue]
*/
@RealmField("defaultValue")
var defaultValue: RealmAny? = null
/**
* Returns the signature of the method.
*/
fun signature(): String {
val sb = StringBuilder()
sb.append(name)
sb.append("(")
sb.append(")")
sb.append(type?.internalForm())
return sb.toString()
}
companion object {
@JvmStatic
fun newAnnotationElement(
name: String,
type: JavaType,
accessFlags: Int,
defaultValue: RealmAny? = null
): AnnotationElement {
return AnnotationElement().apply {
this.name = name
this.type = type
this.accessFlags = accessFlags
this.defaultValue = defaultValue
}
}
}
}
@@ -1,105 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaType
import io.realm.RealmList
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* A Java annotation type.
*
* Implementation note:
* An annotation type is a Java interface, and hence, it inherits from [JavaInterface] instead of
* [IJavaType]. Also, [IJavaType.isInterface] and [IJavaType.isAnnotation] both
* return `true` for annotation types.
*
* @author Akash Yadav
*/
@RealmClass
open class JavaAnnotation : IJavaType<JavaField, AnnotationElement> {
@Index
@PrimaryKey
@Required
@RealmField("fqn")
override var fqn: String? = null
@Required
@RealmField("name")
override var name: String? = null
@Required
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@RealmField("superInterfacesFqn")
override var superInterfacesFqn: RealmList<String>? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@RealmField("fields")
override var fields: RealmList<JavaField>? = null
@RealmField("methods")
override var methods: RealmList<AnnotationElement>? = null
override val isInterface: Boolean
get() = true
override val isAnnotation: Boolean
get() = true
companion object {
@JvmStatic
fun newInstance(
fqn: String,
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superInterfacesFqn: RealmList<String>? = null,
fields: RealmList<JavaField>? = null,
methods: RealmList<AnnotationElement>? = null
): JavaAnnotation {
return JavaAnnotation().apply {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superClassFqn = superClassFqn
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
this.fields = fields
this.methods = methods
}
}
}
}
@@ -1,98 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaType
import io.realm.RealmList
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* A Java class type.
*
* @author Akash Yadav
*/
@RealmClass
open class JavaClass : IJavaType<JavaField, JavaMethod> {
@Index
@PrimaryKey
@Required
@RealmField("fqn")
override var fqn: String? = null
@Required
@RealmField("name")
override var name: String? = null
@Required
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@RealmField("superInterfacesFqn")
override var superInterfacesFqn: RealmList<String>? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@RealmField("fields")
override var fields: RealmList<JavaField>? = null
@RealmField("methods")
override var methods: RealmList<JavaMethod>? = null
override val isClass: Boolean
get() = true
companion object {
@JvmStatic
fun newInstance(
fqn: String,
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superClassFqn: String? = null,
superInterfacesFqn: RealmList<String>? = null,
fields: RealmList<JavaField>? = null,
methods: RealmList<JavaMethod>? = null
): JavaClass {
return JavaClass().apply {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superClassFqn = superClassFqn
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
this.fields = fields
this.methods = methods
}
}
}
}
@@ -1,152 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.google.common.base.Objects
import com.itsaky.androidide.lsp.java.indexing.ISharedJavaIndexable
import io.realm.RealmAny
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
/**
* A constant value in Java.
*
* A unique ID is used as the primary key in order to ensure that a constant is not stored multiple
* times in the database. Also, a string constant always has type [JavaType.KIND_REF]. A constant
* is never of type [JavaType.KIND_VOID].
*
* @property id A unique ID which is generated for the constant.
* @property kind The kind of contant. See [JavaType] for a list of kinds.
* @property value The value of the constant. Cast this value based on the value of [kind].
* @author Akash Yadav
*/
@RealmClass
open class JavaConstant : ISharedJavaIndexable {
@PrimaryKey
@RealmField("id")
override var id: Int? = null
@RealmField("kind")
var kind: Int = JavaType.KIND_UNKNOWN
private set
@RealmField("value")
var value: RealmAny? = null
private set
companion object {
fun newInstance(kind: Int, value: RealmAny): JavaConstant {
return JavaConstant().apply {
this.kind = kind
this.value = value
this.computeId()
}
}
}
fun update(kind: Int = this.kind, value: RealmAny? = this.value) {
this.kind = kind
this.value = value
this.computeId()
}
fun update(src: JavaConstant) {
this.kind = src.kind
this.value = src.value
this.computeId()
}
override fun computeId() {
this.id = Objects.hashCode(this.kind, this.kind, this.value)
}
// boolean, byte, short, int are stored as int in the bytecode
fun asBoolean(): Boolean? {
return safeCast(JavaType.KIND_BOOLEAN) { asInteger() == 1 }
}
fun asByte(): Byte? {
return safeCast(JavaType.KIND_BYTE) { asInteger()?.toByte() }
}
fun asShort(): Short? {
return safeCast(JavaType.KIND_SHORT) { asInteger()?.toShort() }
}
fun asInt(): Int? {
return safeCast(JavaType.KIND_INT) { asInteger() }
}
fun asLong(): Long? {
return safeCast(JavaType.KIND_LONG) { asLong() }
}
fun asFloat(): Float? {
return safeCast(JavaType.KIND_FLOAT) { asFloat() }
}
fun asDouble(): Double? {
return safeCast(JavaType.KIND_DOUBLE) { asDouble() }
}
fun asString(): String? {
return safeCast(JavaType.KIND_REF) { asString() }
}
fun asChar(): Char? {
return safeCast(JavaType.KIND_CHAR) { asInteger()?.toChar() }
}
private inline fun <R> safeCast(kind: Int, action: RealmAny.() -> R): R? {
return value?.let {
if (this.kind == kind) {
try {
it.action()
} catch (ex: ClassCastException) {
throw IllegalStateException("Kind == $kind but safeCast failed", ex)
}
} else null
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is JavaConstant) return false
if (id != other.id) return false
if (kind != other.kind) return false
if (value != other.value) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + kind
result = 31 * result + (value?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "JavaConstant(id=$id, kind=$kind, value=$value)"
}
}
@@ -1,97 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaType
import io.realm.RealmList
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* A Java enum type.
*
* @author Akash Yadav
*/
@RealmClass
open class JavaEnum : IJavaType<JavaField, JavaMethod> {
@Index
@PrimaryKey
@Required
@RealmField("fqn")
override var fqn: String? = null
@Required
@RealmField("name")
override var name: String? = null
@Required
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@RealmField("superInterfacesFqn")
override var superInterfacesFqn: RealmList<String>? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@RealmField("fields")
override var fields: RealmList<JavaField>? = null
@RealmField("methods")
override var methods: RealmList<JavaMethod>? = null
override val isEnum: Boolean
get() = true
companion object {
@JvmStatic
fun newInstance(
fqn: String,
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superClassFqn: String? = null,
superInterfacesFqn: RealmList<String>? = null,
fields: RealmList<JavaField>? = null,
methods: RealmList<JavaMethod>? = null
): JavaEnum {
return JavaEnum().apply {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superClassFqn = superClassFqn
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
this.fields = fields
this.methods = methods
}
}
}
}
@@ -1,65 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaSymbol
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
/**
* @author Akash Yadav
*/
@RealmClass(embedded = true)
open class JavaField : IJavaSymbol {
@Index
@RealmField("name")
var name: String? = null
@RealmField("type")
var type: JavaType? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@RealmField("constantValue")
var constantValue: JavaConstant? = null
@LinkingObjects("fields")
val ofClass: RealmResults<JavaClass>? = null
companion object {
@JvmStatic
fun newField(
name: String,
type: JavaType,
accessFlags: Int,
constantValue: JavaConstant? = null
): JavaField {
return JavaField().apply {
this.name = name
this.type = type
this.accessFlags = accessFlags
this.constantValue = constantValue
}
}
}
}
@@ -1,96 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaType
import io.realm.RealmList
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* A Java interface type.
*
* @author Akash Yadav
*/
@RealmClass
open class JavaInterface : IJavaType<JavaField, JavaMethod> {
@Index
@PrimaryKey
@Required
@RealmField("fqn")
override var fqn: String? = null
@Required
@RealmField("name")
override var name: String? = null
@Required
@RealmField("pck")
override var packageName: String? = null
@RealmField("isInner")
override var isInner: Boolean = false
@RealmField("superClassFqn")
override var superClassFqn: String? = null
@RealmField("superInterfacesFqn")
override var superInterfacesFqn: RealmList<String>? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@RealmField("fields")
override var fields: RealmList<JavaField>? = null
@RealmField("methods")
override var methods: RealmList<JavaMethod>? = null
override val isInterface: Boolean
get() = true
companion object {
@JvmStatic
fun newInstance(
fqn: String,
name: String,
packageName: String,
accessFlags: Int,
isInner: Boolean = false,
superInterfacesFqn: RealmList<String>? = null,
fields: RealmList<JavaField>? = null,
methods: RealmList<JavaMethod>? = null
): JavaInterface {
return JavaInterface().apply {
this.fqn = fqn
this.name = name
this.packageName = packageName
this.isInner = isInner
this.superInterfacesFqn = superInterfacesFqn
this.accessFlags = accessFlags
this.fields = fields
this.methods = methods
}
}
}
}
@@ -1,83 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.itsaky.androidide.lsp.java.indexing.IJavaSymbol
import io.realm.RealmList
import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
/**
* @author Akash Yadav
*/
@RealmClass(embedded = true)
open class JavaMethod : IJavaSymbol {
@Index
@Required
@RealmField("name")
var name: String? = null
@RealmField("paramTypes")
var paramsTypes: RealmList<JavaType>? = null
@RealmField("returnType")
var returnType: JavaType? = null
@RealmField("accessFlags")
override var accessFlags: Int = 0
@LinkingObjects("methods")
val ofClass: RealmResults<JavaClass>? = null
/**
* Returns the signature of the method.
*/
fun signature(): String {
val sb = StringBuilder()
sb.append(name)
sb.append("(")
paramsTypes?.forEach {
sb.append(it.internalForm())
}
sb.append(")")
sb.append(returnType?.internalForm())
return sb.toString()
}
companion object {
@JvmStatic
fun newInstance(
name: String,
paramsTypes: RealmList<JavaType>,
returnType: JavaType,
accessFlags: Int,
): JavaMethod {
return JavaMethod().apply {
this.name = name
this.paramsTypes = paramsTypes
this.returnType = returnType
this.accessFlags = accessFlags
}
}
}
}
@@ -1,340 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.google.common.base.Objects
import com.itsaky.androidide.db.utils.hash
import com.itsaky.androidide.db.utils.isEqualTo
import com.itsaky.androidide.lsp.java.indexing.ISharedJavaIndexable
import io.realm.RealmAny
import io.realm.RealmDictionary
import io.realm.RealmList
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
sealed interface IAnnotationElementValue : ISharedJavaIndexable {
var kind: Int
companion object {
const val KIND_UNKNOWN = JavaType.KIND_UNKNOWN
const val KIND_BOOLEAN = JavaType.KIND_BOOLEAN
const val KIND_BYTE = JavaType.KIND_BYTE
const val KIND_CHAR = JavaType.KIND_CHAR
const val KIND_DOUBLE = JavaType.KIND_DOUBLE
const val KIND_FLOAT = JavaType.KIND_FLOAT
const val KIND_INT = JavaType.KIND_INT
const val KIND_LONG = JavaType.KIND_LONG
const val KIND_SHORT = JavaType.KIND_SHORT
const val KIND_STRING = JavaType.__KIND_STRING
const val KIND_CLASS = JavaType.__KIND_CLASS
const val KIND_ENUM = JavaType.__KIND_ENUM
const val KIND_ANNOTATION = JavaType.__KIND_ANNOTATION
const val KIND_ARRAY = JavaType.__KIND_ARRAY
}
}
@RealmClass
open class PrimitiveAnnotationElementValue : IAnnotationElementValue {
@PrimaryKey
@Required
@RealmField("id")
override var id: Int? = null
@RealmField("kind")
override var kind: Int = 0
@RealmField("value")
var value: JavaConstant? = null
override fun computeId() {
this.id = Objects.hashCode(this.kind, this.value)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PrimitiveAnnotationElementValue) return false
if (id != other.id) return false
if (kind != other.kind) return false
if (value != other.value) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + kind
result = 31 * result + (value?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "PrimitiveAnnotationElementValue(id=$id, kind=$kind, value=$value)"
}
companion object {
@JvmStatic
fun newInstance(kind: Int, value: JavaConstant): PrimitiveAnnotationElementValue {
return PrimitiveAnnotationElementValue().apply {
this.kind = kind
this.value = value
this.computeId()
}
}
}
}
@RealmClass
open class EnumAnnotationElementValue : IAnnotationElementValue {
@PrimaryKey
@Required
@RealmField("id")
override var id: Int? = null
@RealmField("kind")
override var kind: Int = IAnnotationElementValue.KIND_ENUM
@RealmField("name")
var name: String? = null
@RealmField("type")
var type: JavaType? = null
override fun computeId() {
this.id = Objects.hashCode(this.kind, this.name, this.type)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is EnumAnnotationElementValue) return false
if (id != other.id) return false
if (kind != other.kind) return false
if (name != other.name) return false
if (type != other.type) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + kind
result = 31 * result + (name?.hashCode() ?: 0)
result = 31 * result + (type?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "EnumAnnotationElementValue(id=$id, kind=$kind, name=$name, type=$type)"
}
companion object {
@JvmStatic
fun newInstance(name: String, type: JavaType): EnumAnnotationElementValue {
return EnumAnnotationElementValue().apply {
this.name = name
this.type = type
this.computeId()
}
}
}
}
@RealmClass
open class ArrayAnnotationElementValue : IAnnotationElementValue {
@PrimaryKey
@Required
@RealmField("id")
override var id: Int? = null
@RealmField("kind")
override var kind: Int = IAnnotationElementValue.KIND_ARRAY
@RealmField("numValues")
var numValues: Int = 0
/**
* The elements of the array element value. Always of type [IAnnotationElementValue].
*/
@RealmField("values")
var values: RealmList<RealmAny>? = null
override fun computeId() {
this.id = Objects.hashCode(this.kind, this.numValues, this.values)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ArrayAnnotationElementValue) return false
if (id != other.id) return false
if (kind != other.kind) return false
if (numValues != other.numValues) return false
if (values != other.values) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + kind
result = 31 * result + numValues
result = 31 * result + (values?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "ArrayAnnotationElementValue(id=$id, kind=$kind, numValues=$numValues, values=$values)"
}
companion object {
@JvmStatic
fun newInstance(values: RealmList<RealmAny>): ArrayAnnotationElementValue {
return ArrayAnnotationElementValue().apply {
this.numValues = values.size
this.values = values
this.computeId()
}
}
}
}
@RealmClass
open class ClassAnnotationElementValue : IAnnotationElementValue {
@PrimaryKey
@Required
@RealmField("id")
override var id: Int? = null
@RealmField("kind")
override var kind: Int = IAnnotationElementValue.KIND_CLASS
@RealmField("type")
var type: JavaType? = null
override fun computeId() {
this.id = Objects.hashCode(this.kind, this.type)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ClassAnnotationElementValue) return false
if (id != other.id) return false
if (kind != other.kind) return false
if (type != other.type) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + kind
result = 31 * result + (type?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "ClassAnnotationElementValue(id=$id, kind=$kind, type=$type)"
}
companion object {
@JvmStatic
fun newInstance(type: JavaType): ClassAnnotationElementValue {
return ClassAnnotationElementValue().apply {
this.type = type
this.computeId()
}
}
}
}
@RealmClass
open class AnnotationAnnotationElementValue : IAnnotationElementValue {
@PrimaryKey
@Required
@RealmField("id")
override var id: Int? = null
@RealmField("kind")
override var kind: Int = IAnnotationElementValue.KIND_ANNOTATION
@RealmField("type")
var type: JavaType? = null
/**
* The named elements of the annotation element value. Always of type [IAnnotationElementValue].
*/
@RealmField("values")
var values: RealmDictionary<RealmAny>? = null
override fun computeId() {
// RealmDictionary seems to not override hashCode()
this.id = Objects.hashCode(this.kind, this.type, this.values?.hash())
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is AnnotationAnnotationElementValue) return false
if (id != other.id) return false
if (kind != other.kind) return false
if (type != other.type) return false
if (!values.isEqualTo(other)) return false
return true
}
override fun hashCode(): Int {
var result = id ?: 0
result = 31 * result + kind
result = 31 * result + (type?.hashCode() ?: 0)
result = 31 * result + (values?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return "AnnotationAnnotationElementValue(id=$id, kind=$kind, type=$type, values=$values)"
}
companion object {
@JvmStatic
fun newInstance(
type: JavaType,
values: RealmDictionary<RealmAny>
): AnnotationAnnotationElementValue {
return AnnotationAnnotationElementValue().apply {
this.type = type
this.values = values
this.computeId()
}
}
}
}
@@ -1,43 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.internal
import com.google.auto.service.AutoService
import com.itsaky.androidide.indexing.IIndexService
import com.itsaky.androidide.projects.IWorkspace
import java.io.File
/**
* @author Akash Yadav
*/
@AutoService(IIndexService::class)
internal class JavaIndexServiceImpl : IIndexService {
override val displayName: String
get() = "Java LSP Indexing Service"
override fun scanFiles(workspace: IWorkspace): Collection<File> {
return emptySet()
}
override suspend fun indexFiles(
workspace: IWorkspace,
files: Collection<File>
) {
}
}
@@ -1,232 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.utils
import com.itsaky.androidide.lsp.java.indexing.IJavaType
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaField
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaMethod
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import java.util.Objects
/**
* @author Akash Yadav
*/
object DescriptorUtils {
/**
* Create a method ID for the given method.
*/
fun createMethodId(
klass: IJavaType<*, *>,
method: JavaMethod
): Int {
return Objects.hash(klass.fqn, method.name, method.paramsTypes, method.returnType)
}
/**
* Create a method ID for the given method.
*/
fun createMethodId(
klass: IJavaType<*, *>,
element: AnnotationElement
): Int {
return Objects.hash(klass.fqn, element.name, element.type)
}
/**
* Create a field ID for the given field. The string representation is similar to how fields
* are represented in Smali.
*
* ```
* Lcom/example/Foo;->bar:I
* ```
*/
fun createFieldId(
klass: IJavaType<*, *>,
field: JavaField
): Int {
return Objects.hash(klass.fqn, field.name, field.type)
}
/**
* Parse the return type of a method descriptor.
*
* @param descriptor The method descriptor.
*/
fun returnType(descriptor: String): JavaType {
val idx = descriptor.indexOfLast { it == ')' }
if (descriptor[0] != '(' || idx == -1) {
throw IllegalArgumentException("Invalid method descriptor: $descriptor")
}
return type(descriptor.substring(idx + 1))
}
/**
* Parse the parameter types of a method descriptor.
*
* @param descriptor The method descriptor.
*/
fun paramTypes(descriptor: String): List<JavaType> {
val idx = descriptor.indexOfLast { it == ')' }
if (descriptor[0] != '(' || idx == -1) {
throw IllegalArgumentException("Invalid method descriptor: $descriptor")
}
val types = mutableListOf<JavaType>()
val params = descriptor.substring(1, idx)
var i = 0
while (i < params.length) {
types.add(
when (params[i]) {
'L' -> {
val end = params.indexOf(';', startIndex = i + 1)
referenceType(params, fromIndex = i, toIndex = end).also {
i = end + 1
}
}
'[' -> arrayType(params, fromIndex = i).also {
// number of '['
// + length of type (I or java/lang/String)
// + 2 if kind is ref (because refs have 'L' and ';')
i += it.arrayDims + it.name!!.length + (if (it.kind == JavaType.KIND_REF) 2 else 0)
}
else -> primitive(params[i].toString()).also {
++i
}
}
)
}
return types
}
/**
* Parse a type descriptor. A type descriptor can be any of :
* - a primitive type descriptor
* - a reference type descriptor
* - an array type descriptor
*
* @param descriptor The type descriptor.
*/
fun type(descriptor: String): JavaType {
if (descriptor.startsWith("L")) {
return referenceType(descriptor)
}
if (descriptor.startsWith("[")) {
return arrayType(descriptor)
}
if (descriptor.length != 1) {
throw IllegalArgumentException("Invalid type descriptor: $descriptor")
}
return primitive(descriptor)
}
/**
* Parse a primitive type descriptor.
*
* @param descriptor The primitive type descriptor.
*/
fun primitive(descriptor: String): JavaType = primitive(descriptor[0])
/**
* Parse a primitive type descriptor.
*
* @param char The primitive type.
*/
fun primitive(char: Char): JavaType {
val primitive = JavaType.primitiveFor(char)
return requireNotNull(primitive) {
"Invalid type descriptor: $char"
}
}
/**
* Parse a reference type descriptor.
*
* Format:
* ```
* Lcom/example/Foo;
* ```
*
* @param descriptor The descriptor of a reference type
* @param fromIndex The index of the first character of the reference type name.
* @param toIndex The index of the last character of the reference type name (inclusive).
*/
fun referenceType(
descriptor: String, fromIndex: Int = 0, toIndex: Int = descriptor.length - 1
): JavaType {
if (descriptor[fromIndex] != 'L' || descriptor[toIndex] != ';') {
throw IllegalArgumentException("Invalid reference type descriptor: $descriptor, fromIndex: $fromIndex, toIndex: $toIndex")
}
return when (descriptor) {
"Ljava/lang/String;" -> JavaType.STRING
"Ljava/lang/Object;" -> JavaType.OBJECT
"Ljava/lang/Class;" -> JavaType.CLASS
else -> {
// remove leading 'L' and trailing ';'
val name = descriptor.substring(fromIndex + 1, toIndex)
JavaType.newInstance(name = name, kind = JavaType.KIND_REF)
}
}
}
/**
* Parse an array type descriptor.
*
* Format:
* ```
* [I
* [[I
* [[Lcom/example/Foo;
* ```
*
* @param descriptor The array type descriptor.
* @param fromIndex The index of the first character of the array type descriptor.
*/
fun arrayType(descriptor: String, fromIndex: Int = 0): JavaType {
if (descriptor[fromIndex] != '[') {
throw IllegalArgumentException("Invalid array type descriptor: $descriptor, fromIndex: $fromIndex")
}
var idx = fromIndex + 1
var dims = 1
while (descriptor[idx] == '[') {
idx++
dims++
}
val type = if (descriptor[idx] == 'L') {
referenceType(
descriptor, fromIndex = idx, toIndex = descriptor.indexOf(';', startIndex = idx + 1)
)
} else {
primitive(descriptor[idx])
}
return JavaType.newInstance(name = type.name, kind = type.kind, arrayDims = dims)
}
}
@@ -15,38 +15,17 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing.classfile
import com.google.common.base.Objects
import com.itsaky.androidide.lsp.java.indexing.ISharedJavaIndexable
import com.itsaky.androidide.models.ICloneable
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import io.realm.annotations.RealmField
import io.realm.annotations.Required
package com.itsaky.androidide.lsp.java.utils
/**
* @author Akash Yadav
*/
open class JavaType {
@RealmClass
open class JavaType : ISharedJavaIndexable, ICloneable {
@Required
@PrimaryKey
@RealmField("id")
override var id: Int? = null
@Index
@Required
@RealmField("name")
var name: String? = null
@RealmField("kind")
var kind: Int = KIND_UNKNOWN
@RealmField("arrayDims")
var arrayDims: Int = 0
/**
@@ -76,14 +55,7 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
*/
fun isPrimitive(): Boolean {
return when (kind) {
KIND_BOOLEAN,
KIND_BYTE,
KIND_CHAR,
KIND_DOUBLE,
KIND_FLOAT,
KIND_INT,
KIND_LONG,
KIND_SHORT -> true
KIND_BOOLEAN, KIND_BYTE, KIND_CHAR, KIND_DOUBLE, KIND_FLOAT, KIND_INT, KIND_LONG, KIND_SHORT -> true
else -> false
}
@@ -117,14 +89,6 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
return name == CLASS.name
}
override fun clone(): JavaType {
return newInstance(name = name, kind = kind, arrayDims = arrayDims)
}
override fun computeId() {
this.id = Objects.hashCode(this.kind, this.name, this.arrayDims)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is JavaType) return false
@@ -144,7 +108,7 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
}
override fun toString(): String {
return "JavaType(id=$id, name=$name, kind=$kind, arrayDims=$arrayDims)"
return "JavaType(name=$name, kind=$kind, arrayDims=$arrayDims)"
}
companion object {
@@ -160,14 +124,6 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
const val KIND_VOID = 8
const val KIND_REF = 9
// kind values used in IAnnotationElementValue
// defined here for convenience
internal const val __KIND_STRING = 10
internal const val __KIND_CLASS = 11
internal const val __KIND_ENUM = 12
internal const val __KIND_ANNOTATION = 13
internal const val __KIND_ARRAY = 14
const val TYPE_BOOLEAN = 'Z'
const val TYPE_BYTE = 'B'
const val TYPE_CHAR = 'C'
@@ -264,7 +220,6 @@ open class JavaType : ISharedJavaIndexable, ICloneable {
this.name = name
this.kind = kind
this.arrayDims = arrayDims
this.computeId()
}
}
@@ -1,65 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.utils.FileProvider
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.File
/**
* @author Akash Yadav
*/
class AndroidJarModelingRule : TestRule {
private val androidJar: File by lazy {
FileProvider.projectRoot()
.resolve("utilities")
.resolve("framework-stubs")
.resolve("libs")
.resolve("android.jar")
.toFile().also {
check(it.exists()) {
"android.jar file could not be found"
}
}
}
private val androidJarIndexer: JavaJarModelBuilder by lazy {
assertThat(androidJar.exists()).isTrue()
JavaJarModelBuilder(androidJar)
}
val types: List<IJavaType<*, *>> by lazy {
androidJarIndexer.buildTypes()
}
override fun apply(base: Statement?, description: Description?): Statement {
// call 'types' to build the type models
types
base?.evaluate()
return object : Statement() {
override fun evaluate() {}
}
}
}
@@ -1,121 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.utils.DescriptorUtils
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
/**
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
class DescriptorUtilsTest {
@Test
fun testReturnType() {
val descriptor1 = "(I)V"
val expected1 = JavaType.newInstance(name = "V", kind = JavaType.KIND_VOID)
assertEquals(expected1, DescriptorUtils.returnType(descriptor1))
val descriptor2 = "(Ljava/lang/String;)I"
val expected2 = JavaType.newInstance(name = "I", kind = JavaType.KIND_INT)
assertEquals(expected2, DescriptorUtils.returnType(descriptor2))
val descriptor3 = "(I[Ljava/lang/String;)V"
val expected3 = JavaType.newInstance(name = "V", kind = JavaType.KIND_VOID)
assertEquals(expected3, DescriptorUtils.returnType(descriptor3))
}
@Test(expected = IllegalArgumentException::class)
fun testReturnTypeInvalidDescriptor() {
DescriptorUtils.returnType("Ljava/lang/String;I")
}
@Test
fun testParamTypes() {
val descriptor1 = "(I)V"
val expected1 = listOf(JavaType.newInstance(name = "I", kind = JavaType.KIND_INT))
assertEquals(expected1, DescriptorUtils.paramTypes(descriptor1))
val descriptor2 = "(Ljava/lang/String;I)V"
val expected2 = listOf(
JavaType.newInstance(name = "java/lang/String", kind = JavaType.KIND_REF),
JavaType.newInstance(name = "I", kind = JavaType.KIND_INT)
)
assertEquals(expected2, DescriptorUtils.paramTypes(descriptor2))
val descriptor3 = "([I[Ljava/lang/String;)V"
val expected3 = listOf(
JavaType.newInstance(name = "I", kind = JavaType.KIND_INT, arrayDims = 1),
JavaType.newInstance(name = "java/lang/String", kind = JavaType.KIND_REF, arrayDims = 1)
)
assertEquals(expected3, DescriptorUtils.paramTypes(descriptor3))
}
@Test(expected = IllegalArgumentException::class)
fun testParamTypesInvalidDescriptor() {
DescriptorUtils.paramTypes("Ljava/lang/String;I")
}
@Test
fun testPrimitive() {
val booleanType = JavaType.newInstance(name = "Z", kind = JavaType.KIND_BOOLEAN)
assertEquals(booleanType, DescriptorUtils.primitive('Z'))
val intType = JavaType.newInstance(name = "I", kind = JavaType.KIND_INT)
assertEquals(intType, DescriptorUtils.primitive('I'))
}
@Test(expected = IllegalArgumentException::class)
fun testPrimitiveInvalidType() {
DescriptorUtils.primitive('X')
}
@Test
fun testReferenceType() {
val descriptor = "Ljava/lang/String;"
val expected = JavaType.newInstance(name = "java/lang/String", kind = JavaType.KIND_REF)
assertEquals(expected, DescriptorUtils.referenceType(descriptor))
}
@Test(expected = IllegalArgumentException::class)
fun testReferenceTypeInvalidDescriptor() {
DescriptorUtils.referenceType("Ljava/lang/String")
}
@Test
fun testArrayType() {
val descriptor1 = "[I"
val expected1 = JavaType.newInstance(name = "I", kind = JavaType.KIND_INT, arrayDims = 1)
assertEquals(expected1, DescriptorUtils.arrayType(descriptor1))
val descriptor2 = "[[Ljava/lang/String;"
val expected2 =
JavaType.newInstance(name = "java/lang/String", kind = JavaType.KIND_REF, arrayDims = 2)
assertEquals(expected2, DescriptorUtils.arrayType(descriptor2))
}
@Test(expected = IllegalArgumentException::class)
fun testArrayTypeInvalidDescriptor() {
DescriptorUtils.arrayType("Ljava/lang/String;")
}
}
@@ -1,524 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.indexing
import android.view.View
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement
import com.itsaky.androidide.lsp.java.indexing.classfile.IAnnotationElementValue
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaAnnotation
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaClass
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaEnum
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaField
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaInterface
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaMethod
import com.itsaky.androidide.lsp.java.indexing.classfile.JavaType
import com.itsaky.androidide.lsp.java.indexing.classfile.PrimitiveAnnotationElementValue
import io.realm.RealmAny
import openjdk.tools.classfile.AccessFlags
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.lang.reflect.Modifier
/**
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
class JavaJarModelBuilderTest {
@Rule
@JvmField
val indexRule = AndroidJarModelingRule()
private val types: List<IJavaType<*, *>>
get() = indexRule.types
@Test
fun `test JavaClass properties`() {
val sampleClass = types.firstOrNull { it.fqn == "java/lang/String" }
assertThat(sampleClass).isNotNull()
sampleClass?.let {
assertThat(it.fqn).isEqualTo("java/lang/String")
assertThat(it.name).isEqualTo("String")
assertThat(it.packageName).isEqualTo("java/lang")
assertThat(it.superClassFqn).isEqualTo("java/lang/Object")
assertThat(it.superInterfacesFqn).containsAtLeast(
"java/io/Serializable",
"java/lang/Comparable",
"java/lang/CharSequence"
)
}
}
@Test
fun `test JavaField properties`() {
val sampleClass = types.firstOrNull { it.fqn == "java/lang/String" }
val fields = sampleClass?.fields
assertThat(fields).isNotEmpty()
fields?.forEach { f ->
val field = f as JavaField
assertThat(field.name).isNotEmpty()
assertThat(field.accessFlags).isGreaterThan(0)
assertThat(field.type).isNotNull()
}
// java.lang.String in Android only has a single public field
assertThat(fields?.size).isEqualTo(1)
val field = fields?.firstOrNull() as JavaField?
assertThat(field).isNotNull()
assertThat(field?.name).isEqualTo("CASE_INSENSITIVE_ORDER")
assertThat(field?.type?.name).isEqualTo("java/util/Comparator")
assertThat(field?.type?.internalForm()).isEqualTo("Ljava/util/Comparator;")
assertThat(field?.type?.kind).isEqualTo(JavaType.KIND_REF)
assertThat(Modifier.isPublic(field?.accessFlags ?: 0)).isTrue()
assertThat(Modifier.isStatic(field?.accessFlags ?: 0)).isTrue()
assertThat(Modifier.isFinal(field?.accessFlags ?: 0)).isTrue()
}
@Test
fun `test JavaMethod properties`() {
val sampleClass = types.firstOrNull { it.fqn == "java/lang/String" }
val methods = sampleClass?.methods
assertThat(methods).isNotEmpty()
methods?.forEach { mth ->
val method = mth as JavaMethod
assertThat(method.name).isNotEmpty()
assertThat(method.accessFlags).isGreaterThan(0)
assertThat(method.paramsTypes).isNotNull()
assertThat(method.returnType).isNotNull()
}
assertThat(methods?.size).isAtLeast(1)
val method = (methods as List<JavaMethod>?)?.firstOrNull { it.name == "compareTo" }
assertThat(method).isNotNull()
assertThat(method?.name).isEqualTo("compareTo")
assertThat(method?.paramsTypes?.size).isEqualTo(1)
assertThat(method?.returnType?.name).isEqualTo("I")
assertThat(Modifier.isPublic(method?.accessFlags ?: 0)).isTrue()
assertThat(Modifier.isStatic(method?.accessFlags ?: 0)).isFalse()
assertThat(Modifier.isFinal(method?.accessFlags ?: 0)).isFalse()
val param = method?.paramsTypes?.firstOrNull()
assertThat(param).isNotNull()
assertThat(param?.name).isEqualTo("java/lang/String")
assertThat(param?.internalForm()).isEqualTo("Ljava/lang/String;")
assertThat(param?.kind).isEqualTo(JavaType.KIND_REF)
assertThat(param?.arrayDims).isEqualTo(0)
}
@Test
fun `test JavaType properties`() {
val sampleClass = types.firstOrNull { it.fqn == "java/lang/String" }
val fields = sampleClass?.fields
val methods = sampleClass?.methods
fields?.forEach { f ->
val field = f as JavaField
assertThat(field.type?.name).isNotEmpty()
assertThat(field.type?.kind).isNotEqualTo(JavaType.KIND_UNKNOWN)
}
methods?.forEach { mth ->
val method = mth as JavaMethod
method.paramsTypes?.forEach { paramType ->
assertThat(paramType.name).isNotEmpty()
assertThat(paramType.kind).isNotEqualTo(JavaType.KIND_UNKNOWN)
}
assertThat(method.returnType?.name).isNotEmpty()
assertThat(method.returnType?.kind).isNotEqualTo(JavaType.KIND_UNKNOWN)
}
}
@Test
fun `test integer constant field values`() {
val viewClass: IJavaType<JavaField, JavaMethod> =
types.first { it.fqn == "android/view/View" } as IJavaType<JavaField, JavaMethod>
assertThat(viewClass.fields).isNotEmpty()
val fields = viewClass.fields!!
val visible = fields.firstOrNull { it.name == "VISIBLE" }
assertThat(visible).isNotNull()
assertThat(visible?.type).isEqualTo(JavaType.INT)
val constantValue = visible?.constantValue
assertThat(constantValue).isNotNull()
assertThat(constantValue?.kind).isEqualTo(JavaType.KIND_INT)
assertThat(constantValue?.value?.type).isEqualTo(RealmAny.Type.INTEGER)
assertThat(constantValue?.asInt()).isNotNull()
assertThat(constantValue?.asInt()).isEqualTo(View.VISIBLE)
}
@Test
fun `test object type`() {
val obj = types.firstOrNull { it.fqn == "java/lang/Object" }
assertThat(obj).isNotNull()
assertThat(obj?.name).isEqualTo("Object")
assertThat(obj?.superClassFqn).isNull()
assertThat(obj?.superInterfacesFqn).isEmpty()
assertThat(obj?.fields).isEmpty()
assertThat(obj?.methods).isNotEmpty()
assertThat(obj?.isAnnotation).isFalse()
assertThat(obj?.isInterface).isFalse()
assertThat(obj?.isEnum).isFalse()
assertThat(obj?.isClass).isTrue()
assertThat(obj).isInstanceOf(JavaClass::class.java)
assertThat(obj?.packageName).isEqualTo("java/lang")
val access = AccessFlags(obj!!.accessFlags)
assertThat(access.`is`(AccessFlags.ACC_PUBLIC)).isTrue()
assertThat(access.`is`(AccessFlags.ACC_STATIC)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_INTERFACE)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_ANNOTATION)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_ENUM)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_ABSTRACT)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_FINAL)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_NATIVE)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_NATIVE)).isFalse()
}
@Test
fun `test class simple name`() {
val obj = types.firstOrNull { it.fqn == "android/view/View" }
assertThat(obj).isNotNull()
assertThat(obj?.name).isEqualTo("View")
assertThat(obj?.packageName).isEqualTo("android/view")
assertThat(obj?.superClassFqn).isEqualTo("java/lang/Object")
assertThat(obj?.superInterfacesFqn).isNotEmpty()
assertThat(obj?.fields).isNotEmpty()
assertThat(obj?.methods).isNotEmpty()
assertThat(obj?.isAnnotation).isFalse()
assertThat(obj?.isInterface).isFalse()
assertThat(obj?.isEnum).isFalse()
assertThat(obj?.isClass).isTrue()
assertThat(obj).isInstanceOf(JavaClass::class.java)
val access = AccessFlags(obj!!.accessFlags)
assertThat(access.`is`(AccessFlags.ACC_PUBLIC)).isTrue()
assertThat(access.`is`(AccessFlags.ACC_STATIC)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_INTERFACE)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_ANNOTATION)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_ENUM)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_ABSTRACT)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_FINAL)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_NATIVE)).isFalse()
assertThat(access.`is`(AccessFlags.ACC_NATIVE)).isFalse()
}
@Test
fun `test class superclass name`() {
types.firstOrNull { it.fqn == "android/view/View" }.also {
assertThat(it).isNotNull()
assertThat(it?.superClassFqn).isEqualTo("java/lang/Object")
}
}
@Test
fun `test class super interface names`() {
types.firstOrNull { it.fqn == "android/view/View" }.also {
assertThat(it).isNotNull()
assertThat(it?.superInterfacesFqn).containsExactly(
"android/graphics/drawable/Drawable\$Callback",
"android/view/KeyEvent\$Callback",
"android/view/accessibility/AccessibilityEventSource"
)
}
}
@Test
fun `test interface super interface names`() {
types.firstOrNull { it.fqn == "java/io/Closeable" }.also {
assertThat(it).isNotNull()
assertThat(it?.superInterfacesFqn).containsExactly("java/lang/AutoCloseable")
}
}
@Test
fun `test class fields`() {
types.firstOrNull { it.fqn == "android/view/View" }.also { view ->
assertThat(view).isNotNull()
assertThat(view?.fields?.map { field -> (field as JavaField).name }).containsAtLeast(
"VISIBLE",
"INVISIBLE",
"GONE",
"FOCUS_DOWN",
"FOCUS_UP",
"FOCUS_LEFT",
"FOCUS_RIGHT",
"FOCUS_BACKWARD",
"FOCUS_FORWARD",
)
}
types.firstOrNull { it.fqn == "android/view/ViewGroup\$LayoutParams" }.also { view ->
assertThat(view).isNotNull()
assertThat(view?.fields?.map { field -> (field as JavaField).name }).containsAtLeast(
"WRAP_CONTENT",
"MATCH_PARENT",
"FILL_PARENT",
)
}
}
@Test
fun `test interface fields`() {
types.firstOrNull { it.fqn == "android/provider/CalendarContract\$CalendarSyncColumns" }
.also { view ->
assertThat(view).isNotNull()
assertThat(view?.fields?.map { field -> (field as JavaField).name }).containsAtLeast(
"CAL_SYNC1",
"CAL_SYNC2",
"CAL_SYNC3",
"CAL_SYNC4",
"CAL_SYNC5",
"CAL_SYNC6",
"CAL_SYNC7",
"CAL_SYNC8",
"CAL_SYNC9",
"CAL_SYNC10",
)
}
}
@Test
fun `test mandatory enum fields`() {
types.filterIsInstance<JavaEnum>().forEach { enum ->
assertThat(enum.fields?.map { it.name }).contains("\$VALUES")
}
}
@Test
fun `test enum fields`() {
types.firstOrNull { it.fqn == "android/net/wifi/SupplicantState" }.also { enum ->
assertThat(enum?.fields?.map { (it as JavaField).name })
.containsAtLeast(
"DISCONNECTED",
"INTERFACE_DISABLED",
"INACTIVE",
"SCANNING",
"AUTHENTICATING",
"ASSOCIATING",
"ASSOCIATED",
"FOUR_WAY_HANDSHAKE",
"COMPLETED",
"GROUP_HANDSHAKE",
"DORMANT",
"UNINITIALIZED",
"INVALID"
)
}
}
@Test
fun `test annotation fields`() {
// android.jar doesn't seem to contain annotation classes with some fields
// TODO: add test for verifying how annotation fields are handled
}
@Test
fun `test class methods`() {
types.firstOrNull { it.fqn == "android/view/View" }.also { view ->
assertThat(view).isNotNull()
assertThat(view?.methods?.map { field -> (field as JavaMethod).signature() }).containsAtLeast(
"setId(I)V",
"getId()I",
"setOnClickListener(Landroid/view/View\$OnClickListener;)V",
"hasOnClickListeners()Z",
"hasOnLongClickListeners()Z",
"setAccessibilityDelegate(Landroid/view/View\$AccessibilityDelegate;)V",
"getAccessibilityDelegate()Landroid/view/View\$AccessibilityDelegate;",
"setAccessibilityLiveRegion(I)V",
"getAccessibilityLiveRegion()I",
"setAccessibilityHeading(Z)V",
"isAccessibilityHeading()Z",
"setPadding(IIII)V",
"setPaddingRelative(IIII)V",
)
}
}
@Test
fun `test interface methods`() {
types.firstOrNull { it.fqn == "android/view/View\$OnClickListener" }.also { view ->
assertThat(view).isNotNull()
assertThat(view?.methods?.map { field -> (field as JavaMethod).signature() })
.contains("onClick(Landroid/view/View;)V")
}
types.firstOrNull { it.fqn == "android/view/View\$OnLongClickListener" }.also { view ->
assertThat(view).isNotNull()
assertThat(view?.methods?.map { field -> (field as JavaMethod).signature() })
.containsAtLeast(
"onLongClick(Landroid/view/View;)Z",
"onLongClickUseDefaultHapticFeedback(Landroid/view/View;)Z"
)
}
}
@Test
fun `test mandatory enum methods`() {
types.filterIsInstance<JavaEnum>().forEach { enum ->
assertThat(enum.methods?.map { it.signature() }).containsAtLeast(
"values()[L${enum.fqn};",
"\$values()[L${enum.fqn};",
"valueOf(Ljava/lang/String;)L${enum.fqn};",
"<clinit>()V"
)
}
}
@Test
fun `test enum methods`() {
types.firstOrNull { it.fqn == "android/icu/text/DisplayContext" }.also { enum ->
assertThat(enum?.methods?.map { (it as JavaMethod).signature() }).contains(
"type()Landroid/icu/text/DisplayContext\$Type;"
)
}
}
@Test
fun `test annotation methods`() {
types.firstOrNull { it.fqn == "android/annotation/TargetApi" }.also { annotation ->
assertThat(annotation?.methods?.map { (it as AnnotationElement).signature() }).containsExactly(
"value()I"
)
}
}
@Test
fun `test annotation methods with default values`() {
types.firstOrNull { it.fqn == "java/lang/Deprecated" }.also { annotation ->
assertThat(annotation?.methods).isNotEmpty()
val since =
annotation?.methods?.find { (it as AnnotationElement).name == "since" } as AnnotationElement
assertThat(since.type).isEqualTo(JavaType.STRING)
var defValue = since.defaultValue
assertThat(defValue).isNotNull()
assertThat(defValue?.valueClass).isEqualTo(PrimitiveAnnotationElementValue::class.java)
var defVal = defValue?.asRealmModel(PrimitiveAnnotationElementValue::class.java)
assertThat(defVal).isNotNull()
assertThat(defVal?.kind).isEqualTo(IAnnotationElementValue.KIND_STRING)
assertThat(defVal?.value).isNotNull()
assertThat(defVal?.value?.kind).isEqualTo(JavaType.KIND_REF)
assertThat(defVal?.value?.asString()).isEqualTo("")
assertThat(defVal?.value?.asBoolean()).isNull()
assertThat(defVal?.value?.asByte()).isNull()
assertThat(defVal?.value?.asShort()).isNull()
assertThat(defVal?.value?.asChar()).isNull()
assertThat(defVal?.value?.asInt()).isNull()
assertThat(defVal?.value?.asLong()).isNull()
assertThat(defVal?.value?.asFloat()).isNull()
assertThat(defVal?.value?.asDouble()).isNull()
val forRemoval =
annotation.methods?.find { (it as AnnotationElement).name == "forRemoval" } as AnnotationElement
assertThat(forRemoval.type).isEqualTo(JavaType.BOOLEAN)
defValue = forRemoval.defaultValue
assertThat(defValue).isNotNull()
assertThat(defValue?.valueClass).isEqualTo(PrimitiveAnnotationElementValue::class.java)
defVal = defValue?.asRealmModel(PrimitiveAnnotationElementValue::class.java)
assertThat(defVal).isNotNull()
assertThat(defVal?.kind).isEqualTo(IAnnotationElementValue.KIND_BOOLEAN)
assertThat(defVal?.value).isNotNull()
assertThat(defVal?.value?.kind).isEqualTo(JavaType.KIND_BOOLEAN)
assertThat(defVal?.value?.asBoolean()).isEqualTo(false)
assertThat(defVal?.value?.asByte()).isNull()
assertThat(defVal?.value?.asShort()).isNull()
assertThat(defVal?.value?.asChar()).isNull()
assertThat(defVal?.value?.asInt()).isNull()
assertThat(defVal?.value?.asLong()).isNull()
assertThat(defVal?.value?.asFloat()).isNull()
assertThat(defVal?.value?.asDouble()).isNull()
assertThat(defVal?.value?.asString()).isNull()
}
}
@Test
fun `test annotation methods with array value`() {
types.firstOrNull { it.fqn == "java/lang/SuppressWarnings" }.also { annotation ->
assertThat(annotation?.methods).isNotEmpty()
val value =
annotation?.methods?.find { (it as AnnotationElement).name == "value" } as AnnotationElement
assertThat(value.type?.kind).isEqualTo(JavaType.KIND_REF)
assertThat(value.type?.name).isEqualTo("java/lang/String")
assertThat(value.type?.arrayDims).isEqualTo(1)
val defValue = value.defaultValue
assertThat(defValue).isNull()
}
}
@Test
fun `test class type classification`() {
types.firstOrNull { it.fqn == "android/view/View" }.also {
assertThat(it).isNotNull()
assertThat(it?.isClass).isTrue()
assertThat(it?.isInterface).isFalse()
assertThat(it?.isAnnotation).isFalse()
assertThat(it?.isEnum).isFalse()
assertThat(it).isInstanceOf(JavaClass::class.java)
}
}
@Test
fun `test interface type classification`() {
types.firstOrNull { it.fqn == "android/view/View\$OnClickListener" }.also {
assertThat(it).isNotNull()
assertThat(it?.isClass).isFalse()
assertThat(it?.isInterface).isTrue()
assertThat(it?.isAnnotation).isFalse()
assertThat(it?.isEnum).isFalse()
assertThat(it).isInstanceOf(JavaInterface::class.java)
}
}
@Test
fun `test annotation type classification`() {
types.firstOrNull { it.fqn == "java/lang/Deprecated" }.also {
assertThat(it).isNotNull()
assertThat(it?.isClass).isFalse()
assertThat(it?.isInterface).isTrue()
assertThat(it?.isAnnotation).isTrue()
assertThat(it?.isEnum).isFalse()
assertThat(it).isInstanceOf(JavaAnnotation::class.java)
}
}
@Test
fun `test enum type classification`() {
types.firstOrNull { it.fqn == "android/icu/text/DisplayContext" }.also {
assertThat(it).isNotNull()
assertThat(it?.isClass).isFalse()
assertThat(it?.isInterface).isFalse()
assertThat(it?.isAnnotation).isFalse()
assertThat(it?.isEnum).isTrue()
assertThat(it).isInstanceOf(JavaEnum::class.java)
}
}
}
-1
View File
@@ -126,7 +126,6 @@ include(
":core:actions",
":core:app",
":core:common",
":core:database",
":core:indexing-api",
":core:indexing-core",
":core:lsp-api",
-5
View File
@@ -42,11 +42,6 @@ configurations.api {
dependencies {
// compileOnly so that we can provide the APIs for the related test rules
// if a module needs to use those APIs, it needs to explicitly add the database module as a
// dependency
compileOnly(projects.core.database)
api(libs.google.protobuf)
api(libs.tests.androidx.espresso.core)
api(libs.tests.androidx.espresso.contrib)
-1
View File
@@ -66,7 +66,6 @@ dependencies {
androidTestImplementation(libs.androidx.benchmark.junit4)
androidTestImplementation(projects.core.common)
androidTestImplementation(projects.core.database)
androidTestImplementation(projects.core.indexingApi)
androidTestImplementation(projects.java.lsp)
androidTestImplementation(projects.utilities.shared)