Created ClassTrie to manage classes of a module

This commit is contained in:
Akash Yadav
2022-07-03 21:27:47 +05:30
parent 5f82e7d5ca
commit 6017e8ddf8
14 changed files with 299 additions and 30 deletions
@@ -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.")
}
@@ -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.
+2
View File
@@ -26,6 +26,8 @@ java {
dependencies {
implementation common.jkotlin
api project (path: ':build-tools:eventbus')
}
compileKotlin {
@@ -15,20 +15,26 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
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)
}
}
}
/** Unregisteres this handler from [EventBus]. */
fun unregister() {
EventBus.getDefault().unregister(this)
}
}
+3
View File
@@ -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')
@@ -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<GradleTask>
) :
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<File>
override fun register() {
super.register()
}
/** Finds the source files from source directories and indexes them. */
fun indexSources() {
val sourceDirs = getSourceDirectories()
}
fun findClass(className: String) {}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Node>?) {
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<String> {
val segments = segments(packageName)
val classes = mutableListOf<String>()
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<String>) {
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<String> {
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<String, Node> = mutableMapOf()
var isClass = false
fun createChild(name: String): Node {
return children.computeIfAbsent(name) { Node(it) }
}
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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()
}
}
@@ -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 ()
}
@@ -0,0 +1,5 @@
package com.itsaky.androidide.tooling.test;
public class Main {
public static void main(String[] args) {}
}
@@ -9,6 +9,7 @@ java {
dependencies {
implementation 'io.github.itsaky:nb-javac-android:17.0.0.0'
implementation project(path: ':another-java-library')
}
repositories {
@@ -1,4 +1,5 @@
include ':app'
include ':java-library'
include ':android-library'
include ':another-java-library'
rootProject.name = "TestApp"