diff --git a/app/src/main/java/com/itsaky/androidide/handlers/BaseEventHandler.kt b/app/src/main/java/com/itsaky/androidide/handlers/BaseEventHandler.kt
index 6bc0de1cc..87654d5a8 100644
--- a/app/src/main/java/com/itsaky/androidide/handlers/BaseEventHandler.kt
+++ b/app/src/main/java/com/itsaky/androidide/handlers/BaseEventHandler.kt
@@ -20,34 +20,22 @@ package com.itsaky.androidide.handlers
import android.content.Context
import com.itsaky.androidide.EditorActivity
import com.itsaky.androidide.eventbus.events.Event
+import com.itsaky.androidide.eventbus.events.EventReceiver
import com.itsaky.androidide.utils.ILogger
-import org.greenrobot.eventbus.EventBus
/**
* Base class for event handlers.
*
* @author Akash Yadav
*/
-abstract class BaseEventHandler {
+abstract class BaseEventHandler : EventReceiver {
protected val log = ILogger.newInstance(javaClass.simpleName)
- /** Registeres this handler with [EventBus]. */
- fun register() {
- if (!EventBus.getDefault().isRegistered(this)) {
- EventBus.getDefault().register(this)
- }
- }
-
- /** Unregisteres this handler from [EventBus]. */
- fun unregister() {
- EventBus.getDefault().unregister(this)
- }
-
protected open fun checkIsEditorActivity(event: Event): Boolean {
return event.get(Context::class.java) is EditorActivity
}
-
+
protected open fun logCannotHandle(event: Event) {
log.warn("Context is not EditorActivity. Cannot handle ${event.javaClass.simpleName} event.")
}
diff --git a/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt b/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt
index fe8c5edca..8bf616f14 100644
--- a/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt
+++ b/app/src/main/java/com/itsaky/androidide/handlers/FileTreeActionHandler.kt
@@ -48,12 +48,12 @@ import com.itsaky.androidide.utils.ProjectWriter
import com.itsaky.toaster.Toaster.Type.ERROR
import com.itsaky.toaster.Toaster.Type.SUCCESS
import com.unnamed.b.atv.model.TreeNode
-import java.io.File
-import java.util.*
-import java.util.regex.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode.MAIN
+import java.io.File
+import java.util.*
+import java.util.regex.*
/**
* Handles events related to files in filetree.
diff --git a/build-tools/eventbus-events/build.gradle b/build-tools/eventbus-events/build.gradle
index 2432e3fc3..d994e7f10 100644
--- a/build-tools/eventbus-events/build.gradle
+++ b/build-tools/eventbus-events/build.gradle
@@ -26,6 +26,8 @@ java {
dependencies {
implementation common.jkotlin
+
+ api project (path: ':build-tools:eventbus')
}
compileKotlin {
diff --git a/build-tools/projects/src/test/java/com/itsaky/androidide/projects/ExampleUnitTest.kt b/build-tools/eventbus-events/src/main/java/com/itsaky/androidide/eventbus/events/EventReceiver.kt
similarity index 60%
rename from build-tools/projects/src/test/java/com/itsaky/androidide/projects/ExampleUnitTest.kt
rename to build-tools/eventbus-events/src/main/java/com/itsaky/androidide/eventbus/events/EventReceiver.kt
index d208db8d5..87e0911e2 100644
--- a/build-tools/projects/src/test/java/com/itsaky/androidide/projects/ExampleUnitTest.kt
+++ b/build-tools/eventbus-events/src/main/java/com/itsaky/androidide/eventbus/events/EventReceiver.kt
@@ -15,20 +15,26 @@
* along with AndroidIDE. If not, see .
*/
-package com.itsaky.androidide.projects
+package com.itsaky.androidide.eventbus.events
-import org.junit.Test
-
-import org.junit.Assert.*
+import org.greenrobot.eventbus.EventBus
/**
- * Example local unit test, which will execute on the development machine (host).
+ * Any class that receives an [Event].
*
- * See [testing documentation](http://d.android.com/tools/testing).
+ * @author Akash Yadav
*/
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
+interface EventReceiver {
+
+ /** Registeres this handler with [EventBus]. */
+ fun register() {
+ if (!EventBus.getDefault().isRegistered(this)) {
+ EventBus.getDefault().register(this)
+ }
}
-}
\ No newline at end of file
+
+ /** Unregisteres this handler from [EventBus]. */
+ fun unregister() {
+ EventBus.getDefault().unregister(this)
+ }
+}
diff --git a/build-tools/projects/build.gradle b/build-tools/projects/build.gradle
index fd1f63598..59b2e5893 100644
--- a/build-tools/projects/build.gradle
+++ b/build-tools/projects/build.gradle
@@ -29,6 +29,9 @@ android {
}
dependencies {
+ api project (path: ':build-tools:eventbus')
+ api project (path: ':build-tools:eventbus-events')
+
implementation project (path: ':build-tools:tooling-api')
implementation project (path: ':common')
diff --git a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt
index cc2bd3b5d..a900f3a50 100644
--- a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt
+++ b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/api/ModuleProject.kt
@@ -17,6 +17,8 @@
package com.itsaky.androidide.projects.api
+import com.itsaky.androidide.eventbus.events.EventReceiver
+import com.itsaky.androidide.projects.util.ClassTrie
import com.itsaky.androidide.tooling.api.model.GradleTask
import java.io.File
@@ -35,7 +37,10 @@ abstract class ModuleProject(
tasks: List
) :
Project(name, description, path, projectDir, buildDir, buildScript, tasks),
- com.itsaky.androidide.tooling.api.model.ModuleProject {
+ com.itsaky.androidide.tooling.api.model.ModuleProject,
+ EventReceiver {
+
+ private val classes = ClassTrie()
/**
* Get the source directories of this module (transitive).
@@ -43,4 +48,15 @@ abstract class ModuleProject(
* @return The source directories.
*/
abstract fun getSourceDirectories(): Set
+
+ override fun register() {
+ super.register()
+ }
+
+ /** Finds the source files from source directories and indexes them. */
+ fun indexSources() {
+ val sourceDirs = getSourceDirectories()
+ }
+
+ fun findClass(className: String) {}
}
diff --git a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/util/ClassTrie.kt b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/util/ClassTrie.kt
new file mode 100644
index 000000000..5d4240fce
--- /dev/null
+++ b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/util/ClassTrie.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.projects.util
+
+import java.util.function.*
+
+/**
+ * Maps classes with their package name segments.
+ *
+ * @author Akash Yadav
+ */
+class ClassTrie {
+
+ val root = Node()
+
+ /**
+ * Appends the class name entry to this trie.
+ *
+ * @param name The fully qualified class name.
+ * @return The added node.
+ */
+ fun append(name: String): Node {
+ val segments = segments(name)
+ var node = root
+ for (segment in segments) {
+ node = node.createChild(segment)
+ }
+ node.isClass = true
+ return node
+ }
+
+ /**
+ * Removes the class name node with the given fully qualified name.
+ *
+ * @param name The fully qualified name of the package segment or class name to remove.
+ */
+ fun remove(name: String) {
+ remove(name, null)
+ }
+
+ /**
+ * Removes the nodes (package segments and class names) with the given fully qualified name. A
+ * [Predicate] can be provided to check which nodes should be deleted.
+ *
+ * @param name The fully qualified name of the package segment or the class.
+ * @param predicate The predicate which can be used to test which entries should be deleted.
+ */
+ fun remove(name: String, predicate: Predicate?) {
+ val segments = segments(name)
+ val condition = predicate ?: Predicate { it.isClass }
+ var node: Node? = root
+ for (segment in segments) {
+ if (node == null || node.children.isEmpty()) {
+ break
+ }
+
+ val next = node.children[segment]
+ if (next != null && condition.test(next)) {
+ node.children.remove(next.name)
+ }
+ node = next
+ }
+ }
+
+ /**
+ * Finds all class names in the given package.
+ *
+ * @param packageName The package name to list classes from.
+ */
+ fun findClassName(packageName: String): List {
+ val segments = segments(packageName)
+ val classes = mutableListOf()
+ val curr = StringBuilder()
+ var node: Node? = root
+ for (segement in segments) {
+ if (node == null || node.children.isEmpty() || !node.children.containsKey(segement)) {
+ break
+ }
+
+ if (node.isClass) {
+ var name = "$curr.$segement"
+ if (curr.isEmpty()) {
+ name = segement
+ }
+ classes.add(name)
+ } else {
+ if (curr.isNotEmpty()) {
+ curr.append(".")
+ }
+ curr.append(segement)
+ }
+
+ node = node.children[segement]
+ }
+
+ if (node == null || node.children.isEmpty()) {
+ return classes
+ }
+
+ node.children.values.forEach {
+ addRecursively(it, curr.toString(), classes)
+ }
+ return classes
+ }
+
+ private fun addRecursively(node: Node, curr: String, classes: MutableList) {
+ val segement = node.name
+ var packageName = curr
+ if (node.isClass) {
+ var name = "$packageName.$segement"
+ if (packageName.isEmpty()) {
+ name = segement
+ }
+ classes.add(name)
+ } else {
+ packageName = if (curr.isEmpty()) {
+ segement
+ } else {
+ "$packageName.$segement"
+ }
+ }
+
+ if (node.children.isEmpty()) {
+ return
+ }
+
+ node.children.values.forEach {
+ addRecursively(it, packageName, classes)
+ }
+ }
+
+ private fun segments(name: String): List {
+ return if (name.contains('.')) {
+ name.split(".")
+ } else {
+ listOf(name)
+ }
+ }
+
+ /** A Node can be a package segment or a class name in the package trie. */
+ class Node(val name: String) {
+
+ internal constructor() : this("")
+
+ val children: MutableMap = mutableMapOf()
+ var isClass = false
+
+ fun createChild(name: String): Node {
+ return children.computeIfAbsent(name) { Node(it) }
+ }
+ }
+}
diff --git a/build-tools/projects/src/test/java/com/itsaky/androidide/projects/util/ClassTrieTest.kt b/build-tools/projects/src/test/java/com/itsaky/androidide/projects/util/ClassTrieTest.kt
new file mode 100644
index 000000000..6432c5138
--- /dev/null
+++ b/build-tools/projects/src/test/java/com/itsaky/androidide/projects/util/ClassTrieTest.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.projects.util
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+/** @author Akash Yadav */
+@RunWith(RobolectricTestRunner::class)
+@Config(manifest = Config.NONE)
+class ClassTrieTest {
+
+ @Test
+ fun testAppend() {
+ val trie = ClassTrie()
+ trie.append("java.lang.String")
+ trie.append("java.lang.Object")
+ trie.append("java.util.List")
+ trie.append("java.io.File")
+ trie.append("java.nio.file.Path")
+
+ assertThat(trie.findClassName("java.lang"))
+ .containsExactly("java.lang.String", "java.lang.Object")
+ assertThat(trie.findClassName("java.util")).containsExactly("java.util.List")
+ assertThat(trie.findClassName("java.nio")).containsExactly("java.nio.file.Path")
+ }
+
+ @Test
+ fun testRemoval() {
+ val trie = ClassTrie()
+ trie.append("java.lang.String")
+ trie.append("java.lang.Object")
+ trie.append("java.util.List")
+ trie.append("java.io.File")
+ trie.append("java.nio.file.Path")
+
+ trie.remove("java.lang.String")
+ trie.remove("java.nio.file.Path")
+
+ assertThat(trie.findClassName("java.lang"))
+ .containsExactly("java.lang.Object")
+ assertThat(trie.findClassName("java.util")).containsExactly("java.util.List")
+ assertThat(trie.findClassName("java.nio")).isEmpty()
+ }
+}
diff --git a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar
index 75acf3df5..446ad9acc 100644
Binary files a/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar and b/build-tools/tooling-api-impl/src/test/test-home/.androidide/init/model.jar differ
diff --git a/build-tools/tooling-api-impl/src/test/test-project/another-java-library/.gitignore b/build-tools/tooling-api-impl/src/test/test-project/another-java-library/.gitignore
new file mode 100644
index 000000000..d16386367
--- /dev/null
+++ b/build-tools/tooling-api-impl/src/test/test-project/another-java-library/.gitignore
@@ -0,0 +1 @@
+build/
\ No newline at end of file
diff --git a/build-tools/tooling-api-impl/src/test/test-project/another-java-library/build.gradle b/build-tools/tooling-api-impl/src/test/test-project/another-java-library/build.gradle
new file mode 100644
index 000000000..78efbac48
--- /dev/null
+++ b/build-tools/tooling-api-impl/src/test/test-project/another-java-library/build.gradle
@@ -0,0 +1,16 @@
+plugins {
+ id 'java-library'
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+dependencies {
+ implementation 'io.github.itsaky:nb-javac-android:17.0.0.0'
+}
+
+repositories {
+ mavenCentral ()
+}
\ No newline at end of file
diff --git a/build-tools/tooling-api-impl/src/test/test-project/another-java-library/src/main/java/com/itsaky/androidide/tooling/test/Main.java b/build-tools/tooling-api-impl/src/test/test-project/another-java-library/src/main/java/com/itsaky/androidide/tooling/test/Main.java
new file mode 100644
index 000000000..2a00c845f
--- /dev/null
+++ b/build-tools/tooling-api-impl/src/test/test-project/another-java-library/src/main/java/com/itsaky/androidide/tooling/test/Main.java
@@ -0,0 +1,5 @@
+package com.itsaky.androidide.tooling.test;
+
+public class Main {
+ public static void main(String[] args) {}
+}
diff --git a/build-tools/tooling-api-impl/src/test/test-project/java-library/build.gradle b/build-tools/tooling-api-impl/src/test/test-project/java-library/build.gradle
index 78efbac48..6b600fe3a 100644
--- a/build-tools/tooling-api-impl/src/test/test-project/java-library/build.gradle
+++ b/build-tools/tooling-api-impl/src/test/test-project/java-library/build.gradle
@@ -9,6 +9,7 @@ java {
dependencies {
implementation 'io.github.itsaky:nb-javac-android:17.0.0.0'
+ implementation project(path: ':another-java-library')
}
repositories {
diff --git a/build-tools/tooling-api-impl/src/test/test-project/settings.gradle b/build-tools/tooling-api-impl/src/test/test-project/settings.gradle
index 179cc61e1..e45d89cf2 100644
--- a/build-tools/tooling-api-impl/src/test/test-project/settings.gradle
+++ b/build-tools/tooling-api-impl/src/test/test-project/settings.gradle
@@ -1,4 +1,5 @@
include ':app'
include ':java-library'
include ':android-library'
+include ':another-java-library'
rootProject.name = "TestApp"
\ No newline at end of file