refactoring: ModuleGroupingTreeHelper abstracted over type of module instances

This is needed to reuse the helper for trees consisting of ModuleDescription nodes.
This commit is contained in:
nik
2017-06-02 20:43:58 +02:00
parent efc0a43b0f
commit fd04ce89d7
3 changed files with 78 additions and 58 deletions
@@ -25,6 +25,7 @@ import com.intellij.ide.IdeBundle;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.ide.impl.FlattenModulesToggleAction;
import com.intellij.ide.projectView.impl.ModuleGroup;
import com.intellij.ide.projectView.impl.ModuleGroupingImplementation;
import com.intellij.ide.projectView.impl.ModuleGroupingTreeHelper;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.ide.util.projectWizard.NamePathComponent;
@@ -221,8 +222,10 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple
private void createProjectNodes() {
ModuleGrouper moduleGrouper = getModuleGrouper();
ModuleGroupingTreeHelper<MyNode> helper = ModuleGroupingTreeHelper.forEmptyTree(!myHideModuleGroups && !myFlattenModules,
moduleGrouper, ModuleStructureConfigurable::createModuleGroupNode, this::createModuleNode, getNodeComparator());
ModuleGroupingTreeHelper<Module, MyNode> helper = ModuleGroupingTreeHelper.forEmptyTree(!myHideModuleGroups && !myFlattenModules,
ModuleGroupingTreeHelper.createDefaultGrouping(moduleGrouper),
ModuleStructureConfigurable::createModuleGroupNode,
m -> createModuleNode(m, moduleGrouper), getNodeComparator());
helper.createModuleNodes(Arrays.asList(myModuleManager.getModules()), myRoot, getTreeModel());
if (containsSecondLevelNodes(myRoot)) {
myTree.setShowsRootHandles(true);
@@ -290,7 +293,7 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple
LOG.assertTrue(node != null, "Module " + module.getName() + " is not in project.");
nodes.add(Pair.create(node, module));
}
ModuleGroupingTreeHelper<MyNode> helper = createGroupingHelper();
ModuleGroupingTreeHelper<Module, MyNode> helper = createGroupingHelper();
helper.moveModuleNodesToProperGroup(nodes, myRoot, getTreeModel(), myTree);
return true;
}
@@ -300,11 +303,14 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple
}
@NotNull
private ModuleGroupingTreeHelper<MyNode> createGroupingHelper() {
private ModuleGroupingTreeHelper<Module, MyNode> createGroupingHelper() {
ModuleGrouper grouper = getModuleGrouper();
ModuleGroupingImplementation<Module> grouping = ModuleGroupingTreeHelper.createDefaultGrouping(grouper);
return ModuleGroupingTreeHelper.forTree(myRoot, (node) -> node instanceof ModuleGroupNode ? ((ModuleGroupNode)node).getModuleGroup() : null,
(node) -> node instanceof ModuleNode ? ((ModuleNode)node).getModule() : null,
!myHideModuleGroups && !myFlattenModules, getModuleGrouper(), ModuleStructureConfigurable::createModuleGroupNode,
this::createModuleNode, getNodeComparator());
!myHideModuleGroups && !myFlattenModules,
grouping, ModuleStructureConfigurable::createModuleGroupNode,
module -> createModuleNode(module, grouper), getNodeComparator());
}
@Override
@@ -656,7 +662,7 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple
boolean autoScrollWasEnabled = myAutoScrollEnabled;
try {
myAutoScrollEnabled = false;
ModuleGroupingTreeHelper<MyNode> helper = createGroupingHelper();
ModuleGroupingTreeHelper<Module, MyNode> helper = createGroupingHelper();
MyNode newNode = helper.moveModuleNodeToProperGroup(this, getModule(), myRoot, treeModel, myTree);
treeModel.reload(newNode);
}
@@ -42,33 +42,34 @@ import javax.swing.tree.MutableTreeNode
import javax.swing.tree.TreeNode
/**
* Provides methods to build trees where nodes are grouped by modules (and optionally by module groups).
* Provides methods to build trees where nodes are grouped by modules (and optionally by module groups). Type parameter M specified class
* of modules (may be [Module] if real modules are shown, or [com.intellij.openapi.module.ModuleDescription] if loaded and unloaded modules are shown.
*
* @author nik
*/
class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
class ModuleGroupingTreeHelper<M: Any, N: MutableTreeNode> private constructor(
private val groupingEnabled: Boolean,
private val grouper: ModuleGrouper,
private val grouping: ModuleGroupingImplementation<M>,
private val moduleGroupNodeFactory: (ModuleGroup) -> N,
private val moduleNodeFactory: (Module, ModuleGrouper) -> N,
private val moduleNodeFactory: (M) -> N,
private val nodeComparator: Comparator<in N>
) {
private val nodeForGroup = HashMap<ModuleGroup, N>()
private val nodeData = HashMap<N, ModuleTreeNodeData>()
private val nodeData = HashMap<N, ModuleTreeNodeData<M>>()
companion object {
@JvmStatic
fun <N : MutableTreeNode> forEmptyTree(groupingEnabled: Boolean, grouper: ModuleGrouper,
moduleGroupNodeFactory: (ModuleGroup) -> N, moduleNodeFactory: (Module, ModuleGrouper) -> N,
nodeComparator: Comparator<in N>) =
ModuleGroupingTreeHelper(groupingEnabled, grouper, moduleGroupNodeFactory, moduleNodeFactory, nodeComparator)
fun <M: Any, N : MutableTreeNode> forEmptyTree(groupingEnabled: Boolean, grouping: ModuleGroupingImplementation<M>,
moduleGroupNodeFactory: (ModuleGroup) -> N, moduleNodeFactory: (M) -> N,
nodeComparator: Comparator<in N>) =
ModuleGroupingTreeHelper(groupingEnabled, grouping, moduleGroupNodeFactory, moduleNodeFactory, nodeComparator)
@JvmStatic
fun <N : MutableTreeNode> forTree(rootNode: N, moduleGroupByNode: (N) -> ModuleGroup?, moduleByNode: (N) -> Module?,
groupingEnabled: Boolean, grouper: ModuleGrouper,
moduleGroupNodeFactory: (ModuleGroup) -> N, moduleNodeFactory: (Module, ModuleGrouper) -> N,
nodeComparator: Comparator<in N>): ModuleGroupingTreeHelper<N> {
val helper = ModuleGroupingTreeHelper(groupingEnabled, grouper, moduleGroupNodeFactory, moduleNodeFactory, nodeComparator)
fun <M: Any, N : MutableTreeNode> forTree(rootNode: N, moduleGroupByNode: (N) -> ModuleGroup?, moduleByNode: (N) -> M?,
groupingEnabled: Boolean, grouping: ModuleGroupingImplementation<M>,
moduleGroupNodeFactory: (ModuleGroup) -> N, moduleNodeFactory: (M) -> N,
nodeComparator: Comparator<in N>): ModuleGroupingTreeHelper<M, N> {
val helper = ModuleGroupingTreeHelper(groupingEnabled, grouping, moduleGroupNodeFactory, moduleNodeFactory, nodeComparator)
TreeUtil.traverse(rootNode) { node ->
@Suppress("UNCHECKED_CAST")
val group = moduleGroupByNode(node as N)
@@ -83,19 +84,25 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
}
return helper
}
@JvmStatic
fun createDefaultGrouping(grouper: ModuleGrouper) = object : ModuleGroupingImplementation<Module> {
override fun getGroupPath(m: Module) = grouper.getGroupPath(m)
override fun getModuleAsGroupPath(m: Module) = grouper.getModuleAsGroupPath(m)
}
}
fun createModuleNodes(modules: Collection<Module>, rootNode: N, model: DefaultTreeModel): List<N> {
fun createModuleNodes(modules: Collection<M>, rootNode: N, model: DefaultTreeModel): List<N> {
val nodes = modules.map { createModuleNode(it, rootNode, model) }
TreeUtil.sortRecursively(rootNode, nodeComparator)
model.nodeStructureChanged(rootNode)
return nodes
}
private fun createModuleNode(module: Module, rootNode: N, model: DefaultTreeModel): N {
val group = ModuleGroup(grouper.getGroupPath(module))
private fun createModuleNode(module: M, rootNode: N, model: DefaultTreeModel): N {
val group = ModuleGroup(grouping.getGroupPath(module))
val parentNode = getOrCreateNodeForModuleGroup(group, rootNode, model, true)
val moduleNode = moduleNodeFactory(module, grouper)
val moduleNode = moduleNodeFactory(module)
insertModuleNode(moduleNode, parentNode, module, model, true)
return moduleNode
}
@@ -103,7 +110,7 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
/**
* If [bulkOperation] is true, no events will be fired and new node will be added into arbitrary place in the children list
*/
private fun insertModuleNode(moduleNode: N, parentNode: N, module: Module, model: DefaultTreeModel, bulkOperation: Boolean) {
private fun insertModuleNode(moduleNode: N, parentNode: N, module: M, model: DefaultTreeModel, bulkOperation: Boolean) {
val moduleAsGroup = moduleAsGroup(module)
if (moduleAsGroup != null) {
val oldModuleGroupNode = nodeForGroup[moduleAsGroup]
@@ -122,7 +129,7 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
insertNode(moduleNode, parentNode, model, bulkOperation)
}
private fun moduleAsGroup(module: Module) = grouper.getModuleAsGroupPath(module)?.let(::ModuleGroup)
private fun moduleAsGroup(module: M) = grouping.getModuleAsGroupPath(module)?.let(::ModuleGroup)
private fun moveChildren(fromNode: N, toNode: N, model: DefaultTreeModel) {
val children = TreeUtil.listChildren(fromNode)
@@ -150,7 +157,7 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
node = moduleGroupNodeFactory(current)
insertNode(node, parentNode, model, bulkOperation)
nodeForGroup[current] = node
nodeData[node] = ModuleTreeNodeData(null,group)
nodeData[node] = ModuleTreeNodeData<M>(null,group)
}
parentNode = node
}
@@ -174,14 +181,14 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
createModuleNodes(modules, rootNode, model)
}
fun moveModuleNodesToProperGroup(nodes: List<Pair<N, Module>>, rootNode: N, model: DefaultTreeModel, tree: JTree) {
fun moveModuleNodesToProperGroup(nodes: List<Pair<N, M>>, rootNode: N, model: DefaultTreeModel, tree: JTree) {
nodes.forEach {
moveModuleNodeToProperGroup(it.first, it.second, rootNode, model, tree)
}
}
fun moveModuleNodeToProperGroup(node: N, module: Module, rootNode: N, model: DefaultTreeModel, tree: JTree): N {
val actualGroup = ModuleGroup(grouper.getGroupPath(module))
fun moveModuleNodeToProperGroup(node: N, module: M, rootNode: N, model: DefaultTreeModel, tree: JTree): N {
val actualGroup = ModuleGroup(grouping.getGroupPath(module))
val parent = node.parent
val nodeAsGroup = nodeData[node]?.group
val expectedParent = if (groupingEnabled && !actualGroup.groupPathList.isEmpty()) nodeForGroup[actualGroup] else rootNode
@@ -205,7 +212,7 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
removeEmptySyntheticModuleGroupNodes(parent, model)
val newParent = getOrCreateNodeForModuleGroup(actualGroup, rootNode, model, false)
val newNode = moduleNodeFactory(module, grouper)
val newNode = moduleNodeFactory(module)
insertModuleNode(newNode, newParent, module, model, false)
if (wasSelected) {
@@ -220,6 +227,7 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
while (parent is MutableTreeNode && parent in nodeData && nodeData[parent]?.module == null && parent.childCount == 0) {
val grandParent = parent.parent
model.removeNodeFromParent(parent)
@Suppress("UNCHECKED_CAST")
removeNode(parent as N)
parent = grandParent
}
@@ -242,4 +250,9 @@ class ModuleGroupingTreeHelper<N: MutableTreeNode> private constructor(
fun getGroupByNodeMap() = nodeData.mapValues { it.value.group }.filterValues { it != null }
}
private class ModuleTreeNodeData(val module: Module?, val group: ModuleGroup?)
private class ModuleTreeNodeData<M>(val module: M?, val group: ModuleGroup?)
interface ModuleGroupingImplementation<M: Any> {
fun getGroupPath(m: M): List<String>
fun getModuleAsGroupPath(m: M): List<String>?
}
@@ -16,14 +16,11 @@
package com.intellij.ide.projectView
import com.intellij.ide.projectView.impl.ModuleGroup
import com.intellij.ide.projectView.impl.ModuleGroupingImplementation
import com.intellij.ide.projectView.impl.ModuleGroupingTreeHelper
import com.intellij.module.getQualifiedNameModuleGrouper
import com.intellij.module.renameModule
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleGrouper
import com.intellij.openapi.util.Pair
import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.UsefulTestCase
import com.intellij.ui.treeStructure.Tree
import com.intellij.util.ui.tree.TreeUtil
import junit.framework.TestCase
@@ -34,7 +31,7 @@ import javax.swing.tree.DefaultTreeModel
/**
* @author nik
*/
class ModuleGroupingTreeHelperTest: PlatformTestCase() {
class ModuleGroupingTreeHelperTest: UsefulTestCase() {
private lateinit var tree: Tree
private lateinit var root: MockModuleTreeNode
private lateinit var model: DefaultTreeModel
@@ -128,7 +125,7 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
main
util""")
val node = nodes.find { it.second.name == "main" }!!
renameModule(node.second, "a.main")
node.second.name = "a.main"
moveModuleNodeToProperGroupAndCheckResult(node, """
-root
-a
@@ -143,7 +140,7 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
-a
a.main""")
val node = nodes.find { it.second.name == "a.main" }!!
renameModule(node.second, "main")
node.second.name = "main"
moveModuleNodeToProperGroupAndCheckResult(node, """
-root
a
@@ -153,7 +150,7 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
fun `test move module node to parent module`() {
val nodes = createHelper().createModuleNodes("a", "main")
val node = nodes.find { it.second.name == "main" }!!
renameModule(node.second, "a.main")
node.second.name = "a.main"
moveModuleNodeToProperGroupAndCheckResult(node, """
-root
-a
@@ -163,7 +160,7 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
fun `test module node become parent module`() {
val nodes = createHelper().createModuleNodes("b", "a.main")
val node = nodes.find { it.second.name == "b" }!!
renameModule(node.second, "a")
node.second.name = "a"
moveModuleNodeToProperGroupAndCheckResult(node, """
-root
-a
@@ -173,7 +170,7 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
fun `test parent module become ordinary module`() {
val nodes = createHelper().createModuleNodes("a", "a.main")
val node = nodes.find { it.second.name == "a" }!!
renameModule(node.second, "b")
node.second.name = "b"
moveModuleNodeToProperGroupAndCheckResult(node, """
-root
-a
@@ -189,7 +186,7 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
}
}
private fun moveModuleNodeToProperGroupAndCheckResult(node: Pair<MockModuleTreeNode, Module>,
private fun moveModuleNodeToProperGroupAndCheckResult(node: Pair<MockModuleTreeNode, MockModule>,
expected: String) {
val helper = createHelperFromTree()
helper.checkConsistency()
@@ -198,14 +195,14 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
helper.checkConsistency()
}
private fun ModuleGroupingTreeHelper<MockModuleTreeNode>.moveAllModuleNodesAndCheckResult(expected: String) {
private fun ModuleGroupingTreeHelper<MockModule, MockModuleTreeNode>.moveAllModuleNodesAndCheckResult(expected: String) {
checkConsistency()
moveAllModuleNodesToProperGroups(root, model)
assertTreeEqual(expected)
checkConsistency()
}
private fun ModuleGroupingTreeHelper<MockModuleTreeNode>.createModuleNodes(vararg names: String): List<Pair<MockModuleTreeNode, Module>> {
private fun ModuleGroupingTreeHelper<MockModule, MockModuleTreeNode>.createModuleNodes(vararg names: String): List<Pair<MockModuleTreeNode, MockModule>> {
val modules = createModules(*names)
val nodes = createModuleNodes(modules, root, model)
return nodes.map { Pair(it, (it as MockModuleNode).module)}
@@ -216,23 +213,21 @@ class ModuleGroupingTreeHelperTest: PlatformTestCase() {
PlatformTestUtil.assertTreeEqual(tree, expected.trimIndent() + "\n")
}
private fun createHelper(enableGrouping: Boolean = true): ModuleGroupingTreeHelper<MockModuleTreeNode> {
val grouper = getQualifiedNameModuleGrouper(myProject)
return ModuleGroupingTreeHelper.forEmptyTree(enableGrouping, grouper, ::MockModuleGroupNode, ::MockModuleNode, nodeComparator)
private fun createHelper(enableGrouping: Boolean = true): ModuleGroupingTreeHelper<MockModule, MockModuleTreeNode> {
return ModuleGroupingTreeHelper.forEmptyTree(enableGrouping, mockModuleGrouping, ::MockModuleGroupNode, ::MockModuleNode, nodeComparator)
}
private fun createHelperFromTree(enableGrouping: Boolean = true): ModuleGroupingTreeHelper<MockModuleTreeNode> {
val grouper = getQualifiedNameModuleGrouper(myProject)
private fun createHelperFromTree(enableGrouping: Boolean = true): ModuleGroupingTreeHelper<MockModule, MockModuleTreeNode> {
return ModuleGroupingTreeHelper.forTree(root, { it.moduleGroup }, { (it as? MockModuleNode)?.module },
enableGrouping, grouper, ::MockModuleGroupNode, ::MockModuleNode, nodeComparator)
enableGrouping, mockModuleGrouping, ::MockModuleGroupNode, ::MockModuleNode, nodeComparator)
}
private fun createModules(vararg names: String) = names.map { createModule(it) }
private fun createModules(vararg names: String) = names.map { MockModule(it) }
private fun ModuleGroupingTreeHelper<MockModuleTreeNode>.checkConsistency() {
private fun ModuleGroupingTreeHelper<MockModule, MockModuleTreeNode>.checkConsistency() {
val expectedNodeForGroup = HashMap<ModuleGroup, MockModuleTreeNode>(getNodeForGroupMap())
val expectedGroupByNode = HashMap<MockModuleTreeNode, ModuleGroup>(getGroupByNodeMap())
val expectedModuleByNode = HashMap<MockModuleTreeNode, Module>(getModuleByNodeMap())
val expectedModuleByNode = HashMap<MockModuleTreeNode, MockModule>(getModuleByNodeMap())
TreeUtil.traverse(root, { o ->
val node = o as MockModuleTreeNode
if (node == root) return@traverse true
@@ -259,8 +254,14 @@ private open class MockModuleTreeNode(userObject: Any, val text: String = userOb
override fun toString() = text
}
private class MockModuleNode(val module: Module, grouper: ModuleGrouper): MockModuleTreeNode(module, module.name) {
override val moduleGroup = grouper.getModuleAsGroupPath(module)?.let(::ModuleGroup)
private val mockModuleGrouping = object : ModuleGroupingImplementation<MockModule> {
override fun getGroupPath(m: MockModule) = m.name.split('.').dropLast(1)
override fun getModuleAsGroupPath(m: MockModule) = m.name.split('.')
}
private class MockModule(var name: String)
private class MockModuleNode(val module: MockModule): MockModuleTreeNode(module, module.name) {
override val moduleGroup = ModuleGroup(mockModuleGrouping.getModuleAsGroupPath(module)!!)
}
private class MockModuleGroupNode(override val moduleGroup: ModuleGroup): MockModuleTreeNode(moduleGroup)