getDescriptor() {
return myDescriptor;
}
+
+ @Override
+ public GradleProjectStructureNode> getChildAt(int index) {
+ return (GradleProjectStructureNode)super.getChildAt(index);
+ }
+
+ @Override
+ public GradleProjectStructureNode> getParent() {
+ return (GradleProjectStructureNode)super.getParent();
+ }
+
+ @Override
+ public void add(MutableTreeNode newChild) {
+ GradleProjectStructureNode> child = (GradleProjectStructureNode)newChild;
+ final String newText = child.getDescriptor().getText();
+ for (int i = 0; i < getChildCount(); i++) {
+ GradleProjectStructureNode> node = getChildAt(i);
+ if (newText.compareTo(node.getDescriptor().getText()) < 0) {
+ insert(newChild, i);
+ return;
+ }
+ }
+ super.add(newChild);
+ }
+
+ /**
+ * Registers given change within the given node assuming that it is
+ * {@link GradleTextAttributes#GRADLE_CHANGE_CONFLICT 'conflict change'}. We need to track number of such changes per-node because
+ * of the following possible situation:
+ *
+ *
+ * - There are two 'conflict changes' for particular node;
+ * -
+ * One of those changes is resolved but the node still should be marked as 'conflict' because there is still one conflict change;
+ *
+ * - The second conflict change is removed. The node should be marked as 'no change' now;
+ *
+ *
+ *
+ * @param change conflict change to register for the current node
+ */
+ public void addConflictChange(@NotNull GradleProjectStructureChange change) {
+ myConflictChanges.add(change);
+ myDescriptor.setAttributes(GradleTextAttributes.GRADLE_CHANGE_CONFLICT);
+ }
+
+ /**
+ * Performs reverse operation to {@link #addConflictChange(GradleProjectStructureChange)}.
+ *
+ * @param change conflict change to de-register from the current node
+ */
+ public void removeConflictChange(@NotNull GradleProjectStructureChange change) {
+ myConflictChanges.remove(change);
+ if (myConflictChanges.isEmpty()) {
+ myDescriptor.setAttributes(GradleTextAttributes.GRADLE_NO_CHANGE);
+ }
+ }
+
+ /**
+ * Allows to query current node for all children that are associated with the entity of the given type.
+ *
+ * @param clazz target entity type
+ * @param target entity type
+ * @return all children nodes that are associated with the entity of the given type if any;
+ * empty collection otherwise
+ */
+ @SuppressWarnings("unchecked")
+ @NotNull
+ public Collection> getChildren(@NotNull Class clazz) {
+ List> result = null;
+ for (int i = 0; i < getChildCount(); i++) {
+ final GradleProjectStructureNode> child = getChildAt(i);
+ final Object element = child.getDescriptor().getElement();
+ if (!clazz.isInstance(element)) {
+ continue;
+ }
+ if (result == null) {
+ result = new ArrayList>();
+ }
+ result.add((GradleProjectStructureNode)child);
+ }
+ if (result == null) {
+ result = Collections.emptyList();
+ }
+ return result;
+ }
+
+ @NotNull
+ @Override
+ public Iterator> iterator() {
+ return new Iterator>() {
+
+ private int i;
+
+ @Override
+ public boolean hasNext() {
+ return i < getChildCount();
+ }
+
+ @Override
+ public GradleProjectStructureNode> next() {
+ return getChildAt(i++);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
}
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 f68a833439fe..6fe8baa490f0 100644
--- a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java
+++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNodeDescriptor.java
@@ -40,12 +40,18 @@ public class GradleProjectStructureNodeDescriptor extends PresentableNodeDesc
presentation.setClosedIcon(myClosedIcon);
}
+ @NotNull
@Override
public T getElement() {
return myData;
}
- public void setName(@NotNull String name) {
+ @NotNull
+ public String getText() {
+ return myName;
+ }
+
+ public void setText(@NotNull String name) {
myName = name;
}
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 8db99f475a4b..178bd46bf6b2 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy
@@ -42,7 +42,9 @@ public class GradleProjectStructureChangesModelTest {
container.registerComponentImplementation(GradleStructureChangesCalculator, GradleProjectStructureChangesCalculator)
container.registerComponentImplementation(GradleModuleStructureChangesCalculator)
container.registerComponentImplementation(GradleLibraryDependencyStructureChangesCalculator)
+ container.registerComponentImplementation(GradleLibraryStructureChangesCalculator)
container.registerComponentImplementation(GradleProjectStructureTreeModel)
+ container.registerComponentImplementation(GradleProjectStructureHelper)
changesModel = container.getComponentInstance(GradleProjectStructureChangesModel) as GradleProjectStructureChangesModel
}
@@ -56,6 +58,7 @@ public class GradleProjectStructureChangesModelTest {
dependencies {
lib(name: "lib1")
lib(name: "lib2")
+ lib(name: "lib3")
} } },
intellij {
@@ -69,46 +72,157 @@ public class GradleProjectStructureChangesModelTest {
checkChanges {
presence {
lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib2" })
+ lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib3" })
} }
checkTree {
project {
- module("xxx") {
+ module() {
dependencies {
lib1()
lib2('gradle')
+ lib3('gradle')
} } } }
- // Define changed project state.
+ // Add the same library at intellij side. Expecting to have the only change now.
+ intellij {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2")
+ } } }
+ changesModel.update(gradle.project)
+ checkChanges {
+ presence {
+ lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib3" })
+ } }
+ checkTree {
+ project {
+ module() {
+ dependencies {
+ lib1()
+ lib2()
+ lib3('gradle')
+ } } } }
+
+ // Remove the 'gradle local' dependency.
gradle {
module {
dependencies {
lib(name: "lib1")
+ lib(name: "lib2")
} } }
// Apply the changed project state and check if it's correctly parsed.
changesModel.update(gradle.project)
+ checkChanges { } // no changes.
assertEquals([].toSet(), changesModel.changes)
checkTree {
project {
module {
dependencies {
lib1()
+ lib2()
} } } }
}
-
+
+ @Test
+ public void libraryDependenciesWithDifferentPaths() {
+ // Let the model has two differences in a library setup initially.
+ init(
+ gradle {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2", bin: ['1', '2'])
+ } } },
+ intellij {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2", bin: ['2', '3'])
+ }}}
+ )
+
+ checkChanges {
+ libraryConflict(entity: intellij.libraries['lib2']) {
+ binaryPath(gradle: '1', intellij: null)
+ binaryPath(gradle: null, intellij: '3')
+ } }
+ checkTree {
+ project {
+ module {
+ dependencies {
+ lib1()
+ lib2('conflict')
+ } } } }
+
+ // Remove one difference from the library setup and check that the corresponding node is still marked as conflicted
+ gradle {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2", bin: ['2'])
+ } } }
+ intellij {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2", bin: ['2', '3'])
+ }}}
+ changesModel.update(gradle.project)
+ checkChanges {
+ libraryConflict(entity: intellij.dependencies.values().flatten().find {it.library.name == 'lib2' }.library) {
+ binaryPath(gradle: null, intellij: '3')
+ } }
+ checkTree {
+ project {
+ module {
+ dependencies {
+ lib1()
+ lib2('conflict')
+ } } } }
+
+ // Match the remaining change and check that the corresponding node is not marked as conflicted anymore.
+ gradle {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2", bin: ['2', '3'])
+ } } }
+ intellij {
+ module {
+ dependencies {
+ lib(name: "lib1")
+ lib(name: "lib2", bin: ['2', '3'])
+ }}}
+ changesModel.update(gradle.project)
+ checkChanges { } // No changes
+ checkTree {
+ project {
+ module {
+ dependencies {
+ lib1()
+ lib2()
+ } } } }
+ }
+
@SuppressWarnings("GroovyAssignabilityCheck")
private def init(gradleProjectInit, intellijProjectInit) {
treeModel = container.getComponentInstance(GradleProjectStructureTreeModel) as GradleProjectStructureTreeModel
changesModel.addListener({ old, current ->
treeModel.update(current)
- treeModel.pruneObsoleteNodes(ContainerUtil.subtract(old, current));
+ treeModel.processObsoleteChanges(ContainerUtil.subtract(old, current));
} as GradleProjectStructureChangeListener)
changesModel.update(gradle.project)
}
private def checkChanges(c) {
c.delegate = changes
- assertEquals(c(), changesModel.changes)
+ def expected = c()
+ if (!expected) {
+ expected = [].toSet()
+ }
+ assertEquals(expected, changesModel.changes)
}
private def checkTree(c) {
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy
index 6e4d1f3fd140..a42abe160332 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy
@@ -1,19 +1,26 @@
package org.jetbrains.plugins.gradle.testutil
-import com.intellij.pom.java.LanguageLevel;
+import com.intellij.pom.java.LanguageLevel
/**
* @author Denis Zhdanov
* @since 1/25/12 4:06 PM
*/
-public abstract class AbstractProjectBuilder extends BuilderSupport {
+public abstract class AbstractProjectBuilder extends BuilderSupport {
- private static final def SAME_TOKEN = "same"
+ public static final def SAME_TOKEN = "same"
private static int COUNTER
def project
def modules = []
- def libraries = [:].withDefault { createLibrary(it.name?: same, it.paths?: [:]) }
+ /** Holds (library name; library) pairs for the active configuration. */
+ def libraries = [:]
+ /**
+ * Holds (library name; library) pairs for the whole test. I.e. there is a possible case that we define particular configuration
+ * initially and the adjust it. We need to use the same library instance then in order to pass hashCode()/equals() checks then.
+ * This map works as a test-wide storage.
+ */
+ def librariesCache = [:]
def dependencies = [:].withDefault {[]}
@Override
@@ -43,7 +50,7 @@ public abstract class AbstractProjectBuilder extends BuilderSupport {
case "module": def module = createModule(attributes.name?: same); modules << module; return module
case "lib":
def module = getCurrent()
- def dep = createLibraryDependency(module, libraries[attributes])
+ def dep = createLibraryDependency(module, getLibrary(attributes))
dependencies[module] << dep
return dep
}
@@ -57,10 +64,27 @@ public abstract class AbstractProjectBuilder extends BuilderSupport {
protected abstract def createProject(String name, LanguageLevel languageLevel)
protected abstract def createModule(String name)
protected abstract def createLibrary(String name, Map paths)
+ protected abstract def applyLibraryPaths(library, Map paths)
protected abstract def createLibraryDependency(module, library)
protected String getUnique() { "./${COUNTER++}" }
protected String getSame() { SAME_TOKEN }
+
+ private def getLibrary(Map attributes) {
+ def name = attributes.name?: same
+ def result = libraries[name]
+ if (result) return result
+ result = librariesCache[name]
+ if (result) {
+ applyLibraryPaths(result, attributes)
+ }
+ else {
+ result = createLibrary(name, attributes.withDefault { /* empty paths*/ [] })
+ librariesCache[name] = result
+ }
+ libraries[name] = result
+ result
+ }
private def reset() {
[modules, dependencies, libraries]*.clear()
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ChangeBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ChangeBuilder.groovy
index 700bcac3111a..b8ebd3b713d0 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ChangeBuilder.groovy
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ChangeBuilder.groovy
@@ -2,6 +2,9 @@ package org.jetbrains.plugins.gradle.testutil;
import org.jetbrains.plugins.gradle.diff.GradleLibraryDependencyPresenceChange
+import org.jetbrains.plugins.gradle.diff.GradleMismatchedLibraryPathChange
+import com.intellij.openapi.roots.libraries.Library
+import org.jetbrains.plugins.gradle.util.GradleUtil
/**
* @author Denis Zhdanov
@@ -17,10 +20,7 @@ public class ChangeBuilder extends BuilderSupport {
@Override
protected Object createNode(Object name) {
- if (current == null) {
- changes = []
- }
- changes
+ createNode(name, [:])
}
@Override
@@ -28,13 +28,31 @@ public class ChangeBuilder extends BuilderSupport {
@Override
protected Object createNode(Object name, Map attributes) {
+ if (current == null) {
+ changes = []
+ }
switch (name) {
case "presence": return changes
case "lib":
changes.addAll attributes.gradle.collect { new GradleLibraryDependencyPresenceChange(it, null)}
changes.addAll attributes.intellij.collect { new GradleLibraryDependencyPresenceChange(null, it)}
- return
+ return changes
+ case "libraryConflict":
+ def library = attributes.entity
+ if (!library) {
+ throw new IllegalArgumentException("No entity is defined for the library conflict change. Known attributes: $attributes")
+ }
+ if (attributes.gradle) {
+ return register(new GradleMismatchedLibraryPathChange(library, attributes.gradle, attributes.intellij))
+ }
+ return library
+ case "binaryPath":
+ // Assuming that we're processing library binary path conflict here
+ register(new GradleMismatchedLibraryPathChange(
+ current as Library, toCanonicalPath(attributes.gradle), toCanonicalPath(attributes.intellij)
+ ))
}
+ changes
}
@Override
@@ -44,4 +62,13 @@ public class ChangeBuilder extends BuilderSupport {
protected Object postNodeCompletion(Object parent, Object node) {
parent == null ? changes.toSet() : node
}
+
+ protected def register(change) {
+ changes << change
+ change
+ }
+
+ private def toCanonicalPath(String path) {
+ path ? GradleUtil.toCanonicalPath(path) : path
+ }
}
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy
index e889543406fa..348e0842c973 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy
@@ -31,9 +31,7 @@ class GradleProjectBuilder extends AbstractProjectBuilder {
@Override
protected createLibrary(String name, Map paths) {
def result = new GradleLibrary(name)
- (paths.bin?: [same]).each { result.addPath(LibraryPathType.BINARY, it) }
- (paths.src?: [same]).each { result.addPath(LibraryPathType.SOURCE, it) }
- (paths.doc?: [same]).each { result.addPath(LibraryPathType.DOC, it) }
+ applyLibraryPaths(result, paths)
result
}
@@ -43,4 +41,12 @@ class GradleProjectBuilder extends AbstractProjectBuilder {
module.addDependency(result)
result
}
+
+ @Override
+ protected applyLibraryPaths(library, Map paths) {
+ library.forgetAllPaths()
+ ['bin': LibraryPathType.BINARY, 'src': LibraryPathType.SOURCE, 'doc': LibraryPathType.DOC].each {
+ key, type -> paths[key]?.each { library.addPath(type, it) }
+ }
+ }
}
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 4948c10dd0cb..d8ff3e8436e3 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy
@@ -9,19 +9,29 @@ import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.IconLoader
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.roots.OrderRootType
+import org.jetbrains.plugins.gradle.util.GradleUtil
+
/**
* @author Denis Zhdanov
* @since 1/25/12 3:09 PM
*/
class IntellijProjectBuilder extends AbstractProjectBuilder {
+
+ static def LIBRARY_ENTRY_TYPES = [(OrderRootType.CLASSES) : "bin"]
+ static VirtualFile[] DUMMY_VIRTUAL_FILE_ARRAY = new VirtualFile[0]
def projectStub = [:]
def project = projectStub as Project
def platformFacade = [
getModules: { modules },
getOrderEntries: { dependencies[it] },
- getProjectIcon: { IconLoader.getIcon("/nodes/ideaProject.png") }
+ getProjectIcon: { IconLoader.getIcon("/nodes/ideaProject.png") },
+ getLocalFileSystemPath: { it.path }
]
+ /** (library name - (library root type - paths)). */
+ def libraryPaths = [:].withDefault { [:] }
@Override
protected createProject(String name, LanguageLevel languageLevel) {
@@ -37,7 +47,17 @@ class IntellijProjectBuilder extends AbstractProjectBuilder {
@Override
protected createLibrary(String name, Map paths) {
- [ getName: { name }, getPresentableName: { name } ] as Library
+ libraryPaths[name] = paths
+ [
+ getName: { name },
+ getPresentableName: { name },
+ getFiles: {
+ type -> (libraryPaths[name])[LIBRARY_ENTRY_TYPES[type]].findAll { it }.collect {
+ String path = it
+ [getPath: { GradleUtil.toCanonicalPath(path) }] as VirtualFile
+ }.toArray(DUMMY_VIRTUAL_FILE_ARRAY)
+ }
+ ] as Library
}
@Override
@@ -46,6 +66,13 @@ class IntellijProjectBuilder extends AbstractProjectBuilder {
def result = stub as LibraryOrderEntry
stub.accept = { policy, defaultValue -> policy.visitLibraryOrderEntry(result, defaultValue) }
stub.getLibraryName = { library.name }
+ stub.getLibrary = { library }
+ stub.getOwnerModule = { module }
result
}
+
+ @Override
+ protected applyLibraryPaths(library, Map paths) {
+ libraryPaths[library.name] = paths
+ }
}
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy
index 88decefa8a00..62d8ed0c9191 100644
--- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy
+++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ProjectStructureChecker.groovy
@@ -35,6 +35,9 @@ class ProjectStructureChecker {
check it as Node, actual.getChildAt(childIndex++) as DefaultMutableTreeNode
}
for (it in expected.children().findAll { it instanceof Node}) {
+ if (childIndex >= actual.childCount) {
+ fail "Expected node is not matched: $expected"
+ }
check it as Node, actual.getChildAt(childIndex++) as DefaultMutableTreeNode
}
if (childIndex < actual.childCount) {
@@ -43,7 +46,7 @@ class ProjectStructureChecker {
}
private void checkName(Node expected, GradleProjectStructureNodeDescriptor actual) {
- if (expected.name() == actual.toString()) {
+ if (AbstractProjectBuilder.SAME_TOKEN == actual.toString() || expected.name() == actual.toString()) {
return
}
def clazz = BUILT_IN[expected.name()]