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;
This commit is contained in:
Denis.Zhdanov
2012-01-31 17:32:50 +04:00
parent b41d84fc50
commit 38255495c8
8 changed files with 466 additions and 303 deletions
@@ -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));
}
}
@@ -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
*/
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
@@ -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.<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)
}
}
@@ -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
@@ -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)
}
}