IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle...
authorDenis.Zhdanov <Denis.Zhdanov@jetbrains.com>
Tue, 31 Jan 2012 12:45:29 +0000 (16:45 +0400)
committerDenis.Zhdanov <Denis.Zhdanov@jetbrains.com>
Tue, 31 Jan 2012 13:32:50 +0000 (17:32 +0400)
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;

plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java
plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java
plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java
plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java [new file with mode: 0644]
plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java
plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy
plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy
plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy [new file with mode: 0644]

index 254145d26a659580e0cbd82a649f5d139411cfb4..8280bcbc400b0aa8bb1182119e430054e010a791 100644 (file)
@@ -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)); 
   }
 }
index 063cb6eee62bdde9f402776cb70ed6cfaadd1fd9..585e30522962b431fd187585b684959b30420ba8 100644 (file)
@@ -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
    */
index b2d0a5ce81eb79bb76713140190f0d61fdb759c2..65fb403362514c1c4b49e9295b94bf906be23660 100644 (file)
@@ -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 {
 
-  /**
-   * <pre>
-   *     ...
-   *      |_module     &lt;- module's name is a key
-   *          |_...
-   *          |_dependencies   &lt;- dependencies holder node is a value
-   *                  |_dependency1
-   *                  |_dependency2
-   * </pre>
-   */
-  private final Map<String, DefaultMutableTreeNode> myModuleDependencies = new HashMap<String, DefaultMutableTreeNode>();
-  private final Map<String, DefaultMutableTreeNode> myModules            = new HashMap<String, DefaultMutableTreeNode>();
+  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<GradleProjectStructureChange> oldChanges,
                             @NotNull final Collection<GradleProjectStructureChange> 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<LibraryOrderEntry> policy = new RootPolicy<LibraryOrderEntry>() {
-      @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<LibraryOrderEntry> libraryDependencies = new ArrayList<LibraryOrderEntry>();
-      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<Object> buildDescriptor(@NotNull String name, Icon icon) {
-    return new GradleProjectStructureNodeDescriptor<Object>(new Object(), name, icon);
-  }
-
-  private static GradleProjectStructureNodeDescriptor<Named> buildDescriptor(@NotNull Named entity, @NotNull Icon icon) {
-    return new GradleProjectStructureNodeDescriptor<Named>(entity, entity.getName(), icon);
-  } 
-
-  private static GradleProjectStructureNodeDescriptor<Project> buildDescriptor(@NotNull Project project) {
-    return new GradleProjectStructureNodeDescriptor<Project>(project, project.getName(), GradleIcons.PROJECT_ICON);
-  }
-
-  private static GradleProjectStructureNodeDescriptor<Module> buildDescriptor(@NotNull Module module) {
-    return new GradleProjectStructureNodeDescriptor<Module>(module, module.getName(), GradleIcons.MODULE_ICON);
-  }
-
-  private static GradleProjectStructureNodeDescriptor<GradleModule> buildDescriptor(@NotNull GradleModule module) {
-    return new GradleProjectStructureNodeDescriptor<GradleModule>(module, module.getName(), GradleIcons.MODULE_ICON);
-  }
-
-  private static GradleProjectStructureNodeDescriptor<LibraryOrderEntry> buildDescriptor(@NotNull LibraryOrderEntry library) {
-    return new GradleProjectStructureNodeDescriptor<LibraryOrderEntry>(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<GradleProjectStructureChange> 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.
-   * <p/>
-   * Example:
-   * <pre>
-   * <ol>
-   *   <li>There is a particular intellij-local library (change from the gradle project structure);</li>
-   *   <li>Corresponding node is shown at the current UI;</li>
-   *   <li>The library is removed, i.e. corresponding change has become obsolete;</li>
-   *   <li>This method is notified within the obsolete change and is expected to remove the corresponding node;</li>
-   * </ol>
-   * </pre>
-   */
-  private void processObsoleteChanges(Collection<GradleProjectStructureChange> 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 (file)
index 0000000..e851aba
--- /dev/null
@@ -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.
+ * <p/>
+ * Not thread-safe.
+ * 
+ * @author Denis Zhdanov
+ * @since 1/30/12 4:20 PM
+ */
+public class GradleProjectStructureTreeModel extends DefaultTreeModel {
+
+  /**
+   * <pre>
+   *     ...
+   *      |_module     &lt;- module's name is a key
+   *          |_...
+   *          |_dependencies   &lt;- dependencies holder node is a value
+   *                  |_dependency1
+   *                  |_dependency2
+   * </pre>
+   */
+  private final Map<String, DefaultMutableTreeNode> myModuleDependencies = new HashMap<String, DefaultMutableTreeNode>();
+  private final Map<String, DefaultMutableTreeNode> myModules            = new HashMap<String, DefaultMutableTreeNode>();
+
+  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<Module> modules = myProjectStructureHelper.getModules(getProject());
+    RootPolicy<LibraryOrderEntry> policy = new RootPolicy<LibraryOrderEntry>() {
+      @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<LibraryOrderEntry> libraryDependencies = new ArrayList<LibraryOrderEntry>();
+      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<Object> buildDescriptor(@NotNull String name, @Nullable Icon icon) {
+    return new GradleProjectStructureNodeDescriptor<Object>(new Object(), name, icon);
+  }
+
+  private static GradleProjectStructureNodeDescriptor<Named> buildDescriptor(@NotNull Named entity, @NotNull Icon icon) {
+    return new GradleProjectStructureNodeDescriptor<Named>(entity, entity.getName(), icon);
+  }
+
+  private static GradleProjectStructureNodeDescriptor<Project> buildDescriptor(@NotNull Project project) {
+    return new GradleProjectStructureNodeDescriptor<Project>(project, project.getName(), GradleIcons.PROJECT_ICON);
+  }
+
+  private static GradleProjectStructureNodeDescriptor<Module> buildDescriptor(@NotNull Module module) {
+    return new GradleProjectStructureNodeDescriptor<Module>(module, module.getName(), GradleIcons.MODULE_ICON);
+  }
+
+  private static GradleProjectStructureNodeDescriptor<LibraryOrderEntry> buildDescriptor(@NotNull LibraryOrderEntry library) {
+    final String name = library.getLibraryName();
+    assert name != null;
+    return new GradleProjectStructureNodeDescriptor<LibraryOrderEntry>(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<GradleProjectStructureChange> 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.
+   * <p/>
+   * Example:
+   * <pre>
+   * <ol>
+   *   <li>There is a particular intellij-local library (change from the gradle project structure);</li>
+   *   <li>Corresponding node is shown at the current UI;</li>
+   *   <li>The library is removed, i.e. corresponding change has become obsolete;</li>
+   *   <li>This method is notified within the obsolete change and is expected to remove the corresponding node;</li>
+   * </ol>
+   * </pre>
+   */
+  public void pruneObsoleteNodes(Collection<GradleProjectStructureChange> 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);
+  }
+}
index d0ddbb6dd8980eb9b5c3827705bd1403433ff252..f68a833439fe9db0e66f70fef88c37cfe1d61848 100644 (file)
@@ -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<T> 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;
index abe61ee80f1532b8ddb41c4e233c2d78c7e2e1c9..b806804cff6039a03e3e7f8a2899d6d244970d60 100644 (file)
 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")
-    } } }
-    
-    intellij {
-      module {
-        dependencies {
-          lib(name: "lib1")
-    } } }
+    init(
+      gradle {
+        module {
+          dependencies {
+            lib(name: "lib1")
+            lib(name: "lib2")
+      } } },
+
+      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.<GradleProjectStructureChange>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)
   }
 }
index 92fad0749c6447235bc2311fa125c2114911dc60..2cea42fa39a783a6c2927daac9fe3686e514941d 100644 (file)
@@ -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 (file)
index 0000000..efecadc
--- /dev/null
@@ -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..<actual.childCount).collect { actual.getChildAt(it) } join '-'}")
+    }
+  }
+
+  private void checkName(Node expected, GradleProjectStructureNodeDescriptor actual) {
+    if (expected.name() == actual.toString()) {
+      return
+    }
+    def clazz = BUILT_IN[expected.name()]
+    if (clazz == null || !clazz.isAssignableFrom(actual.element.class)) {
+      Assert.fail(
+        "Failed node name check. Expected to find name '${expected.name()}'" + (clazz ? " or user object of type ${clazz.simpleName}" : "")
+          + " but got: name='$actual', user object=${actual.element} "
+      )
+    }
+  }
+
+  def checkMarkup(Node node, GradleProjectStructureNodeDescriptor descriptor) {
+    def expected = COLORS[node.children().find {it instanceof CharSequence}]?: GradleTextAttributes.GRADLE_NO_CHANGE
+    assertEquals(expected, descriptor.attributes)
+  }
+}