diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index 0491c6ce225e..205b4ac96bb6 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -54,6 +54,11 @@ gradle.sync.change.type.intellij={0} local setting gradle.sync.change.type.conflict=Settings conflict gradle.sync.change.type.unchanged=Unchanged setting gradle.sync.change.type.confirmed=Confirmed settings diff +gradle.sync.change.entity.type.module=module +gradle.sync.change.entity.type.library.dependency=library dependency +gradle.sync.change.library.path=library ''{0}'' binary path +gradle.sync.change.project.name.text=project name +gradle.sync.change.project.language.level.text=project language level change gradle.library.resolve.progress.text=Resolving gradle libraries diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index e347b9db2348..ac0f7b331fd5 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -39,9 +39,10 @@ + + serviceImplementation="org.jetbrains.plugins.gradle.diff.PlatformFacadeImpl"/> @@ -62,6 +63,9 @@ org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesModel + + org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java index 6472130b5019..d44f8088e683 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/bootstrap/GradleBootstrap.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.diff.PlatformFacade; import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesModel; import org.jetbrains.plugins.gradle.sync.GradleProjectStructureChangesPanel; +import org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper; import org.jetbrains.plugins.gradle.ui.GradleIcons; import org.jetbrains.plugins.gradle.util.GradleBundle; @@ -24,15 +25,18 @@ import org.jetbrains.plugins.gradle.util.GradleBundle; public class GradleBootstrap extends AbstractProjectComponent { private static final String GRADLE_TOOL_WINDOW_ID = GradleBundle.message("gradle.name"); - + private final GradleProjectStructureChangesModel myChangesModel; - private final PlatformFacade myProjectStructureHelper; + private final PlatformFacade myPlatformFacade; + private final GradleProjectStructureHelper myProjectStructureHelper; public GradleBootstrap(@NotNull Project project, @NotNull GradleProjectStructureChangesModel changesModel, - @NotNull PlatformFacade projectStructureHelper) { + @NotNull PlatformFacade platformFacade, + @NotNull GradleProjectStructureHelper projectStructureHelper) { super(project); myChangesModel = changesModel; + myPlatformFacade = platformFacade; myProjectStructureHelper = projectStructureHelper; } @@ -52,7 +56,7 @@ public class GradleBootstrap extends AbstractProjectComponent { toolWindow.setIcon(GradleIcons.GRADLE_ICON); String syncTitle = GradleBundle.message("gradle.sync.title.tab"); final GradleProjectStructureChangesPanel projectStructureChanges - = new GradleProjectStructureChangesPanel(myProject, myChangesModel, myProjectStructureHelper); + = new GradleProjectStructureChangesPanel(myProject, myChangesModel, myPlatformFacade, myProjectStructureHelper); toolWindow.getContentManager().addContent(new ContentImpl(projectStructureChanges, syncTitle, true)); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java index 7db8c237d79d..7cc983906126 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java @@ -10,6 +10,7 @@ import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.diff.PlatformFacade; +import org.jetbrains.plugins.gradle.sync.GradleProjectStructureHelper; import org.jetbrains.plugins.gradle.util.GradleBundle; import org.jetbrains.plugins.gradle.ui.RichTextControlBuilder; @@ -43,14 +44,17 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel { private final JPanel myContent = new JPanel(myLayout); private final Project myProject; - private final PlatformFacade myProjectStructureHelper; + private final PlatformFacade myProjectFacade; + private final GradleProjectStructureHelper myProjectStructureHelper; protected GradleToolWindowPanel(@NotNull Project project, - @Nullable PlatformFacade projectStructureHelper, + @Nullable PlatformFacade projectFacade, + @NotNull GradleProjectStructureHelper projectStructureHelper, @NotNull String place) { super(true); myProject = project; + myProjectFacade = projectFacade; myProjectStructureHelper = projectStructureHelper; final ActionManager actionManager = ActionManager.getInstance(); final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(TOOL_WINDOW_TOOLBAR_ID); @@ -98,7 +102,12 @@ public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel { } @NotNull - public PlatformFacade getProjectStructureHelper() { + public PlatformFacade getProjectFacade() { + return myProjectFacade; + } + + @NotNull + public GradleProjectStructureHelper getProjectStructureHelper() { return myProjectStructureHelper; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleAbstractPropertyValueChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleAbstractConflictingPropertyChange.java similarity index 65% rename from plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleAbstractPropertyValueChange.java rename to plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleAbstractConflictingPropertyChange.java index f822b7c8df74..cad6ff1eada3 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleAbstractPropertyValueChange.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleAbstractConflictingPropertyChange.java @@ -19,19 +19,25 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** - * Defines contract for change that points to particular property value change. + * Defines general contract for a change that encapsulates information about conflicting property value of the matched gradle + * and intellij entities. + *

+ * For example we may match particular gradle library to an intellij library but they may have different set of attached binaries. + *

+ * Thread-safe. * * @author Denis Zhdanov * @since 11/15/11 7:57 PM + * @param target property value type */ -public abstract class GradleAbstractPropertyValueChange extends GradleAbstractProjectStructureChange { +public abstract class GradleAbstractConflictingPropertyChange extends GradleAbstractProjectStructureChange { + + private final String myPropertyDescription; + private final T myGradleValue; + private final T myIntellijValue; - private final String myProperyName; - private final T myGradleValue; - private final T myIntellijValue; - - public GradleAbstractPropertyValueChange(@NotNull String propertyName, @Nullable T gradleValue, @Nullable T intellijValue) { - myProperyName = propertyName; + public GradleAbstractConflictingPropertyChange(@NotNull String propertyDescription, @Nullable T gradleValue, @Nullable T intellijValue) { + myPropertyDescription = propertyDescription; myGradleValue = gradleValue; myIntellijValue = intellijValue; } @@ -56,7 +62,7 @@ public abstract class GradleAbstractPropertyValueChange extends GradleAbstrac public int hashCode() { int result = super.hashCode(); result = 31 * result + (myGradleValue != null ? myGradleValue.hashCode() : 0); - return 31 * result + (myIntellijValue != null ? myIntellijValue.hashCode() : 0); + return 31 * result + (myIntellijValue != null ? myIntellijValue.hashCode() : 0); } @Override @@ -65,16 +71,15 @@ public abstract class GradleAbstractPropertyValueChange extends GradleAbstrac if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; - GradleAbstractPropertyValueChange that = (GradleAbstractPropertyValueChange)o; + GradleAbstractConflictingPropertyChange that = (GradleAbstractConflictingPropertyChange)o; if (myGradleValue != null ? !myGradleValue.equals(that.myGradleValue) : that.myGradleValue != null) return false; if (myIntellijValue != null ? !myIntellijValue.equals(that.myIntellijValue) : that.myIntellijValue != null) return false; - return true; } @Override public String toString() { - return String.format("%s change: gradle='%s', intellij='%s'", myProperyName, myGradleValue, myIntellijValue); + return String.format("%s change: gradle='%s', intellij='%s'", myPropertyDescription, myGradleValue, myIntellijValue); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleDiffUtil.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleDiffUtil.java index a6b51a7b8204..1f6d92224729 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleDiffUtil.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleDiffUtil.java @@ -37,7 +37,7 @@ public class GradleDiffUtil { @Override public void visit(@NotNull GradleModule module) { - currentChanges.add(new GradleModulePresenceChange(module, null)); + currentChanges.add(new GradleModulePresenceChange(module.getName(), null)); for (GradleDependency dependency : module.getDependencies()) { dependency.invite(this); } @@ -68,11 +68,14 @@ public class GradleDiffUtil { /** * Analogues to {@link #buildLocalChanges} but targets intellij entity. * + * @param gradleProject gradle project which structure is being compared to the intellij one * @param module target intellij-local module that doesn't present at the gradle side * @param currentChanges holder for the changes built during the current call */ - public static void buildLocalChanges(@NotNull Module module, @NotNull Set currentChanges) { - currentChanges.add(new GradleModulePresenceChange(null, module)); + public static void buildLocalChanges(@NotNull Module module, + @NotNull Set currentChanges) + { + currentChanges.add(new GradleModulePresenceChange(null, module.getName())); // TODO den process module sub-entities here (content roots and dependencies). } @@ -85,18 +88,20 @@ public class GradleDiffUtil { public static void buildLocalChanges(@NotNull LibraryOrderEntry libraryDependency, @NotNull Set currentChanges) { - currentChanges.add(new GradleLibraryDependencyPresenceChange(null, libraryDependency)); + final String libraryName = libraryDependency.getLibraryName(); + if (libraryName != null) { + currentChanges.add(new GradleLibraryDependencyPresenceChange(null, libraryDependency)); + } } /** * Performs argument type-based dispatch and delegates to one of strongly typed 'buildLocalChanges()' methods. * + * @param gradleProject gradle project which structure is being compared to the intellij one * @param entity target intellij-local entity that doesn't present at the gradle side * @param currentChanges holder for the changes built during the current call */ - public static void buildLocalChanges(@NotNull Object entity, - @NotNull Set currentChanges) - { + public static void buildLocalChanges(@NotNull Object entity, @NotNull Set currentChanges) { if (entity instanceof GradleEntity) { buildLocalChanges((GradleEntity)entity, currentChanges); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleEntityPresenceChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleEntityPresenceChange.java index 9e7aedbff9c5..f0d06a4047dc 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleEntityPresenceChange.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleEntityPresenceChange.java @@ -15,6 +15,7 @@ */ package org.jetbrains.plugins.gradle.diff; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -22,13 +23,13 @@ import org.jetbrains.annotations.Nullable; * * @author Denis Zhdanov * @since 11/17/11 12:43 PM - * @param entity type at Gradle side - * @param entity type at IntelliJ IDEA side + * @param entity type */ -public abstract class GradleEntityPresenceChange extends GradleAbstractProjectStructureChange { +public abstract class GradleEntityPresenceChange extends GradleAbstractProjectStructureChange { - private final G myGradleEntity; - private final I myIntellijEntity; + private final T myGradleEntity; + private final T myIntellijEntity; + private final String myEntityName; /** * Creates new GradleEntityPresenceChange project. @@ -40,7 +41,11 @@ public abstract class GradleEntityPresenceChange extends GradleAbstractPro * @throws IllegalArgumentException if both of the given entities are defined or undefined. Expecting this constructor to be * called with one null argument and one non-null argument */ - public GradleEntityPresenceChange(@Nullable G gradleEntity, @Nullable I intellijEntity) throws IllegalArgumentException { + public GradleEntityPresenceChange(@NotNull String entityName, + @Nullable T gradleEntity, + @Nullable T intellijEntity) + throws IllegalArgumentException + { if (!(gradleEntity == null ^ intellijEntity == null)) { throw new IllegalArgumentException(String.format( "Can't construct %s object. Reason: expected that only gradle or intellij entity is null, actual: gradle='%s'; intellij='%s'", @@ -49,15 +54,16 @@ public abstract class GradleEntityPresenceChange extends GradleAbstractPro } myGradleEntity = gradleEntity; myIntellijEntity = intellijEntity; + myEntityName = entityName; } @Nullable - public G getGradleEntity() { + public T getGradleEntity() { return myGradleEntity; } @Nullable - public I getIntellijEntity() { + public T getIntellijEntity() { return myIntellijEntity; } @@ -83,6 +89,6 @@ public abstract class GradleEntityPresenceChange extends GradleAbstractPro @Override public String toString() { - return String.format("entity presence change: gradle='%s', intellij='%s'", myGradleEntity, myIntellijEntity); + return String.format("%s presence change: gradle='%s', intellij='%s'", myEntityName, myGradleEntity, myIntellijEntity); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLanguageLevelChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLanguageLevelChange.java index 02ad57dad14e..b65d4f21ab65 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLanguageLevelChange.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLanguageLevelChange.java @@ -18,15 +18,16 @@ package org.jetbrains.plugins.gradle.diff; import com.intellij.pom.java.LanguageLevel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.util.GradleBundle; /** * @author Denis Zhdanov * @since 11/15/11 8:05 PM */ -public class GradleLanguageLevelChange extends GradleAbstractPropertyValueChange { - +public class GradleLanguageLevelChange extends GradleAbstractConflictingPropertyChange { + public GradleLanguageLevelChange(@Nullable LanguageLevel gradleValue, @Nullable LanguageLevel intellijValue) { - super("project language level", gradleValue, intellijValue); + super(GradleBundle.message("gradle.sync.change.project.language.level.text"), gradleValue, intellijValue); } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyPresenceChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyPresenceChange.java index e89823d2ef92..f01b816e4dd4 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyPresenceChange.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyPresenceChange.java @@ -4,17 +4,21 @@ import com.intellij.openapi.roots.LibraryOrderEntry; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.model.GradleLibraryDependency; +import org.jetbrains.plugins.gradle.model.GradleLibraryDependencyId; +import org.jetbrains.plugins.gradle.util.GradleBundle; + +import static org.jetbrains.plugins.gradle.model.GradleLibraryDependencyId.of; /** * @author Denis Zhdanov * @since 1/24/12 9:48 AM */ -public class GradleLibraryDependencyPresenceChange extends GradleEntityPresenceChange { +public class GradleLibraryDependencyPresenceChange extends GradleEntityPresenceChange { - public GradleLibraryDependencyPresenceChange(@Nullable GradleLibraryDependency gradleEntity, @Nullable LibraryOrderEntry intellijEntity) - throws IllegalArgumentException + public GradleLibraryDependencyPresenceChange(@Nullable GradleLibraryDependency gradleDependency, + @Nullable LibraryOrderEntry intellijDependency) throws IllegalArgumentException { - super(gradleEntity, intellijEntity); + super(GradleBundle.message("gradle.sync.change.entity.type.library.dependency"), of(gradleDependency), of(intellijDependency)); } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyStructureChangesCalculator.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyStructureChangesCalculator.java index 077f58b88e90..805783254879 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyStructureChangesCalculator.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyStructureChangesCalculator.java @@ -1,6 +1,7 @@ package org.jetbrains.plugins.gradle.diff; import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.roots.libraries.Library; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.gradle.model.GradleLibraryDependency; @@ -13,6 +14,12 @@ import java.util.Set; public class GradleLibraryDependencyStructureChangesCalculator implements GradleStructureChangesCalculator { + + private final GradleLibraryStructureChangesCalculator myLibraryCalculator; + + public GradleLibraryDependencyStructureChangesCalculator(@NotNull GradleLibraryStructureChangesCalculator libraryCalculator) { + myLibraryCalculator = libraryCalculator; + } @Override public void calculate(@NotNull GradleLibraryDependency gradleEntity, @@ -20,7 +27,11 @@ public class GradleLibraryDependencyStructureChangesCalculator @NotNull Set knownChanges, @NotNull Set currentChanges) { - // TODO den implement + final Library library = intellijEntity.getLibrary(); + if (library == null) { + return; + } + myLibraryCalculator.calculate(gradleEntity.getTarget(), library, knownChanges, currentChanges); } @NotNull @@ -33,7 +44,6 @@ public class GradleLibraryDependencyStructureChangesCalculator @NotNull @Override public Object getGradleKey(@NotNull GradleLibraryDependency entity, @NotNull Set knownChanges) { - // TODO den consider the known changes - return entity.getName(); + return myLibraryCalculator.getGradleKey(entity.getTarget(), knownChanges); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryStructureChangesCalculator.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryStructureChangesCalculator.java new file mode 100644 index 000000000000..f95d2d0c1dbf --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryStructureChangesCalculator.java @@ -0,0 +1,56 @@ +package org.jetbrains.plugins.gradle.diff; + +import com.intellij.openapi.roots.OrderRootType; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.model.GradleLibrary; +import org.jetbrains.plugins.gradle.model.LibraryPathType; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Denis Zhdanov + * @since 2/3/12 11:17 AM + */ +public class GradleLibraryStructureChangesCalculator implements GradleStructureChangesCalculator { + + private final PlatformFacade myPlatformFacade; + + public GradleLibraryStructureChangesCalculator(@NotNull PlatformFacade platformFacade) { + myPlatformFacade = platformFacade; + } + + @Override + public void calculate(@NotNull GradleLibrary gradleEntity, + @NotNull Library intellijEntity, + @NotNull Set knownChanges, + @NotNull Set currentChanges) + { + final Set gradleBinaryPaths = new HashSet(gradleEntity.getPaths(LibraryPathType.BINARY)); + for (VirtualFile file : intellijEntity.getFiles(OrderRootType.CLASSES)) { + final String path = myPlatformFacade.getLocalFileSystemPath(file); + if (!gradleBinaryPaths.remove(path)) { + currentChanges.add(new GradleMismatchedLibraryPathChange(intellijEntity, null, path)); + } + } + + for (String binaryPath : gradleBinaryPaths) { + currentChanges.add(new GradleMismatchedLibraryPathChange(intellijEntity, binaryPath, null)); + } + } + + @NotNull + @Override + public Object getIntellijKey(@NotNull Library entity) { + return entity.getName(); + } + + @NotNull + @Override + public Object getGradleKey(@NotNull GradleLibrary entity, @NotNull Set knownChanges) { + // TODO den consider the known changes + return entity.getName(); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleMismatchedLibraryPathChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleMismatchedLibraryPathChange.java new file mode 100644 index 000000000000..c6dbdbcf2d9b --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleMismatchedLibraryPathChange.java @@ -0,0 +1,52 @@ +package org.jetbrains.plugins.gradle.diff; + +import com.intellij.openapi.roots.libraries.Library; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.util.GradleBundle; + +/** + * @author Denis Zhdanov + * @since 2/2/12 1:32 PM + */ +public class GradleMismatchedLibraryPathChange extends GradleAbstractConflictingPropertyChange { + + private final String myLibraryName; + + public GradleMismatchedLibraryPathChange(@NotNull Library entity, + @Nullable String gradleValue, + @Nullable String intellijValue) + throws IllegalArgumentException + { + super(GradleBundle.message("gradle.sync.change.library.path", entity.getName()), gradleValue, intellijValue); + myLibraryName = entity.getName(); + if (myLibraryName == null) { + throw new IllegalArgumentException(String.format("Can't create %s instance. Reason: given library has no name (%s)", + getClass().getName(), entity)); + } + } + + @NotNull + public String getLibraryName() { + return myLibraryName; + } + + @Override + public void invite(@NotNull GradleProjectStructureChangeVisitor visitor) { + visitor.visit(this); + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + myLibraryName.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (!super.equals(o)) { + return false; + } + GradleMismatchedLibraryPathChange that = (GradleMismatchedLibraryPathChange)o; + return myLibraryName.equals(that.myLibraryName); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleModulePresenceChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleModulePresenceChange.java index 15fb4bcc1d8a..da7cbb4fdeb1 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleModulePresenceChange.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleModulePresenceChange.java @@ -15,19 +15,20 @@ */ package org.jetbrains.plugins.gradle.diff; -import com.intellij.openapi.module.Module; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.gradle.model.GradleModule; +import org.jetbrains.plugins.gradle.util.GradleBundle; /** * @author Denis Zhdanov * @since 11/17/11 12:50 PM */ -public class GradleModulePresenceChange extends GradleEntityPresenceChange { +public class GradleModulePresenceChange extends GradleEntityPresenceChange { - public GradleModulePresenceChange(@Nullable GradleModule gradleEntity, @Nullable Module intellijEntity) throws IllegalArgumentException { - super(gradleEntity, intellijEntity); + public GradleModulePresenceChange(@Nullable String gradleModuleName, @Nullable String intellijModuleName) + throws IllegalArgumentException + { + super(GradleBundle.message("gradle.sync.change.entity.type.module"), gradleModuleName, intellijModuleName); } @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectRenameChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectRenameChange.java new file mode 100644 index 000000000000..1894ae972b28 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectRenameChange.java @@ -0,0 +1,22 @@ +package org.jetbrains.plugins.gradle.diff; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleBundle; + +/** + * Describes project name change. + * + * @author Denis Zhdanov + * @since 11/3/11 3:54 PM + */ +public class GradleProjectRenameChange extends GradleAbstractConflictingPropertyChange { + + public GradleProjectRenameChange(@NotNull String gradleName, @NotNull String intellijName) { + super(GradleBundle.message("gradle.sync.change.project.name.text"), gradleName, intellijName); + } + + @Override + public void invite(@NotNull GradleProjectStructureChangeVisitor visitor) { + visitor.visit(this); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitor.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitor.java index 65310da6673c..0a41c198a88b 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitor.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitor.java @@ -24,8 +24,9 @@ import org.jetbrains.annotations.NotNull; * @since 11/16/11 8:48 PM */ public interface GradleProjectStructureChangeVisitor { - void visit(@NotNull GradleRenameChange change); - void visit(@NotNull GradleProjectStructureChange change); + void visit(@NotNull GradleProjectRenameChange change); + void visit(@NotNull GradleLanguageLevelChange change); void visit(@NotNull GradleModulePresenceChange change); void visit(@NotNull GradleLibraryDependencyPresenceChange change); + void visit(@NotNull GradleMismatchedLibraryPathChange change); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitorAdapter.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitorAdapter.java index 76a108393c04..6e963b8ea895 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitorAdapter.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangeVisitorAdapter.java @@ -24,11 +24,11 @@ import org.jetbrains.annotations.NotNull; public class GradleProjectStructureChangeVisitorAdapter implements GradleProjectStructureChangeVisitor { @Override - public void visit(@NotNull GradleRenameChange change) { + public void visit(@NotNull GradleProjectRenameChange change) { } @Override - public void visit(@NotNull GradleProjectStructureChange change) { + public void visit(@NotNull GradleLanguageLevelChange change) { } @Override @@ -38,4 +38,8 @@ public class GradleProjectStructureChangeVisitorAdapter implements GradleProject @Override public void visit(@NotNull GradleLibraryDependencyPresenceChange change) { } + + @Override + public void visit(@NotNull GradleMismatchedLibraryPathChange change) { + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangesCalculator.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangesCalculator.java index c8f3236b71de..a8262463f61a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangesCalculator.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangesCalculator.java @@ -70,13 +70,13 @@ public class GradleProjectStructureChangesCalculator implements GradleStructureC String gradleName = gradleProject.getName(); String intellijName = intellijProject.getName(); if (!gradleName.equals(intellijName)) { - currentChanges.add(new GradleRenameChange(GradleRenameChange.Entity.PROJECT, gradleName, intellijName)); + currentChanges.add(new GradleProjectRenameChange(gradleName, intellijName)); } } private void checkLanguageLevel(@NotNull GradleProject gradleProject, - @NotNull Project intellijProject, - @NotNull Set currentChanges) + @NotNull Project intellijProject, + @NotNull Set currentChanges) { LanguageLevel gradleLevel = gradleProject.getLanguageLevel(); LanguageLevel intellijLevel = myStructureHelper.getLanguageLevel(intellijProject); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleRenameChange.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleRenameChange.java deleted file mode 100644 index 6aaacfe104c6..000000000000 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleRenameChange.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.jetbrains.plugins.gradle.diff; - -import org.jetbrains.annotations.NotNull; - -/** - * Describes particular entity name change. - * - * @author Denis Zhdanov - * @since 11/3/11 3:54 PM - */ -public class GradleRenameChange extends GradleAbstractPropertyValueChange { - - public enum Entity { PROJECT, MODULE, LIBRARY } - - private final Entity myEntity; - - public GradleRenameChange(@NotNull Entity entity, @NotNull String gradleName, @NotNull String intellijName) { - super(entity.toString().toLowerCase(), gradleName, intellijName); - myEntity = entity; - } - - @NotNull - public Entity getEntity() { - return myEntity; - } - - @Override - public int hashCode() { - return 31 * super.hashCode() + myEntity.hashCode(); - } - - @Override - public boolean equals(Object o) { - if (!super.equals(o)) { - return false; - } - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GradleRenameChange that = (GradleRenameChange)o; - return myEntity.equals(that.myEntity); - } - - @Override - public void invite(@NotNull GradleProjectStructureChangeVisitor visitor) { - visitor.visit(this); - } -} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacade.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacade.java index c03c12035d59..529fea913a98 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacade.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacade.java @@ -3,6 +3,7 @@ package org.jetbrains.plugins.gradle.diff; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import org.jetbrains.annotations.NotNull; @@ -41,4 +42,16 @@ public interface PlatformFacade { */ @NotNull Icon getProjectIcon(); + + /** + * Allows to derive from the given VFS file path that may be compared to the path used by the gradle api. + *

+ * Generally, this method is necessary for processing binary library paths - they point to jar files and VFS uses + * '!' marks in their paths internally. + * + * @param file target file + * @return given file's path that may be compared to the one used by the gradle api + */ + @NotNull + String getLocalFileSystemPath(@NotNull VirtualFile file); } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelperImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacadeImpl.java similarity index 71% rename from plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelperImpl.java rename to plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacadeImpl.java index ff3fb2ada17a..b4a82429a169 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelperImpl.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/PlatformFacadeImpl.java @@ -1,6 +1,7 @@ package org.jetbrains.plugins.gradle.diff; import com.intellij.openapi.application.ex.ApplicationInfoEx; +import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; @@ -8,6 +9,8 @@ import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.vfs.JarFileSystem; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import org.jetbrains.annotations.NotNull; @@ -19,7 +22,7 @@ import java.util.Collection; * @author Denis Zhdanov * @since 1/26/12 11:54 AM */ -public class GradleProjectStructureHelperImpl implements PlatformFacade { +public class PlatformFacadeImpl implements PlatformFacade { @NotNull @Override @@ -44,4 +47,16 @@ public class GradleProjectStructureHelperImpl implements PlatformFacade { public Icon getProjectIcon() { return IconLoader.getIcon(ApplicationInfoEx.getInstanceEx().getSmallIconUrl()); } + + @NotNull + @Override + public String getLocalFileSystemPath(@NotNull VirtualFile file) { + if (file.getFileType() == FileTypes.ARCHIVE) { + final VirtualFile jar = JarFileSystem.getInstance().getVirtualFileForJar(file); + if (jar != null) { + return jar.getPath(); + } + } + return file.getPath(); + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java index e504101aa38a..4d6fb05e6100 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleAdjustImportSettingsStep.java @@ -260,7 +260,7 @@ public class GradleAdjustImportSettingsStep extends AbstractImportFromGradleWiza // We build tree node, its settings control and map them altogether. The only trick here is that nodes can reuse the same // settings control (e.g. more than one node may have the same library as a dependency, so, library dependency node for // every control will use the same settings control). - GradleProjectStructureNode result = new GradleProjectStructureNode(myFactory.buildDescriptor(entity)); + GradleProjectStructureNode result = new GradleProjectStructureNode(myFactory.buildDescriptor(entity)); Pair> pair = processed.get(entity); if (pair == null) { String cardName = String.valueOf(counter); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleProjectStructureFactory.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleProjectStructureFactory.java index a488a328071b..95989623ae96 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleProjectStructureFactory.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/importing/wizard/adjust/GradleProjectStructureFactory.java @@ -30,9 +30,9 @@ public class GradleProjectStructureFactory { /** Shared instance of the current (stateless) class. */ public static final GradleProjectStructureFactory INSTANCE = new GradleProjectStructureFactory(); - @SuppressWarnings("MethodMayBeStatic") + @SuppressWarnings({"MethodMayBeStatic", "unchecked"}) @NotNull - public GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull T entity) { + public GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull T entity) { final Ref> result = new Ref>(); entity.invite(new GradleEntityVisitor() { @Override @@ -70,7 +70,7 @@ public class GradleProjectStructureFactory { visit(dependency.getTarget()); } }); - return result.get(); + return (GradleProjectStructureNodeDescriptor)result.get(); } @SuppressWarnings("MethodMayBeStatic") @@ -138,7 +138,7 @@ public class GradleProjectStructureFactory { return; } for (GradleProjectStructureNode node : treeNodes) { - node.getDescriptor().setName(evt.getNewValue().toString()); + node.getDescriptor().setText(evt.getNewValue().toString()); model.nodeChanged(node); } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibrary.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibrary.java index 55f39878e9e9..cfd267d41064 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibrary.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibrary.java @@ -39,6 +39,10 @@ public class GradleLibrary extends AbstractNamedGradleEntity implements Named { paths.add(GradleUtil.toCanonicalPath(path)); } + public void forgetAllPaths() { + myPaths.clear(); + } + @Override public void invite(@NotNull GradleEntityVisitor visitor) { visitor.visit(this); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibraryDependencyId.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibraryDependencyId.java new file mode 100644 index 000000000000..e42cc70130de --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/model/GradleLibraryDependencyId.java @@ -0,0 +1,78 @@ +package org.jetbrains.plugins.gradle.model; + +import com.intellij.openapi.roots.LibraryOrderEntry; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This class serves as an id for any library dependency. We can't use library object itself because its hashCode()/equals() may + * be changed over time (e.g. source root is added). + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 2/6/12 12:09 PM + */ +public class GradleLibraryDependencyId { + + private final String myLibraryName; + private final String myModuleName; + + public GradleLibraryDependencyId(@NotNull String libraryName, @NotNull String moduleName) { + myLibraryName = libraryName; + myModuleName = moduleName; + } + + @Nullable + public static GradleLibraryDependencyId of(@Nullable GradleLibraryDependency dependency) { + return dependency == null ? null : new GradleLibraryDependencyId(dependency.getName(), dependency.getOwnerModule().getName()); + } + + @Nullable + public static GradleLibraryDependencyId of(@Nullable LibraryOrderEntry dependency) { + if (dependency == null) { + return null; + } + final String libraryName = dependency.getLibraryName(); + if (libraryName == null || StringUtil.isEmpty(libraryName)) { + return null; + } + return new GradleLibraryDependencyId(libraryName, dependency.getOwnerModule().getName()); + } + + @NotNull + public String getLibraryName() { + return myLibraryName; + } + + @NotNull + public String getModuleName() { + return myModuleName; + } + + @Override + public int hashCode() { + int result = myLibraryName.hashCode(); + result = 31 * result + myModuleName.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GradleLibraryDependencyId id = (GradleLibraryDependencyId)o; + + if (!myLibraryName.equals(id.myLibraryName)) return false; + if (!myModuleName.equals(id.myModuleName)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("library dependency: library='%s', module='%s'", myLibraryName, myModuleName); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModel.java index f1ee0022e65d..66934ce49864 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModel.java @@ -3,6 +3,7 @@ package org.jetbrains.plugins.gradle.sync; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange; import org.jetbrains.plugins.gradle.diff.GradleStructureChangesCalculator; import org.jetbrains.plugins.gradle.model.GradleProject; @@ -13,7 +14,7 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReference; /** - * // TODO den add doc + * Manages information about the changes between the gradle and intellij project structures. *

* Thread-safe. * @@ -25,7 +26,9 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent private final Set myListeners = new CopyOnWriteArraySet(); private final AtomicReference> myChanges = new AtomicReference>(new HashSet()); - + + private final AtomicReference myGradleProject = new AtomicReference(); + private final GradleStructureChangesCalculator myChangesCalculator; public GradleProjectStructureChangesModel(@NotNull Project project, @@ -54,6 +57,7 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent * @param gradleProject gradle project to sync with */ public void update(@NotNull GradleProject gradleProject) { + myGradleProject.set(gradleProject); Set knownChanges = new HashSet(myChanges.get()); Set currentChanges = new HashSet(); myChangesCalculator.calculate(gradleProject, myProject, knownChanges, currentChanges); @@ -66,6 +70,14 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent } } + /** + * @return last known gradle project state + */ + @Nullable + public GradleProject getGradleProject() { + return myGradleProject.get(); + } + /** * Registers given listener within the current model. * diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java index 28a389e9aeed..72c5e424f100 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java @@ -29,9 +29,10 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { public GradleProjectStructureChangesPanel(@NotNull Project project, @NotNull GradleProjectStructureChangesModel model, - @NotNull PlatformFacade projectStructureHelper) + @NotNull PlatformFacade platformFacade, + @NotNull GradleProjectStructureHelper projectStructureHelper) { - super(project, projectStructureHelper, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); + super(project, platformFacade, projectStructureHelper, GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); model.addListener(new GradleProjectStructureChangeListener() { @Override public void onChanges(@NotNull final Collection oldChanges, @@ -41,7 +42,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { @Override public void run() { myTreeModel.update(currentChanges); - myTreeModel.pruneObsoleteNodes(ContainerUtil.subtract(oldChanges, currentChanges)); + myTreeModel.processObsoleteChanges(ContainerUtil.subtract(oldChanges, currentChanges)); } }); } @@ -50,7 +51,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { private void init() { myContent = new JPanel(new GridBagLayout()); - myTreeModel = new GradleProjectStructureTreeModel(getProject(), getProjectStructureHelper()); + myTreeModel = new GradleProjectStructureTreeModel(getProject(), getProjectFacade(), getProjectStructureHelper()); Tree tree = new Tree(myTreeModel); applyInitialAppearance(tree, (DefaultMutableTreeNode)myTreeModel.getRoot()); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureHelper.java new file mode 100644 index 000000000000..96b2c8d8ece0 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureHelper.java @@ -0,0 +1,92 @@ +package org.jetbrains.plugins.gradle.sync; + +import com.intellij.openapi.components.AbstractProjectComponent; +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.Ref; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.diff.PlatformFacade; +import org.jetbrains.plugins.gradle.model.*; + +/** + * @author Denis Zhdanov + * @since 2/6/12 3:28 PM + */ +public class GradleProjectStructureHelper extends AbstractProjectComponent { + + private final GradleProjectStructureChangesModel myModel; + private final PlatformFacade myFacade; + + public GradleProjectStructureHelper(@NotNull Project project, + @NotNull GradleProjectStructureChangesModel model, + @NotNull PlatformFacade facade) + { + super(project); + myModel = model; + myFacade = facade; + } + + /** + * Allows to answer if target library dependency is still available at the target project. + * + * @param id target library id + * @return true if target library dependency is still available at the target project; + * false otherwise + */ + public boolean isIntellijLibraryDependencyExist(@NotNull final GradleLibraryDependencyId id) { + for (Module module : myFacade.getModules(myProject)) { + if (!id.getModuleName().equals(module.getName())) { + continue; + } + RootPolicy visitor = new RootPolicy() { + @Override + public Boolean visitLibraryOrderEntry(LibraryOrderEntry libraryOrderEntry, Boolean value) { + return id.getLibraryName().equals(libraryOrderEntry.getLibraryName()); + } + }; + for (OrderEntry entry : myFacade.getOrderEntries(module)) { + if (entry.accept(visitor, false)) { + return true; + } + } + } + return false; + } + + /** + * Allows to answer if target library dependency is still available at the target project. + * + * @param id target library id + * @return true if target library dependency is still available at the target project; + * false otherwise + */ + public boolean isGradleLibraryDependencyExist(@NotNull final GradleLibraryDependencyId id) { + final GradleProject project = myModel.getGradleProject(); + if (project == null) { + return false; + } + final Ref matched = new Ref(); + GradleEntityVisitor visitor = new GradleEntityVisitorAdapter() { + @Override + public void visit(@NotNull GradleLibraryDependency dependency) { + matched.set(id.getLibraryName().equals(dependency.getName())); + } + }; + for (GradleModule module : project.getModules()) { + if (!id.getModuleName().equals(module.getName())) { + continue; + } + for (GradleDependency dependency : module.getDependencies()) { + dependency.invite(visitor); + if (matched.get()) { + return true; + } + } + return false; + } + return false; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java index 31d500edce33..9374826609b2 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureTreeModel.java @@ -1,26 +1,23 @@ package org.jetbrains.plugins.gradle.sync; +import com.intellij.openapi.editor.colors.TextAttributesKey; 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.model.GradleLibraryDependencyId; import org.jetbrains.plugins.gradle.ui.GradleIcons; +import org.jetbrains.plugins.gradle.ui.GradleProjectStructureNode; 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; @@ -37,7 +34,9 @@ import java.util.Map; */ public class GradleProjectStructureTreeModel extends DefaultTreeModel { - public static final GradleProjectStructureNodeDescriptor DEPENDENCIES_NODE_DESCRIPTOR = buildDescriptor( + public static final GradleProjectStructureNodeDescriptor DEPENDENCIES_NODE_DESCRIPTOR + = new GradleProjectStructureNodeDescriptor( + GradleBundle.message("gradle.project.structure.tree.node.dependencies"), GradleBundle.message("gradle.project.structure.tree.node.dependencies"), null ); @@ -52,27 +51,33 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel { * |_dependency2 * */ - private final Map myModuleDependencies = new HashMap(); - private final Map myModules = new HashMap(); + private final Map> myModuleDependencies + = new HashMap>(); + private final Map> myModules + = new HashMap>(); private final TreeNode[] myNodeHolder = new TreeNode[1]; private final int[] myIndexHolder = new int[1]; - private final Project myProject; - private final PlatformFacade myPlatformFacade; + private final Project myProject; + private final PlatformFacade myPlatformFacade; + private final GradleProjectStructureHelper myProjectStructureHelper; - public GradleProjectStructureTreeModel(@NotNull Project project, @NotNull PlatformFacade platformFacade) { + public GradleProjectStructureTreeModel(@NotNull Project project, + @NotNull PlatformFacade platformFacade, + @NotNull GradleProjectStructureHelper projectStructureHelper) { super(null); myProject = project; myPlatformFacade = platformFacade; + myProjectStructureHelper = projectStructureHelper; rebuild(); } public void rebuild() { myModuleDependencies.clear(); myModules.clear(); - - DefaultMutableTreeNode root = new DefaultMutableTreeNode(buildDescriptor(getProject())); + + GradleProjectStructureNode root = buildNode(getProject(), getProject().getName(), myPlatformFacade.getProjectIcon()); final Collection modules = myPlatformFacade.getModules(getProject()); RootPolicy policy = new RootPolicy() { @Override @@ -81,19 +86,21 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel { } }; for (Module module : modules) { - final DefaultMutableTreeNode moduleNode = new DefaultMutableTreeNode(buildDescriptor(module)); + final GradleProjectStructureNode moduleNode = buildNode(module.getName(), GradleIcons.MODULE_ICON); myModules.put(module.getName(), moduleNode); // Assuming that module names are unique. List libraryDependencies = new ArrayList(); for (OrderEntry orderEntry : myPlatformFacade.getOrderEntries(module)) { final LibraryOrderEntry libraryDependency = orderEntry.accept(policy, null); - if (libraryDependency != null && !StringUtil.isEmpty(libraryDependency.getLibraryName())) { - libraryDependencies.add(libraryDependency); - } + libraryDependencies.add(libraryDependency); } if (!libraryDependencies.isEmpty()) { - DefaultMutableTreeNode dependenciesNode = getDependenciesNode(module.getName()); + GradleProjectStructureNode dependenciesNode = getDependenciesNode(module.getName()); for (LibraryOrderEntry dependency : libraryDependencies) { - dependenciesNode.add(new DefaultMutableTreeNode(buildDescriptor(dependency))); + GradleLibraryDependencyId id = GradleLibraryDependencyId.of(dependency); + if (id == null) { + continue; + } + dependenciesNode.add(buildNode(id, id.getLibraryName(), GradleIcons.LIB_ICON)); } moduleNode.add(dependenciesNode); } @@ -108,40 +115,30 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel { return myProject; } - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull String name, @Nullable Icon icon) { - return new GradleProjectStructureNodeDescriptor(new Object(), name, icon); + private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull T entity, @NotNull String name, @NotNull Icon icon) { + return new GradleProjectStructureNodeDescriptor(entity, name, icon); } - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Named entity, @NotNull Icon icon) { - return new GradleProjectStructureNodeDescriptor(entity, entity.getName(), icon); + private static GradleProjectStructureNode buildNode(@NotNull String name, @NotNull Icon icon) { + return buildNode(name, name, icon); } - private GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Project project) { - return new GradleProjectStructureNodeDescriptor(project, project.getName(), myPlatformFacade.getProjectIcon()); + private static GradleProjectStructureNode buildNode(@NotNull T entity, @NotNull String name, @NotNull Icon icon) { + return new GradleProjectStructureNode(buildDescriptor(entity, name, icon)); } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull Module module) { - return new GradleProjectStructureNodeDescriptor(module, module.getName(), GradleIcons.MODULE_ICON); - } - - private static GradleProjectStructureNodeDescriptor buildDescriptor(@NotNull LibraryOrderEntry library) { - final String name = library.getLibraryName(); - assert name != null; - return new GradleProjectStructureNodeDescriptor(library, name, GradleIcons.LIB_ICON); - } - - private DefaultMutableTreeNode getDependenciesNode(@NotNull String moduleName) { - final DefaultMutableTreeNode cached = myModuleDependencies.get(moduleName); + + private GradleProjectStructureNode getDependenciesNode(@NotNull String moduleName) { + final GradleProjectStructureNode cached = myModuleDependencies.get(moduleName); if (cached != null) { return cached; } - DefaultMutableTreeNode moduleNode = myModules.get(moduleName); + GradleProjectStructureNode moduleNode = myModules.get(moduleName); if (moduleNode == null) { - moduleNode = new DefaultMutableTreeNode(buildDescriptor(moduleName, GradleIcons.MODULE_ICON)); + moduleNode = buildNode(moduleName, GradleIcons.MODULE_ICON); myModules.put(moduleName, moduleNode); } - DefaultMutableTreeNode result = new DefaultMutableTreeNode(DEPENDENCIES_NODE_DESCRIPTOR); + GradleProjectStructureNode result = new GradleProjectStructureNode(DEPENDENCIES_NODE_DESCRIPTOR); moduleNode.add(result); myModuleDependencies.put(moduleName, result); @@ -155,62 +152,40 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel { */ public void update(@NotNull Collection changes) { for (GradleProjectStructureChange change : changes) { - change.invite(new GradleProjectStructureChangeVisitor() { + change.invite(new GradleProjectStructureChangeVisitorAdapter() { @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 + public void visit(@NotNull GradleMismatchedLibraryPathChange change) { + for (GradleProjectStructureNode holder : myModuleDependencies.values()) { + for (GradleProjectStructureNode dependencyNode : holder.getChildren(GradleLibraryDependencyId.class)) { + final GradleLibraryDependencyId id = dependencyNode.getDescriptor().getElement(); + if (change.getLibraryName().equals(id.getLibraryName())) { + dependencyNode.addConflictChange(change); + break; + } + } + } } @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; + GradleLibraryDependencyId id = change.getGradleEntity(); + TextAttributesKey attributes = GradleTextAttributes.GRADLE_LOCAL_CHANGE; + if (id == null) { + id = change.getIntellijEntity(); + attributes = GradleTextAttributes.INTELLIJ_LOCAL_CHANGE; } - - 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()); - nodeStructureChanged(child); + assert id != null; + final GradleProjectStructureNode dependenciesNode = getDependenciesNode(id.getModuleName()); + for (GradleProjectStructureNode node : dependenciesNode.getChildren(GradleLibraryDependencyId.class)) { + GradleProjectStructureNodeDescriptor d = node.getDescriptor(); + if (id.equals(d.getElement())) { + d.setAttributes(attributes); + nodeStructureChanged(node); return; } } - DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(descriptor); + GradleProjectStructureNode newNode = buildNode(id, id.getLibraryName(), GradleIcons.LIB_ICON); + newNode.getDescriptor().setAttributes(attributes); dependenciesNode.add(newNode); nodeStructureChanged(dependenciesNode); } @@ -219,7 +194,7 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel { } /** - * Asks current model to remove all obsolete nodes for the considering that the given changes are obsolete. + * Asks current model to process given changes assuming that they are obsolete. *

* Example: *

@@ -231,63 +206,72 @@ public class GradleProjectStructureTreeModel extends DefaultTreeModel {
    * 
    * 
*/ - public void pruneObsoleteNodes(Collection changes) { + public void processObsoleteChanges(Collection changes) { for (GradleProjectStructureChange change : changes) { - change.invite(new GradleProjectStructureChangeVisitor() { + change.invite(new GradleProjectStructureChangeVisitorAdapter() { @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 + public void visit(@NotNull GradleMismatchedLibraryPathChange change) { + for (GradleProjectStructureNode holder : myModuleDependencies.values()) { + for (GradleProjectStructureNode node : holder.getChildren(GradleLibraryDependencyId.class)) { + final GradleLibraryDependencyId id = node.getDescriptor().getElement(); + if (id.getLibraryName().equals(change.getLibraryName())) { + node.removeConflictChange(change); + nodeChanged(node); + break; + } + } + } } @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; + GradleLibraryDependencyId id = change.getGradleEntity(); + boolean removeNode; + if (id == null) { + id = change.getIntellijEntity(); + assert id != null; + removeNode = !myProjectStructureHelper.isIntellijLibraryDependencyExist(id); } else { - moduleName = gradleEntity.getOwnerModule().getName(); - library = gradleEntity; + removeNode = !myProjectStructureHelper.isGradleLibraryDependencyExist(id); } - final DefaultMutableTreeNode holder = myModuleDependencies.get(moduleName); + final GradleProjectStructureNode holder = myModuleDependencies.get(id.getModuleName()); 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; + + // There are two possible cases why 'local library dependency' change is obsolete: + // 1. Corresponding dependency has been added at the counterparty; + // 2. The 'local dependency' has been removed; + // We should distinguish between those situations because we need to mark the node as 'synced' at one case and + // completely removed at another one. + + for (GradleProjectStructureNode node : holder.getChildren(GradleLibraryDependencyId.class)) { + GradleProjectStructureNodeDescriptor descriptor = node.getDescriptor(); + if (!id.equals(descriptor.getElement())) { + continue; } + if (removeNode) { + removeNode(node); + } + else { + descriptor.setAttributes(GradleTextAttributes.GRADLE_NO_CHANGE); + } + return; } } }); } } - private void removeNode(@NotNull TreeNode node) { - final MutableTreeNode parent = (MutableTreeNode)node.getParent(); + private void removeNode(@NotNull GradleProjectStructureNode node) { + final GradleProjectStructureNode parent = node.getParent(); if (parent == null) { return; } int i = parent.getIndex(node); - if (i <= 0) { + if (i < 0) { assert false : node; return; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNode.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNode.java index 06e7285d2861..2551a3162a2e 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNode.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleProjectStructureNode.java @@ -1,24 +1,140 @@ package org.jetbrains.plugins.gradle.ui; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.config.GradleTextAttributes; +import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange; import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.MutableTreeNode; +import java.util.*; /** * @author Denis Zhdanov * @since 8/23/11 3:50 PM + * @param type of the target entity {@link GradleProjectStructureNodeDescriptor#getElement() associated} with the current node */ -public class GradleProjectStructureNode extends DefaultMutableTreeNode { - - private final GradleProjectStructureNodeDescriptor myDescriptor; +public class GradleProjectStructureNode extends DefaultMutableTreeNode implements Iterable> { - public GradleProjectStructureNode(@NotNull GradleProjectStructureNodeDescriptor descriptor) { + private final Set myConflictChanges = new HashSet(); + private final GradleProjectStructureNodeDescriptor myDescriptor; + + public GradleProjectStructureNode(@NotNull GradleProjectStructureNodeDescriptor descriptor) { super(descriptor); myDescriptor = descriptor; } @NotNull - public GradleProjectStructureNodeDescriptor getDescriptor() { + public GradleProjectStructureNodeDescriptor 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: + *
+   * 
    + *
  1. There are two 'conflict changes' for particular node;
  2. + *
  3. + * One of those changes is resolved but the node still should be marked as 'conflict' because there is still one conflict change; + *
  4. + *
  5. The second conflict change is removed. The node should be marked as 'no change' now;
  6. + *
+ *
+ * + * @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()]