mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix: add base files for the indexing API
This commit is contained in:
@@ -12,6 +12,7 @@ plugins {
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
id("realm-android")
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("com.itsaky.androidide.desugaring")
|
||||
}
|
||||
@@ -51,7 +52,11 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
kapt { arguments { arg("eventBusIndex", "${BuildConfig.packageName}.events.AppEventsIndex") } }
|
||||
kapt {
|
||||
arguments {
|
||||
arg("eventBusIndex", "${BuildConfig.packageName}.events.AppEventsIndex")
|
||||
}
|
||||
}
|
||||
|
||||
desugaring {
|
||||
replacements {
|
||||
@@ -142,6 +147,7 @@ dependencies {
|
||||
implementation(projects.gradlePluginConfig)
|
||||
implementation(projects.idestats)
|
||||
implementation(projects.subprojects.aaptcompiler)
|
||||
implementation(projects.subprojects.indexing)
|
||||
implementation(projects.subprojects.javacServices)
|
||||
implementation(projects.subprojects.xmlUtils)
|
||||
implementation(projects.subprojects.projects)
|
||||
@@ -166,4 +172,4 @@ dependencies {
|
||||
|
||||
testImplementation(projects.testing.unit)
|
||||
androidTestImplementation(projects.testing.android)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import androidx.core.view.updatePaddingRelative
|
||||
import com.blankj.utilcode.constant.MemoryConstants
|
||||
import com.blankj.utilcode.util.ConvertUtils.byte2MemorySize
|
||||
import com.blankj.utilcode.util.FileUtils
|
||||
import com.blankj.utilcode.util.KeyboardUtils
|
||||
import com.blankj.utilcode.util.ThreadUtils
|
||||
import com.github.mikephil.charting.components.AxisBase
|
||||
import com.github.mikephil.charting.data.Entry
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.work.NetworkType
|
||||
import androidx.work.Operation
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import ch.qos.logback.classic.Logger
|
||||
import com.blankj.utilcode.util.ThrowableUtils.getFullStackTrace
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.itsaky.androidide.BuildConfig
|
||||
@@ -44,8 +43,6 @@ import com.itsaky.androidide.events.EditorEventsIndex
|
||||
import com.itsaky.androidide.events.LspApiEventsIndex
|
||||
import com.itsaky.androidide.events.LspJavaEventsIndex
|
||||
import com.itsaky.androidide.events.ProjectsApiEventsIndex
|
||||
import com.itsaky.androidide.logging.LogcatAppender
|
||||
import com.itsaky.androidide.logging.encoder.IDELogFormatEncoder
|
||||
import com.itsaky.androidide.preferences.internal.DevOpsPreferences
|
||||
import com.itsaky.androidide.preferences.internal.GeneralPreferences
|
||||
import com.itsaky.androidide.preferences.internal.StatPreferences
|
||||
@@ -62,18 +59,19 @@ 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
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.lsposed.hiddenapibypass.HiddenApiBypass
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.lang.Thread.UncaughtExceptionHandler
|
||||
import java.time.Duration
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
class IDEApplication : TermuxApplication() {
|
||||
|
||||
private var uncaughtExceptionHandler: UncaughtExceptionHandler? = null
|
||||
@@ -97,7 +95,8 @@ class IDEApplication : TermuxApplication() {
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setVmPolicy(
|
||||
StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()).penaltyLog().detectAll().build())
|
||||
StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()).penaltyLog().detectAll().build()
|
||||
)
|
||||
if (DevOpsPreferences.dumpLogs) {
|
||||
startLogcatReader()
|
||||
}
|
||||
@@ -109,6 +108,8 @@ class IDEApplication : TermuxApplication() {
|
||||
|
||||
EventBus.getDefault().register(this)
|
||||
|
||||
Realm.init(this)
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(GeneralPreferences.uiMode)
|
||||
|
||||
if (IThemeManager.getInstance().getCurrentTheme() == IDETheme.MATERIAL_YOU) {
|
||||
@@ -123,26 +124,6 @@ class IDEApplication : TermuxApplication() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCrash(thread: Thread, th: Throwable) {
|
||||
writeException(th)
|
||||
|
||||
try {
|
||||
|
||||
val intent = Intent()
|
||||
intent.action = CrashHandlerActivity.REPORT_ACTION
|
||||
intent.putExtra(CrashHandlerActivity.TRACE_KEY, getFullStackTrace(th))
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
if (uncaughtExceptionHandler != null) {
|
||||
uncaughtExceptionHandler!!.uncaughtException(thread, th)
|
||||
}
|
||||
|
||||
exitProcess(1)
|
||||
} catch (error: Throwable) {
|
||||
log.error("Unable to show crash handler activity", error)
|
||||
}
|
||||
}
|
||||
|
||||
fun showChangelog() {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
var version = BuildInfo.VERSION_NAME_SIMPLE
|
||||
@@ -168,14 +149,17 @@ class IDEApplication : TermuxApplication() {
|
||||
|
||||
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
|
||||
val request = PeriodicWorkRequestBuilder<StatUploadWorker>(Duration.ofHours(24)).setInputData(
|
||||
AndroidIDEStats.statData.toInputData()).setConstraints(constraints)
|
||||
AndroidIDEStats.statData.toInputData()
|
||||
).setConstraints(constraints)
|
||||
.addTag(StatUploadWorker.WORKER_WORK_NAME).build()
|
||||
|
||||
val workManager = WorkManager.getInstance(this)
|
||||
|
||||
log.info("reportStatsIfNecessary: Enqueuing StatUploadWorker...")
|
||||
val operation = workManager.enqueueUniquePeriodicWork(StatUploadWorker.WORKER_WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||
val operation = workManager.enqueueUniquePeriodicWork(
|
||||
StatUploadWorker.WORKER_WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE, request
|
||||
)
|
||||
|
||||
operation.state.observeForever(object : Observer<Operation.State> {
|
||||
override fun onChanged(value: Operation.State) {
|
||||
@@ -185,22 +169,6 @@ class IDEApplication : TermuxApplication() {
|
||||
})
|
||||
}
|
||||
|
||||
private fun startLogcatReader() {
|
||||
if (ideLogcatReader != null) {
|
||||
// already started
|
||||
return
|
||||
}
|
||||
|
||||
log.info("Starting logcat reader...")
|
||||
ideLogcatReader = IDELogcatReader().also { it.start() }
|
||||
}
|
||||
|
||||
private fun stopLogcatReader() {
|
||||
log.info("Stopping logcat reader...")
|
||||
ideLogcatReader?.stop()
|
||||
ideLogcatReader = null
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onPrefChanged(event: PreferenceChangeEvent) {
|
||||
val enabled = event.value as? Boolean?
|
||||
@@ -230,6 +198,26 @@ class IDEApplication : TermuxApplication() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCrash(thread: Thread, th: Throwable) {
|
||||
writeException(th)
|
||||
|
||||
try {
|
||||
|
||||
val intent = Intent()
|
||||
intent.action = CrashHandlerActivity.REPORT_ACTION
|
||||
intent.putExtra(CrashHandlerActivity.TRACE_KEY, getFullStackTrace(th))
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(intent)
|
||||
if (uncaughtExceptionHandler != null) {
|
||||
uncaughtExceptionHandler!!.uncaughtException(thread, th)
|
||||
}
|
||||
|
||||
exitProcess(1)
|
||||
} catch (error: Throwable) {
|
||||
log.error("Unable to show crash handler activity", error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelStatUploadWorker() {
|
||||
log.info("Opted-out of stat collection. Cancelling StatUploadWorker if enqueued...")
|
||||
val operation = WorkManager.getInstance(this)
|
||||
@@ -242,6 +230,22 @@ class IDEApplication : TermuxApplication() {
|
||||
})
|
||||
}
|
||||
|
||||
private fun startLogcatReader() {
|
||||
if (ideLogcatReader != null) {
|
||||
// already started
|
||||
return
|
||||
}
|
||||
|
||||
log.info("Starting logcat reader...")
|
||||
ideLogcatReader = IDELogcatReader().also { it.start() }
|
||||
}
|
||||
|
||||
private fun stopLogcatReader() {
|
||||
log.info("Stopping logcat reader...")
|
||||
ideLogcatReader?.stop()
|
||||
ideLogcatReader = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = LoggerFactory.getLogger(IDEApplication::class.java)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.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
|
||||
*/
|
||||
data class DatabaseEntity(
|
||||
@Required
|
||||
@PrimaryKey
|
||||
@RealmField("path")
|
||||
var path: String = "",
|
||||
|
||||
@Required
|
||||
@RealmField("name")
|
||||
var name: String = "",
|
||||
|
||||
@Required
|
||||
@RealmField("directory")
|
||||
var directory: String = "",
|
||||
) : RealmObject()
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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 com.google.auto.service.AutoService
|
||||
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")
|
||||
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 = createDb(MASTER_DB_PATH, null)
|
||||
|
||||
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")
|
||||
|
||||
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> {
|
||||
require(path.matches(dbNameRegex)) {
|
||||
"Invalid DB path: $path"
|
||||
}
|
||||
|
||||
val idx = path.indexOfLast { it == IRealmProvider.PATH_SEPARATOR }
|
||||
require(idx > 0) {
|
||||
"Invalid DB path: $path"
|
||||
}
|
||||
|
||||
val parentPath = path.substring(0, idx)
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath(libs.kotlin.gradle.plugin)
|
||||
classpath(libs.nav.safe.args.gradle.plugin)
|
||||
classpath(libs.realm.gradle.plugin)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
package com.itsaky.androidide.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@SuppressLint("SdCardPath")
|
||||
public final class Environment {
|
||||
@@ -47,6 +51,7 @@ public final class Environment {
|
||||
public static File BIN_DIR;
|
||||
public static File LIB_DIR;
|
||||
public static File PROJECTS_DIR;
|
||||
public static File REALM_DB_DIR;
|
||||
|
||||
/**
|
||||
* Used by Java LSP until the project is initialized.
|
||||
@@ -64,8 +69,8 @@ public final class Environment {
|
||||
|
||||
public static void init() {
|
||||
ROOT = mkdirIfNotExits(new File(DEFAULT_ROOT));
|
||||
PREFIX = mkdirIfNotExits(new File(ROOT, "usr"));
|
||||
HOME = mkdirIfNotExits(new File(ROOT, "home"));
|
||||
PREFIX = mkdirIfNotExits(new File(DEFAULT_PREFIX));
|
||||
HOME = mkdirIfNotExits(new File(DEFAULT_HOME));
|
||||
ANDROIDIDE_HOME = mkdirIfNotExits(new File(HOME, ".androidide"));
|
||||
TMP_DIR = mkdirIfNotExits(new File(PREFIX, "tmp"));
|
||||
BIN_DIR = mkdirIfNotExits(new File(PREFIX, "bin"));
|
||||
@@ -73,9 +78,10 @@ public final class Environment {
|
||||
PROJECTS_DIR = mkdirIfNotExits(new File(FileUtil.getExternalStorageDir(), PROJECTS_FOLDER));
|
||||
ANDROID_JAR = mkdirIfNotExits(new File(ANDROIDIDE_HOME, "android.jar"));
|
||||
TOOLING_API_JAR = new File(mkdirIfNotExits(new File(ANDROIDIDE_HOME, "tooling-api")),
|
||||
"tooling-api-all.jar");
|
||||
"tooling-api-all.jar");
|
||||
AAPT2 = new File(ANDROIDIDE_HOME, "aapt2");
|
||||
ANDROIDIDE_UI = mkdirIfNotExits(new File(ANDROIDIDE_HOME, "ui"));
|
||||
REALM_DB_DIR = mkdirIfNotExits(new File(ROOT, "realm-dbs"));
|
||||
|
||||
INIT_SCRIPT = new File(mkdirIfNotExits(new File(ANDROIDIDE_HOME, "init")), "init.gradle");
|
||||
GRADLE_USER_HOME = new File(HOME, ".gradle");
|
||||
|
||||
@@ -18,6 +18,7 @@ retrofit = "2.9.0"
|
||||
markwon = "4.6.2"
|
||||
maven-publish-plugin = "0.27.0"
|
||||
logback = "1.5.3"
|
||||
realm = "10.18.0"
|
||||
|
||||
[libraries]
|
||||
|
||||
@@ -156,6 +157,8 @@ 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" }
|
||||
|
||||
@@ -21,10 +21,17 @@ plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("realm-android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "${BuildConfig.packageName}.lsp.java"
|
||||
|
||||
sourceSets {
|
||||
getByName("androidTest") {
|
||||
assets.srcDirs(rootProject.file("subprojects/framework-stubs/libs"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
@@ -54,9 +61,11 @@ dependencies {
|
||||
implementation(projects.editorApi)
|
||||
implementation(projects.resources)
|
||||
implementation(projects.lsp.api)
|
||||
implementation(projects.subprojects.indexing)
|
||||
implementation(projects.subprojects.javacServices)
|
||||
|
||||
implementation(libs.composite.javac)
|
||||
implementation(libs.composite.jdkJdeps)
|
||||
implementation(libs.composite.javapoet)
|
||||
implementation(libs.composite.googleJavaFormat)
|
||||
|
||||
@@ -64,4 +73,5 @@ dependencies {
|
||||
implementation(libs.common.kotlin)
|
||||
|
||||
testImplementation(projects.testing.lsp)
|
||||
androidTestImplementation(projects.testing.android)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.RealmModel
|
||||
import io.realm.log.LogLevel
|
||||
import io.realm.log.RealmLog
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class JavaIndexerTest {
|
||||
|
||||
@Test
|
||||
fun testSimpleRealmInit() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().context
|
||||
Realm.init(context.applicationContext)
|
||||
|
||||
val file = File.createTempFile("ajar", null, context.cacheDir)
|
||||
context.assets.open("android.jar").use { asset ->
|
||||
assertThat(asset).isNotNull()
|
||||
file.outputStream().buffered().use { out ->
|
||||
asset.copyTo(out)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
val dbDir = context.cacheDir.resolve("realm-db")
|
||||
if (dbDir.exists()) {
|
||||
if (dbDir.isDirectory) {
|
||||
dbDir.deleteRecursively()
|
||||
} else {
|
||||
dbDir.delete()
|
||||
}
|
||||
}
|
||||
|
||||
RealmLog.setLevel(LogLevel.ALL)
|
||||
val realm = Realm.getInstance(
|
||||
RealmConfiguration.Builder()
|
||||
.directory(dbDir)
|
||||
.name("android-jar-classes.realm")
|
||||
.deleteRealmIfMigrationNeeded()
|
||||
.build()
|
||||
)
|
||||
|
||||
try {
|
||||
val start = System.currentTimeMillis()
|
||||
val worker = JavaIndexWorker(file)
|
||||
val batches = mutableMapOf<Class<*>, MutableList<RealmModel>>()
|
||||
|
||||
worker.consumeTypes { type ->
|
||||
val batched = batches.computeIfAbsent(type.javaClass) { mutableListOf() }
|
||||
batched.add(type)
|
||||
|
||||
if (batched.size >= 100) {
|
||||
realm.executeTransaction {
|
||||
realm.insertOrUpdate(batched)
|
||||
}
|
||||
|
||||
batched.clear()
|
||||
}
|
||||
}
|
||||
|
||||
for ((_, batched) in batches) {
|
||||
if (batched.isNotEmpty()) {
|
||||
realm.executeTransaction {
|
||||
realm.insertOrUpdate(batched)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println("Took ${System.currentTimeMillis() - start}ms")
|
||||
} finally {
|
||||
realm.close()
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.models.AnnotationElement
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.IJavaType
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaField
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaMethod
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* 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.models.AnnotationAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.AnnotationElement
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.AnnotationElement.Companion.newAnnotationElement
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.ArrayAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.ClassAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.EnumAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.IAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.IJavaType
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaAnnotation
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaClass
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaConstant
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaEnum
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaField
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaInterface
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaMethod
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaType
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.PrimitiveAnnotationElementValue
|
||||
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
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class JavaIndexWorker(
|
||||
private val jar: File
|
||||
) {
|
||||
|
||||
fun buildTypes(): List<IJavaType<*, *>> {
|
||||
val types = mutableListOf<IJavaType<*, *>>()
|
||||
consumeTypes { types.add(it) }
|
||||
return types
|
||||
}
|
||||
|
||||
fun consumeTypes(consumer: (IJavaType<*, *>) -> Unit) {
|
||||
ZipFile(jar).use { jar ->
|
||||
for (entry in jar.entries()) {
|
||||
if (!entry.name.endsWith(".class")) {
|
||||
continue
|
||||
}
|
||||
|
||||
jar.getInputStream(entry).use { inputStream ->
|
||||
val classFile = ClassFile.read(inputStream)!!
|
||||
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 name = simpleName(fqn)
|
||||
val pck = fqn.substring(0, fqn.length - name.length - 1)
|
||||
return buildType(fqn, name, pck, classFile)
|
||||
}
|
||||
|
||||
private fun buildType(
|
||||
fqn: String, name: String, pck: String, classFile: ClassFile
|
||||
): IJavaType<*, *> {
|
||||
if (classFile.access_flags.`is`(AccessFlags.ACC_ENUM)) {
|
||||
return buildEnum(fqn, name, pck, classFile)
|
||||
}
|
||||
|
||||
if (classFile.access_flags.`is`(AccessFlags.ACC_ANNOTATION)) {
|
||||
return buildAnnotation(fqn, name, pck, classFile)
|
||||
}
|
||||
|
||||
if (classFile.access_flags.`is`(AccessFlags.ACC_INTERFACE)) {
|
||||
return buildInterface(fqn, name, pck, classFile)
|
||||
}
|
||||
|
||||
return buildClass(fqn, name, pck, classFile)
|
||||
}
|
||||
|
||||
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 {
|
||||
addAll(classFile.methods.map { 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))
|
||||
}
|
||||
|
||||
JavaMethod.newInstance(methodName, paramTypes, methodType, method.access_flags.flags)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun IJavaType<JavaField, *>.setFields(classFile: ClassFile) {
|
||||
fields = RealmList<JavaField>().apply {
|
||||
addAll(classFile.fields.map { field ->
|
||||
val descriptor = classFile.constant_pool.getUTF8Value(field.descriptor.index)
|
||||
val fieldName = field.getName(classFile.constant_pool)
|
||||
val fieldType = DescriptorUtils.type(descriptor)
|
||||
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 {
|
||||
addAll(classFile.methods.map { method ->
|
||||
val descriptor = classFile.constant_pool.getUTF8Value(method.descriptor.index)
|
||||
val methodName = method.getName(classFile.constant_pool)
|
||||
val methodType = DescriptorUtils.returnType(descriptor)
|
||||
|
||||
this@setAnnotationElements.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(
|
||||
kind = when (kind) {
|
||||
JavaType.KIND_BOOLEAN -> IAnnotationElementValue.KIND_BOOLEAN
|
||||
JavaType.KIND_BYTE -> IAnnotationElementValue.KIND_BYTE
|
||||
JavaType.KIND_CHAR -> IAnnotationElementValue.KIND_CHAR
|
||||
JavaType.KIND_SHORT -> IAnnotationElementValue.KIND_SHORT
|
||||
JavaType.KIND_INT -> IAnnotationElementValue.KIND_INT
|
||||
JavaType.KIND_LONG -> IAnnotationElementValue.KIND_LONG
|
||||
JavaType.KIND_FLOAT -> IAnnotationElementValue.KIND_FLOAT
|
||||
JavaType.KIND_DOUBLE -> IAnnotationElementValue.KIND_DOUBLE
|
||||
else -> throw IllegalArgumentException("Unknown value tag: $char")
|
||||
},
|
||||
|
||||
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' -> {
|
||||
val clazz = defaultValue as Class_element_value
|
||||
|
||||
// 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(
|
||||
clazz.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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
'[' -> {
|
||||
val arr = defaultValue as Array_element_value
|
||||
ArrayAnnotationElementValue.newInstance(values = RealmList<RealmAny>().apply {
|
||||
addAll(arr.values.map { element ->
|
||||
toAnnotationElementValue(element, classFile).let { elementValue ->
|
||||
RealmAny.valueOf(elementValue)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown value tag: ${defaultValue.tag}")
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private fun toPrimitiveConstant(
|
||||
constantPool: ConstantPool, kind: Short, 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: Short, 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
|
||||
)
|
||||
}
|
||||
|
||||
private fun simpleName(fqn: String): String {
|
||||
var idx = fqn.lastIndexOf('$')
|
||||
idx = if (idx == -1) fqn.lastIndexOf('/') else idx
|
||||
return fqn.substring(idx + 1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.IIndex
|
||||
import com.itsaky.androidide.indexing.IIndexer
|
||||
import com.itsaky.androidide.indexing.IIndexer.Params
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.IJavaIndexable
|
||||
import com.itsaky.androidide.tasks.cancelIfActive
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class JavaIndexer(
|
||||
private val jarPaths: List<File>
|
||||
) : IIndexer<IJavaIndexable, Unit> {
|
||||
|
||||
companion object {
|
||||
const val INDEX_PATH = "/java"
|
||||
}
|
||||
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
override val indexPath: String = INDEX_PATH
|
||||
|
||||
override fun index(index: IIndex<IJavaIndexable>, params: Params): Collection<Deferred<Unit>> {
|
||||
return jarPaths.map { jar ->
|
||||
coroutineScope.async {
|
||||
val worker = JavaIndexWorker(jar)
|
||||
// worker.index(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
coroutineScope.cancelIfActive("Cancellation requested")
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.itsaky.androidide.lsp.java.indexing.DescriptorUtils
|
||||
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("methodId")
|
||||
var methodId: String? = null
|
||||
|
||||
@Index
|
||||
@Required
|
||||
@RealmField("name")
|
||||
var name: String? = null
|
||||
|
||||
@RealmField("type")
|
||||
var type: JavaType? = null
|
||||
|
||||
@RealmField("accessFlags")
|
||||
override var accessFlags: Int = 0
|
||||
|
||||
@RealmField("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@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 JavaAnnotation.newAnnotationElement(
|
||||
name: String,
|
||||
type: JavaType,
|
||||
accessFlags: Int,
|
||||
apiInfo: ApiInfo? = null,
|
||||
defaultValue: RealmAny? = null
|
||||
): AnnotationElement {
|
||||
return AnnotationElement().apply {
|
||||
this.name = name
|
||||
this.type = type
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
this.defaultValue = defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.google.common.base.Objects
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import io.realm.annotations.RealmClass
|
||||
import io.realm.annotations.RealmField
|
||||
import io.realm.annotations.Required
|
||||
|
||||
/**
|
||||
* @property since The API in which the symbol was added.
|
||||
* @property deprecatedIn The API in which the symbol was deprecated.
|
||||
* @property removedIn The API in which the symbol was removed.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
|
||||
@RealmClass
|
||||
open class ApiInfo : ISharedJavaIndexable {
|
||||
|
||||
@Required
|
||||
@PrimaryKey
|
||||
@RealmField("id")
|
||||
override var id: Int? = null
|
||||
|
||||
@RealmField("since")
|
||||
var since: Int = 1
|
||||
|
||||
@RealmField("deprecatedIn")
|
||||
var deprecatedIn: Int = 0
|
||||
|
||||
@RealmField("removedIn")
|
||||
var removedIn: Int = 0
|
||||
|
||||
override fun computeId() {
|
||||
this.id = Objects.hashCode(this.since, this.removedIn, this.deprecatedIn)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(since: Int, deprecatedIn: Int, removedIn: Int): ApiInfo {
|
||||
return ApiInfo().apply {
|
||||
this.since = since
|
||||
this.deprecatedIn = deprecatedIn
|
||||
this.removedIn = removedIn
|
||||
this.computeId()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.itsaky.androidide.indexing.IIndexable
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface IJavaIndexable : IIndexable
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
/**
|
||||
* 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
|
||||
var apiInfo: ApiInfo?
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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?
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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("superClassFqn")
|
||||
override var superClassFqn: String? = null
|
||||
|
||||
@RealmField("superInterfacesFqn")
|
||||
override var superInterfacesFqn: RealmList<String>? = null
|
||||
|
||||
@RealmField("accessFlags")
|
||||
override var accessFlags: Int = 0
|
||||
|
||||
@RealmField("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@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,
|
||||
superInterfacesFqn: RealmList<String>? = null,
|
||||
apiInfo: ApiInfo? = null,
|
||||
fields: RealmList<JavaField>? = null,
|
||||
methods: RealmList<AnnotationElement>? = null
|
||||
): JavaAnnotation {
|
||||
return JavaAnnotation().apply {
|
||||
this.fqn = fqn
|
||||
this.name = name
|
||||
this.packageName = packageName
|
||||
this.superClassFqn = superClassFqn
|
||||
this.superInterfacesFqn = superInterfacesFqn
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
this.fields = fields
|
||||
this.methods = methods
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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("superClassFqn")
|
||||
override var superClassFqn: String? = null
|
||||
|
||||
@RealmField("superInterfacesFqn")
|
||||
override var superInterfacesFqn: RealmList<String>? = null
|
||||
|
||||
@RealmField("accessFlags")
|
||||
override var accessFlags: Int = 0
|
||||
|
||||
@RealmField("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@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,
|
||||
superClassFqn: String? = null,
|
||||
superInterfacesFqn: RealmList<String>? = null,
|
||||
apiInfo: ApiInfo? = null,
|
||||
fields: RealmList<JavaField>? = null,
|
||||
methods: RealmList<JavaMethod>? = null
|
||||
): JavaClass {
|
||||
return JavaClass().apply {
|
||||
this.fqn = fqn
|
||||
this.name = name
|
||||
this.packageName = packageName
|
||||
this.superClassFqn = superClassFqn
|
||||
this.superInterfacesFqn = superInterfacesFqn
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
this.fields = fields
|
||||
this.methods = methods
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.google.common.base.Objects
|
||||
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: Short = JavaType.KIND_UNKNOWN
|
||||
|
||||
@RealmField("value")
|
||||
var value: RealmAny? = null
|
||||
|
||||
companion object {
|
||||
fun newInstance(kind: Short, value: RealmAny): JavaConstant {
|
||||
return JavaConstant().apply {
|
||||
this.kind = kind
|
||||
this.value = 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: Short, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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("superClassFqn")
|
||||
override var superClassFqn: String? = null
|
||||
|
||||
@RealmField("superInterfacesFqn")
|
||||
override var superInterfacesFqn: RealmList<String>? = null
|
||||
|
||||
@RealmField("accessFlags")
|
||||
override var accessFlags: Int = 0
|
||||
|
||||
@RealmField("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@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,
|
||||
superClassFqn: String? = null,
|
||||
superInterfacesFqn: RealmList<String>? = null,
|
||||
apiInfo: ApiInfo? = null,
|
||||
fields: RealmList<JavaField>? = null,
|
||||
methods: RealmList<JavaMethod>? = null
|
||||
): JavaEnum {
|
||||
return JavaEnum().apply {
|
||||
this.fqn = fqn
|
||||
this.name = name
|
||||
this.packageName = packageName
|
||||
this.superClassFqn = superClassFqn
|
||||
this.superInterfacesFqn = superInterfacesFqn
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
this.fields = fields
|
||||
this.methods = methods
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@RealmField("constantValue")
|
||||
var constantValue: JavaConstant? = null
|
||||
|
||||
@LinkingObjects("fields")
|
||||
val ofClass: RealmResults<JavaClass>? = null
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newField(
|
||||
name: String,
|
||||
type: JavaType,
|
||||
accessFlags: Int,
|
||||
apiInfo: ApiInfo? = null,
|
||||
constantValue: JavaConstant? = null
|
||||
): JavaField {
|
||||
return JavaField().apply {
|
||||
this.name = name
|
||||
this.type = type
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
this.constantValue = constantValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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("superClassFqn")
|
||||
override var superClassFqn: String? = null
|
||||
|
||||
@RealmField("superInterfacesFqn")
|
||||
override var superInterfacesFqn: RealmList<String>? = null
|
||||
|
||||
@RealmField("accessFlags")
|
||||
override var accessFlags: Int = 0
|
||||
|
||||
@RealmField("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@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,
|
||||
superInterfacesFqn: RealmList<String>? = null,
|
||||
apiInfo: ApiInfo? = null,
|
||||
fields: RealmList<JavaField>? = null,
|
||||
methods: RealmList<JavaMethod>? = null
|
||||
): JavaInterface {
|
||||
return JavaInterface().apply {
|
||||
this.fqn = fqn
|
||||
this.name = name
|
||||
this.packageName = packageName
|
||||
this.superInterfacesFqn = superInterfacesFqn
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
this.fields = fields
|
||||
this.methods = methods
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
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
|
||||
|
||||
@RealmField("apiInfo")
|
||||
override var apiInfo: ApiInfo? = null
|
||||
|
||||
@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,
|
||||
apiInfo: ApiInfo? = null,
|
||||
): JavaMethod {
|
||||
return JavaMethod().apply {
|
||||
this.name = name
|
||||
this.paramsTypes = paramsTypes
|
||||
this.returnType = returnType
|
||||
this.accessFlags = accessFlags
|
||||
this.apiInfo = apiInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.google.common.base.Objects
|
||||
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
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
|
||||
@RealmClass
|
||||
open class JavaType : ISharedJavaIndexable {
|
||||
|
||||
@Required
|
||||
@PrimaryKey
|
||||
@RealmField("id")
|
||||
override var id: Int? = null
|
||||
|
||||
@Index
|
||||
@Required
|
||||
@RealmField("name")
|
||||
var name: String? = null
|
||||
|
||||
@RealmField("kind")
|
||||
var kind: Short = KIND_UNKNOWN
|
||||
|
||||
@RealmField("arrayDims")
|
||||
var arrayDims: Int = 0
|
||||
|
||||
/**
|
||||
* Returns the internal representation of the type.
|
||||
*/
|
||||
fun internalForm(): String {
|
||||
val sb = StringBuilder()
|
||||
if (arrayDims > 0) {
|
||||
sb.append("[".repeat(arrayDims))
|
||||
}
|
||||
|
||||
if (kind == KIND_REF) {
|
||||
sb.append("L")
|
||||
}
|
||||
|
||||
sb.append(name)
|
||||
|
||||
if (kind == KIND_REF) {
|
||||
sb.append(";")
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the type represents a primitive type.
|
||||
*/
|
||||
fun isPrimitive(): Boolean {
|
||||
return when (kind) {
|
||||
KIND_BOOLEAN,
|
||||
KIND_BYTE,
|
||||
KIND_CHAR,
|
||||
KIND_DOUBLE,
|
||||
KIND_FLOAT,
|
||||
KIND_INT,
|
||||
KIND_LONG,
|
||||
KIND_SHORT -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the type represents a void type.
|
||||
*/
|
||||
fun isVoid(): Boolean {
|
||||
return kind == KIND_VOID
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the type represents the `java.lang.String` type.
|
||||
*/
|
||||
fun isStringType(): Boolean {
|
||||
return name == STRING.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the type represents the `java.lang.Object` type.
|
||||
*/
|
||||
fun isObjectType(): Boolean {
|
||||
return name == OBJECT.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the type represents the `java.lang.Class` type.
|
||||
*/
|
||||
fun isClassType(): Boolean {
|
||||
return name == CLASS.name
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if (name != other.name) return false
|
||||
if (kind != other.kind) return false
|
||||
if (arrayDims != other.arrayDims) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name?.hashCode() ?: 0
|
||||
result = 31 * result + kind
|
||||
result = 31 * result + arrayDims
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "JavaType(id=$id, name=$name, kind=$kind, arrayDims=$arrayDims)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KIND_UNKNOWN: Short = -1
|
||||
const val KIND_BOOLEAN: Short = 0
|
||||
const val KIND_BYTE: Short = 1
|
||||
const val KIND_CHAR: Short = 2
|
||||
const val KIND_DOUBLE: Short = 3
|
||||
const val KIND_FLOAT: Short = 4
|
||||
const val KIND_INT: Short = 5
|
||||
const val KIND_LONG: Short = 6
|
||||
const val KIND_SHORT: Short = 7
|
||||
const val KIND_VOID: Short = 8
|
||||
const val KIND_REF: Short = 9
|
||||
|
||||
const val TYPE_BOOLEAN = 'Z'
|
||||
const val TYPE_BYTE = 'B'
|
||||
const val TYPE_CHAR = 'C'
|
||||
const val TYPE_DOUBLE = 'D'
|
||||
const val TYPE_FLOAT = 'F'
|
||||
const val TYPE_INT = 'I'
|
||||
const val TYPE_LONG = 'J'
|
||||
const val TYPE_SHORT = 'S'
|
||||
const val TYPE_VOID = 'V'
|
||||
|
||||
@JvmStatic
|
||||
val BOOLEAN = newInstance(name = TYPE_BOOLEAN, kind = KIND_INT)
|
||||
|
||||
@JvmStatic
|
||||
val BYTE = newInstance(name = TYPE_BYTE, kind = KIND_BYTE)
|
||||
|
||||
@JvmStatic
|
||||
val CHAR = newInstance(name = TYPE_CHAR, kind = KIND_CHAR)
|
||||
|
||||
@JvmStatic
|
||||
val DOUBLE = newInstance(name = TYPE_DOUBLE, kind = KIND_DOUBLE)
|
||||
|
||||
@JvmStatic
|
||||
val FLOAT = newInstance(name = TYPE_FLOAT, kind = KIND_FLOAT)
|
||||
|
||||
@JvmStatic
|
||||
val INT = newInstance(name = TYPE_INT, kind = KIND_INT)
|
||||
|
||||
@JvmStatic
|
||||
val LONG = newInstance(name = TYPE_LONG, kind = KIND_LONG)
|
||||
|
||||
@JvmStatic
|
||||
val SHORT = newInstance(name = TYPE_SHORT, kind = KIND_SHORT)
|
||||
|
||||
@JvmStatic
|
||||
val VOID = newInstance(name = TYPE_VOID, kind = KIND_VOID)
|
||||
|
||||
@JvmStatic
|
||||
val STRING = newInstance(name = "java/lang/String", kind = KIND_REF)
|
||||
|
||||
@JvmStatic
|
||||
val OBJECT = newInstance(name = "java/lang/Object", kind = KIND_REF)
|
||||
|
||||
@JvmStatic
|
||||
val CLASS = newInstance(name = "java/lang/Class", kind = KIND_REF)
|
||||
|
||||
@JvmStatic
|
||||
fun kindForType(type: Char) = when (type) {
|
||||
TYPE_BOOLEAN -> KIND_BOOLEAN
|
||||
TYPE_BYTE -> KIND_BYTE
|
||||
TYPE_CHAR -> KIND_CHAR
|
||||
TYPE_DOUBLE -> KIND_DOUBLE
|
||||
TYPE_FLOAT -> KIND_FLOAT
|
||||
TYPE_INT -> KIND_INT
|
||||
TYPE_LONG -> KIND_LONG
|
||||
TYPE_SHORT -> KIND_SHORT
|
||||
TYPE_VOID -> KIND_VOID
|
||||
else -> KIND_UNKNOWN
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun typeForKind(kind: Short) = when (kind) {
|
||||
KIND_BOOLEAN -> TYPE_BOOLEAN
|
||||
KIND_BYTE -> TYPE_BYTE
|
||||
KIND_CHAR -> TYPE_CHAR
|
||||
KIND_DOUBLE -> TYPE_DOUBLE
|
||||
KIND_FLOAT -> TYPE_FLOAT
|
||||
KIND_INT -> TYPE_INT
|
||||
KIND_LONG -> TYPE_LONG
|
||||
KIND_SHORT -> TYPE_SHORT
|
||||
KIND_VOID -> TYPE_VOID
|
||||
else -> TYPE_VOID
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun primitiveFor(char: Char): JavaType? {
|
||||
return when (char) {
|
||||
TYPE_BOOLEAN -> BOOLEAN
|
||||
TYPE_BYTE -> BYTE
|
||||
TYPE_CHAR -> CHAR
|
||||
TYPE_DOUBLE -> DOUBLE
|
||||
TYPE_FLOAT -> FLOAT
|
||||
TYPE_INT -> INT
|
||||
TYPE_LONG -> LONG
|
||||
TYPE_SHORT -> SHORT
|
||||
TYPE_VOID -> VOID
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(name: String?, kind: Short, arrayDims: Int = 0): JavaType {
|
||||
return JavaType().apply {
|
||||
this.name = name
|
||||
this.kind = kind
|
||||
this.arrayDims = arrayDims
|
||||
this.computeId()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(name: Char, kind: Short, arrayDims: Int = 0): JavaType {
|
||||
return newInstance(name.toString(), kind, arrayDims)
|
||||
}
|
||||
}
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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.models
|
||||
|
||||
import com.google.common.base.Objects
|
||||
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: Byte
|
||||
|
||||
companion object {
|
||||
const val KIND_UNKNOWN: Byte = -1
|
||||
const val KIND_BOOLEAN: Byte = 0
|
||||
const val KIND_BYTE: Byte = 1
|
||||
const val KIND_CHAR: Byte = 2
|
||||
const val KIND_DOUBLE: Byte = 3
|
||||
const val KIND_FLOAT: Byte = 4
|
||||
const val KIND_INT: Byte = 5
|
||||
const val KIND_LONG: Byte = 6
|
||||
const val KIND_SHORT: Byte = 7
|
||||
const val KIND_STRING: Byte = 8
|
||||
const val KIND_CLASS: Byte = 9
|
||||
const val KIND_ENUM: Byte = 10
|
||||
const val KIND_ANNOTATION: Byte = 11
|
||||
const val KIND_ARRAY: Byte = 12
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RealmClass
|
||||
open class PrimitiveAnnotationElementValue : IAnnotationElementValue {
|
||||
|
||||
@PrimaryKey
|
||||
@Required
|
||||
@RealmField("id")
|
||||
override var id: Int? = null
|
||||
|
||||
@RealmField("kind")
|
||||
override var kind: Byte = 0
|
||||
|
||||
@RealmField("value")
|
||||
var value: JavaConstant? = null
|
||||
|
||||
override fun computeId() {
|
||||
this.id = Objects.hashCode(this.kind, this.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(kind: Byte, 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: Byte = 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)
|
||||
}
|
||||
|
||||
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: Byte = 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)
|
||||
}
|
||||
|
||||
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: Byte = IAnnotationElementValue.KIND_CLASS
|
||||
|
||||
@RealmField("type")
|
||||
var type: JavaType? = null
|
||||
|
||||
override fun computeId() {
|
||||
this.id = Objects.hashCode(this.kind, this.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: Byte = 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?.toMap()?.hashCode())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(
|
||||
type: JavaType,
|
||||
values: RealmDictionary<RealmAny>
|
||||
): AnnotationAnnotationElementValue {
|
||||
return AnnotationAnnotationElementValue().apply {
|
||||
this.type = type
|
||||
this.values = values
|
||||
this.computeId()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.lsp.java.indexing.models.IJavaType
|
||||
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 AndroidJarIndexingRule : TestRule {
|
||||
|
||||
private val androidJar: File by lazy {
|
||||
FileProvider.projectRoot()
|
||||
.resolve("subprojects")
|
||||
.resolve("framework-stubs")
|
||||
.resolve("libs")
|
||||
.resolve("android.jar")
|
||||
.toFile()
|
||||
}
|
||||
|
||||
private val androidJarIndexer: JavaIndexWorker by lazy {
|
||||
assertThat(androidJar.exists()).isTrue()
|
||||
JavaIndexWorker(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() {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.models.JavaType
|
||||
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;")
|
||||
}
|
||||
}
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* 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.models.AnnotationElement
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.IAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.IJavaType
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaClass
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaEnum
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaField
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaMethod
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.JavaType
|
||||
import com.itsaky.androidide.lsp.java.indexing.models.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 JavaIndexWorkerTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val indexRule = AndroidJarIndexingRule()
|
||||
|
||||
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 integer value`() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import com.itsaky.androidide.lsp.api.LSPTest
|
||||
import com.itsaky.androidide.projects.api.ModuleProject
|
||||
import com.itsaky.androidide.projects.util.findAppModule
|
||||
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
|
||||
import com.itsaky.androidide.xml.versions.ApiVersions
|
||||
import com.itsaky.androidide.xml.widgets.WidgetTable
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
|
||||
@@ -140,6 +140,7 @@ include(
|
||||
":subprojects:builder-model-impl",
|
||||
":subprojects:flashbar",
|
||||
":subprojects:framework-stubs",
|
||||
":subprojects:indexing",
|
||||
":subprojects:javac-services",
|
||||
":subprojects:projects",
|
||||
":subprojects:tooling-api",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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.utils
|
||||
|
||||
import androidx.collection.mutableIntObjectMapOf
|
||||
|
||||
/**
|
||||
* Create an IntIntMap from a vararg of pairs.
|
||||
*/
|
||||
fun intByteMapOf(vararg pairs: Pair<Int, Byte>) = mutableIntObjectMapOf<Byte>().apply {
|
||||
pairs.forEach { pair -> put(pair.first, pair.second) }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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}.indexing"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
kapt(libs.google.auto.service)
|
||||
implementation(libs.google.auto.service.annotations)
|
||||
|
||||
api(projects.logger)
|
||||
api(projects.common)
|
||||
api(projects.shared)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 com.itsaky.androidide.indexing.internal.DefaultIndex
|
||||
|
||||
/**
|
||||
* An index of symbols of type [IndexableT].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface IIndex<IndexableT : IIndexable> {
|
||||
|
||||
companion object {
|
||||
private const val DEF_IS_ASYNC = true
|
||||
|
||||
/**
|
||||
* Create an index path.
|
||||
*/
|
||||
fun createIndexPath(parentPath: String, name: String): String =
|
||||
"${parentPath}${IRealmProvider.PATH_SEPARATOR}${name}"
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the index.
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* The path of the index. This does not represent the actual file path on the disk but rather used
|
||||
* to identify the index.
|
||||
*/
|
||||
val path: String
|
||||
|
||||
/**
|
||||
* Index the given symbol.
|
||||
*
|
||||
* @param symbol The symbol to index.
|
||||
*/
|
||||
fun index(symbol: IndexableT, async: Boolean = DEF_IS_ASYNC)
|
||||
|
||||
/**
|
||||
* Index the given symbols.
|
||||
*
|
||||
* @param symbols The symbols to index.
|
||||
*/
|
||||
fun indexAll(symbols: Collection<IndexableT>, async: Boolean = DEF_IS_ASYNC) = indexBatched(
|
||||
symbols,
|
||||
symbols.size
|
||||
)
|
||||
|
||||
/**
|
||||
* Index the given symbols in batches.
|
||||
*
|
||||
* @param symbols The symbols to index.
|
||||
* @param batchSize The number of symbols to index in each batch.
|
||||
*/
|
||||
fun indexBatched(
|
||||
symbols: Collection<IndexableT>,
|
||||
batchSize: Int = DefaultIndex.DEFAULT_PUT_BATCH_SIZE,
|
||||
async: Boolean = DEF_IS_ASYNC,
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a sub-index in this index.
|
||||
*
|
||||
* @param name The name of the sub-index.
|
||||
* @return The created sub-index.
|
||||
*/
|
||||
fun <I : IIndexable> createSubIndex(
|
||||
name: String,
|
||||
config: IndexConfig? = null
|
||||
): IIndex<I>
|
||||
|
||||
/**
|
||||
* Delete the index.
|
||||
*/
|
||||
fun delete()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.RealmModel
|
||||
|
||||
/**
|
||||
* An indexable object.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface IIndexable : RealmModel {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 kotlinx.coroutines.Deferred
|
||||
|
||||
/**
|
||||
* An indexer is responsible for indexing symbols.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface IIndexer<IndexableT : IIndexable, ReturnT> : AutoCloseable {
|
||||
|
||||
/**
|
||||
* The path of the index. This does not represent the actual path in the file system, but rather the path
|
||||
* that will be used to uniquely identify the index.
|
||||
*/
|
||||
val indexPath: String
|
||||
|
||||
/**
|
||||
* Perform the indexing in the given index.
|
||||
*
|
||||
* @param index The index to perform the indexing in.
|
||||
* @param params The parameters for the indexing.
|
||||
* @return The collection of deferred results.
|
||||
*/
|
||||
fun index(index: IIndex<IndexableT>, params: Params): Collection<Deferred<ReturnT>>
|
||||
|
||||
/**
|
||||
* The parameters for the indexer.
|
||||
*/
|
||||
interface Params
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 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 = '/'
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 com.itsaky.androidide.indexing.IRealmProvider.Companion.PATH_SEPARATOR
|
||||
import com.itsaky.androidide.indexing.internal.DefaultIndex
|
||||
import io.realm.RealmConfiguration
|
||||
|
||||
/**
|
||||
* The [IndexProvider] provides APIs to index symbols in a database.
|
||||
*
|
||||
* @author itsaky
|
||||
*/
|
||||
object IndexProvider {
|
||||
|
||||
const val INDEX_BASE_PATH = "${PATH_SEPARATOR}indexes"
|
||||
|
||||
/**
|
||||
* Get the index for the given indexable type in the root path.
|
||||
*
|
||||
* @param T The indexable type.
|
||||
* @param config The index configuration.
|
||||
* @param name The internal path of the index.
|
||||
*/
|
||||
fun <T : IIndexable> getRootIndex(
|
||||
name: String,
|
||||
config: IndexConfig? = null
|
||||
): IIndex<T> {
|
||||
require(name.indexOf(PATH_SEPARATOR) == -1)
|
||||
return getIndexForPath("${PATH_SEPARATOR}${name}", config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index for the given indexable type.
|
||||
*
|
||||
* @param path The internal path of the index.
|
||||
* @param config The index configuration.
|
||||
*/
|
||||
fun <T : IIndexable> getIndexForPath(
|
||||
path: String,
|
||||
config: IndexConfig? = null
|
||||
): IIndex<T> {
|
||||
val sepIdx = path.indexOfLast { it == PATH_SEPARATOR }
|
||||
val name = if (sepIdx > 0) path.substring(sepIdx + 1) else path
|
||||
val provider = IRealmProvider.instance()
|
||||
val realm = provider.get("${INDEX_BASE_PATH}${path}", config)
|
||||
val index = DefaultIndex.create<T>(name, path, realm)
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The [IndexConfig] is used to configure the index.
|
||||
*/
|
||||
typealias IndexConfig = RealmConfiguration.Builder.() -> Unit
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.internal
|
||||
|
||||
import com.itsaky.androidide.indexing.IIndex
|
||||
import com.itsaky.androidide.indexing.IIndexable
|
||||
import com.itsaky.androidide.indexing.IndexConfig
|
||||
import com.itsaky.androidide.indexing.IndexProvider
|
||||
import io.realm.Realm
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
* Default implementation of [IIndex].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class DefaultIndex<IndexableT : IIndexable> private constructor(
|
||||
override val name: String,
|
||||
override val path: String,
|
||||
private val realm: Realm,
|
||||
) : IIndex<IndexableT> {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(DefaultIndex::class.java)
|
||||
const val DEFAULT_PUT_BATCH_SIZE = 100
|
||||
|
||||
/**
|
||||
* Creates a new [IIndex] instance.
|
||||
*/
|
||||
fun <IndexableT : IIndexable> create(
|
||||
name: String,
|
||||
path: String,
|
||||
realm: Realm,
|
||||
): IIndex<IndexableT> {
|
||||
return DefaultIndex(name, path, realm)
|
||||
}
|
||||
}
|
||||
|
||||
// override fun query(condition: QueryCondition<IndexableT>): Query<IndexableT> {
|
||||
// return box.query(condition).build()
|
||||
// }
|
||||
|
||||
override fun index(symbol: IndexableT, async: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun indexBatched(symbols: Collection<IndexableT>, batchSize: Int, async: Boolean) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun <I : IIndexable> createSubIndex(
|
||||
name: String,
|
||||
config: IndexConfig?
|
||||
): IIndex<I> {
|
||||
return IndexProvider.getIndexForPath(IIndex.createIndexPath(this.path, name), config)
|
||||
}
|
||||
|
||||
override fun delete() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -152,7 +152,7 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
|
||||
dirs.addAll(mainSourceSet.sourceProvider.resDirectories!!)
|
||||
}
|
||||
|
||||
val dependencies = getCompileModuleProjects().filterIsInstance(AndroidModule::class.java)
|
||||
val dependencies = getCompileModuleProjects().filterIsInstance<AndroidModule>()
|
||||
|
||||
for (dependency in dependencies) {
|
||||
dirs.addAll(dependency.getResourceDirectories())
|
||||
|
||||
@@ -26,13 +26,25 @@ android {
|
||||
namespace = "${BuildConfig.packageName}.testing.android"
|
||||
}
|
||||
|
||||
configurations.api {
|
||||
// There is a transitive dependency on 'protobuf-lite' in 'androidTest' configurations
|
||||
// which conflicts with the 'protobuf-javalite' library we use
|
||||
// hence we exclude the dependency and add ours manually
|
||||
//+--- androidx.test.espresso:espresso-core:<...>
|
||||
// +--- com.google.android.material:material:<...>
|
||||
// \--- com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:<...>
|
||||
// +--- com.google.protobuf:protobuf-lite:3.0.1
|
||||
exclude(group = "com.google.protobuf", module = "protobuf-lite")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.google.protobuf)
|
||||
api(libs.tests.androidx.espresso.core)
|
||||
api(libs.tests.androidx.espresso.contrib)
|
||||
api(libs.tests.androidx.junit)
|
||||
api(libs.tests.androidx.test.core)
|
||||
api(libs.tests.androidx.test.runner)
|
||||
api(libs.tests.androidx.test.rules)
|
||||
api(libs.tests.androidx.junit)
|
||||
api(libs.tests.androidx.espresso.core)
|
||||
api(libs.tests.androidx.espresso.contrib)
|
||||
api(libs.tests.androidx.uiautomator)
|
||||
api(libs.tests.junit)
|
||||
api(libs.tests.google.truth)
|
||||
|
||||
@@ -18,25 +18,27 @@
|
||||
import com.itsaky.androidide.build.config.BuildConfig
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "${BuildConfig.packageName}.lsp.testing"
|
||||
namespace = "${BuildConfig.packageName}.lsp.testing"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.androidx.core.ktx)
|
||||
api(libs.common.editor)
|
||||
api(libs.common.kotlin)
|
||||
api(libs.androidx.core.ktx)
|
||||
api(libs.common.editor)
|
||||
api(libs.common.kotlin)
|
||||
|
||||
api(projects.actions)
|
||||
api(projects.common)
|
||||
api(projects.eventbusAndroid)
|
||||
api(projects.lsp.api)
|
||||
api(libs.tests.mockito.kotlin)
|
||||
|
||||
api(projects.testing.common)
|
||||
api(projects.testing.tooling)
|
||||
api(projects.testing.unit)
|
||||
api(projects.actions)
|
||||
api(projects.common)
|
||||
api(projects.eventbusAndroid)
|
||||
api(projects.lsp.api)
|
||||
|
||||
api(projects.testing.common)
|
||||
api(projects.testing.tooling)
|
||||
api(projects.testing.unit)
|
||||
}
|
||||
Reference in New Issue
Block a user