From 3d917fda6241ad090d016b04f0aff9796d4b9cc1 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 8 Jul 2020 10:42:06 -0500 Subject: [PATCH] Convert SubtasksFilterUpdater to Kotlin --- .../subtasks/SubtasksFilterUpdater.java | 477 ------------------ .../astrid/subtasks/SubtasksFilterUpdater.kt | 394 +++++++++++++++ 2 files changed, 394 insertions(+), 477 deletions(-) delete mode 100644 app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java create mode 100644 app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.kt diff --git a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java deleted file mode 100644 index cf608b37f..000000000 --- a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java +++ /dev/null @@ -1,477 +0,0 @@ -package com.todoroo.astrid.subtasks; - -import static org.tasks.Strings.isNullOrEmpty; -import static org.tasks.db.QueryUtils.showHiddenAndCompleted; - -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.dao.TaskDaoBlocking; -import com.todoroo.astrid.data.Task; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.inject.Inject; -import org.json.JSONArray; -import org.json.JSONException; -import org.tasks.data.TaskListMetadata; -import org.tasks.data.TaskListMetadataDaoBlocking; -import timber.log.Timber; - -public class SubtasksFilterUpdater { - - public static final String ACTIVE_TASKS_ORDER = "active_tasks_order"; // $NON-NLS-1$ - public static final String TODAY_TASKS_ORDER = "today_tasks_order"; // $NON-NLS-1$ - - private final TaskListMetadataDaoBlocking taskListMetadataDao; - private final TaskDaoBlocking taskDao; - private final HashMap idToNode = new HashMap<>(); - private Node treeRoot; - - @Inject - public SubtasksFilterUpdater(TaskListMetadataDaoBlocking taskListMetadataDao, TaskDaoBlocking taskDao) { - this.taskDao = taskDao; - this.taskListMetadataDao = taskListMetadataDao; - } - - static String buildOrderString(String[] ids) { - StringBuilder builder = new StringBuilder(); - if (ids.length == 0) { - return "(1)"; // $NON-NLS-1$ - } - for (int i = ids.length - 1; i >= 0; i--) { - builder.append(Task.UUID.eq(ids[i]).toString()); - if (i > 0) { - builder.append(", "); // $NON-NLS-1$ - } - } - return builder.toString(); - } - - static Node buildTreeModel(String serializedTree, JSONTreeModelBuilder callback) { - Node root = new Node("-1", null, -1); // $NON-NLS-1$ - try { - JSONArray tree = new JSONArray(serializedTree); - recursivelyBuildChildren(root, tree, callback); - } catch (JSONException e) { - Timber.e(e); - } - return root; - } - - private static void recursivelyBuildChildren( - Node node, JSONArray children, JSONTreeModelBuilder callback) throws JSONException { - for (int i = 1; i < children.length(); i++) { - JSONArray subarray = children.optJSONArray(i); - String uuid; - if (subarray == null) { - uuid = children.getString(i); - } else { - uuid = subarray.getString(0); - } - - Node child = new Node(uuid, node, node.indent + 1); - if (subarray != null) { - recursivelyBuildChildren(child, subarray, callback); - } - node.children.add(child); - if (callback != null) { - callback.afterAddNode(child); - } - } - } - - static String serializeTree(Node root) { - JSONArray tree = new JSONArray(); - if (root == null) { - return tree.toString(); - } - - recursivelySerialize(root, tree); - return tree.toString(); - } - - private static void recursivelySerialize(Node node, JSONArray serializeTo) { - ArrayList children = node.children; - serializeTo.put(node.uuid); - for (Node child : children) { - if (child.children.size() > 0) { - JSONArray branch = new JSONArray(); - recursivelySerialize(child, branch); - serializeTo.put(branch); - } else { - serializeTo.put(child.uuid); - } - } - } - - private String getSerializedTree(TaskListMetadata list) { - if (list == null) { - return "[]"; // $NON-NLS-1$ - } - String order = list.getTaskIds(); - if (isNullOrEmpty(order) || "null".equals(order)) // $NON-NLS-1$ - { - order = "[]"; // $NON-NLS-1$ - } - - return order; - } - - public void writeSerialization(TaskListMetadata list, String serialized) { - if (list != null) { - list.setTaskIds(serialized); - taskListMetadataDao.update(list); - } - } - - public void initialize(TaskListMetadata list, Filter filter) { - initializeFromSerializedTree(list, filter, getSerializedTree(list)); - applyToFilter(filter); - } - - private void applyToFilter(Filter filter) { - String query = filter.getSqlQuery(); - - query = query.replaceAll("ORDER BY .*", ""); - query = query + String.format("ORDER BY %s", getOrderString()); - query = showHiddenAndCompleted(query); - - filter.setFilterQueryOverride(query); - } - - public int getIndentForTask(String targetTaskId) { - Node n = idToNode.get(targetTaskId); - if (n == null) { - return 0; - } - return n.indent; - } - - void initializeFromSerializedTree(TaskListMetadata list, Filter filter, String serializedTree) { - idToNode.clear(); - treeRoot = buildTreeModel(serializedTree, node -> idToNode.put(node.uuid, node)); - verifyTreeModel(list, filter); - } - - private void verifyTreeModel(TaskListMetadata list, Filter filter) { - boolean changedThings = false; - Set keySet = idToNode.keySet(); - Set currentIds = new HashSet<>(keySet); - Set idsInQuery = new HashSet<>(); - String sql = filter.getSqlQuery().replaceAll("ORDER BY .*", ""); // $NON-NLS-1$//$NON-NLS-2$ - sql = sql + " ORDER BY created"; // $NON-NLS-1$ - sql = showHiddenAndCompleted(sql); - List tasks = taskDao.fetchFiltered(sql); - for (Task task : tasks) { - String id = task.getUuid(); - idsInQuery.add(id); - if (idToNode.containsKey(id)) { - continue; - } - - changedThings = true; - Node newNode = new Node(id, treeRoot, 0); - treeRoot.children.add(0, newNode); - idToNode.put(id, newNode); - } - - currentIds.removeAll(idsInQuery); - if (currentIds.size() > 0) { - removeNodes(currentIds); - changedThings = true; - } - if (changedThings) { - writeSerialization(list, serializeTree()); - } - } - - private void removeNodes(Set idsToRemove) { - for (String id : idsToRemove) { - Node node = idToNode.get(id); - if (node == null) { - continue; - } - - // Remove node from tree, put all children under parent - Node parent = node.parent; - parent.children.remove(node); - for (Node child : node.children) { - child.parent = parent; - parent.children.add(child); - setNodeIndent(child, parent.indent + 1); - } - } - } - - Node findNodeForTask(String taskId) { - return idToNode.get(taskId); - } - - private String[] getOrderedIds() { - ArrayList ids = new ArrayList<>(); - orderedIdHelper(treeRoot, ids); - return ids.toArray(new String[0]); - } - - private String getOrderString() { - String[] ids = getOrderedIds(); - return buildOrderString(ids); - } - - private void orderedIdHelper(Node node, List ids) { - if (node != treeRoot) { - ids.add(node.uuid); - } - - for (Node child : node.children) { - orderedIdHelper(child, ids); - } - } - - public void applyToDescendants(String taskId, OrderedListNodeVisitor visitor) { - Node n = idToNode.get(taskId); - if (n == null) { - return; - } - applyToDescendantsHelper(n, visitor); - } - - private void applyToDescendantsHelper(Node n, OrderedListNodeVisitor visitor) { - ArrayList children = n.children; - for (Node child : children) { - visitor.visitNode(child); - applyToDescendantsHelper(child, visitor); - } - } - - public void indent(TaskListMetadata list, Filter filter, String targetTaskId, int delta) { - Node node = idToNode.get(targetTaskId); - indentHelper(list, filter, node, delta); - } - - private void indentHelper(TaskListMetadata list, Filter filter, Node node, int delta) { - if (node == null) { - return; - } - if (delta == 0) { - return; - } - Node parent = node.parent; - if (parent == null) { - return; - } - - if (delta > 0) { - ArrayList siblings = parent.children; - int index = siblings.indexOf(node); - if (index <= 0) // Can't indent first child - { - return; - } - Node newParent = siblings.get(index - 1); - siblings.remove(index); - node.parent = newParent; - newParent.children.add(node); - setNodeIndent(node, newParent.indent + 1); - } else if (delta < 0) { - if (parent == treeRoot) // Can't deindent a top level item - { - return; - } - - ArrayList siblings = parent.children; - int index = siblings.indexOf(node); - if (index < 0) { - return; - } - - Node newParent = parent.parent; - ArrayList newSiblings = newParent.children; - int insertAfter = newSiblings.indexOf(parent); - siblings.remove(index); - node.parent = newParent; - setNodeIndent(node, newParent.indent + 1); - newSiblings.add(insertAfter + 1, node); - } - - writeSerialization(list, serializeTree()); - applyToFilter(filter); - } - - private void setNodeIndent(Node node, int indent) { - node.indent = indent; - adjustDescendantsIndent(node, indent); - } - - private void adjustDescendantsIndent(Node node, int baseIndent) { - for (Node child : node.children) { - child.indent = baseIndent + 1; - adjustDescendantsIndent(child, child.indent); - } - } - - public void moveTo(TaskListMetadata list, Filter filter, String targetTaskId, String beforeTaskId) { - Node target = idToNode.get(targetTaskId); - if (target == null) { - return; - } - - if ("-1".equals(beforeTaskId)) { // $NON-NLS-1$ - moveToEndOfList(list, filter, target); - return; - } - - Node before = idToNode.get(beforeTaskId); - - if (before == null) { - return; - } - - if (isDescendantOf(before, target)) { - return; - } - - moveHelper(list, filter, target, before); - } - - public void moveToParentOf(String moveThis, String toParentOfThis) { - Node target = idToNode.get(toParentOfThis); - if (target == null) { - return; - } - - Node toMove = idToNode.get(moveThis); - if (toMove == null) { - return; - } - - Node newParent = target.parent; - Node oldParent = toMove.parent; - - oldParent.children.remove(toMove); - toMove.parent = newParent; - newParent.children.add(toMove); - setNodeIndent(toMove, toMove.parent.indent + 1); - } - - private void moveHelper(TaskListMetadata list, Filter filter, Node moveThis, Node beforeThis) { - Node oldParent = moveThis.parent; - ArrayList oldSiblings = oldParent.children; - - Node newParent = beforeThis.parent; - ArrayList newSiblings = newParent.children; - - int beforeIndex = newSiblings.indexOf(beforeThis); - if (beforeIndex < 0) { - return; - } - - int nodeIndex = oldSiblings.indexOf(moveThis); - if (nodeIndex < 0) { - return; - } - - moveThis.parent = newParent; - setNodeIndent(moveThis, newParent.indent + 1); - oldSiblings.remove(moveThis); - - if (newSiblings == oldSiblings && beforeIndex > nodeIndex) { - beforeIndex--; - } - newSiblings.add(beforeIndex, moveThis); - writeSerialization(list, serializeTree()); - applyToFilter(filter); - } - - public boolean isDescendantOf(String desc, String parent) { - return isDescendantOf(idToNode.get(desc), idToNode.get(parent)); - } - - // Returns true if desc is a descendant of parent - private boolean isDescendantOf(Node desc, Node parent) { - Node curr = desc; - while (curr != treeRoot) { - if (curr == parent) { - return true; - } - curr = curr.parent; - } - return false; - } - - private void moveToEndOfList(TaskListMetadata list, Filter filter, Node moveThis) { - Node parent = moveThis.parent; - parent.children.remove(moveThis); - treeRoot.children.add(moveThis); - moveThis.parent = treeRoot; - setNodeIndent(moveThis, 0); - writeSerialization(list, serializeTree()); - applyToFilter(filter); - } - - public void onCreateTask(TaskListMetadata list, Filter filter, String uuid) { - if (idToNode.containsKey(uuid) || !Task.isValidUuid(uuid)) { - return; - } - - Node newNode = new Node(uuid, treeRoot, 0); - treeRoot.children.add(0, newNode); - idToNode.put(uuid, newNode); - writeSerialization(list, serializeTree()); - applyToFilter(filter); - } - - public void onDeleteTask(TaskListMetadata list, Filter filter, String taskId) { - Node task = idToNode.get(taskId); - if (task == null) { - return; - } - - Node parent = task.parent; - ArrayList siblings = parent.children; - int index = siblings.indexOf(task); - - if (index >= 0) { - siblings.remove(index); - } - for (Node child : task.children) { - child.parent = parent; - siblings.add(index, child); - setNodeIndent(child, parent.indent + 1); - index++; - } - idToNode.remove(taskId); - - writeSerialization(list, serializeTree()); - applyToFilter(filter); - } - - public String serializeTree() { - return serializeTree(treeRoot); - } - - public interface OrderedListNodeVisitor { - - void visitNode(Node node); - } - - private interface JSONTreeModelBuilder { - - void afterAddNode(Node node); - } - - public static class Node { - - final ArrayList children = new ArrayList<>(); - public String uuid; - public Node parent; - int indent; - - Node(String uuid, Node parent, int indent) { - this.uuid = uuid; - this.parent = parent; - this.indent = indent; - } - } -} diff --git a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.kt b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.kt new file mode 100644 index 000000000..f9be04461 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.kt @@ -0,0 +1,394 @@ +package com.todoroo.astrid.subtasks + +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.dao.TaskDaoBlocking +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.data.Task.Companion.isValidUuid +import org.json.JSONArray +import org.json.JSONException +import org.tasks.Strings.isNullOrEmpty +import org.tasks.data.TaskListMetadata +import org.tasks.data.TaskListMetadataDaoBlocking +import org.tasks.db.QueryUtils.showHiddenAndCompleted +import timber.log.Timber +import java.util.* +import javax.inject.Inject + +class SubtasksFilterUpdater @Inject constructor( + private val taskListMetadataDao: TaskListMetadataDaoBlocking, + private val taskDao: TaskDaoBlocking) { + private val idToNode = HashMap() + private var treeRoot: Node? = null + private fun getSerializedTree(list: TaskListMetadata?): String? { + if (list == null) { + return "[]" // $NON-NLS-1$ + } + var order = list.taskIds + if (isNullOrEmpty(order) || "null" == order) // $NON-NLS-1$ + { + order = "[]" // $NON-NLS-1$ + } + return order + } + + fun writeSerialization(list: TaskListMetadata?, serialized: String?) { + if (list != null) { + list.taskIds = serialized + taskListMetadataDao.update(list) + } + } + + fun initialize(list: TaskListMetadata?, filter: Filter) { + initializeFromSerializedTree(list, filter, getSerializedTree(list)) + applyToFilter(filter) + } + + private fun applyToFilter(filter: Filter) { + var query = filter.getSqlQuery() + query = query.replace("ORDER BY .*".toRegex(), "") + query += "ORDER BY $orderString" + query = showHiddenAndCompleted(query) + filter.setFilterQueryOverride(query) + } + + fun getIndentForTask(targetTaskId: String?): Int { + val n = idToNode[targetTaskId] ?: return 0 + return n.indent + } + + fun initializeFromSerializedTree(list: TaskListMetadata?, filter: Filter, serializedTree: String?) { + idToNode.clear() + treeRoot = buildTreeModel(serializedTree) { node -> node?.let { idToNode[it.uuid] = it } } + verifyTreeModel(list, filter) + } + + private fun verifyTreeModel(list: TaskListMetadata?, filter: Filter) { + var changedThings = false + val keySet: Set = idToNode.keys + val currentIds: MutableSet = HashSet(keySet) + val idsInQuery: MutableSet = HashSet() + var sql = filter.getSqlQuery().replace("ORDER BY .*".toRegex(), "") // $NON-NLS-1$//$NON-NLS-2$ + sql = "$sql ORDER BY created" // $NON-NLS-1$ + sql = showHiddenAndCompleted(sql) + val tasks = taskDao.fetchFiltered(sql) + for (task in tasks) { + val id = task.uuid + idsInQuery.add(id) + if (idToNode.containsKey(id)) { + continue + } + changedThings = true + val newNode = Node(id, treeRoot, 0) + treeRoot!!.children.add(0, newNode) + idToNode[id] = newNode + } + currentIds.removeAll(idsInQuery) + if (currentIds.size > 0) { + removeNodes(currentIds) + changedThings = true + } + if (changedThings) { + writeSerialization(list, serializeTree()) + } + } + + private fun removeNodes(idsToRemove: Set) { + for (id in idsToRemove) { + val node = idToNode[id] ?: continue + + // Remove node from tree, put all children under parent + val parent = node.parent + parent!!.children.remove(node) + for (child in node.children) { + child.parent = parent + parent.children.add(child) + setNodeIndent(child, parent.indent + 1) + } + } + } + + fun findNodeForTask(taskId: String?): Node? { + return idToNode[taskId] + } + + private val orderedIds: Array + get() { + val ids = ArrayList() + orderedIdHelper(treeRoot, ids) + return ids.toTypedArray() + } + + private val orderString: String + get() { + val ids = orderedIds + return buildOrderString(ids) + } + + private fun orderedIdHelper(node: Node?, ids: MutableList) { + if (node !== treeRoot) { + ids.add(node!!.uuid) + } + for (child in node!!.children) { + orderedIdHelper(child, ids) + } + } + + fun applyToDescendants(taskId: String?, visitor: (Node) -> Unit) { + val n = idToNode[taskId] ?: return + applyToDescendantsHelper(n, visitor) + } + + private fun applyToDescendantsHelper(n: Node, visitor: (Node) -> Unit) { + val children = n.children + for (child in children) { + visitor.invoke(child) + applyToDescendantsHelper(child, visitor) + } + } + + fun indent(list: TaskListMetadata, filter: Filter, targetTaskId: String?, delta: Int) { + val node = idToNode[targetTaskId] + indentHelper(list, filter, node, delta) + } + + private fun indentHelper(list: TaskListMetadata, filter: Filter, node: Node?, delta: Int) { + if (node == null) { + return + } + if (delta == 0) { + return + } + val parent = node.parent ?: return + if (delta > 0) { + val siblings = parent.children + val index = siblings.indexOf(node) + if (index <= 0) // Can't indent first child + { + return + } + val newParent = siblings[index - 1] + siblings.removeAt(index) + node.parent = newParent + newParent.children.add(node) + setNodeIndent(node, newParent.indent + 1) + } else if (delta < 0) { + if (parent === treeRoot) // Can't deindent a top level item + { + return + } + val siblings = parent.children + val index = siblings.indexOf(node) + if (index < 0) { + return + } + val newParent = parent.parent + val newSiblings = newParent!!.children + val insertAfter = newSiblings.indexOf(parent) + siblings.removeAt(index) + node.parent = newParent + setNodeIndent(node, newParent.indent + 1) + newSiblings.add(insertAfter + 1, node) + } + writeSerialization(list, serializeTree()) + applyToFilter(filter) + } + + private fun setNodeIndent(node: Node, indent: Int) { + node.indent = indent + adjustDescendantsIndent(node, indent) + } + + private fun adjustDescendantsIndent(node: Node, baseIndent: Int) { + for (child in node.children) { + child.indent = baseIndent + 1 + adjustDescendantsIndent(child, child.indent) + } + } + + fun moveTo(list: TaskListMetadata, filter: Filter, targetTaskId: String?, beforeTaskId: String) { + val target = idToNode[targetTaskId] ?: return + if ("-1" == beforeTaskId) { // $NON-NLS-1$ + moveToEndOfList(list, filter, target) + return + } + val before = idToNode[beforeTaskId] ?: return + if (isDescendantOf(before, target)) { + return + } + moveHelper(list, filter, target, before) + } + + fun moveToParentOf(moveThis: String?, toParentOfThis: String?) { + val target = idToNode[toParentOfThis] ?: return + val toMove = idToNode[moveThis] ?: return + val newParent = target.parent + val oldParent = toMove.parent + oldParent!!.children.remove(toMove) + toMove.parent = newParent + newParent!!.children.add(toMove) + setNodeIndent(toMove, toMove.parent!!.indent + 1) + } + + private fun moveHelper(list: TaskListMetadata, filter: Filter, moveThis: Node, beforeThis: Node) { + val oldParent = moveThis.parent + val oldSiblings = oldParent!!.children + val newParent = beforeThis.parent + val newSiblings = newParent!!.children + var beforeIndex = newSiblings.indexOf(beforeThis) + if (beforeIndex < 0) { + return + } + val nodeIndex = oldSiblings.indexOf(moveThis) + if (nodeIndex < 0) { + return + } + moveThis.parent = newParent + setNodeIndent(moveThis, newParent.indent + 1) + oldSiblings.remove(moveThis) + if (newSiblings === oldSiblings && beforeIndex > nodeIndex) { + beforeIndex-- + } + newSiblings.add(beforeIndex, moveThis) + writeSerialization(list, serializeTree()) + applyToFilter(filter) + } + + fun isDescendantOf(desc: String?, parent: String?): Boolean { + return isDescendantOf(idToNode[desc], idToNode[parent]) + } + + // Returns true if desc is a descendant of parent + private fun isDescendantOf(desc: Node?, parent: Node?): Boolean { + var curr = desc + while (curr !== treeRoot) { + if (curr === parent) { + return true + } + curr = curr!!.parent + } + return false + } + + private fun moveToEndOfList(list: TaskListMetadata, filter: Filter, moveThis: Node) { + val parent = moveThis.parent + parent!!.children.remove(moveThis) + treeRoot!!.children.add(moveThis) + moveThis.parent = treeRoot + setNodeIndent(moveThis, 0) + writeSerialization(list, serializeTree()) + applyToFilter(filter) + } + + fun onCreateTask(list: TaskListMetadata?, filter: Filter, uuid: String) { + if (idToNode.containsKey(uuid) || !isValidUuid(uuid)) { + return + } + val newNode = Node(uuid, treeRoot, 0) + treeRoot!!.children.add(0, newNode) + idToNode[uuid] = newNode + writeSerialization(list, serializeTree()) + applyToFilter(filter) + } + + fun onDeleteTask(list: TaskListMetadata?, filter: Filter, taskId: String?) { + val task = idToNode[taskId] ?: return + val parent = task.parent + val siblings = parent!!.children + var index = siblings.indexOf(task) + if (index >= 0) { + siblings.removeAt(index) + } + for (child in task.children) { + child.parent = parent + siblings.add(index, child) + setNodeIndent(child, parent.indent + 1) + index++ + } + idToNode.remove(taskId) + writeSerialization(list, serializeTree()) + applyToFilter(filter) + } + + fun serializeTree(): String { + return serializeTree(treeRoot) + } + + class Node internal constructor(@JvmField var uuid: String, var parent: Node?, var indent: Int) { + @JvmField + val children = ArrayList() + } + + companion object { + const val ACTIVE_TASKS_ORDER = "active_tasks_order" // $NON-NLS-1$ + const val TODAY_TASKS_ORDER = "today_tasks_order" // $NON-NLS-1$ + + @JvmStatic + fun buildOrderString(ids: Array): String { + val builder = StringBuilder() + if (ids.isEmpty()) { + return "(1)" // $NON-NLS-1$ + } + for (i in ids.indices.reversed()) { + builder.append(Task.UUID.eq(ids[i]).toString()) + if (i > 0) { + builder.append(", ") // $NON-NLS-1$ + } + } + return builder.toString() + } + + @JvmStatic + fun buildTreeModel(serializedTree: String?, callback: ((Node?) -> Unit)?): Node { + val root = Node("-1", null, -1) // $NON-NLS-1$ + try { + val tree = JSONArray(serializedTree) + recursivelyBuildChildren(root, tree, callback) + } catch (e: JSONException) { + Timber.e(e) + } + return root + } + + @Throws(JSONException::class) + private fun recursivelyBuildChildren( + node: Node, children: JSONArray, callback: ((Node?) -> Unit)?) { + for (i in 1 until children.length()) { + val subarray = children.optJSONArray(i) + var uuid: String + uuid = if (subarray == null) { + children.getString(i) + } else { + subarray.getString(0) + } + val child = Node(uuid, node, node.indent + 1) + subarray?.let { recursivelyBuildChildren(child, it, callback) } + node.children.add(child) + callback?.invoke(child) + } + } + + @JvmStatic + fun serializeTree(root: Node?): String { + val tree = JSONArray() + if (root == null) { + return tree.toString() + } + recursivelySerialize(root, tree) + return tree.toString() + } + + private fun recursivelySerialize(node: Node, serializeTo: JSONArray) { + val children = node.children + serializeTo.put(node.uuid) + for (child in children) { + if (child.children.size > 0) { + val branch = JSONArray() + recursivelySerialize(child, branch) + serializeTo.put(branch) + } else { + serializeTo.put(child.uuid) + } + } + } + } +} \ No newline at end of file