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