diff --git a/composite-builds/external/logback-android b/composite-builds/external/logback-android
new file mode 160000
index 000000000..d108db548
--- /dev/null
+++ b/composite-builds/external/logback-android
@@ -0,0 +1 @@
+Subproject commit d108db548b51a5c36694247cd31c3a8ff1cfca47
diff --git a/lsp/java/build.gradle.kts b/lsp/java/build.gradle.kts
index a90023832..b297a1119 100644
--- a/lsp/java/build.gradle.kts
+++ b/lsp/java/build.gradle.kts
@@ -18,60 +18,61 @@
import com.itsaky.androidide.build.config.BuildConfig
plugins {
- id("com.android.library")
- id("kotlin-android")
- id("kotlin-kapt")
- id("realm-android")
+ id("com.android.library")
+ id("kotlin-android")
+ id("kotlin-kapt")
+ id("realm-android")
}
android {
- namespace = "${BuildConfig.packageName}.lsp.java"
+ namespace = "${BuildConfig.packageName}.lsp.java"
- sourceSets {
- getByName("androidTest") {
- assets.srcDirs(rootProject.file("subprojects/framework-stubs/libs"))
- }
+ sourceSets {
+ getByName("androidTest") {
+ assets.srcDirs(rootProject.file("subprojects/framework-stubs/libs"))
}
+ }
}
kapt {
- arguments {
- arg ("eventBusIndex", "${BuildConfig.packageName}.events.LspJavaEventsIndex")
- }
+ arguments {
+ arg("eventBusIndex", "${BuildConfig.packageName}.events.LspJavaEventsIndex")
+ }
}
dependencies {
-
- kapt(projects.annotationProcessors)
-
- implementation(libs.androidide.ts)
- implementation(libs.androidide.ts.java)
- implementation(libs.common.editor)
- implementation(libs.common.javaparser)
- implementation(libs.common.utilcode)
- implementation(libs.androidx.annotation)
- implementation(libs.google.guava)
- implementation(libs.google.gson)
-
- compileOnly(libs.androidx.appcompat)
- compileOnly(libs.google.material)
- compileOnly(projects.actions)
- compileOnly(projects.common)
-
- implementation(projects.editorApi)
- implementation(projects.resources)
- implementation(projects.lsp.api)
- implementation(projects.subprojects.indexing)
- implementation(projects.subprojects.javacServices)
+ kapt(projects.annotationProcessors)
- implementation(libs.composite.javac)
- implementation(libs.composite.jdkJdeps)
- implementation(libs.composite.javapoet)
- implementation(libs.composite.googleJavaFormat)
+ implementation(libs.androidide.ts)
+ implementation(libs.androidide.ts.java)
+ implementation(libs.common.editor)
+ implementation(libs.common.javaparser)
+ implementation(libs.common.utilcode)
+ implementation(libs.androidx.annotation)
+ implementation(libs.google.guava)
+ implementation(libs.google.gson)
- implementation(libs.androidx.core.ktx)
- implementation(libs.common.kotlin)
+ compileOnly(libs.androidx.appcompat)
+ compileOnly(libs.google.material)
+ compileOnly(projects.actions)
+ compileOnly(projects.common)
- testImplementation(projects.testing.lsp)
- androidTestImplementation(projects.testing.android)
+ 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)
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.common.kotlin)
+
+ testImplementation(projects.testing.lsp)
+ androidTestImplementation(projects.testing.android)
+ androidTestImplementation(projects.common)
+ androidTestImplementation(projects.shared)
}
\ No newline at end of file
diff --git a/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/IndexingHelper.kt b/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/IndexingHelper.kt
new file mode 100644
index 000000000..1c16d3ddb
--- /dev/null
+++ b/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/IndexingHelper.kt
@@ -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 .
+ */
+
+package com.itsaky.androidide.lsp.java.indexing
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth
+import com.itsaky.androidide.db.IRealmProvider
+import com.itsaky.androidide.lsp.java.indexing.models.JavaIndexingRealmModule
+import com.itsaky.androidide.utils.Environment
+import io.realm.Realm
+import io.realm.log.LogLevel
+import io.realm.log.RealmLog
+import java.io.File
+
+/**
+ * @author Akash Yadav
+ */
+class IndexingHelper(
+ private val dbName: String
+) {
+ private val context: Context
+ get() = ApplicationProvider.getApplicationContext()
+
+ val androidJar: File by lazy {
+ val file = File.createTempFile("ajar", null, context.cacheDir)
+ context.assets.open("android.jar").use { asset ->
+ Truth.assertThat(asset).isNotNull()
+ file.outputStream().buffered().use { out ->
+ asset.copyTo(out)
+ out.flush()
+ }
+ }
+ file
+ }
+
+ fun doWithRealm(deleteDbAfterAction: Boolean = true, action: Realm.() -> Unit) {
+ Realm.init(context.applicationContext)
+ RealmLog.setLevel(LogLevel.ALL)
+
+ if (Environment.REALM_DB_DIR == null) {
+ Environment.init(context)
+ }
+
+ val dbName = dbName.replace(IRealmProvider.PATH_SEPARATOR, '-')
+ val realm = IRealmProvider.instance().get("/indexing/java/$dbName") {
+ modules(JavaIndexingRealmModule())
+ }
+ try {
+ realm.action()
+ } finally {
+ if (deleteDbAfterAction) {
+ realm.configuration.realmDirectory.deleteRecursively()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaClassCRUDTest.kt b/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaClassCRUDTest.kt
new file mode 100644
index 000000000..04888981b
--- /dev/null
+++ b/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaClassCRUDTest.kt
@@ -0,0 +1,134 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.lsp.java.indexing
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.itsaky.androidide.lsp.java.indexing.models.JavaClass
+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 io.realm.RealmList
+import io.realm.RealmObject
+import openjdk.tools.classfile.AccessFlags
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * @author Akash Yadav
+ */
+@RunWith(AndroidJUnit4::class)
+class JavaClassCRUDTest {
+
+ @Test
+ fun testSimpleJavaClassCRUD() {
+ IndexingHelper("java-class-create").doWithRealm {
+ val klass = JavaClass.newInstance(
+ fqn = "com/itsaky/androidide/indexing/TestClass",
+ name = "TestClass",
+ packageName = "com/itsaky/androidide/indexing",
+ accessFlags = AccessFlags.ACC_PUBLIC or AccessFlags.ACC_FINAL,
+ superClassFqn = "java/lang/Object",
+ superInterfacesFqn = RealmList(),
+ fields = RealmList().apply {
+ add(JavaField.newField("someString", JavaType.STRING, AccessFlags.ACC_PRIVATE))
+ },
+ methods = RealmList().apply {
+ add(
+ JavaMethod.newInstance(
+ name = "getSomeString",
+ paramsTypes = RealmList(),
+ returnType = JavaType.STRING,
+ accessFlags = AccessFlags.ACC_PUBLIC
+ )
+ )
+ })
+
+ // CREATE
+ executeTransaction {
+ insert(klass)
+ }
+
+ var dbKlass: JavaClass? = null
+ executeTransaction {
+ // READ
+ dbKlass = where(JavaClass::class.java)
+ .equalTo("fqn", "com/itsaky/androidide/indexing/TestClass")
+ .findFirst()
+
+ assertThat(dbKlass).isNotNull()
+ assertThat(dbKlass?.fqn).isEqualTo("com/itsaky/androidide/indexing/TestClass")
+ assertThat(dbKlass?.name).isEqualTo("TestClass")
+ assertThat(dbKlass?.packageName).isEqualTo("com/itsaky/androidide/indexing")
+ assertThat(dbKlass?.accessFlags).isEqualTo(AccessFlags.ACC_PUBLIC or AccessFlags.ACC_FINAL)
+ assertThat(dbKlass?.superClassFqn).isEqualTo("java/lang/Object")
+ assertThat(dbKlass?.superInterfacesFqn).isEqualTo(RealmList())
+ assertThat(dbKlass?.fields).isNotEmpty()
+ assertThat(dbKlass?.fields).hasSize(1)
+
+ dbKlass?.fields?.get(0).apply {
+ assertThat(this).isNotNull()
+ assertThat(this?.name).isEqualTo("someString")
+ assertThat(this?.type).isEqualTo(JavaType.STRING)
+ assertThat(this?.accessFlags).isEqualTo(AccessFlags.ACC_PRIVATE)
+ }
+
+ assertThat(dbKlass?.methods).isNotEmpty()
+ assertThat(dbKlass?.methods).hasSize(1)
+
+ dbKlass?.methods?.get(0).apply {
+ assertThat(this).isNotNull()
+ assertThat(this?.name).isEqualTo("getSomeString")
+ assertThat(this?.paramsTypes).isEqualTo(RealmList())
+ assertThat(this?.returnType).isEqualTo(JavaType.STRING)
+ assertThat(this?.accessFlags).isEqualTo(AccessFlags.ACC_PUBLIC)
+ }
+ }
+
+ assertThat(dbKlass).isNotNull()
+
+ // UPDATE
+ executeTransaction {
+ dbKlass?.accessFlags = AccessFlags.ACC_PUBLIC or AccessFlags.ACC_ABSTRACT
+ }
+
+ // verify UPDATE
+ executeTransaction {
+ assertThat(
+ where(JavaClass::class.java)
+ .equalTo("fqn", "com/itsaky/androidide/indexing/TestClass")
+ .findFirst()?.accessFlags
+ ).isEqualTo(AccessFlags.ACC_PUBLIC or AccessFlags.ACC_ABSTRACT)
+ }
+
+ // DELETE
+ executeTransaction {
+ RealmObject.deleteFromRealm(dbKlass!!)
+ }
+
+ // verify DELTE
+ executeTransaction {
+ assertThat(
+ where(JavaClass::class.java)
+ .equalTo("fqn", "com/itsaky/androidide/indexing/TestClass")
+ .findFirst()
+ ).isNull()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexerTest.kt b/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexerTest.kt
index 2e9c92528..b9b556611 100644
--- a/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexerTest.kt
+++ b/lsp/java/src/androidTest/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexerTest.kt
@@ -18,16 +18,10 @@
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 com.blankj.utilcode.util.ConvertUtils
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
@@ -36,50 +30,26 @@ import java.io.File
class JavaIndexerTest {
@Test
- fun testSimpleRealmInit() {
- val context = InstrumentationRegistry.getInstrumentation().context
- Realm.init(context.applicationContext)
+ fun testSimpleAndroidJarIndexingDurationCheck() {
+ val helper = IndexingHelper("android-jar-classes.realm")
+ val worker = JavaIndexModelBuilder(helper.androidJar)
+ val batches = mutableMapOf, MutableList>()
- 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, MutableList>()
+ val totalDuration = 0L
+ var dbWriteDuration = 0L
+ helper.doWithRealm {
+ val totalStart = System.currentTimeMillis()
worker.consumeTypes { type ->
val batched = batches.computeIfAbsent(type.javaClass) { mutableListOf() }
batched.add(type)
if (batched.size >= 100) {
- realm.executeTransaction {
- realm.insertOrUpdate(batched)
+ val start = System.currentTimeMillis()
+ executeTransaction {
+ insertOrUpdate(batched)
}
+ dbWriteDuration += System.currentTimeMillis() - start
batched.clear()
}
@@ -87,16 +57,22 @@ class JavaIndexerTest {
for ((_, batched) in batches) {
if (batched.isNotEmpty()) {
- realm.executeTransaction {
- realm.insertOrUpdate(batched)
+ val start = System.currentTimeMillis()
+ executeTransaction {
+ insertOrUpdate(batched)
}
+ dbWriteDuration += System.currentTimeMillis() - start
}
}
- println("Took ${System.currentTimeMillis() - start}ms")
- } finally {
- realm.close()
- file.delete()
+ println(
+ "Took ${dbWriteDuration}ms to write android.jar (${
+ ConvertUtils.byte2FitMemorySize(
+ helper.androidJar.length()
+ )
+ }) classes to Realm"
+ )
+ println("Total time: ${System.currentTimeMillis() - totalStart}ms")
}
}
}
\ No newline at end of file
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorker.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexModelBuilder.kt
similarity index 98%
rename from lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorker.kt
rename to lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexModelBuilder.kt
index da39b7833..9501a88b6 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorker.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexModelBuilder.kt
@@ -60,12 +60,16 @@ import java.io.File
import java.util.zip.ZipFile
/**
+ * [JavaIndexModelBuilder] builds models for Java classes in a JAR file.
+ *
* @author Akash Yadav
*/
-class JavaIndexWorker(
+class JavaIndexModelBuilder(
private val jar: File
) {
+ // for testing purposes
+ // prefer using consumeTypes(Function)
fun buildTypes(): List> {
val types = mutableListOf>()
consumeTypes { types.add(it) }
@@ -278,7 +282,8 @@ class JavaIndexWorker(
if (value == null) {
value = when (char) {
's' -> PrimitiveAnnotationElementValue.newInstance(
- kind = IAnnotationElementValue.KIND_STRING, value = toPrimitiveConstant(
+ kind = IAnnotationElementValue.KIND_STRING,
+ value = toPrimitiveConstant(
classFile.constant_pool,
JavaType.KIND_REF,
char,
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexer.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexer.kt
index 36e0a174a..36879434e 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexer.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexer.kt
@@ -45,7 +45,7 @@ class JavaIndexer(
override fun index(index: IIndex, params: Params): Collection> {
return jarPaths.map { jar ->
coroutineScope.async {
- val worker = JavaIndexWorker(jar)
+ val worker = JavaIndexModelBuilder(jar)
// worker.index(index)
}
}
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/AnnotationElement.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/AnnotationElement.kt
index 164490a46..109b6c1f9 100644
--- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/AnnotationElement.kt
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/AnnotationElement.kt
@@ -36,11 +36,6 @@ import io.realm.annotations.Required
@RealmClass(embedded = true)
open class AnnotationElement : IJavaSymbol {
- @Index
- @Required
- @RealmField("methodId")
- var methodId: String? = null
-
@Index
@Required
@RealmField("name")
diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/JavaIndexingRealmModule.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/JavaIndexingRealmModule.kt
new file mode 100644
index 000000000..755affc93
--- /dev/null
+++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/indexing/models/JavaIndexingRealmModule.kt
@@ -0,0 +1,46 @@
+/*
+ * This file is part of AndroidIDE.
+ *
+ * AndroidIDE is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * AndroidIDE is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with AndroidIDE. If not, see .
+ */
+
+package com.itsaky.androidide.lsp.java.indexing.models
+
+import io.realm.annotations.RealmModule
+
+/**
+ * [RealmModule] for the indexing databased for Java.
+ *
+ * @author Akash Yadav
+ */
+@RealmModule(
+ library = true, classes = [
+ AnnotationElement::class,
+ AnnotationAnnotationElementValue::class,
+ ArrayAnnotationElementValue::class,
+ ClassAnnotationElementValue::class,
+ EnumAnnotationElementValue::class,
+ PrimitiveAnnotationElementValue::class,
+ ApiInfo::class,
+ JavaAnnotation::class,
+ JavaClass::class,
+ JavaConstant::class,
+ JavaEnum::class,
+ JavaField::class,
+ JavaInterface::class,
+ JavaMethod::class,
+ JavaType::class,
+ ]
+)
+internal class JavaIndexingRealmModule
\ No newline at end of file
diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/AndroidJarIndexingRule.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/AndroidJarModelingRule.kt
similarity index 91%
rename from lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/AndroidJarIndexingRule.kt
rename to lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/AndroidJarModelingRule.kt
index 1ce770605..b46ba07a0 100644
--- a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/AndroidJarIndexingRule.kt
+++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/AndroidJarModelingRule.kt
@@ -28,7 +28,7 @@ import java.io.File
/**
* @author Akash Yadav
*/
-class AndroidJarIndexingRule : TestRule {
+class AndroidJarModelingRule : TestRule {
private val androidJar: File by lazy {
FileProvider.projectRoot()
@@ -39,9 +39,9 @@ class AndroidJarIndexingRule : TestRule {
.toFile()
}
- private val androidJarIndexer: JavaIndexWorker by lazy {
+ private val androidJarIndexer: JavaIndexModelBuilder by lazy {
assertThat(androidJar.exists()).isTrue()
- JavaIndexWorker(androidJar)
+ JavaIndexModelBuilder(androidJar)
}
val types: List> by lazy {
diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorkerText.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorkerText.kt
index 41b12f0c2..dad68efca 100644
--- a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorkerText.kt
+++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/indexing/JavaIndexWorkerText.kt
@@ -22,9 +22,11 @@ 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.JavaAnnotation
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.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
@@ -40,11 +42,11 @@ import java.lang.reflect.Modifier
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
-class JavaIndexWorkerTest {
+class JavaIndexModelBuilderTest {
@Rule
@JvmField
- val indexRule = AndroidJarIndexingRule()
+ val indexRule = AndroidJarModelingRule()
private val types: List>
get() = indexRule.types
@@ -405,7 +407,7 @@ class JavaIndexWorkerTest {
}
@Test
- fun `test annotation methods with default integer value`() {
+ fun `test annotation methods with default values`() {
types.firstOrNull { it.fqn == "java/lang/Deprecated" }.also { annotation ->
assertThat(annotation?.methods).isNotEmpty()
@@ -472,4 +474,52 @@ class JavaIndexWorkerTest {
assertThat(defValue).isNull()
}
}
+
+ @Test
+ fun `test class type classification`() {
+ types.firstOrNull { it.fqn == "android/view/View" }.also {
+ assertThat(it).isNotNull()
+ assertThat(it?.isClass).isTrue()
+ assertThat(it?.isInterface).isFalse()
+ assertThat(it?.isAnnotation).isFalse()
+ assertThat(it?.isEnum).isFalse()
+ assertThat(it).isInstanceOf(JavaClass::class.java)
+ }
+ }
+
+ @Test
+ fun `test interface type classification`() {
+ types.firstOrNull { it.fqn == "android/view/View\$OnClickListener" }.also {
+ assertThat(it).isNotNull()
+ assertThat(it?.isClass).isFalse()
+ assertThat(it?.isInterface).isTrue()
+ assertThat(it?.isAnnotation).isFalse()
+ assertThat(it?.isEnum).isFalse()
+ assertThat(it).isInstanceOf(JavaInterface::class.java)
+ }
+ }
+
+ @Test
+ fun `test annotation type classification`() {
+ types.firstOrNull { it.fqn == "java/lang/Deprecated" }.also {
+ assertThat(it).isNotNull()
+ assertThat(it?.isClass).isFalse()
+ assertThat(it?.isInterface).isTrue()
+ assertThat(it?.isAnnotation).isTrue()
+ assertThat(it?.isEnum).isFalse()
+ assertThat(it).isInstanceOf(JavaAnnotation::class.java)
+ }
+ }
+
+ @Test
+ fun `test enum type classification`() {
+ types.firstOrNull { it.fqn == "android/icu/text/DisplayContext" }.also {
+ assertThat(it).isNotNull()
+ assertThat(it?.isClass).isFalse()
+ assertThat(it?.isInterface).isFalse()
+ assertThat(it?.isAnnotation).isFalse()
+ assertThat(it?.isEnum).isTrue()
+ assertThat(it).isInstanceOf(JavaEnum::class.java)
+ }
+ }
}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2260e4d3d..aa594273b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -141,6 +141,7 @@ include(
":subprojects:builder-model-impl",
":subprojects:flashbar",
":subprojects:framework-stubs",
+ ":subprojects:database",
":subprojects:indexing",
":subprojects:javac-services",
":subprojects:projects",
diff --git a/subprojects/database/.gitignore b/subprojects/database/.gitignore
new file mode 100644
index 000000000..42afabfd2
--- /dev/null
+++ b/subprojects/database/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/subprojects/database/build.gradle.kts b/subprojects/database/build.gradle.kts
new file mode 100644
index 000000000..4d89bdc3e
--- /dev/null
+++ b/subprojects/database/build.gradle.kts
@@ -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 .
+ */
+
+import com.itsaky.androidide.build.config.BuildConfig
+
+plugins {
+ id("com.android.library")
+ id("kotlin-android")
+ id("kotlin-kapt")
+ id("realm-android")
+}
+
+android {
+ namespace = "${BuildConfig.packageName}.database"
+}
+
+dependencies {
+ kapt(libs.google.auto.service)
+ implementation(libs.google.auto.service.annotations)
+
+ implementation(projects.logger)
+ implementation(projects.common)
+ implementation(projects.shared)
+}
\ No newline at end of file
diff --git a/subprojects/database/consumer-rules.pro b/subprojects/database/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
diff --git a/subprojects/database/proguard-rules.pro b/subprojects/database/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/subprojects/database/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IRealmProvider.kt b/subprojects/database/src/main/java/com/itsaky/androidide/db/IRealmProvider.kt
similarity index 97%
rename from subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IRealmProvider.kt
rename to subprojects/database/src/main/java/com/itsaky/androidide/db/IRealmProvider.kt
index f347b3554..539d91208 100644
--- a/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IRealmProvider.kt
+++ b/subprojects/database/src/main/java/com/itsaky/androidide/db/IRealmProvider.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.indexing
+package com.itsaky.androidide.db
import com.itsaky.androidide.utils.ServiceLoader
import io.realm.Realm
diff --git a/app/src/main/java/com/itsaky/androidide/indexing/DatabaseEntity.kt b/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/DatabaseEntity.kt
similarity index 62%
rename from app/src/main/java/com/itsaky/androidide/indexing/DatabaseEntity.kt
rename to subprojects/database/src/main/java/com/itsaky/androidide/db/internal/DatabaseEntity.kt
index 41a264e24..0974859f5 100644
--- a/app/src/main/java/com/itsaky/androidide/indexing/DatabaseEntity.kt
+++ b/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/DatabaseEntity.kt
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.indexing
+package com.itsaky.androidide.db.internal
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
@@ -27,7 +27,7 @@ import io.realm.annotations.Required
*
* @author Akash Yadav
*/
-data class DatabaseEntity(
+open class DatabaseEntity(
@Required
@PrimaryKey
@RealmField("path")
@@ -40,4 +40,27 @@ data class DatabaseEntity(
@Required
@RealmField("directory")
var directory: String = "",
-) : RealmObject()
\ No newline at end of file
+) : RealmObject() {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is DatabaseEntity) return false
+
+ if (path != other.path) return false
+ if (name != other.name) return false
+ if (directory != other.directory) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = path.hashCode()
+ result = 31 * result + name.hashCode()
+ result = 31 * result + directory.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "DatabaseEntity(path='$path', name='$name', directory='$directory')"
+ }
+}
\ No newline at end of file
diff --git a/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/InternalDBRealmModule.kt b/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/InternalDBRealmModule.kt
new file mode 100644
index 000000000..4c5a9a7dd
--- /dev/null
+++ b/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/InternalDBRealmModule.kt
@@ -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 .
+ */
+
+package com.itsaky.androidide.db.internal
+
+import io.realm.annotations.RealmModule
+
+/**
+ * [RealmModule] for the internal database entitites.
+ *
+ * @author Akash Yadav
+ */
+@RealmModule(library = true, classes = [DatabaseEntity::class])
+internal class InternalDBRealmModule
\ No newline at end of file
diff --git a/app/src/main/java/com/itsaky/androidide/indexing/RealmProviderImpl.kt b/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/RealmProviderImpl.kt
similarity index 78%
rename from app/src/main/java/com/itsaky/androidide/indexing/RealmProviderImpl.kt
rename to subprojects/database/src/main/java/com/itsaky/androidide/db/internal/RealmProviderImpl.kt
index 0ab5053a2..4741b8f70 100644
--- a/app/src/main/java/com/itsaky/androidide/indexing/RealmProviderImpl.kt
+++ b/subprojects/database/src/main/java/com/itsaky/androidide/db/internal/RealmProviderImpl.kt
@@ -15,9 +15,10 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.indexing
+package com.itsaky.androidide.db.internal
import com.google.auto.service.AutoService
+import com.itsaky.androidide.db.IRealmProvider
import com.itsaky.androidide.utils.Environment
import io.realm.Realm
import io.realm.RealmConfiguration
@@ -30,7 +31,7 @@ import org.slf4j.LoggerFactory
*/
@AutoService(IRealmProvider::class)
@Suppress("unused")
-class RealmProviderImpl : IRealmProvider {
+internal class RealmProviderImpl : IRealmProvider {
companion object {
private val log = LoggerFactory.getLogger(RealmProviderImpl::class.java)
@@ -38,10 +39,12 @@ class RealmProviderImpl : IRealmProvider {
// 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 const val MASTER_DB_PATH = "/$MASTER_DB_NAME"
}
- private val masterDb = createDb(MASTER_DB_PATH, null)
+ private val masterDb: Realm by lazy {
+ createDb(MASTER_DB_PATH, null)
+ }
override fun get(path: String, config: (RealmConfiguration.Builder.() -> Unit)?): Realm {
require(path != MASTER_DB_PATH) {
@@ -73,24 +76,30 @@ class RealmProviderImpl : IRealmProvider {
log.info("Creating DB at $dbDir/$dbName")
- masterDb.executeTransactionAsync { realm ->
- realm.insertOrUpdate(DatabaseEntity(name = name, path = path, directory = dbDir.absolutePath))
+ if (parentPath.isEmpty() && name != MASTER_DB_NAME) {
+ masterDb.executeTransactionAsync { realm ->
+ realm.insertOrUpdate(DatabaseEntity(name = name, path = path, directory = dbDir.absolutePath))
+ }
}
return Realm.getInstance(config)
}
private fun validateDbPath(path: String): Pair {
- require(path.matches(dbNameRegex)) {
- "Invalid DB path: $path"
- }
-
val idx = path.indexOfLast { it == IRealmProvider.PATH_SEPARATOR }
- require(idx > 0) {
+ require(
+ path.matches(dbNameRegex)
+ && path[0] == IRealmProvider.PATH_SEPARATOR
+ && idx >= 0
+ && path.indexOf("//") == -1
+ ) {
"Invalid DB path: $path"
}
- val parentPath = path.substring(0, idx)
+ val parentPath = if (idx > 1) {
+ path.substring(1, idx)
+ } else ""
+
val name = path.substring(idx + 1)
if (name == MASTER_DB_NAME) {
diff --git a/subprojects/indexing/build.gradle.kts b/subprojects/indexing/build.gradle.kts
index f02a6b14a..519172d8f 100644
--- a/subprojects/indexing/build.gradle.kts
+++ b/subprojects/indexing/build.gradle.kts
@@ -35,4 +35,5 @@ dependencies {
api(projects.logger)
api(projects.common)
api(projects.shared)
+ api(projects.subprojects.database)
}
\ No newline at end of file
diff --git a/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IIndex.kt b/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IIndex.kt
index 4b983653e..a31568765 100644
--- a/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IIndex.kt
+++ b/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IIndex.kt
@@ -17,6 +17,7 @@
package com.itsaky.androidide.indexing
+import com.itsaky.androidide.db.IRealmProvider
import com.itsaky.androidide.indexing.internal.DefaultIndex
/**
diff --git a/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IndexProvider.kt b/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IndexProvider.kt
index 5590c9fda..5d8fcfc3c 100644
--- a/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IndexProvider.kt
+++ b/subprojects/indexing/src/main/java/com/itsaky/androidide/indexing/IndexProvider.kt
@@ -17,7 +17,8 @@
package com.itsaky.androidide.indexing
-import com.itsaky.androidide.indexing.IRealmProvider.Companion.PATH_SEPARATOR
+import com.itsaky.androidide.db.IRealmProvider
+import com.itsaky.androidide.db.IRealmProvider.Companion.PATH_SEPARATOR
import com.itsaky.androidide.indexing.internal.DefaultIndex
import io.realm.RealmConfiguration