fix(lsp/java): use realm db provider to create indexes in Java indexer tests

This commit is contained in:
Akash Yadav
2024-05-26 02:06:38 +05:30
parent a80846ea0e
commit 719ab38778
23 changed files with 526 additions and 122 deletions
+43 -42
View File
@@ -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)
}
@@ -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
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()
}
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<JavaField>().apply {
add(JavaField.newField("someString", JavaType.STRING, AccessFlags.ACC_PRIVATE))
},
methods = RealmList<JavaMethod>().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<String>())
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<String>())
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()
}
}
}
}
@@ -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<Class<*>, MutableList<RealmModel>>()
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>>()
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")
}
}
}
@@ -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<IJavaType<*, *>> {
val types = mutableListOf<IJavaType<*, *>>()
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,
@@ -45,7 +45,7 @@ class JavaIndexer(
override fun index(index: IIndex<IJavaIndexable>, params: Params): Collection<Deferred<Unit>> {
return jarPaths.map { jar ->
coroutineScope.async {
val worker = JavaIndexWorker(jar)
val worker = JavaIndexModelBuilder(jar)
// worker.index(index)
}
}
@@ -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")
@@ -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 <https://www.gnu.org/licenses/>.
*/
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
@@ -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<IJavaType<*, *>> by lazy {
@@ -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<IJavaType<*, *>>
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)
}
}
}
+1
View File
@@ -141,6 +141,7 @@ include(
":subprojects:builder-model-impl",
":subprojects:flashbar",
":subprojects:framework-stubs",
":subprojects:database",
":subprojects:indexing",
":subprojects:javac-services",
":subprojects:projects",
+1
View File
@@ -0,0 +1 @@
/build
+38
View File
@@ -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}.database"
}
dependencies {
kapt(libs.google.auto.service)
implementation(libs.google.auto.service.annotations)
implementation(projects.logger)
implementation(projects.common)
implementation(projects.shared)
}
+21
View File
@@ -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
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.indexing
package com.itsaky.androidide.db
import com.itsaky.androidide.utils.ServiceLoader
import io.realm.Realm
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
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()
) : 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')"
}
}
@@ -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.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
@@ -15,9 +15,10 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
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<String, String> {
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) {
+1
View File
@@ -35,4 +35,5 @@ dependencies {
api(projects.logger)
api(projects.common)
api(projects.shared)
api(projects.subprojects.database)
}
@@ -17,6 +17,7 @@
package com.itsaky.androidide.indexing
import com.itsaky.androidide.db.IRealmProvider
import com.itsaky.androidide.indexing.internal.DefaultIndex
/**
@@ -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