From 1d2d4bc17fe727c6c38864d0fae3c70a2190a32d Mon Sep 17 00:00:00 2001 From: nik Date: Tue, 17 Jan 2017 18:51:28 +0300 Subject: [PATCH] project configuration: automatically group modules accordingly to qualified names (IDEA-166061) Initial implementation: if 'project.qualified.module.names' registry option is enabled and the project has no groups defined, modules are automatically grouped accordingly to their names. --- .../sourceItems/ModuleGroupItem.java | 14 +- .../sourceItems/ModuleSourceItemGroup.java | 5 +- ...odulesAndLibrariesSourceItemsProvider.java | 27 ++-- .../ModuleStructureConfigurable.java | 140 +++++++++++++----- .../analysis/BaseClassesAnalysisAction.java | 6 +- .../ui/TreeModelBuilder.java | 20 +-- .../actions/MoveModulesToSubGroupAction.java | 11 +- .../ide/projectView/impl/ModuleGroup.java | 85 ++++------- .../ide/projectView/impl/ModuleGroupUrl.java | 4 +- .../ide/projectView/impl/ModuleGroupUtil.java | 19 +-- .../projectView/impl/MoveModuleToGroup.java | 6 +- .../impl/MoveModuleToGroupTopLevel.java | 47 +++--- .../impl/nodes/AbstractProjectNode.java | 2 +- .../impl/nodes/ModuleGroupNode.java | 6 +- .../util/DirectoryChooserModuleTreeView.java | 12 +- .../ui/FileTreeModelBuilder.java | 23 +-- .../packageDependencies/ui/ModuleNode.java | 8 +- .../openapi/ui/MasterDetailsComponent.java | 6 +- .../module/ExplicitModuleGroupTest.kt | 72 +++++++++ .../GroupModulesByQualifiedNamesTest.kt | 72 +++++++++ .../openapi/module/ModuleManager.java | 2 + .../intellij/openapi/module/ModuleGrouper.kt | 67 +++++++++ .../module/impl/ModuleManagerImpl.java | 5 + .../util/resources/misc/registry.properties | 3 + .../util/containers/ContainerUtil.java | 5 + .../quickfix/PluginDescriptorChooser.java | 23 ++- 26 files changed, 481 insertions(+), 209 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/module/ExplicitModuleGroupTest.kt create mode 100644 platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt create mode 100644 platform/projectModel-impl/src/com/intellij/openapi/module/ModuleGrouper.kt diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleGroupItem.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleGroupItem.java index b31d7a85b28a..6853e09afab0 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleGroupItem.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleGroupItem.java @@ -16,7 +16,6 @@ package com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems; import com.intellij.ide.projectView.PresentationData; -import com.intellij.openapi.util.Comparing; import com.intellij.packaging.elements.PackagingElement; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingSourceItem; @@ -26,7 +25,6 @@ import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.PlatformIcons; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -35,20 +33,20 @@ import java.util.List; */ public class ModuleGroupItem extends PackagingSourceItem { private final String myGroupName; - private final String[] myPath; + private final List myPath; - public ModuleGroupItem(String[] path) { + public ModuleGroupItem(@NotNull List path) { super(false); - myGroupName = path[path.length - 1]; + myGroupName = path.get(path.size() - 1); myPath = path; } public boolean equals(Object obj) { - return obj instanceof ModuleGroupItem && Comparing.equal(myPath, ((ModuleGroupItem)obj).myPath); + return obj instanceof ModuleGroupItem && myPath.equals(((ModuleGroupItem)obj).myPath); } public int hashCode() { - return Arrays.hashCode(myPath); + return myPath.hashCode(); } @Override @@ -62,7 +60,7 @@ public class ModuleGroupItem extends PackagingSourceItem { return Collections.emptyList(); } - public String[] getPath() { + public List getPath() { return myPath; } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java index b9fa15606007..898ad4086844 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModuleSourceItemGroup.java @@ -16,6 +16,7 @@ package com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems; import com.intellij.ide.projectView.PresentationData; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.roots.DependencyScope; @@ -120,14 +121,14 @@ public class ModuleSourceItemGroup extends PackagingSourceItem { @Override public String getPresentableName() { - return myModule.getName(); + return ModuleGrouper.Companion.instanceFor(myContext.getProject(), myContext.getModifiableModuleModel()).getPresentableName(myModule); } @Override public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) { presentationData.setIcon(ModuleType.get(myModule).getIcon()); - presentationData.addText(myModule.getName(), mainAttributes); + presentationData.addText(getPresentableName(), mainAttributes); } @Override diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModulesAndLibrariesSourceItemsProvider.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModulesAndLibrariesSourceItemsProvider.java index b9fc2aaa0d19..87008c8531b2 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModulesAndLibrariesSourceItemsProvider.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/ModulesAndLibrariesSourceItemsProvider.java @@ -15,23 +15,22 @@ */ package com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.impl.artifacts.ArtifactUtil; -import com.intellij.packaging.impl.elements.FileCopyPackagingElement; -import com.intellij.packaging.impl.elements.ProductionModuleOutputElementType; import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement; import com.intellij.packaging.impl.elements.PackagingElementFactoryImpl; +import com.intellij.packaging.impl.elements.ProductionModuleOutputElementType; import com.intellij.packaging.ui.ArtifactEditorContext; import com.intellij.packaging.ui.PackagingSourceItem; import com.intellij.packaging.ui.PackagingSourceItemsProvider; -import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -46,7 +45,7 @@ public class ModulesAndLibrariesSourceItemsProvider extends PackagingSourceItems public Collection getSourceItems(@NotNull ArtifactEditorContext editorContext, @NotNull Artifact artifact, PackagingSourceItem parent) { if (parent == null) { - return createModuleItems(editorContext, ArrayUtil.EMPTY_STRING_ARRAY); + return createModuleItems(editorContext, Collections.emptyList()); } else if (parent instanceof ModuleGroupItem) { return createModuleItems(editorContext, ((ModuleGroupItem)parent).getPath()); @@ -85,25 +84,21 @@ public class ModulesAndLibrariesSourceItemsProvider extends PackagingSourceItems } @NotNull - private static Collection createModuleItems(@NotNull ArtifactEditorContext editorContext, @NotNull String[] groupPath) { - final Module[] modules = editorContext.getModulesProvider().getModules(); + private static Collection createModuleItems(@NotNull ArtifactEditorContext editorContext, @NotNull List groupPath) { final List items = new ArrayList<>(); + ModuleGrouper grouper = ModuleGrouper.Companion.instanceFor(editorContext.getProject(), editorContext.getModifiableModuleModel()); Set groups = new HashSet<>(); - for (Module module : modules) { - String[] path = ModuleManager.getInstance(editorContext.getProject()).getModuleGroupPath(module); - if (path == null) { - path = ArrayUtil.EMPTY_STRING_ARRAY; - } - + for (Module module : grouper.getAllModules()) { + List path = grouper.getGroupPath(module); if (Comparing.equal(path, groupPath)) { items.add(new ModuleSourceItemGroup(module)); } - else if (ArrayUtil.startsWith(path, groupPath)) { - groups.add(path[groupPath.length]); + else if (ContainerUtil.startsWith(path, groupPath)) { + groups.add(path.get(groupPath.size())); } } for (String group : groups) { - items.add(0, new ModuleGroupItem(ArrayUtil.append(groupPath, group))); + items.add(0, new ModuleGroupItem(ContainerUtil.append(groupPath, group))); } return items; } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java index 0655d9ea5ec3..edf8c45edb29 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/ModuleStructureConfigurable.java @@ -25,6 +25,7 @@ import com.intellij.ide.IdeBundle; import com.intellij.ide.highlighter.ModuleFileType; import com.intellij.ide.projectView.impl.ModuleGroup; import com.intellij.ide.projectView.impl.ModuleGroupUtil; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.ide.util.projectWizard.ModuleBuilder; import com.intellij.ide.util.projectWizard.NamePathComponent; import com.intellij.ide.util.projectWizard.ProjectWizardUtil; @@ -68,10 +69,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; +import javax.swing.tree.*; import java.awt.*; import java.util.*; import java.util.List; @@ -222,24 +220,22 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple final Module[] modules = myModuleManager.getModules(); for (final Module module : modules) { ModuleConfigurable configurable = new ModuleConfigurable(myContext.myModulesConfigurator, module, TREE_UPDATER); - final MyNode moduleNode = new MyNode(configurable); + ModuleGrouper moduleGrouper = getModuleGrouper(); + final MyNode moduleNode = new ModuleNode(configurable, moduleGrouper); boolean nodesAdded = myFacetEditorFacade.addFacetsNodes(module, moduleNode); nodesAdded |= addNodesFromExtensions(module, moduleNode); if (nodesAdded) { myTree.setShowsRootHandles(true); } - final String[] groupPath = myHideModuleGroups ? null : myContext.myModulesConfigurator.getModuleModel().getModuleGroupPath(module); - if (groupPath == null || groupPath.length == 0){ + final List groupPath = myHideModuleGroups ? Collections.emptyList() : moduleGrouper.getGroupPath(module); + if (groupPath.isEmpty()) { myRoot.add(moduleNode); } else { final MyNode moduleGroupNode = ModuleGroupUtil .buildModuleGroupPath(new ModuleGroup(groupPath), myRoot, moduleGroup2NodeMap, parentChildRelation -> parentChildRelation.getParent().add(parentChildRelation.getChild()), - moduleGroup -> { - final NamedConfigurable moduleGroupConfigurable = createModuleGroupConfigurable(moduleGroup); - return new MyNode(moduleGroupConfigurable, true); - }); + ModuleStructureConfigurable::createModuleGroupNode); moduleGroupNode.add(moduleNode); } } @@ -256,6 +252,15 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple //myProjectNode.add(myLevel2Nodes.get(LibraryTablesRegistrar.PROJECT_LEVEL)); } + @NotNull + private static MyNode createModuleGroupNode(ModuleGroup moduleGroup) { + final NamedConfigurable moduleGroupConfigurable = new TextConfigurable<>(moduleGroup, moduleGroup.toString(), + ProjectBundle.message("module.group.banner.text", moduleGroup.toString()), + ProjectBundle.message("project.roots.module.groups.text"), + PlatformIcons.CLOSED_MODULE_GROUP_ICON); + return new ModuleGroupNode(moduleGroupConfigurable, moduleGroup); + } + private void addRootNodesFromExtensions(final MyNode root, final Project project) { for (final ModuleStructureExtension extension : ModuleStructureExtension.EP_NAME.getExtensions()) { extension.addRootNodes(root, project, TREE_UPDATER); @@ -281,17 +286,13 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple nodes[i ++] = node; } for (final MyNode moduleNode : nodes) { - final String[] groupPath = myHideModuleGroups - ? null - : group != null ? group.getGroupPath() : null; - if (groupPath == null || groupPath.length == 0){ + List groupPath = myHideModuleGroups + ? Collections.emptyList() + : group != null ? group.getGroupPathList() : Collections.emptyList(); + if (groupPath.isEmpty()) { myRoot.add(moduleNode); } else { - final MyNode moduleGroupNode = ModuleGroupUtil - .updateModuleGroupPath(new ModuleGroup(groupPath), myRoot, group1 -> findNodeByObject(myRoot, group1), parentChildRelation -> parentChildRelation.getParent().add(parentChildRelation.getChild()), moduleGroup -> { - final NamedConfigurable moduleGroupConfigurable = createModuleGroupConfigurable(moduleGroup); - return new MyNode(moduleGroupConfigurable, true); - }); + final MyNode moduleGroupNode = getOrCreateModuleGroupNode(groupPath, null); moduleGroupNode.add(moduleNode); } Module module = (Module)moduleNode.getConfigurable().getEditableObject(); @@ -302,6 +303,18 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple return true; } + private MyNode getOrCreateModuleGroupNode(List groupPath, @Nullable DefaultTreeModel treeModel) { + return ModuleGroupUtil.updateModuleGroupPath(new ModuleGroup(groupPath), myRoot, group1 -> findNodeByObject(myRoot, group1), + parentChildRelation -> { + MyNode parent = parentChildRelation.getParent(); + parent.add(parentChildRelation.getChild()); + if (treeModel != null) { + treeModel.nodesWereInserted(parent, new int[] {parent.getChildCount() - 1}); + } + }, + ModuleStructureConfigurable::createModuleGroupNode); + } + @Override protected Comparator getNodeComparator() { List> comparators = ContainerUtil @@ -456,6 +469,10 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple return r != null ? result.doWhenDone(r) : result; } + private ModuleGrouper getModuleGrouper() { + return ModuleGrouper.Companion.instanceFor(myProject, myContext.myModulesConfigurator.getModuleModel()); + } + public static ModuleStructureConfigurable getInstance(final Project project) { return ServiceManager.getService(project, ModuleStructureConfigurable.class); @@ -534,7 +551,7 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple } private void addModuleNode(final Module module) { - final MyNode node = new MyNode(new ModuleConfigurable(myContext.myModulesConfigurator, module, TREE_UPDATER)); + final MyNode node = new ModuleNode(new ModuleConfigurable(myContext.myModulesConfigurator, module, TREE_UPDATER), getModuleGrouper()); final TreePath selectionPath = myTree.getSelectionPath(); MyNode parent = null; if (selectionPath != null) { @@ -548,7 +565,7 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple final String[] groupPath = modifiableModuleModel.getModuleGroupPath((Module)o); if (groupPath != null) { modifiableModuleModel.setModuleGroupPath(module, groupPath); - parent = findNodeByObject(myRoot, new ModuleGroup(groupPath)); + parent = findNodeByObject(myRoot, new ModuleGroup(Arrays.asList(groupPath))); } } } @@ -595,13 +612,6 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple return myContext; } - private static TextConfigurable createModuleGroupConfigurable(final ModuleGroup moduleGroup) { - return new TextConfigurable<>(moduleGroup, moduleGroup.toString(), - ProjectBundle.message("module.group.banner.text", moduleGroup.toString()), - ProjectBundle.message("project.roots.module.groups.text"), - PlatformIcons.CLOSED_MODULE_GROUP_ICON); - } - private static boolean canBeCopiedByExtension(final NamedConfigurable configurable) { for (final ModuleStructureExtension extension : ModuleStructureExtension.EP_NAME.getExtensions()) { if (extension.canBeCopied(configurable)) { @@ -617,6 +627,69 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple } } + private class ModuleNode extends MyNode { + private final ModuleGrouper myGrouper; + + public ModuleNode(@NotNull ModuleConfigurable configurable, ModuleGrouper grouper) { + super(configurable); + myGrouper = grouper; + } + + @NotNull + @Override + public String getDisplayName() { + return myGrouper.getPresentableName(getModule()); + } + + private Module getModule() { + return ((ModuleConfigurable)getConfigurable()).getModule(); + } + + @Override + protected void reloadNode(DefaultTreeModel treeModel) { + TreeNode parent = getParent(); + if (parent instanceof ModuleGroupNode) { + List actualPath = ((ModuleGroupNode)parent).myModuleGroup.getGroupPathList(); + List expectedPath = myGrouper.getGroupPath(getModule()); + if (!actualPath.equals(expectedPath)) { + TreePath path = myTree.getSelectionPath(); + boolean wasSelected = path != null && this.equals(path.getLastPathComponent()); + boolean autoScrollWasEnabled = myAutoScrollEnabled; + try { + myAutoScrollEnabled = false; + MyNode newParent = getOrCreateModuleGroupNode(expectedPath, treeModel); + treeModel.removeNodeFromParent(this); + while (parent instanceof ModuleGroupNode && parent.getChildCount() == 0) { + TreeNode grandParent = parent.getParent(); + treeModel.removeNodeFromParent((MutableTreeNode)parent); + parent = grandParent; + } + + newParent.add(this); + treeModel.nodesWereInserted(newParent, new int[] {newParent.getChildCount()-1}); + if (wasSelected) { + myTree.expandPath(TreeUtil.getPath(myRoot, newParent)); + myTree.setSelectionPath(TreeUtil.getPath(myRoot, this)); + } + } + finally { + myAutoScrollEnabled = autoScrollWasEnabled; + } + } + } + super.reloadNode(treeModel); + } + } + + private static class ModuleGroupNode extends MyNode { + private final ModuleGroup myModuleGroup; + + public ModuleGroupNode(@NotNull NamedConfigurable configurable, ModuleGroup moduleGroup) { + super(configurable, true); + myModuleGroup = moduleGroup; + } + } + private class FacetInModuleRemoveHandler extends RemoveConfigurableHandler { public FacetInModuleRemoveHandler() { super(FacetConfigurable.class); @@ -727,11 +800,10 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple if (selectionPath != null){ selection = (DefaultMutableTreeNode)selectionPath.getLastPathComponent(); } - final ModifiableModuleModel model = myContext.myModulesConfigurator.getModuleModel(); - final Module[] modules = model.getModules(); - for (Module module : modules) { - final String[] groupPath = model.getModuleGroupPath(module); - updateProjectTree(new Module[]{module}, groupPath != null ? new ModuleGroup(groupPath) : null); + ModuleGrouper grouper = getModuleGrouper(); + for (Module module : grouper.getAllModules()) { + final List groupPath = grouper.getGroupPath(module); + updateProjectTree(new Module[]{module}, !groupPath.isEmpty() ? new ModuleGroup(groupPath) : null); } if (state) { removeModuleGroups(); diff --git a/java/java-impl/src/com/intellij/analysis/BaseClassesAnalysisAction.java b/java/java-impl/src/com/intellij/analysis/BaseClassesAnalysisAction.java index fe1ad16e81a2..5b921b2a5b05 100644 --- a/java/java-impl/src/com/intellij/analysis/BaseClassesAnalysisAction.java +++ b/java/java-impl/src/com/intellij/analysis/BaseClassesAnalysisAction.java @@ -36,7 +36,7 @@ public abstract class BaseClassesAnalysisAction extends BaseAnalysisAction { super(title, analysisNoon); } - protected abstract void analyzeClasses(final Project project, final AnalysisScope scope, ProgressIndicator indicator); + protected abstract void analyzeClasses(@NotNull Project project, @NotNull AnalysisScope scope, @NotNull ProgressIndicator indicator); @Override protected void analyze(@NotNull final Project project, @NotNull final AnalysisScope scope) { @@ -79,7 +79,7 @@ public abstract class BaseClassesAnalysisAction extends BaseAnalysisAction { }); } - private void doAnalyze(final Project project, final AnalysisScope scope) { + private void doAnalyze(@NotNull Project project, @NotNull AnalysisScope scope) { ProgressManager.getInstance().run(new Task.Backgroundable(project, AnalysisScopeBundle.message("analyzing.project"), true) { @Override @Nullable @@ -94,7 +94,7 @@ public abstract class BaseClassesAnalysisAction extends BaseAnalysisAction { }); } - private void compileAndAnalyze(final Project project, final AnalysisScope scope) { + private void compileAndAnalyze(@NotNull Project project, @NotNull AnalysisScope scope) { if (project.isDisposed()) { return; } diff --git a/java/java-impl/src/com/intellij/packageDependencies/ui/TreeModelBuilder.java b/java/java-impl/src/com/intellij/packageDependencies/ui/TreeModelBuilder.java index a12018cc5c30..1699d684d208 100644 --- a/java/java-impl/src/com/intellij/packageDependencies/ui/TreeModelBuilder.java +++ b/java/java-impl/src/com/intellij/packageDependencies/ui/TreeModelBuilder.java @@ -18,6 +18,7 @@ package com.intellij.packageDependencies.ui; import com.intellij.analysis.AnalysisScopeBundle; import com.intellij.icons.AllIcons; import com.intellij.ide.projectView.impl.ModuleGroup; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; @@ -71,6 +72,7 @@ public class TreeModelBuilder { private final Map> myModuleNodes = new HashMap<>(); private final Map> myModuleGroupNodes = new HashMap<>(); private final Map> myLibraryNodes = new HashMap<>(); + private final ModuleGrouper myModuleGrouper; private int myScannedFileCount; private int myTotalFileCount; private int myMarkedFileCount; @@ -84,7 +86,7 @@ public class TreeModelBuilder { public static final String TEST_NAME = AnalysisScopeBundle.message("package.dependencies.test.node.text"); public static final String LIBRARY_NAME = AnalysisScopeBundle.message("package.dependencies.library.node.text"); - public TreeModelBuilder(Project project, boolean showIndividualLibs, Marker marker, DependenciesPanel.DependencyPanelSettings settings) { + public TreeModelBuilder(@NotNull Project project, boolean showIndividualLibs, Marker marker, DependenciesPanel.DependencyPanelSettings settings) { myProject = project; final boolean multiModuleProject = ModuleManager.getInstance(project).getModules().length > 1; myShowModules = settings.UI_SHOW_MODULES && multiModuleProject; @@ -93,6 +95,7 @@ public class TreeModelBuilder { myShowFiles = settings.UI_SHOW_FILES; myShowIndividualLibs = showIndividualLibs; myShowModuleGroups = settings.UI_SHOW_MODULE_GROUPS && multiModuleProject; + myModuleGrouper = ModuleGrouper.Companion.instanceFor(project); myMarker = marker; myAddUnmarkedFiles = !settings.UI_FILTER_LEGALS; myRoot = new RootNode(project); @@ -383,10 +386,9 @@ public class TreeModelBuilder { } ModuleNode node = getMap(myModuleNodes, scopeType).get(module); if (node != null) return node; - node = new ModuleNode(module); - final ModuleManager moduleManager = ModuleManager.getInstance(myProject); - final String[] groupPath = moduleManager.getModuleGroupPath(module); - if (groupPath == null) { + node = new ModuleNode(module, myShowModuleGroups ? myModuleGrouper : null); + final List groupPath = myModuleGrouper.getGroupPath(module); + if (groupPath.isEmpty()) { getMap(myModuleNodes, scopeType).put(module, node); getRootNode(scopeType).add(node); return node; @@ -400,7 +402,7 @@ public class TreeModelBuilder { return node; } - private PackageDependenciesNode getParentModuleGroup(String [] groupPath, ScopeType scopeType){ + private PackageDependenciesNode getParentModuleGroup(List groupPath, ScopeType scopeType){ final String key = StringUtil.join(groupPath, ","); ModuleGroupNode groupNode = getMap(myModuleGroupNodes, scopeType).get(key); if (groupNode == null) { @@ -408,10 +410,8 @@ public class TreeModelBuilder { getMap(myModuleGroupNodes, scopeType).put(key, groupNode); getRootNode(scopeType).add(groupNode); } - if (groupPath.length > 1) { - String [] path = new String[groupPath.length - 1]; - System.arraycopy(groupPath, 0, path, 0, groupPath.length - 1); - final PackageDependenciesNode node = getParentModuleGroup(path, scopeType); + if (groupPath.size() > 1) { + final PackageDependenciesNode node = getParentModuleGroup(groupPath.subList(0, groupPath.size()-1), scopeType); node.add(groupNode); } return groupNode; diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MoveModulesToSubGroupAction.java b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MoveModulesToSubGroupAction.java index c3aab4b8f1ab..a40738b30686 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/actions/MoveModulesToSubGroupAction.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/actions/MoveModulesToSubGroupAction.java @@ -24,7 +24,10 @@ import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.module.Module; import com.intellij.openapi.ui.Messages; -import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; + +import java.util.Collections; +import java.util.List; public class MoveModulesToSubGroupAction extends MoveModulesToGroupAction { public MoveModulesToSubGroupAction(ModuleGroup moduleGroup) { @@ -42,18 +45,18 @@ public class MoveModulesToSubGroupAction extends MoveModulesToGroupAction { public void actionPerformed(AnActionEvent e) { final DataContext dataContext = e.getDataContext(); final Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext); - final String[] newGroup; + final List newGroup; if (myModuleGroup != null) { String message = IdeBundle.message("prompt.specify.name.of.module.subgroup", myModuleGroup.presentableText(), whatToMove(modules)); String subgroup = Messages.showInputDialog(message, IdeBundle.message("title.module.sub.group"), Messages.getQuestionIcon()); if (subgroup == null || "".equals(subgroup.trim())) return; - newGroup = ArrayUtil.append(myModuleGroup.getGroupPath(), subgroup); + newGroup = ContainerUtil.append(myModuleGroup.getGroupPathList(), subgroup); } else { String message = IdeBundle.message("prompt.specify.module.group.name", whatToMove(modules)); String group = Messages.showInputDialog(message, IdeBundle.message("title.module.group"), Messages.getQuestionIcon()); if (group == null || "".equals(group.trim())) return; - newGroup = new String[]{group}; + newGroup = Collections.singletonList(group); } doMove(modules, new ModuleGroup(newGroup), dataContext); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroup.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroup.java index 82f990510453..22437aaf3233 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroup.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroup.java @@ -17,22 +17,24 @@ package com.intellij.ide.projectView.impl; import com.intellij.openapi.actionSystem.DataKey; -import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.project.Project; import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; public class ModuleGroup { public static final DataKey ARRAY_DATA_KEY = DataKey.create("moduleGroup.array"); + private final List myGroupPath; - private final String[] myGroupPath; - - public ModuleGroup(@NotNull String[] groupPath) { + public ModuleGroup(@NotNull List groupPath) { myGroupPath = groupPath; } @@ -40,58 +42,45 @@ public class ModuleGroup { if (this == o) return true; if (!(o instanceof ModuleGroup)) return false; - final ModuleGroup moduleGroup = (ModuleGroup)o; - - if (!Arrays.equals(myGroupPath, moduleGroup.myGroupPath)) return false; - - return true; + return myGroupPath.equals(((ModuleGroup)o).myGroupPath); } public int hashCode() { - return myGroupPath[myGroupPath.length-1].hashCode(); + return myGroupPath.hashCode(); } public String[] getGroupPath() { + return ArrayUtil.toStringArray(myGroupPath); + } + + public List getGroupPathList() { return myGroupPath; } @NotNull public Collection modulesInGroup(Project project, boolean recursively) { - final Module[] allModules = ModuleManager.getInstance(project).getModules(); + return modulesInGroup(ModuleGrouper.Companion.instanceFor(project), recursively); + } + + @NotNull + public Collection modulesInGroup(ModuleGrouper grouper, boolean recursively) { List result = new ArrayList<>(); - for (final Module module : allModules) { - String[] group = ModuleManager.getInstance(project).getModuleGroupPath(module); - if (group == null) continue; - if (Arrays.equals(myGroupPath, group) || (recursively && isChild(myGroupPath, group))) { + for (final Module module : grouper.getAllModules()) { + List group = grouper.getGroupPath(module); + if (myGroupPath.equals(group) || (recursively && isChild(myGroupPath, group))) { result.add(module); } } return result; } - public Collection childGroups(Project project) { - return childGroups(null, project); - } - - public Collection childGroups(ModifiableModuleModel model, Project project) { - final Module[] allModules; - if ( model != null ) { - allModules = model.getModules(); - } else { - allModules = ModuleManager.getInstance(project).getModules(); - } - + @NotNull + public Collection childGroups(ModuleGrouper grouper) { Set result = new THashSet<>(); - for (Module module : allModules) { - String[] group; - if ( model != null ) { - group = model.getModuleGroupPath(module); - } else { - group = ModuleManager.getInstance(project).getModuleGroupPath(module); - } - if (group == null) continue; - final String[] directChild = directChild(myGroupPath, group); - if (directChild != null) { + for (Module module : grouper.getAllModules()) { + List group = grouper.getGroupPath(module); + if (isChild(myGroupPath, group)) { + final List directChild = ContainerUtil.append(myGroupPath, group.get(myGroupPath.size())); result.add(new ModuleGroup(directChild)); } } @@ -99,25 +88,15 @@ public class ModuleGroup { return result; } - private static boolean isChild(final String[] parent, final String[] descendant) { - if (parent.length >= descendant.length) return false; - for (int i = 0; i < parent.length; i++) { - String group = parent[i]; - if (!group.equals(descendant[i])) return false; - } - return true; - } - - private static String[] directChild(final String[] parent, final String[] descendant) { - if (!isChild(parent, descendant)) return null; - return ArrayUtil.append(parent, descendant[parent.length]); + private static boolean isChild(final List parent, final List descendant) { + return descendant.size() > parent.size() && descendant.subList(0, parent.size()).equals(parent); } public String presentableText() { - return "'" + myGroupPath[myGroupPath.length - 1] + "'"; + return "'" + myGroupPath.get(myGroupPath.size() - 1) + "'"; } public String toString() { - return myGroupPath[myGroupPath.length - 1]; + return myGroupPath.get(myGroupPath.size() - 1); } } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUrl.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUrl.java index 4686c6c874b6..87a56df3f9e3 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUrl.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUrl.java @@ -20,6 +20,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NonNls; +import java.util.List; + public class ModuleGroupUrl extends AbstractUrl { @NonNls private static final String ELEMENT_TYPE = "module_group"; @@ -30,7 +32,7 @@ public class ModuleGroupUrl extends AbstractUrl { @Override public Object[] createPath(Project project) { - final String[] groupPath = url.split(";"); + List groupPath = StringUtil.split(url, ";"); return new Object[]{new ModuleGroup(groupPath)}; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUtil.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUtil.java index 0e2cd6c51ac8..f9bbb5837c01 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUtil.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ModuleGroupUtil.java @@ -22,11 +22,10 @@ */ package com.intellij.ide.projectView.impl; -import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; import com.intellij.util.Function; -import java.util.ArrayList; +import java.util.List; import java.util.Map; public class ModuleGroupUtil { @@ -38,11 +37,9 @@ public class ModuleGroupUtil { final Map map, final Consumer> insertNode, final Function createNewNode) { - final ArrayList path = new ArrayList<>(); - final String[] groupPath = group.getGroupPath(); - for (String pathElement : groupPath) { - path.add(pathElement); - final ModuleGroup moduleGroup = new ModuleGroup(ArrayUtil.toStringArray(path)); + final List groupPath = group.getGroupPathList(); + for (int i = 0; i < groupPath.size(); i++) { + final ModuleGroup moduleGroup = new ModuleGroup(groupPath.subList(0, i+1)); T moduleGroupNode = map.get(moduleGroup); if (moduleGroupNode == null) { moduleGroupNode = createNewNode.fun(moduleGroup); @@ -59,11 +56,9 @@ public class ModuleGroupUtil { final Function needToCreateNode, final Consumer> insertNode, final Function createNewNode) { - final ArrayList path = new ArrayList<>(); - final String[] groupPath = group.getGroupPath(); - for (String pathElement : groupPath) { - path.add(pathElement); - final ModuleGroup moduleGroup = new ModuleGroup(ArrayUtil.toStringArray(path)); + final List groupPath = group.getGroupPathList(); + for (int i = 0; i < groupPath.size(); i++) { + final ModuleGroup moduleGroup = new ModuleGroup(groupPath.subList(0, i+1)); T moduleGroupNode = needToCreateNode.fun(moduleGroup); if (moduleGroupNode == null) { moduleGroupNode = createNewNode.fun(moduleGroup); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroup.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroup.java index 55c82b33c2e8..bd1b65c65cad 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroup.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroup.java @@ -22,6 +22,7 @@ import com.intellij.ide.projectView.actions.MoveModulesToSubGroupAction; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -53,13 +54,16 @@ public class MoveModuleToGroup extends ActionGroup { @NotNull public AnAction[] getChildren(@Nullable AnActionEvent e) { if (e == null) return EMPTY_ARRAY; + Project project = getEventProject(e); + if (project == null) return EMPTY_ARRAY; ModifiableModuleModel modifiableModuleModel = LangDataKeys.MODIFIABLE_MODULE_MODEL.getData(e.getDataContext()); List result = new ArrayList<>(); result.add(new MoveModulesToGroupAction(myModuleGroup, IdeBundle.message("action.move.module.to.this.group"))); result.add(new MoveModulesToSubGroupAction(myModuleGroup)); result.add(Separator.getInstance()); - result.addAll(myModuleGroup.childGroups(modifiableModuleModel, getEventProject(e)).stream().sorted((moduleGroup1, moduleGroup2) -> { + ModuleGrouper grouper = ModuleGrouper.Companion.instanceFor(project, modifiableModuleModel); + result.addAll(myModuleGroup.childGroups(grouper).stream().sorted((moduleGroup1, moduleGroup2) -> { assert moduleGroup1.getGroupPath().length == moduleGroup2.getGroupPath().length; return moduleGroup1.toString().compareToIgnoreCase(moduleGroup2.toString()); }).map(MoveModuleToGroup::new).collect(Collectors.toList())); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroupTopLevel.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroupTopLevel.java index 60d024ae9c48..a911e153b483 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroupTopLevel.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/MoveModuleToGroupTopLevel.java @@ -21,7 +21,8 @@ import com.intellij.ide.projectView.actions.MoveModulesToSubGroupAction; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleGrouper; +import com.intellij.openapi.module.ModuleGrouperKt; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,51 +35,39 @@ public class MoveModuleToGroupTopLevel extends ActionGroup { final DataContext dataContext = e.getDataContext(); final Project project = CommonDataKeys.PROJECT.getData(dataContext); final Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext); - boolean active = project != null && modules != null && modules.length != 0; + boolean active = project != null && modules != null && modules.length != 0 && !ModuleGrouperKt.isQualifiedModuleGroupsEnabled(); e.getPresentation().setVisible(active); } @Override @NotNull public AnAction[] getChildren(@Nullable AnActionEvent e) { - if (e == null) { - return EMPTY_ARRAY; - } - List topLevelGroupNames = new ArrayList<>(getTopLevelGroupNames(e.getDataContext())); - Collections.sort ( topLevelGroupNames ); + if (e == null) return EMPTY_ARRAY; + + Project project = getEventProject(e); + if (project == null) return EMPTY_ARRAY; + + ModifiableModuleModel moduleModel = LangDataKeys.MODIFIABLE_MODULE_MODEL.getData(e.getDataContext()); + ModuleGrouper grouper = ModuleGrouper.Companion.instanceFor(project, moduleModel); + List topLevelGroupNames = new ArrayList<>(getTopLevelGroupNames(grouper)); + Collections.sort(topLevelGroupNames); List result = new ArrayList<>(); result.add(new MoveModulesOutsideGroupAction()); result.add(new MoveModulesToSubGroupAction(null)); result.add(Separator.getInstance()); for (String name : topLevelGroupNames) { - result.add(new MoveModuleToGroup(new ModuleGroup(new String[]{name}))); + result.add(new MoveModuleToGroup(new ModuleGroup(Collections.singletonList(name)))); } return result.toArray(new AnAction[result.size()]); } - private static Collection getTopLevelGroupNames(final DataContext dataContext) { - final Project project = CommonDataKeys.PROJECT.getData(dataContext); - - final ModifiableModuleModel model = LangDataKeys.MODIFIABLE_MODULE_MODEL.getData(dataContext); - - Module[] allModules; - if ( model != null ) { - allModules = model.getModules(); - } else { - allModules = ModuleManager.getInstance(project).getModules(); - } - + private static Collection getTopLevelGroupNames(ModuleGrouper grouper) { Set topLevelGroupNames = new HashSet<>(); - for (final Module child : allModules) { - String[] group; - if ( model != null ) { - group = model.getModuleGroupPath(child); - } else { - group = ModuleManager.getInstance(project).getModuleGroupPath(child); - } - if (group != null) { - topLevelGroupNames.add(group[0]); + for (final Module child : grouper.getAllModules()) { + List group = grouper.getGroupPath(child); + if (!group.isEmpty()) { + topLevelGroupNames.add(group.get(0)); } } return topLevelGroupNames; diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractProjectNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractProjectNode.java index b7e24a7a3cb5..9b63422e13be 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractProjectNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/AbstractProjectNode.java @@ -53,7 +53,7 @@ public abstract class AbstractProjectNode extends ProjectViewNode { List result = new ArrayList<>(); try { for (String groupPath : groups.keySet()) { - result.add(createModuleGroupNode(new ModuleGroup(new String[]{groupPath}))); + result.add(createModuleGroupNode(new ModuleGroup(Collections.singletonList(groupPath)))); } for (Module module : nonGroupedModules) { result.add(createModuleGroup(module)); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ModuleGroupNode.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ModuleGroupNode.java index 89d61ca54a29..2752e4fb23ca 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ModuleGroupNode.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/nodes/ModuleGroupNode.java @@ -23,6 +23,7 @@ import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.projectView.actions.MoveModulesToGroupAction; import com.intellij.ide.projectView.impl.AbstractProjectViewPane; import com.intellij.ide.projectView.impl.ModuleGroup; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.module.Module; @@ -52,12 +53,13 @@ public abstract class ModuleGroupNode extends ProjectViewNode imple @Override @NotNull public Collection getChildren() { - final Collection childGroups = getValue().childGroups(getProject()); + ModuleGrouper grouper = ModuleGrouper.Companion.instanceFor(getProject()); + final Collection childGroups = getValue().childGroups(grouper); final List result = new ArrayList<>(); for (final ModuleGroup childGroup : childGroups) { result.add(createModuleGroupNode(childGroup)); } - Collection modules = getValue().modulesInGroup(getProject(), false); + Collection modules = getValue().modulesInGroup(grouper, false); try { for (Module module : modules) { result.add(createModuleNode(module)); diff --git a/platform/lang-impl/src/com/intellij/ide/util/DirectoryChooserModuleTreeView.java b/platform/lang-impl/src/com/intellij/ide/util/DirectoryChooserModuleTreeView.java index 639f49960bd0..db3e157b8cab 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/DirectoryChooserModuleTreeView.java +++ b/platform/lang-impl/src/com/intellij/ide/util/DirectoryChooserModuleTreeView.java @@ -18,9 +18,9 @@ package com.intellij.ide.util; import com.intellij.ide.projectView.impl.ModuleGroup; import com.intellij.ide.projectView.impl.ModuleGroupUtil; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; @@ -32,8 +32,6 @@ import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.TreeSpeedSearch; import com.intellij.ui.treeStructure.Tree; -import com.intellij.util.Consumer; -import com.intellij.util.Function; import com.intellij.util.PlatformIcons; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.HashMap; @@ -60,6 +58,7 @@ public class DirectoryChooserModuleTreeView implements DirectoryChooserView { private final DefaultMutableTreeNode myRootNode; private final ProjectFileIndex myFileIndex; private final Project myProject; + private final ModuleGrouper myModuleGrouper; public DirectoryChooserModuleTreeView(@NotNull Project project) { myRootNode = new DefaultMutableTreeNode(); @@ -67,6 +66,7 @@ public class DirectoryChooserModuleTreeView implements DirectoryChooserView { myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); myFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); myProject = project; + myModuleGrouper = ModuleGrouper.Companion.instanceFor(myProject); myTree.setRootVisible(false); myTree.setShowsRootHandles(true); myTree.setCellRenderer(new MyTreeCellRenderer()); @@ -153,8 +153,8 @@ public class DirectoryChooserModuleTreeView implements DirectoryChooserView { DefaultMutableTreeNode node = myModuleNodes.get(module); if (node == null) { node = new DefaultMutableTreeNode(module, true); - final String[] groupPath = module != null ? ModuleManager.getInstance(myProject).getModuleGroupPath(module) : null; - if (groupPath == null || groupPath.length == 0){ + final List groupPath = module != null ? myModuleGrouper.getGroupPath(module) : null; + if (groupPath == null || groupPath.isEmpty()) { insertNode(node, myRootNode); } else { final DefaultMutableTreeNode parentNode = ModuleGroupUtil.buildModuleGroupPath(new ModuleGroup(groupPath), @@ -242,7 +242,7 @@ public class DirectoryChooserModuleTreeView implements DirectoryChooserView { } else if (value instanceof Module) { final Module module = (Module)value; - append(module.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); + append(myModuleGrouper.getPresentableName(module), SimpleTextAttributes.REGULAR_ATTRIBUTES); setIcon(ModuleType.get(module).getIcon()); } else if (value instanceof ModuleGroup) { append(value.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES); diff --git a/platform/lang-impl/src/com/intellij/packageDependencies/ui/FileTreeModelBuilder.java b/platform/lang-impl/src/com/intellij/packageDependencies/ui/FileTreeModelBuilder.java index 9fb409eade03..697bf54dde39 100644 --- a/platform/lang-impl/src/com/intellij/packageDependencies/ui/FileTreeModelBuilder.java +++ b/platform/lang-impl/src/com/intellij/packageDependencies/ui/FileTreeModelBuilder.java @@ -20,6 +20,7 @@ import com.intellij.analysis.AnalysisScopeBundle; import com.intellij.icons.AllIcons; import com.intellij.ide.dnd.aware.DnDAwareTree; import com.intellij.ide.projectView.impl.ModuleGroup; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.ide.projectView.impl.nodes.ProjectViewDirectoryHelper; import com.intellij.ide.scopeView.nodes.BasePsiNode; import com.intellij.openapi.diagnostic.Logger; @@ -52,6 +53,7 @@ import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -74,6 +76,7 @@ public class FileTreeModelBuilder { private final Map myModuleDirNodes = new HashMap<>(); private final Map myModuleNodes = new HashMap<>(); private final Map myModuleGroupNodes = new HashMap<>(); + private final ModuleGrouper myGrouper; private GeneralGroupNode myExternalNode; private int myScannedFileCount = 0; @@ -84,12 +87,13 @@ public class FileTreeModelBuilder { protected final VirtualFile myBaseDir; protected VirtualFile[] myContentRoots; - public FileTreeModelBuilder(Project project, Marker marker, DependenciesPanel.DependencyPanelSettings settings) { + public FileTreeModelBuilder(@NotNull Project project, Marker marker, DependenciesPanel.DependencyPanelSettings settings) { myProject = project; myBaseDir = myProject.getBaseDir(); myContentRoots = ProjectRootManager.getInstance(myProject).getContentRoots(); final boolean multiModuleProject = ModuleManager.getInstance(myProject).getModules().length > 1; myShowModules = settings.UI_SHOW_MODULES && multiModuleProject; + myGrouper = ModuleGrouper.Companion.instanceFor(project); final ProjectViewDirectoryHelper directoryHelper = ProjectViewDirectoryHelper.getInstance(myProject); myFlattenPackages = directoryHelper.supportsFlattenPackages() && settings.UI_FLATTEN_PACKAGES; myCompactEmptyMiddlePackages = directoryHelper.supportsHideEmptyMiddlePackages() && settings.UI_COMPACT_EMPTY_MIDDLE_PACKAGES; @@ -555,10 +559,9 @@ public class FileTreeModelBuilder { } ModuleNode node = myModuleNodes.get(module); if (node != null) return node; - node = new ModuleNode(module); - final ModuleManager moduleManager = ModuleManager.getInstance(myProject); - final String[] groupPath = moduleManager.getModuleGroupPath(module); - if (groupPath == null) { + node = new ModuleNode(module, myShowModuleGroups ? myGrouper : null); + final List groupPath = myGrouper.getGroupPath(module); + if (groupPath.isEmpty()) { myModuleNodes.put(module, node); myRoot.add(node); return node; @@ -572,18 +575,16 @@ public class FileTreeModelBuilder { return node; } - private PackageDependenciesNode getParentModuleGroup(String[] groupPath){ - final String key = StringUtil.join(groupPath); + private PackageDependenciesNode getParentModuleGroup(List groupPath){ + final String key = StringUtil.join(groupPath, ""); ModuleGroupNode groupNode = myModuleGroupNodes.get(key); if (groupNode == null) { groupNode = new ModuleGroupNode(new ModuleGroup(groupPath), myProject); myModuleGroupNodes.put(key, groupNode); myRoot.add(groupNode); } - if (groupPath.length > 1) { - String [] path = new String[groupPath.length - 1]; - System.arraycopy(groupPath, 0, path, 0, groupPath.length - 1); - final PackageDependenciesNode node = getParentModuleGroup(path); + if (groupPath.size() > 1) { + final PackageDependenciesNode node = getParentModuleGroup(groupPath.subList(0, groupPath.size() - 1)); node.add(groupNode); } return groupNode; diff --git a/platform/lang-impl/src/com/intellij/packageDependencies/ui/ModuleNode.java b/platform/lang-impl/src/com/intellij/packageDependencies/ui/ModuleNode.java index c05087fbec02..f9f0dcfac257 100644 --- a/platform/lang-impl/src/com/intellij/packageDependencies/ui/ModuleNode.java +++ b/platform/lang-impl/src/com/intellij/packageDependencies/ui/ModuleNode.java @@ -15,6 +15,7 @@ */ package com.intellij.packageDependencies.ui; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.idea.ActionsBundle; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; @@ -23,16 +24,19 @@ import com.intellij.openapi.util.Comparing; import com.intellij.pom.NavigatableWithText; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Set; public class ModuleNode extends PackageDependenciesNode implements NavigatableWithText { private final @NotNull Module myModule; + private final ModuleGrouper myModuleGrouper; - public ModuleNode(@NotNull Module module) { + public ModuleNode(@NotNull Module module, @Nullable ModuleGrouper moduleGrouper) { super(module.getProject()); myModule = module; + myModuleGrouper = moduleGrouper; } @Override @@ -67,7 +71,7 @@ public class ModuleNode extends PackageDependenciesNode implements NavigatableWi @Override public String toString() { - return myModule.getName(); + return myModuleGrouper != null ? myModuleGrouper.getPresentableName(myModule) : myModule.getName(); } public String getModuleName() { diff --git a/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java b/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java index b094e933df02..cacb324022c4 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java @@ -100,7 +100,7 @@ public abstract class MasterDetailsComponent implements Configurable, DetailsCom myState.setLastEditedConfigurable(getNodePathString(node)); //survive after rename; myDetails.setText(node.getConfigurable().getBannerSlogan()); - ((DefaultTreeModel)myTree.getModel()).reload(node); + node.reloadNode((DefaultTreeModel)MasterDetailsComponent.this.myTree.getModel()); fireItemsChangedExternally(); } }; @@ -869,6 +869,10 @@ public abstract class MasterDetailsComponent implements Configurable, DetailsCom } return null; } + + protected void reloadNode(DefaultTreeModel treeModel) { + treeModel.reload(this); + } } @SuppressWarnings({"ConstantConditions"}) diff --git a/platform/platform-tests/testSrc/com/intellij/module/ExplicitModuleGroupTest.kt b/platform/platform-tests/testSrc/com/intellij/module/ExplicitModuleGroupTest.kt new file mode 100644 index 000000000000..02ed3c0e7f14 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/module/ExplicitModuleGroupTest.kt @@ -0,0 +1,72 @@ + +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.module + +import com.intellij.ide.projectView.impl.ModuleGroup +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleGrouper +import com.intellij.openapi.module.ModuleManager +import com.intellij.testFramework.PlatformTestCase +import org.junit.Assert.assertArrayEquals +import org.junit.Test + +/** + * @author nik + */ +class ExplicitModuleGroupTest : PlatformTestCase() { + @Test + fun `test single module`() { + val module = createModuleInGroup("module", "a", "b") + val path = ModuleManager.getInstance(myProject).getModuleGroupPath(module)!! + assertArrayEquals(path, arrayOf("a", "b")) + + val parentGroup = ModuleGroup(listOf("a")) + assertEmpty(parentGroup.modulesInGroup(myProject, false)) + assertSameElements(parentGroup.modulesInGroup(myProject, true), module) + + val group = assertOneElement(parentGroup.childGroups(grouper)) + assertArrayEquals(group.groupPath, arrayOf("a", "b")) + assertSameElements(group.modulesInGroup(grouper, false), module) + assertEmpty(group.childGroups(grouper)) + } + + fun `test two modules`() { + val module1 = createModuleInGroup("module1", "a") + val module2 = createModuleInGroup("module2", "a", "b") + + val parentGroup = ModuleGroup(listOf("a")) + assertSameElements(parentGroup.modulesInGroup(myProject, false), module1) + assertSameElements(parentGroup.modulesInGroup(myProject, true), module1, module2) + + val group = assertOneElement(parentGroup.childGroups(grouper)) + assertArrayEquals(group.groupPath, arrayOf("a", "b")) + assertSameElements(group.modulesInGroup(myProject, false), module2) + assertEmpty(group.childGroups(grouper)) + } + + private val grouper: ModuleGrouper + get() = ModuleGrouper.instanceFor(myProject) + + private fun createModuleInGroup(name: String, vararg path: String): Module { + val module = createModule(name) + val model = ModuleManager.getInstance(myProject).modifiableModel + model.setModuleGroupPath(module, path) + runWriteAction { model.commit() } + return module + } +} \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt b/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt new file mode 100644 index 000000000000..703cb9083fe7 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/module/GroupModulesByQualifiedNamesTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.module + +import com.intellij.ide.projectView.impl.ModuleGroup +import com.intellij.openapi.module.ModuleGrouper +import com.intellij.openapi.util.registry.Registry +import com.intellij.testFramework.PlatformTestCase +import org.junit.Assert +import org.junit.Test + +/** + * @author nik + */ +class GroupModulesByQualifiedNamesTest : PlatformTestCase() { + @Test + fun `test single module`() { + val module = createModule("a.b.module") + assertEquals("module", grouper.getPresentableName(module)) + + val parentGroup = ModuleGroup(listOf("a")) + assertEmpty(parentGroup.modulesInGroup(grouper, false)) + assertSameElements(parentGroup.modulesInGroup(grouper, true), module) + + val group = assertOneElement(parentGroup.childGroups(grouper)) + Assert.assertArrayEquals(group.groupPath, arrayOf("a", "b")) + assertSameElements(group.modulesInGroup(grouper, false), module) + assertEmpty(group.childGroups(grouper)) + } + + fun `test two modules`() { + val module1 = createModule("a.module1") + val module2 = createModule("a.b.module2") + + assertEquals("module1", grouper.getPresentableName(module1)) + assertEquals("module2", grouper.getPresentableName(module2)) + + val parentGroup = ModuleGroup(listOf("a")) + assertSameElements(parentGroup.modulesInGroup(grouper, false), module1) + assertSameElements(parentGroup.modulesInGroup(grouper, true), module1, module2) + + val group = assertOneElement(parentGroup.childGroups(grouper)) + Assert.assertArrayEquals(group.groupPath, arrayOf("a", "b")) + assertSameElements(group.modulesInGroup(grouper, false), module2) + assertEmpty(group.childGroups(grouper)) + } + + private val grouper: ModuleGrouper + get() { + val property = Registry.get("project.qualified.module.names") + return try { + property.setValue(true) + ModuleGrouper.instanceFor(myProject) + } + finally { + property.setValue(false) + } + } +} diff --git a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java index 96904b5bd4e9..3c919453828c 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java +++ b/platform/projectModel-api/src/com/intellij/openapi/module/ModuleManager.java @@ -167,4 +167,6 @@ public abstract class ModuleManager extends SimpleModificationTracker { */ @Nullable public abstract String[] getModuleGroupPath(@NotNull Module module); + + public abstract boolean hasModuleGroups(); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/ModuleGrouper.kt b/platform/projectModel-impl/src/com/intellij/openapi/module/ModuleGrouper.kt new file mode 100644 index 000000000000..e3d83781094f --- /dev/null +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/ModuleGrouper.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.module + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.util.text.StringUtil +import java.util.* + +/** + * @author nik + */ +interface ModuleGrouper { + fun getGroupPath(module: Module): List + + fun getPresentableName(module: Module): String + + fun getAllModules(): Array + + companion object { + @JvmOverloads + fun instanceFor(project: Project, moduleModel: ModifiableModuleModel? = null): ModuleGrouper { + val hasGroups = moduleModel?.hasModuleGroups() ?: ModuleManager.getInstance(project).hasModuleGroups() + if (!isQualifiedModuleGroupsEnabled() || hasGroups) { + return ExplicitModuleGrouper(project, moduleModel) + } + return QualifiedNameGrouper(project, moduleModel) + } + } +} + +fun isQualifiedModuleGroupsEnabled() = Registry.`is`("project.qualified.module.names") + +private abstract class ModuleGrouperBase(protected val project: Project, protected val model: ModifiableModuleModel?) : ModuleGrouper { + override fun getAllModules(): Array = model?.modules ?: ModuleManager.getInstance(project).modules + protected fun getModuleName(module: Module) = model?.getNewName(module) ?: module.name +} + +private class QualifiedNameGrouper(project: Project, model: ModifiableModuleModel?) : ModuleGrouperBase(project, model) { + override fun getGroupPath(module: Module): List { + return getModuleName(module).split('.').dropLast(1) + } + + override fun getPresentableName(module: Module) = StringUtil.getShortName(getModuleName(module)) +} + +private class ExplicitModuleGrouper(project: Project, model: ModifiableModuleModel?): ModuleGrouperBase(project, model) { + override fun getGroupPath(module: Module): List { + val path = if (model != null) model.getModuleGroupPath(module) else ModuleManager.getInstance(project).getModuleGroupPath(module) + return if (path != null) Arrays.asList(*path) else emptyList() + } + + override fun getPresentableName(module: Module) = getModuleName(module) +} diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java index 18d3d93f4874..b596f74b7fc3 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleManagerImpl.java @@ -953,6 +953,11 @@ public abstract class ModuleManagerImpl extends ModuleManager implements Disposa return myModuleModel.getModuleGroupPath(module); } + @Override + public boolean hasModuleGroups() { + return myModuleModel.hasModuleGroups(); + } + public void setModuleGroupPath(Module module, String[] groupPath) { myModuleModel.setModuleGroupPath(module, groupPath); } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 4cf9bd971d37..7e76e0b7396d 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -816,6 +816,9 @@ custom.folding.max.lookup.depth.description=Max lookup depth for custom folding project.structure.add.tools.jar.to.new.jdk=false project.structure.add.tools.jar.to.new.jdk.description=Automatically add tools.jar to 'Classpath' when new JDK is created +project.qualified.module.names=false +project.qualified.module.names.description=Organize module nodes accordingly to their qualified (dot-separated) names + tfs.set.connection.timeout=false application.deactivation.timeout=1500 diff --git a/platform/util/src/com/intellij/util/containers/ContainerUtil.java b/platform/util/src/com/intellij/util/containers/ContainerUtil.java index 8dddb3316491..6c6c8a3a3659 100644 --- a/platform/util/src/com/intellij/util/containers/ContainerUtil.java +++ b/platform/util/src/com/intellij/util/containers/ContainerUtil.java @@ -2758,6 +2758,11 @@ public class ContainerUtil extends ContainerUtilRt { return set == null ? Collections.emptySet() : set; } + @Contract(pure = true) + public static boolean startsWith(@NotNull List list, @NotNull List prefix) { + return list.size() >= prefix.size() && list.subList(0, prefix.size()).equals(prefix); + } + @Nullable @Contract(pure=true) public static > C nullize(@Nullable C collection) { diff --git a/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java b/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java index 32346be41e5c..1f3228454a0b 100644 --- a/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java +++ b/plugins/devkit/src/inspections/quickfix/PluginDescriptorChooser.java @@ -17,9 +17,9 @@ package org.jetbrains.idea.devkit.inspections.quickfix; import com.intellij.codeInsight.hint.HintManager; import com.intellij.icons.AllIcons; +import com.intellij.openapi.module.ModuleGrouper; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; @@ -150,8 +150,8 @@ public class PluginDescriptorChooser { private static List createCandidates(final Module currentModule, List> elements) { - ModuleManager moduleManager = ModuleManager.getInstance(currentModule.getProject()); - final String[] groupPath = moduleManager.getModuleGroupPath(currentModule); + ModuleGrouper grouper = ModuleGrouper.Companion.instanceFor(currentModule.getProject()); + final List groupPath = grouper.getGroupPath(currentModule); elements.sort((o1, o2) -> { // current module = first group @@ -162,8 +162,8 @@ public class PluginDescriptorChooser { if (currentModule.equals(module2)) return 1; if (module1 != null && module2 != null) { - int groupComparison = Comparing.compare(groupMatchLevel(groupPath, moduleManager.getModuleGroupPath(module2)), - groupMatchLevel(groupPath, moduleManager.getModuleGroupPath(module1))); + int groupComparison = Comparing.compare(groupMatchLevel(groupPath, grouper.getGroupPath(module2)), + groupMatchLevel(groupPath, grouper.getGroupPath(module1))); if (groupComparison != 0) { return groupComparison; } @@ -196,16 +196,13 @@ public class PluginDescriptorChooser { }); } - private static int groupMatchLevel(@Nullable String[] targetGroupPath, @Nullable String[] groupPath) { - if (targetGroupPath != null && groupPath != null) { - for (int i = 0; i < Math.min(targetGroupPath.length, groupPath.length); i++) { - if (!targetGroupPath[i].equals(groupPath[i])) { - return i; - } + private static int groupMatchLevel(@NotNull List targetGroupPath, @NotNull List groupPath) { + for (int i = 0; i < Math.min(targetGroupPath.size(), groupPath.size()); i++) { + if (!targetGroupPath.get(i).equals(groupPath.get(i))) { + return i; } - return Math.min(targetGroupPath.length, groupPath.length); } - return 0; + return Math.min(targetGroupPath.size(), groupPath.size()); } public static List> findAppropriateIntelliJModule(String moduleName,