From 38255495c8156358c7bba31f9dbe3c37c31b333c Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Tue, 31 Jan 2012 16:45:29 +0400 Subject: [PATCH] IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle files changes 1. Refactored gradle project structure tree model to the distinct class; 2. Setup test infrastructure to allow to test project structure tree as well; 3. Corresponding project structure tree checks are added to the existing tests; --- .../gradle/bootstrap/GradleBootstrap.java | 10 +- .../gradle/config/GradleToolWindowPanel.java | 15 +- .../GradleProjectStructureChangesPanel.java | 288 +---------------- .../sync/GradleProjectStructureTreeModel.java | 295 ++++++++++++++++++ .../GradleProjectStructureNodeDescriptor.java | 3 +- ...dleProjectStructureChangesModelTest.groovy | 94 ++++-- .../testutil/IntellijProjectBuilder.groovy | 2 +- .../testutil/ProjectStructureChecker.groovy | 62 ++++ 8 files changed, 466 insertions(+), 303 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java create mode 100644 plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java index 254145d26a65..8280bcbc400b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java @@ -9,6 +9,7 @@ import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ex.ToolWindowManagerEx; import com.intellij.ui.content.impl.ContentImpl; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelper; import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesModel; import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesPanel; import org.jetbrains.plugins.gradle.ui.GradleIcons; @@ -25,10 +26,14 @@ public class GradleBootstrap extends AbstractProjectComponent { private static final String GRADLE_TOOL_WINDOW_ID = GradleBundle.message("gradle.name"); private final GradleProjectStructureChangesModel myChangesModel; + private final GradleProjectStructureHelper myProjectStructureHelper; - public GradleBootstrap(@NotNull Project project, @NotNull GradleProjectStructureChangesModel changesModel) { + public GradleBootstrap(@NotNull Project project, + @NotNull GradleProjectStructureChangesModel changesModel, + @NotNull GradleProjectStructureHelper projectStructureHelper) { super(project); myChangesModel = changesModel; + myProjectStructureHelper = projectStructureHelper; } @Override @@ -46,7 +51,8 @@ public class GradleBootstrap extends AbstractProjectComponent { ToolWindow toolWindow = manager.registerToolWindow(GRADLE_TOOL_WINDOW_ID, false, ToolWindowAnchor.RIGHT); toolWindow.setIcon(GradleIcons.GRADLE_ICON); String syncTitle = GradleBundle.message("gradle.sync.title.tab"); - final GradleProjectStructureChangesPanel projectStructureChanges = new GradleProjectStructureChangesPanel(myProject, myChangesModel); + final GradleProjectStructureChangesPanel projectStructureChanges + = new GradleProjectStructureChangesPanel(myProject, myChangesModel, myProjectStructureHelper); toolWindow.getContentManager().addContent(new ContentImpl(projectStructureChanges, syncTitle, true)); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java index 063cb6eee62b..585e30522962 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java @@ -9,6 +9,7 @@ import com.intellij.ui.ScrollPaneFactory; import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelper; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.ui.RichTextControlBuilder; @@ -41,11 +42,16 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel { /** Top-level container, managed by the card layout. */ private final JPanel myContent = new JPanel(myLayout); - private final Project myProject; + private final Project myProject; + private final GradleProjectStructureHelper myProjectStructureHelper; - protected GradleToolWindowPanel(@NotNull Project project, @NotNull String place) { + protected GradleToolWindowPanel(@NotNull Project project, + @Nullable GradleProjectStructureHelper projectStructureHelper, + @NotNull String place) + { super(true); myProject = project; + myProjectStructureHelper = projectStructureHelper; final ActionManager actionManager = ActionManager.getInstance(); final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(TOOL_WINDOW_TOOLBAR_ID); ActionToolbar actionToolbar = actionManager.createActionToolbar(place, actionGroup, true); @@ -91,6 +97,11 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel { return myProject; } + @NotNull + public GradleProjectStructureHelper getProjectStructureHelper() { + return myProjectStructureHelper; + } + /** * @return GUI control to be displayed at the current tab */ diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java index b2d0a5ce81eb..65fb40336251 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java @@ -1,38 +1,18 @@ package org.jetbrains.plugins.gradle.sync; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.LibraryOrderEntry; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.OrderEntry; -import com.intellij.openapi.roots.RootPolicy; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.hash.HashMap; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.plugins.gradle.config.GradleTextAttributes; import org.jetbrains.plugins.gradle.config.GradleToolWindowPanel; -import org.jetbrains.plugins.gradle.diff.*; -import org.jetbrains.plugins.gradle.model.GradleLibraryDependency; -import org.jetbrains.plugins.gradle.model.GradleModule; -import org.jetbrains.plugins.gradle.model.Named; -import org.jetbrains.plugins.gradle.ui.GradleIcons; -import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNodeDescriptor; -import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelper; import org.jetbrains.plugins.gradle.util.GradleConstants; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.MutableTreeNode; -import javax.swing.tree.TreeNode; import java.awt.*; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; -import java.util.Map; /** * UI control for showing difference between the gradle and intellij project structure. @@ -42,31 +22,15 @@ import java.util.Map; */ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { - /** - *
-   *     ...
-   *      |_module     <- module's name is a key
-   *          |_...
-   *          |_dependencies   <- dependencies holder node is a value
-   *                  |_dependency1
-   *                  |_dependency2
-   * 
- */ - private final Map myModuleDependencies = new HashMap(); - private final Map myModules = new HashMap(); + private GradleProjectStructureTreeModel myTreeModel; + private JPanel myContent; - private final TreeNode[] myNodeHolder = new TreeNode[1]; - private final int[] myIndexHolder = new int[1]; - - private final GradleProjectStructureChangesModel myChangesModel; - - private DefaultTreeModel myTreeModel; - private JPanel myContent; - - public GradleProjectStructureChangesPanel(@NotNull Project project, @NotNull GradleProjectStructureChangesModel model) { - super(project, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); - myChangesModel = model; - myChangesModel.addListener(new GradleProjectStructureChangeListener() { + public GradleProjectStructureChangesPanel(@NotNull Project project, + @NotNull GradleProjectStructureChangesModel model, + @NotNull GradleProjectStructureHelper projectStructureHelper) + { + super(project, projectStructureHelper, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); + model.addListener(new GradleProjectStructureChangeListener() { @Override public void onChanges(@NotNull final Collection oldChanges, @NotNull final Collection currentChanges) @@ -74,111 +38,30 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { - updateTree(currentChanges); - processObsoleteChanges(ContainerUtil.subtract(oldChanges, currentChanges)); - + myTreeModel.update(currentChanges); + myTreeModel.pruneObsoleteNodes(ContainerUtil.subtract(oldChanges, currentChanges)); } }); } }); - rebuildTree(); } - @NotNull - private DefaultTreeModel init() { + private void init() { myContent = new JPanel(new GridBagLayout()); - DefaultTreeModel treeModel = new DefaultTreeModel(null); - Tree tree = new Tree(treeModel); + myTreeModel = new GradleProjectStructureTreeModel(getProject(), getProjectStructureHelper()); + Tree tree = new Tree(myTreeModel); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraints.weightx = constraints.weighty = 1; myContent.add(tree, constraints); myContent.setBackground(tree.getBackground()); - return treeModel; - } - - private void rebuildTree() { - myModules.clear(); - myModuleDependencies.clear(); - - DefaultMutableTreeNode root = new DefaultMutableTreeNode(buildDescriptor(getProject())); - final Module[] modules = ModuleManager.getInstance(getProject()).getModules(); - RootPolicy policy = new RootPolicy() { - @Override - public LibraryOrderEntry visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, LibraryOrderEntry value) { - return libraryOrderEntry; - } - }; - for (Module module : modules) { - final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(buildDescriptor(module)); - myModules.put(module.getName(), moduleNode); // Assuming that module names are unique. - List libraryDependencies = new ArrayList(); - final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); - for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) { - final LibraryOrderEntry library = orderEntry.accept(policy, null); - if (library != null) { - libraryDependencies.add(library); - } - } - if (!libraryDependencies.isEmpty()) { - DefaultMutableTreeNode dependenciesNode = getDependenciesNode(module.getName()); - for (LibraryOrderEntry dependency : libraryDependencies) { - dependenciesNode.add(new DefaultMutableTreeNode(buildDescriptor(dependency))); - } - } - root.add(moduleNode); - } - - myTreeModel.setRoot(root); - updateTree(myChangesModel.getChanges()); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull String name, Icon icon) { - return new GradleProjectStructureNodeDescriptor(new Object(), name, icon); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Named entity, @NotNull Icon icon) { - return new GradleProjectStructureNodeDescriptor(entity, entity.getName(), icon); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Project project) { - return new GradleProjectStructureNodeDescriptor(project, project.getName(), GradleIcons.PROJECT_ICON); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Module module) { - return new GradleProjectStructureNodeDescriptor(module, module.getName(), GradleIcons.MODULE_ICON); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull GradleModule module) { - return new GradleProjectStructureNodeDescriptor(module, module.getName(), GradleIcons.MODULE_ICON); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull LibraryOrderEntry library) { - return new GradleProjectStructureNodeDescriptor(library, library.getPresentableName(), GradleIcons.LIB_ICON); - } - - private DefaultMutableTreeNode getDependenciesNode(@NotNull String moduleName) { - final DefaultMutableTreeNode cached = myModuleDependencies.get(moduleName); - if (cached != null) { - return cached; - } - DefaultMutableTreeNode moduleNode = myModules.get(moduleName); - if (moduleNode == null) { - moduleNode = new DefaultMutableTreeNode(buildDescriptor(moduleName, GradleIcons.MODULE_ICON)); - myModules.put(moduleName, moduleNode); - } - - DefaultMutableTreeNode result = new DefaultMutableTreeNode(GradleBundle.message("gradle.project.structure.tree.node.dependencies")); - moduleNode.add(result); - myModuleDependencies.put(moduleName, result); - return result; } @NotNull @Override protected JComponent buildContent() { - myTreeModel = init(); + init(); return myContent; } @@ -188,143 +71,4 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { int i = 1; } - private void updateTree(@NotNull Collection changes) { - for (GradleProjectStructureChange change : changes) { - change.invite(new GradleProjectStructureChangeVisitor() { - @Override - public void visit(@NotNull GradleRenameChange change) { - // TODO den implement - } - - @Override - public void visit(@NotNull GradleProjectStructureChange change) { - // TODO den implement - } - - @Override - public void visit(@NotNull GradleModulePresenceChange change) { - // TODO den implement - } - - @Override - public void visit(@NotNull GradleLibraryDependencyPresenceChange change) { - String moduleName; - GradleProjectStructureNodeDescriptor descriptor; - final GradleLibraryDependency gradleEntity = change.getGradleEntity(); - final LibraryOrderEntry intellijEntity = change.getIntellijEntity(); - final Object missingEntity; - if (gradleEntity == null && intellijEntity == null) { - // Never expect to be here. - assert false; - } - - if (gradleEntity == null) { - // Particular library dependency is added at the intellij side. - moduleName = intellijEntity.getOwnerModule().getName(); - descriptor = buildDescriptor(intellijEntity); - descriptor.setAttributes(GradleTextAttributes.INTELLIJ_LOCAL_CHANGE); - missingEntity = intellijEntity; - } - else { - // Particular library dependency is added at the gradle side. - moduleName = gradleEntity.getOwnerModule().getName(); - descriptor = buildDescriptor(gradleEntity, GradleIcons.LIB_ICON); - descriptor.setAttributes(GradleTextAttributes.GRADLE_LOCAL_CHANGE); - missingEntity = gradleEntity; - } - final DefaultMutableTreeNode dependenciesNode = getDependenciesNode(moduleName); - for (int i = 0, max = dependenciesNode.getChildCount(); i < max; i++) { - final DefaultMutableTreeNode child = (DefaultMutableTreeNode)dependenciesNode.getChildAt(i); - GradleProjectStructureNodeDescriptor d = (GradleProjectStructureNodeDescriptor)child.getUserObject(); - if (missingEntity.equals(d.getElement())) { - d.setAttributes(descriptor.getAttributes()); - return; - } - } - DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(descriptor); - dependenciesNode.add(newNode); - myTreeModel.nodeStructureChanged(dependenciesNode); - } - }); - } - } - - /** - * Updates the tree state considering that the given changes are obsolete. - *

- * Example: - *

-   * 
    - *
  1. There is a particular intellij-local library (change from the gradle project structure);
  2. - *
  3. Corresponding node is shown at the current UI;
  4. - *
  5. The library is removed, i.e. corresponding change has become obsolete;
  6. - *
  7. This method is notified within the obsolete change and is expected to remove the corresponding node;
  8. - *
- *
- */ - private void processObsoleteChanges(Collection changes) { - for (GradleProjectStructureChange change : changes) { - change.invite(new GradleProjectStructureChangeVisitor() { - @Override - public void visit(@NotNull GradleRenameChange change) { - // TODO den implement - } - - @Override - public void visit(@NotNull GradleProjectStructureChange change) { - // TODO den implement - } - - @Override - public void visit(@NotNull GradleModulePresenceChange change) { - // TODO den implement - } - - @Override - public void visit(@NotNull GradleLibraryDependencyPresenceChange change) { - // We need to remove the corresponding node then. - String moduleName; - Object library; - final GradleLibraryDependency gradleEntity = change.getGradleEntity(); - final LibraryOrderEntry intellijEntity = change.getIntellijEntity(); - assert gradleEntity != null || intellijEntity != null; - if (gradleEntity == null) { - moduleName = intellijEntity.getOwnerModule().getName(); - library = intellijEntity; - } - else { - moduleName = gradleEntity.getOwnerModule().getName(); - library = gradleEntity; - } - final DefaultMutableTreeNode holder = myModuleDependencies.get(moduleName); - if (holder == null) { - return; - } - for (DefaultMutableTreeNode node = holder.getFirstLeaf(); node != null; node = node.getNextSibling()) { - GradleProjectStructureNodeDescriptor descriptor = (GradleProjectStructureNodeDescriptor)node.getUserObject(); - if (descriptor.getElement().equals(library)) { - removeNode(node); - return; - } - } - } - }); - } - } - - private void removeNode(@NotNull TreeNode node) { - final MutableTreeNode parent = (MutableTreeNode)node.getParent(); - if (parent == null) { - return; - } - int i = parent.getIndex(node); - if (i <= 0) { - assert false : node; - return; - } - parent.remove(i); - myIndexHolder[0] = i; - myNodeHolder[0] = node; - myTreeModel.nodesWereRemoved(parent, myIndexHolder, myNodeHolder); - } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java new file mode 100644 index 000000000000..e851aba7a941 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java @@ -0,0 +1,295 @@ +package org.jetbrains.plugins.gradle.sync; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.roots.RootPolicy; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.config.GradleTextAttributes; +import org.jetbrains.plugins.gradle.diff.*; +import org.jetbrains.plugins.gradle.model.GradleLibraryDependency; +import org.jetbrains.plugins.gradle.model.Named; +import org.jetbrains.plugins.gradle.ui.GradleIcons; +import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNodeDescriptor; +import org.jetbrains.plugins.gradle.util.GradleBundle; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Model for the target project structure tree used by the gradle integration. + *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since 1/30/12 4:20 PM + */ +public class GradleProjectStructureTreeModel extends DefaultTreeModel { + + /** + *

+   *     ...
+   *      |_module     <- module's name is a key
+   *          |_...
+   *          |_dependencies   <- dependencies holder node is a value
+   *                  |_dependency1
+   *                  |_dependency2
+   * 
+ */ + private final Map myModuleDependencies = new HashMap(); + private final Map myModules = new HashMap(); + + private final TreeNode[] myNodeHolder = new TreeNode[1]; + private final int[] myIndexHolder = new int[1]; + + private final Project myProject; + private final GradleProjectStructureHelper myProjectStructureHelper; + + public GradleProjectStructureTreeModel(@NotNull Project project, @NotNull GradleProjectStructureHelper projectStructureHelper) { + super(null); + myProject = project; + myProjectStructureHelper = projectStructureHelper; + rebuild(); + } + + public void rebuild() { + myModuleDependencies.clear(); + myModules.clear(); + + DefaultMutableTreeNode root = new DefaultMutableTreeNode(buildDescriptor(getProject())); + final Collection modules = myProjectStructureHelper.getModules(getProject()); + RootPolicy policy = new RootPolicy() { + @Override + public LibraryOrderEntry visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, LibraryOrderEntry value) { + return libraryOrderEntry; + } + }; + for (Module module : modules) { + final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(buildDescriptor(module)); + myModules.put(module.getName(), moduleNode); // Assuming that module names are unique. + List libraryDependencies = new ArrayList(); + for (OrderEntry orderEntry : myProjectStructureHelper.getOrderEntries(module)) { + final LibraryOrderEntry libraryDependency = orderEntry.accept(policy, null); + if (libraryDependency != null && !StringUtil.isEmpty(libraryDependency.getLibraryName())) { + libraryDependencies.add(libraryDependency); + } + } + if (!libraryDependencies.isEmpty()) { + DefaultMutableTreeNode dependenciesNode = getDependenciesNode(module.getName()); + for (LibraryOrderEntry dependency : libraryDependencies) { + dependenciesNode.add(new DefaultMutableTreeNode(buildDescriptor(dependency))); + } + moduleNode.add(dependenciesNode); + } + root.add(moduleNode); + } + + setRoot(root); + } + + @NotNull + public Project getProject() { + return myProject; + } + + private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull String name, @Nullable Icon icon) { + return new GradleProjectStructureNodeDescriptor(new Object(), name, icon); + } + + private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Named entity, @NotNull Icon icon) { + return new GradleProjectStructureNodeDescriptor(entity, entity.getName(), icon); + } + + private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Project project) { + return new GradleProjectStructureNodeDescriptor(project, project.getName(), GradleIcons.PROJECT_ICON); + } + + private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Module module) { + return new GradleProjectStructureNodeDescriptor(module, module.getName(), GradleIcons.MODULE_ICON); + } + + private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull LibraryOrderEntry library) { + final String name = library.getLibraryName(); + assert name != null; + return new GradleProjectStructureNodeDescriptor(library, name, GradleIcons.LIB_ICON); + } + + private DefaultMutableTreeNode getDependenciesNode(@NotNull String moduleName) { + final DefaultMutableTreeNode cached = myModuleDependencies.get(moduleName); + if (cached != null) { + return cached; + } + DefaultMutableTreeNode moduleNode = myModules.get(moduleName); + if (moduleNode == null) { + moduleNode = new DefaultMutableTreeNode(buildDescriptor(moduleName, GradleIcons.MODULE_ICON)); + myModules.put(moduleName, moduleNode); + } + + DefaultMutableTreeNode result = new DefaultMutableTreeNode(buildDescriptor( + GradleBundle.message("gradle.project.structure.tree.node.dependencies"), + null + )); + moduleNode.add(result); + myModuleDependencies.put(moduleName, result); + return result; + } + + /** + * Asks current model to update its state in accordance with the given changes. + * + * @param changes collections that contains all changes between the current gradle and intellij project structures + */ + public void update(@NotNull Collection changes) { + for (GradleProjectStructureChange change : changes) { + change.invite(new GradleProjectStructureChangeVisitor() { + @Override + public void visit(@NotNull GradleRenameChange change) { + // TODO den implement + } + + @Override + public void visit(@NotNull GradleProjectStructureChange change) { + // TODO den implement + } + + @Override + public void visit(@NotNull GradleModulePresenceChange change) { + // TODO den implement + } + + @Override + public void visit(@NotNull GradleLibraryDependencyPresenceChange change) { + String moduleName; + GradleProjectStructureNodeDescriptor descriptor; + final GradleLibraryDependency gradleEntity = change.getGradleEntity(); + final LibraryOrderEntry intellijEntity = change.getIntellijEntity(); + final Object missingEntity; + if (gradleEntity == null && intellijEntity == null) { + // Never expect to be here. + assert false; + } + + if (gradleEntity == null) { + // Particular library dependency is added at the intellij side. + moduleName = intellijEntity.getOwnerModule().getName(); + if (intellijEntity.getLibraryName() == null) { + return; + } + descriptor = buildDescriptor(intellijEntity); + descriptor.setAttributes(GradleTextAttributes.INTELLIJ_LOCAL_CHANGE); + missingEntity = intellijEntity; + } + else { + // Particular library dependency is added at the gradle side. + moduleName = gradleEntity.getOwnerModule().getName(); + descriptor = buildDescriptor(gradleEntity, GradleIcons.LIB_ICON); + descriptor.setAttributes(GradleTextAttributes.GRADLE_LOCAL_CHANGE); + missingEntity = gradleEntity; + } + final DefaultMutableTreeNode dependenciesNode = getDependenciesNode(moduleName); + for (int i = 0, max = dependenciesNode.getChildCount(); i < max; i++) { + final DefaultMutableTreeNode child = (DefaultMutableTreeNode)dependenciesNode.getChildAt(i); + GradleProjectStructureNodeDescriptor d = (GradleProjectStructureNodeDescriptor)child.getUserObject(); + if (missingEntity.equals(d.getElement())) { + d.setAttributes(descriptor.getAttributes()); + return; + } + } + DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(descriptor); + dependenciesNode.add(newNode); + nodeStructureChanged(dependenciesNode); + } + }); + } + } + + /** + * Asks current model to remove all obsolete nodes for the considering that the given changes are obsolete. + *

+ * Example: + *

+   * 
    + *
  1. There is a particular intellij-local library (change from the gradle project structure);
  2. + *
  3. Corresponding node is shown at the current UI;
  4. + *
  5. The library is removed, i.e. corresponding change has become obsolete;
  6. + *
  7. This method is notified within the obsolete change and is expected to remove the corresponding node;
  8. + *
+ *
+ */ + public void pruneObsoleteNodes(Collection changes) { + for (GradleProjectStructureChange change : changes) { + change.invite(new GradleProjectStructureChangeVisitor() { + @Override + public void visit(@NotNull GradleRenameChange change) { + // TODO den implement + } + + @Override + public void visit(@NotNull GradleProjectStructureChange change) { + // TODO den implement + } + + @Override + public void visit(@NotNull GradleModulePresenceChange change) { + // TODO den implement + } + + @Override + public void visit(@NotNull GradleLibraryDependencyPresenceChange change) { + // We need to remove the corresponding node then. + String moduleName; + Object library; + final GradleLibraryDependency gradleEntity = change.getGradleEntity(); + final LibraryOrderEntry intellijEntity = change.getIntellijEntity(); + assert gradleEntity != null || intellijEntity != null; + if (gradleEntity == null) { + moduleName = intellijEntity.getOwnerModule().getName(); + library = intellijEntity; + } + else { + moduleName = gradleEntity.getOwnerModule().getName(); + library = gradleEntity; + } + final DefaultMutableTreeNode holder = myModuleDependencies.get(moduleName); + if (holder == null) { + return; + } + for (DefaultMutableTreeNode node = holder.getFirstLeaf(); node != null; node = node.getNextSibling()) { + GradleProjectStructureNodeDescriptor descriptor = (GradleProjectStructureNodeDescriptor)node.getUserObject(); + if (descriptor.getElement().equals(library)) { + removeNode(node); + return; + } + } + } + }); + } + } + + private void removeNode(@NotNull TreeNode node) { + final MutableTreeNode parent = (MutableTreeNode)node.getParent(); + if (parent == null) { + return; + } + int i = parent.getIndex(node); + if (i <= 0) { + assert false : node; + return; + } + parent.remove(i); + myIndexHolder[0] = i; + myNodeHolder[0] = node; + nodesWereRemoved(parent, myIndexHolder, myNodeHolder); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java index d0ddbb6dd898..f68a833439fe 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java @@ -4,6 +4,7 @@ import com.intellij.ide.projectView.PresentationData; import com.intellij.ide.util.treeView.PresentableNodeDescriptor; import com.intellij.openapi.editor.colors.TextAttributesKey; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.config.GradleTextAttributes; import javax.swing.*; @@ -24,7 +25,7 @@ public class GradleProjectStructureNodeDescriptor extends PresentableNodeDesc private final T myData; @SuppressWarnings("NullableProblems") - public GradleProjectStructureNodeDescriptor(@NotNull T data, @NotNull String text, @NotNull Icon icon) { + public GradleProjectStructureNodeDescriptor(@NotNull T data, @NotNull String text, @Nullable Icon icon) { super(null, null); myData = data; myOpenIcon = myClosedIcon = icon; diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy index abe61ee80f15..b806804cff60 100644 --- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy @@ -1,76 +1,120 @@ package org.jetbrains.plugins.gradle.sync; +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.Application +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ex.ApplicationInfoEx import com.intellij.openapi.project.Project +import com.intellij.util.containers.ContainerUtil import org.jetbrains.plugins.gradle.testutil.ChangeBuilder import org.jetbrains.plugins.gradle.testutil.GradleProjectBuilder import org.jetbrains.plugins.gradle.testutil.IntellijProjectBuilder +import org.jetbrains.plugins.gradle.testutil.ProjectStructureChecker import org.junit.Before import org.junit.Test import org.picocontainer.defaults.DefaultPicoContainer import org.jetbrains.plugins.gradle.diff.* import static org.junit.Assert.assertEquals +import com.intellij.testFramework.SkipInHeadlessEnvironment /** * @author Denis Zhdanov * @since 01/25/2012 */ +@SkipInHeadlessEnvironment public class GradleProjectStructureChangesModelTest { - private GradleProjectStructureChangesModel myModel; - def gradle; - def intellij; - def changes; + GradleProjectStructureChangesModel changesModel + GradleProjectStructureTreeModel treeModel + def gradle + def intellij + def changes + def treeChecker + def container @Before public void setUp() { gradle = new GradleProjectBuilder() intellij = new IntellijProjectBuilder() changes = new ChangeBuilder() - def container = new DefaultPicoContainer() + treeChecker = new ProjectStructureChecker() + container = new DefaultPicoContainer() container.registerComponentInstance(Project, intellij.project) container.registerComponentInstance(GradleProjectStructureHelper, intellij.projectStructureHelper as GradleProjectStructureHelper) container.registerComponentImplementation(GradleProjectStructureChangesModel) container.registerComponentImplementation(GradleStructureChangesCalculator, GradleProjectStructureChangesCalculator) container.registerComponentImplementation(GradleModuleStructureChangesCalculator) container.registerComponentImplementation(GradleLibraryDependencyStructureChangesCalculator) - - myModel = container.getComponentInstance(GradleProjectStructureChangesModel.class) as GradleProjectStructureChangesModel + container.registerComponentImplementation(GradleProjectStructureTreeModel) + + changesModel = container.getComponentInstance(GradleProjectStructureChangesModel) as GradleProjectStructureChangesModel + def applicationInfo = [getSmallIconUrl: {"/nodes/ideaProject.png"}] as ApplicationInfoEx + ApplicationManager.setApplication([getComponent: { applicationInfo } ] as Application, [dispose: { }] as Disposable) } @Test public void mergeGradleLocalToIntellij() { - gradle { - module { - dependencies { - lib(name: "lib1") - lib(name: "lib2") - } } } + init( + gradle { + module { + dependencies { + lib(name: "lib1") + lib(name: "lib2") + } } }, + + intellij { + module { + dependencies { + lib(name: "lib1") + } } } + ) - intellij { - module { - dependencies { - lib(name: "lib1") - } } } - - myModel.update(gradle.project) checkChanges { presence { lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib2" }) - } - } + } } + checkTree { + project { + module("xxx") { + dependencies { + lib1() + lib2('gradle') + } } } } gradle { module { dependencies { lib(name: "lib1") } } } - myModel.update(gradle.project) - assertEquals([].toSet(), myModel.changes) + changesModel.update(gradle.project) + assertEquals([].toSet(), changesModel.changes) + checkTree { + project { + module { + dependencies { + lib1() + } } } } + } + + private def init(gradleProjectInit, intellijProjectInit) { + treeModel = container.getComponentInstance(GradleProjectStructureTreeModel) as GradleProjectStructureTreeModel + changesModel.addListener({ old, current -> + treeModel.update(current) + treeModel.pruneObsoleteNodes(ContainerUtil.subtract(old, current)); + } as GradleProjectStructureChangeListener) + changesModel.update(gradle.project) } private def checkChanges(c) { c.delegate = changes - assertEquals(c(), myModel.changes) + assertEquals(c(), changesModel.changes) + } + + private def checkTree(c) { + def nodeBuilder = new NodeBuilder() + c.delegate = nodeBuilder + def expected = c() + treeChecker.check(expected, treeModel.root) } } diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy index 92fad0749c64..2cea42fa39a7 100644 --- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy @@ -35,7 +35,7 @@ class IntellijProjectBuilder extends AbstractProjectBuilder { @Override protected createLibrary(String name, Map paths) { - [ getName: { name } ] as Library + [ getName: { name }, getPresentableName: { name } ] as Library } @Override diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy new file mode 100644 index 000000000000..efecadcf7563 --- /dev/null +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy @@ -0,0 +1,62 @@ +package org.jetbrains.plugins.gradle.testutil + +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import javax.swing.tree.DefaultMutableTreeNode +import org.jetbrains.plugins.gradle.config.GradleTextAttributes +import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNodeDescriptor +import org.junit.Assert +import static junit.framework.Assert.assertEquals +import static junit.framework.Assert.fail + +/** + * @author Denis Zhdanov + * @since 1/30/12 6:04 PM + */ +class ProjectStructureChecker { + + static def BUILT_IN = [ + "project": Project, + "module" : Module + ] + + static def COLORS = [ + 'gradle' : GradleTextAttributes.GRADLE_LOCAL_CHANGE, + 'intellij' : GradleTextAttributes.INTELLIJ_LOCAL_CHANGE, + 'conflict' : GradleTextAttributes.GRADLE_CHANGE_CONFLICT + ] + + def check(Node expected, DefaultMutableTreeNode actual) { + GradleProjectStructureNodeDescriptor descriptor = actual.userObject as GradleProjectStructureNodeDescriptor + checkName(expected, descriptor) + checkMarkup(expected, descriptor) + int childIndex = 0 + for (it in expected.children().findAll { it instanceof Collection}) { + check it as Node, actual.getChildAt(childIndex++) as DefaultMutableTreeNode + } + for (it in expected.children().findAll { it instanceof Node}) { + check it as Node, actual.getChildAt(childIndex++) as DefaultMutableTreeNode + } + if (childIndex < actual.childCount) { + fail("Unexpected nodes detected: ${(childIndex..