IDEA-76142: Gradle support - cannot update IDEA projects once one of build.gradle...
authorDenis.Zhdanov <Denis.Zhdanov@jetbrains.com>
Mon, 30 Jan 2012 11:49:26 +0000 (15:49 +0400)
committerDenis.Zhdanov <Denis.Zhdanov@jetbrains.com>
Mon, 30 Jan 2012 12:40:49 +0000 (16:40 +0400)
1. Test DSL for testing gradle changes model is introduced;
2. Test for the 'merge local' changes use-case is added;
3. More gradle class dependencies are configured via IoC now;

18 files changed:
platform/util/src/com/intellij/util/containers/ConcurrentHashSet.java
plugins/gradle/src/META-INF/plugin.xml
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleDiffUtil.java
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleLibraryDependencyStructureChangesCalculator.java
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleModuleStructureChangesCalculator.java
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureChangesCalculator.java
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelper.java [new file with mode: 0644]
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelperImpl.java [new file with mode: 0644]
plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleStructureChangesCalculator.java
plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangeListener.java
plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModel.java
plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java
plugins/gradle/src/org/jetbrains/plugins/gradle/task/GradleResolveProjectTask.java
plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy [new file with mode: 0644]
plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy [new file with mode: 0644]
plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/ChangeBuilder.groovy [new file with mode: 0644]
plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy [new file with mode: 0644]
plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy [new file with mode: 0644]

index a792361c4cf267d19047f29e646312d7d66d321f..3db9efffae82f44622a433853939ede0be183c32 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2012 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -92,5 +92,10 @@ public class ConcurrentHashSet<K> implements Set<K> {
   public void clear() {
     map.clear();
   }
+
+  @Override
+  public String toString() {
+    return map.keySet().toString();
+  }
 }
 
index 1487b063dbc56fd1553707eda26e0806b638da1e..36d93560b32e92128894a848c43327471e4423da 100644 (file)
 
     <applicationService serviceImplementation="org.jetbrains.plugins.gradle.remote.GradleApiFacadeManager"/>
     <applicationService serviceImplementation="org.jetbrains.plugins.gradle.util.GradleLibraryManager"/>
-    <applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangesCalculator"/>
+    <applicationService serviceInterface="org.jetbrains.plugins.gradle.diff.GradleStructureChangesCalculator"
+                        serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangesCalculator"/>
+    <applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleModuleStructureChangesCalculator"/>
+    <applicationService serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleLibraryDependencyStructureChangesCalculator"/>
+    <applicationService serviceInterface="org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelper"
+                        serviceImplementation="org.jetbrains.plugins.gradle.diff.GradleProjectStructureHelperImpl"/>
     <applicationService serviceInterface="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManager"
                         serviceImplementation="org.jetbrains.plugins.gradle.notification.GradleProgressNotificationManagerImpl"/>
 
index bbafe4cd653a8585d7750f4112487a90b0aa632a..a6b51a7b820481f549c77660777c7773ada0a306 100644 (file)
@@ -25,11 +25,10 @@ public class GradleDiffUtil {
    * Example: particular module has been added at the gradle side. We want to mark that module, its content root(s), dependencies etc
    * as gradle-local changes.
    * 
-   * @param entity  target gradle-local entity
-   * @return        collection of gradle-local changes for the given entity and its interested sub-entities
+   * @param entity          target gradle-local entity
+   * @param currentChanges  holder for the changes built during the current call
    */
-  public static Set<GradleProjectStructureChange> buildLocalChanges(@NotNull GradleEntity entity) {
-    final Set<GradleProjectStructureChange> result = new HashSet<GradleProjectStructureChange>();
+  public static void buildLocalChanges(@NotNull GradleEntity entity, @NotNull final Set<GradleProjectStructureChange> currentChanges) {
     entity.invite(new GradleEntityVisitor() {
       @Override
       public void visit(@NotNull GradleProject project) {
@@ -38,7 +37,7 @@ public class GradleDiffUtil {
 
       @Override
       public void visit(@NotNull GradleModule module) {
-        result.add(new GradleModulePresenceChange(module, null));
+        currentChanges.add(new GradleModulePresenceChange(module, null));
         for (GradleDependency dependency : module.getDependencies()) {
           dependency.invite(this);
         }
@@ -61,79 +60,52 @@ public class GradleDiffUtil {
 
       @Override
       public void visit(@NotNull GradleLibraryDependency dependency) {
-        result.add(new GradleLibraryDependencyPresenceChange(dependency, null));
+        currentChanges.add(new GradleLibraryDependencyPresenceChange(dependency, null));
       }
     });
-    return result;
   }
 
   /**
    * Analogues to {@link #buildLocalChanges} but targets intellij entity.
    *
-   * @param module  target intellij-local module that doesn't present at the gradle side
-   * @return        collection of intellij-local changes for the given entity and its interested sub-entities
+   * @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 Set<? extends GradleProjectStructureChange> buildLocalChanges(@NotNull Module module) {
-    Set<GradleProjectStructureChange> result = new HashSet<GradleProjectStructureChange>();
-    result.add(new GradleModulePresenceChange(null, module));
+  public static void buildLocalChanges(@NotNull Module module, @NotNull Set<GradleProjectStructureChange> currentChanges) {
+    currentChanges.add(new GradleModulePresenceChange(null, module));
     // TODO den process module sub-entities here (content roots and dependencies).
-    return result;
   }
 
   /**
    * Analogues to {@link #buildLocalChanges} but targets intellij entity.
    * 
    * @param libraryDependency  target intellij-local library dependency that doesn't present at the gradle side
-   * @return                   collection of intellij-local changes for the given entity and its interested sub-entities
+   * @param currentChanges     holder for the changes built during the current call
    */
-  public static Set<? extends GradleProjectStructureChange> buildLocalChanges(@NotNull LibraryOrderEntry libraryDependency) {
-    return Collections.singleton(new GradleLibraryDependencyPresenceChange(null, libraryDependency));
+  public static void buildLocalChanges(@NotNull LibraryOrderEntry libraryDependency,
+                                       @NotNull Set<GradleProjectStructureChange> currentChanges)
+  {
+    currentChanges.add(new GradleLibraryDependencyPresenceChange(null, libraryDependency));
   }
 
   /**
    * Performs argument type-based dispatch and delegates to one of strongly typed <code>'buildLocalChanges()'</code> methods.
    *
-   * @param entity  target intellij-local entity that doesn't present at the gradle side
-   * @return        collection of intellij-local changes for the given entity and its interested sub-entities
+   * @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
    */
-  @NotNull
-  public static Set<? extends GradleProjectStructureChange> buildLocalChanges(@NotNull Object entity) {
+  public static void buildLocalChanges(@NotNull Object entity,
+                                       @NotNull Set<GradleProjectStructureChange> currentChanges)
+  {
     if (entity instanceof GradleEntity) {
-      return buildLocalChanges((GradleEntity)entity);
+      buildLocalChanges((GradleEntity)entity, currentChanges);
     }
     else if (entity instanceof Module) {
-      return buildLocalChanges((Module)entity);
+      buildLocalChanges((Module)entity, currentChanges);
     }
     else if (entity instanceof LibraryOrderEntry) {
-      return buildLocalChanges((LibraryOrderEntry)entity);
-    }
-    else {
-      return Collections.emptySet();
-    }
-  }
-
-  /**
-   * Concatenates given entities into the single collection and returns it.
-   * <p/>
-   * The main idea behind this method is that most of the time we don't expect changes at all, hence, corresponding changes calculators
-   * can use {@link Collections#emptySet()}. However, if some sub-nodes do have changes, attempt
-   * to {@link Collection#addAll(Collection) merge} them within the empty set mentioned above would cause an exception.
-   * <p/>
-   * That's why we provide dedicated method for creating new collection as a merge result.
-   * 
-   * @param collections  collections to merge
-   * @return             merge result
-   */
-  @NotNull
-  public static Set<GradleProjectStructureChange> concatenate(Collection<? extends GradleProjectStructureChange>... collections) {
-    Set<GradleProjectStructureChange> result = null;
-    for (Collection<? extends GradleProjectStructureChange> collection : collections) {
-      if (result == null) {
-        result = new HashSet<GradleProjectStructureChange>();
-      }
-      result.addAll(collection);
+      buildLocalChanges((LibraryOrderEntry)entity, currentChanges);
     }
-    return result == null ? Collections.<GradleProjectStructureChange>emptySet() : result;
   }
 
   /**
@@ -146,39 +118,34 @@ public class GradleDiffUtil {
    * @param gradleEntities    entities available at the gradle side
    * @param intellijEntities  entities available at the intellij side
    * @param knownChanges      collection that contains known changes about the entities
+   * @param currentChanges    holder for the changes discovered during the current call
    * @param <I>               target intellij entity type
    * @param <G>               target gradle entity type
-   * @return                  set of changes between the given entity collections
    */
-  @NotNull
-  public static <I, G extends GradleEntity> Set<GradleProjectStructureChange> calculate(
+  public static <I, G extends GradleEntity> void calculate(
     @NotNull GradleStructureChangesCalculator<G, I> calculator,
     @NotNull Iterable<? extends G> gradleEntities,
     @NotNull Iterable<? extends I> intellijEntities,
-    @NotNull Set<GradleProjectStructureChange> knownChanges)
+    @NotNull Set<GradleProjectStructureChange> knownChanges,
+    @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
-    Set<GradleProjectStructureChange> result = Collections.emptySet();
     Map<Object, I> intellijEntitiesByKeys = new HashMap<Object, I>();
     for (I entity : intellijEntities) {
-      final I previous = intellijEntitiesByKeys.put(calculator.getIntellijKey(entity, knownChanges), entity);
+      final I previous = intellijEntitiesByKeys.put(calculator.getIntellijKey(entity), entity);
       assert previous == null;
     }
     for (G gradleEntity: gradleEntities) {
       I intellijEntity = intellijEntitiesByKeys.remove(calculator.getGradleKey(gradleEntity, knownChanges));
-      Set<GradleProjectStructureChange> changesToMerge;
       if (intellijEntity == null) {
-        changesToMerge = buildLocalChanges(gradleEntity);
+        buildLocalChanges(gradleEntity, currentChanges);
       }
       else {
-        changesToMerge = calculator.calculate(gradleEntity, intellijEntity, knownChanges);
+        calculator.calculate(gradleEntity, intellijEntity, knownChanges, currentChanges);
       }
-      result = concatenate(result, changesToMerge);
     }
 
     for (I entity : intellijEntitiesByKeys.values()) {
-      result = concatenate(result, buildLocalChanges(entity));
+      buildLocalChanges(entity, currentChanges);
     }
-    
-    return result;
   }
 }
index 4c2490a48af3048f94e8cbd2851033f035d2f96d..077f58b88e90e4615adac734c9d0d2e09cb6827c 100644 (file)
@@ -4,7 +4,6 @@ import com.intellij.openapi.roots.LibraryOrderEntry;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.gradle.model.GradleLibraryDependency;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -14,20 +13,19 @@ import java.util.Set;
 public class GradleLibraryDependencyStructureChangesCalculator
   implements GradleStructureChangesCalculator<GradleLibraryDependency, LibraryOrderEntry>
 {
-  @NotNull
+
   @Override
-  public Set<GradleProjectStructureChange> calculate(@NotNull GradleLibraryDependency gradleEntity,
-                                                     @NotNull LibraryOrderEntry intellijEntity,
-                                                     @NotNull Set<GradleProjectStructureChange> knownChanges)
+  public void calculate(@NotNull GradleLibraryDependency gradleEntity,
+                        @NotNull LibraryOrderEntry intellijEntity,
+                        @NotNull Set<GradleProjectStructureChange> knownChanges,
+                        @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
     // TODO den implement 
-    return Collections.emptySet();
   }
 
   @NotNull
   @Override
-  public Object getIntellijKey(@NotNull LibraryOrderEntry entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
-    // TODO den consider the known changes
+  public Object getIntellijKey(@NotNull LibraryOrderEntry entity) {
     final String result = entity.getLibraryName();
     return result == null ? "" : result;
   }
index 84c62ec4ff4216c32296f6821eeb6b3ba007329a..28f5ec482cba3da58ee1b6eb9af7e95c3708e774 100644 (file)
@@ -34,24 +34,30 @@ import java.util.Set;
  */
 public class GradleModuleStructureChangesCalculator implements GradleStructureChangesCalculator<GradleModule, Module> {
   
-  private final GradleLibraryDependencyStructureChangesCalculator myLibraryDependencyCalculator
-    = new GradleLibraryDependencyStructureChangesCalculator();
-  
-  @NotNull
+  private final GradleLibraryDependencyStructureChangesCalculator myLibraryDependencyCalculator;
+  private final GradleProjectStructureHelper myStructureHelper;
+
+  public GradleModuleStructureChangesCalculator(@NotNull GradleLibraryDependencyStructureChangesCalculator libraryDependencyCalculator,
+                                                @NotNull GradleProjectStructureHelper structureHelper)
+  {
+    myLibraryDependencyCalculator = libraryDependencyCalculator;
+    myStructureHelper = structureHelper;
+  }
+
   @Override
-  public Set<GradleProjectStructureChange> calculate(@NotNull GradleModule gradleEntity,
-                                                     @NotNull Module intellijEntity,
-                                                     @NotNull Set<GradleProjectStructureChange> knownChanges)
+  public void calculate(@NotNull GradleModule gradleEntity,
+                        @NotNull Module intellijEntity,
+                        @NotNull Set<GradleProjectStructureChange> knownChanges,
+                        @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
     //TODO den process module-local settings
-    //TODO den process content roots 
-    return checkDependencies(gradleEntity, intellijEntity, knownChanges);
+    //TODO den process content roots
+    checkDependencies(gradleEntity, intellijEntity, knownChanges, currentChanges); 
   }
 
   @NotNull
   @Override
-  public Object getIntellijKey(@NotNull Module entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
-    // TODO den consider the known changes
+  public Object getIntellijKey(@NotNull Module entity) {
     return entity.getName();
   }
 
@@ -62,9 +68,10 @@ public class GradleModuleStructureChangesCalculator implements GradleStructureCh
     return entity.getName();
   }
 
-  private Set<GradleProjectStructureChange> checkDependencies(@NotNull GradleModule gradleModule,
-                                                              @NotNull Module intellijModule,
-                                                              @NotNull Set<GradleProjectStructureChange> knownChanges)
+  private void checkDependencies(@NotNull GradleModule gradleModule,
+                                 @NotNull Module intellijModule,
+                                 @NotNull Set<GradleProjectStructureChange> knownChanges,
+                                 @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
     // Prepare intellij part.
     final List<ModuleOrderEntry> intellijModuleDependencies = new ArrayList<ModuleOrderEntry>();
@@ -82,11 +89,10 @@ public class GradleModuleStructureChangesCalculator implements GradleStructureCh
         return libraryOrderEntry;
       }
     };
-    final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(intellijModule);
-    for (OrderEntry orderEntry : moduleRootManager.getOrderEntries()) {
+    for (OrderEntry orderEntry : myStructureHelper.getOrderEntries(intellijModule)) {
       orderEntry.accept(policy, null);
     }
-    
+
     // Prepare gradle part.
     final List<GradleModuleDependency> gradleModuleDependencies = new ArrayList<GradleModuleDependency>();
     final List<GradleLibraryDependency> gradleLibraryDependencies = new ArrayList<GradleLibraryDependency>();
@@ -104,12 +110,10 @@ public class GradleModuleStructureChangesCalculator implements GradleStructureCh
     for (GradleDependency dependency : gradleModule.getDependencies()) {
       dependency.invite(visitor);
     }
-    
+
     // Calculate changes.
     // TODO den process module dependencies here as well.
-    final Set<GradleProjectStructureChange> libraryChanges
-      = GradleDiffUtil.calculate(myLibraryDependencyCalculator, gradleLibraryDependencies, intellijLibraryDependencies, knownChanges);
-    return libraryChanges;
-    
+    GradleDiffUtil.calculate(myLibraryDependencyCalculator, gradleLibraryDependencies, intellijLibraryDependencies,
+                             knownChanges, currentChanges);
   }
 }
index 9c099280cecfc28d5623bc1b65d6cc160ee0fb9e..ad9bc2827854c32f9e9f8534391e7797f617e2a5 100644 (file)
@@ -1,21 +1,15 @@
 package org.jetbrains.plugins.gradle.diff;
 
 import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.LanguageLevelProjectExtension;
 import com.intellij.pom.java.LanguageLevel;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.gradle.model.GradleModule;
 import org.jetbrains.plugins.gradle.model.GradleProject;
 
-import java.util.Collections;
-import java.util.List;
+import java.util.Collection;
 import java.util.Set;
 
-import static java.util.Arrays.asList;
-import static org.jetbrains.plugins.gradle.diff.GradleDiffUtil.concatenate;
-
 /**
  * Encapsulates functionality of calculating changes between Gradle and IntelliJ IDEA project hierarchies.
  * <p/>
@@ -26,25 +20,31 @@ import static org.jetbrains.plugins.gradle.diff.GradleDiffUtil.concatenate;
  */
 public class GradleProjectStructureChangesCalculator implements GradleStructureChangesCalculator<GradleProject, Project> {
 
-  private final GradleModuleStructureChangesCalculator myModuleChangesCalculator = new GradleModuleStructureChangesCalculator();
+  private final GradleModuleStructureChangesCalculator myModuleChangesCalculator;
+  private final GradleProjectStructureHelper           myStructureHelper;
+
+  public GradleProjectStructureChangesCalculator(@NotNull GradleModuleStructureChangesCalculator moduleCalculator,
+                                                 @NotNull GradleProjectStructureHelper structureHelper) {
+    myModuleChangesCalculator = moduleCalculator;
+    myStructureHelper = structureHelper;
+  }
 
-  @NotNull
   @Override
-  public Set<GradleProjectStructureChange> calculate(@NotNull GradleProject gradleEntity,
-                                                     @NotNull Project intellijEntity,
-                                                     @NotNull Set<GradleProjectStructureChange> knownChanges)
+  public void calculate(@NotNull GradleProject gradleEntity,
+                        @NotNull Project intellijEntity,
+                        @NotNull Set<GradleProjectStructureChange> knownChanges,
+                        @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
-    final Set<GradleProjectStructureChange> result = calculateProjectChanges(gradleEntity, intellijEntity, knownChanges);
+    calculateProjectChanges(gradleEntity, intellijEntity, currentChanges);
 
     final Set<? extends GradleModule> gradleSubEntities = gradleEntity.getModules();
-    final List<Module> intellijSubEntities = asList(ModuleManager.getInstance(intellijEntity).getModules());
-    return concatenate(result, GradleDiffUtil.calculate(myModuleChangesCalculator, gradleSubEntities, intellijSubEntities, knownChanges));
+    final Collection<Module> intellijSubEntities = myStructureHelper.getModules(intellijEntity);
+    GradleDiffUtil.calculate(myModuleChangesCalculator, gradleSubEntities, intellijSubEntities, knownChanges, currentChanges);
   }
 
   @NotNull
   @Override
-  public Object getIntellijKey(@NotNull Project entity, @NotNull Set<GradleProjectStructureChange> knownChanges) {
-    // TODO den consider the known changes
+  public Object getIntellijKey(@NotNull Project entity) {
     return entity.getName();
   }
 
@@ -55,43 +55,33 @@ public class GradleProjectStructureChangesCalculator implements GradleStructureC
     return entity.getName();
   }
 
-  @NotNull
-  private static Set<GradleProjectStructureChange> calculateProjectChanges(@NotNull GradleProject gradleProject,
-                                                                           @NotNull Project intellijProject,
-                                                                           @NotNull Set<GradleProjectStructureChange> knownChanges)
+  private void calculateProjectChanges(@NotNull GradleProject gradleProject,
+                                       @NotNull Project intellijProject,
+                                       @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
-    final Set<GradleProjectStructureChange> nameChanges = checkName(gradleProject, intellijProject, knownChanges);
-    final Set<GradleProjectStructureChange> levelChanges = checkLanguageLevel(gradleProject, intellijProject, knownChanges);
-    return concatenate(nameChanges, levelChanges);
+    checkName(gradleProject, intellijProject, currentChanges);
+    checkLanguageLevel(gradleProject, intellijProject, currentChanges);
   }
 
-  @NotNull
-  private static Set<GradleProjectStructureChange> checkName(@NotNull GradleProject gradleProject,
-                                                             @NotNull Project intellijProject,
-                                                             @NotNull Set<GradleProjectStructureChange> knownChanges)
+  private static void checkName(@NotNull GradleProject gradleProject,
+                                @NotNull Project intellijProject,
+                                @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
     String gradleName = gradleProject.getName();
     String intellijName = intellijProject.getName();
-    if (gradleName.equals(intellijName)) {
-      return Collections.emptySet();
+    if (!gradleName.equals(intellijName)) {
+      currentChanges.add(new GradleRenameChange(GradleRenameChange.Entity.PROJECT, gradleName, intellijName));
     }
-    final GradleRenameChange change = new GradleRenameChange(GradleRenameChange.Entity.PROJECT, gradleName, intellijName);
-    return knownChanges.contains(change) ? Collections.<GradleProjectStructureChange>emptySet()
-                                         : Collections.<GradleProjectStructureChange>singleton(change);
   }
 
-  @NotNull
-  private static Set<GradleProjectStructureChange> checkLanguageLevel(@NotNull GradleProject gradleProject,
-                                                                      @NotNull Project intellijProject,
-                                                                      @NotNull Set<GradleProjectStructureChange> knownChanges)
+  private void checkLanguageLevel(@NotNull GradleProject gradleProject,
+                                                               @NotNull Project intellijProject,
+                                                               @NotNull Set<GradleProjectStructureChange> currentChanges)
   {
     LanguageLevel gradleLevel = gradleProject.getLanguageLevel();
-    LanguageLevel intellijLevel = LanguageLevelProjectExtension.getInstance(intellijProject).getLanguageLevel();
-    if (gradleLevel == intellijLevel) {
-      return Collections.emptySet();
+    LanguageLevel intellijLevel = myStructureHelper.getLanguageLevel(intellijProject);
+    if (gradleLevel != intellijLevel) {
+      currentChanges.add(new GradleLanguageLevelChange(gradleLevel, intellijLevel));
     }
-    final GradleLanguageLevelChange change = new GradleLanguageLevelChange(gradleLevel, intellijLevel);
-    return knownChanges.contains(change) ? Collections.<GradleProjectStructureChange>emptySet()
-                                         : Collections.<GradleProjectStructureChange>singleton(change);
   }
 }
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelper.java
new file mode 100644 (file)
index 0000000..f0c81df
--- /dev/null
@@ -0,0 +1,37 @@
+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.pom.java.LanguageLevel;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * IntelliJ code provides a lot of statical bindings to the interested pieces of data. For example we need to execute code
+ * like below to get list of modules for the target project:
+ * <pre>
+ *   ModuleManager.getInstance(project).getModules()
+ * </pre>
+ * That means that it's not possible to test target classes in isolation if corresponding infrastructure is not set up.
+ * However, we don't want to set it up if we execute a simple standalone test.
+ * <p/>
+ * This interface is intended to encapsulate access to the underlying project infrastructure.
+ * <p/>
+ * Implementations of this interface are expected to be thread-safe.
+ * 
+ * @author Denis Zhdanov
+ * @since 1/26/12 11:32 AM
+ */
+public interface GradleProjectStructureHelper {
+
+  @NotNull
+  LanguageLevel getLanguageLevel(@NotNull Project project);
+
+  @NotNull
+  Collection<Module> getModules(@NotNull Project project);
+
+  @NotNull
+  Collection<OrderEntry> getOrderEntries(@NotNull Module module);
+}
diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelperImpl.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/diff/GradleProjectStructureHelperImpl.java
new file mode 100644 (file)
index 0000000..6b30b59
--- /dev/null
@@ -0,0 +1,38 @@
+package org.jetbrains.plugins.gradle.diff;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.LanguageLevelProjectExtension;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.OrderEntry;
+import com.intellij.pom.java.LanguageLevel;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/26/12 11:54 AM
+ */
+public class GradleProjectStructureHelperImpl implements GradleProjectStructureHelper {
+
+  @NotNull
+  @Override
+  public LanguageLevel getLanguageLevel(@NotNull Project project) {
+    return LanguageLevelProjectExtension.getInstance(project).getLanguageLevel();
+  }
+
+  @NotNull
+  @Override
+  public Collection<Module> getModules(@NotNull Project project) {
+    return Arrays.asList(ModuleManager.getInstance(project).getModules());
+  }
+
+  @NotNull
+  @Override
+  public Collection<OrderEntry> getOrderEntries(@NotNull Module module) {
+    return Arrays.asList(ModuleRootManager.getInstance(module).getOrderEntries());
+  }
+}
index 778d4cc1fdbd6a5c372dd18e0ea4837f37c3f32e..362e0cf18fb763462a72667952f54e87fd7e03a3 100644 (file)
@@ -18,17 +18,60 @@ import java.util.Set;
  */
 public interface GradleStructureChangesCalculator<G extends GradleEntity, I> {
 
-  // TODO den add doc
-  @NotNull
-  Set<GradleProjectStructureChange> calculate(@NotNull G gradleEntity,
-                                              @NotNull I intellijEntity,
-                                              @NotNull Set<GradleProjectStructureChange> knownChanges);
+  /**
+   * Calculates changes between the given entities.
+   * 
+   * @param gradleEntity    target gradle entity
+   * @param intellijEntity  target intellij entity
+   * @param knownChanges    changes between the gradle and intellij project structure that has been known up until now
+   * @param currentChanges  holder for the changes between the given entities discovered by the current call. Note that
+   *                        it must contain the change objects that have been known (contained at the <code>'knownChanges'</code>)
+   *                        but are still in place
+   */
+  void calculate(@NotNull G gradleEntity,
+                 @NotNull I intellijEntity,
+                 @NotNull Set<GradleProjectStructureChange> knownChanges,
+                 @NotNull Set<GradleProjectStructureChange> currentChanges);
 
-  // TODO den add doc
+  /**
+   * There are three possible situations when we compare a set of gradle entities with a set of intellij entities:
+   * <pre>
+   * <ul>
+   *   <li>particular entity presents only at the gradle side;</li>
+   *   <li>particular entity presents only at the intellij side;</li>
+   *   <li>particular gradle entity is matched to particular intellij entity (they may have difference in their settings though);</li>
+   * </ul>
+   * </pre>
+   * <p/>
+   * The general idea is to map evey item at the given sets of gradle and intellij entities to particular key (both gradle and
+   * intellij keys are expected to belong to the same class) and then compare them. Matched keys shows that corresponding
+   * entities should be {@link #calculate(GradleEntity, Object, Set) compared to each other}; non-matched indicate that corresponding
+   * entities are gradle- or intellij-local.
+   * <p/>
+   * This method allows to match intellij entity to the target key.
+   * 
+   * @param entity  intellij entity to match
+   * @return        key for the given entity
+   * @see #getGradleKey(GradleEntity, Set)
+   */
   @NotNull
-  Object getIntellijKey(@NotNull I entity, @NotNull Set<GradleProjectStructureChange> knownChanges);
-  
-  // TODO den add doc
+  Object getIntellijKey(@NotNull I entity);
+
+  /**
+   * Serves the same purpose as {@link #getIntellijKey(Object)} but targets gradle entities.
+   * <p/>
+   * There is a possible case that two corresponding gradle and intellij entities differ from each other by the setting that
+   * affects the resulting key (e.g. we may use module name as a key for 'module' entities and intellij module name differs from
+   * the name of the corresponding gradle module). We need to match only in one direction then (e.g. consider a situation when
+   * particular module is named differently at gradle and intellij. We shouldn't consider that change during both
+   * {@code intellij-entity -> key} and {@code gradle-entity -> key} mappings because that would produce two different keys). 
+   * So, we take into consideration the known changes only during {@code gradle-entity -> key} processing.
+   * 
+   * @param entity        target gradle entity that should be mapped to a key
+   * @param knownChanges  known changes between the gradle and intellij structures
+   * @return              key for the given entity
+   * @see #getIntellijKey(Object)
+   */
   @NotNull
   Object getGradleKey(@NotNull G entity, @NotNull Set<GradleProjectStructureChange> knownChanges);
 }
index fe167530b145723baddfba8429d18420ed8f7aac..1a60653391691fd1a0ef5060e08f1c9ee002ee3f 100644 (file)
@@ -18,7 +18,9 @@ public interface GradleProjectStructureChangeListener {
   /**
    * Notifies current listener on the newly discovered changes between the gradle and intellij project models.
    * 
-   * @param changes  newly discovered changes between the gradle and intellij project models.
+   * @param oldChanges      changes between the gradle and intellij project models that had been known prior to the current update
+   * @param currentChanges  the most up-to-date changes between the gradle and intellij project models
    */
-  void onChanges(@NotNull Collection<GradleProjectStructureChange> changes);
+  void onChanges(@NotNull Collection<GradleProjectStructureChange> oldChanges,
+                 @NotNull Collection<GradleProjectStructureChange> currentChanges);
 }
index 7825821034eea61f4b8d99536fb133e6ab8e298e..f1ee0022e65deee195ed15bc65cc251d23e024f6 100644 (file)
@@ -2,15 +2,15 @@ package org.jetbrains.plugins.gradle.sync;
 
 import com.intellij.openapi.components.AbstractProjectComponent;
 import com.intellij.openapi.project.Project;
-import com.intellij.util.containers.ConcurrentHashSet;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChange;
-import org.jetbrains.plugins.gradle.diff.GradleProjectStructureChangesCalculator;
+import org.jetbrains.plugins.gradle.diff.GradleStructureChangesCalculator;
 import org.jetbrains.plugins.gradle.model.GradleProject;
 
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * // TODO den add doc
@@ -23,11 +23,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
 public class GradleProjectStructureChangesModel extends AbstractProjectComponent {
 
   private final Set<GradleProjectStructureChangeListener> myListeners = new CopyOnWriteArraySet<GradleProjectStructureChangeListener>();
-  private final Set<GradleProjectStructureChange>         myChanges   = new ConcurrentHashSet<GradleProjectStructureChange>();
+  private final AtomicReference<Set<GradleProjectStructureChange>> myChanges
+    = new AtomicReference<Set<GradleProjectStructureChange>>(new HashSet<GradleProjectStructureChange>());
 
-  private final GradleProjectStructureChangesCalculator myChangesCalculator;
+  private final GradleStructureChangesCalculator<GradleProject, Project> myChangesCalculator;
 
-  public GradleProjectStructureChangesModel(@NotNull Project project, @NotNull GradleProjectStructureChangesCalculator changesCalculator) {
+  public GradleProjectStructureChangesModel(@NotNull Project project,
+                                            @NotNull GradleStructureChangesCalculator<GradleProject, Project> changesCalculator)
+  {
     super(project);
     myChangesCalculator = changesCalculator;
   }
@@ -44,15 +47,22 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent
    *  </li>
    *  <li>{@link #addListener(GradleProjectStructureChangeListener) Registered listeners} are notified if any new change is detected;</li>
    * </ol>
+   * <p/>
+   * <b>Note:</b> it's very important that the listeners are notified <b>after</b> the actual state change, i.e. {@link #getChanges()}
+   * during the update returns up-to-date data.
    *
    * @param gradleProject  gradle project to sync with
    */
   public void update(@NotNull GradleProject gradleProject) {
-    Set<GradleProjectStructureChange> knownChanges = new HashSet<GradleProjectStructureChange>(myChanges);
-    final Set<GradleProjectStructureChange> newChanges = myChangesCalculator.calculate(gradleProject, myProject, knownChanges);
-    myChanges.addAll(newChanges);
+    Set<GradleProjectStructureChange> knownChanges = new HashSet<GradleProjectStructureChange>(myChanges.get());
+    Set<GradleProjectStructureChange> currentChanges = new HashSet<GradleProjectStructureChange>();
+    myChangesCalculator.calculate(gradleProject, myProject, knownChanges, currentChanges);
+    if (currentChanges.equals(knownChanges)) {
+      return;
+    }
+    myChanges.set(currentChanges);
     for (GradleProjectStructureChangeListener listener : myListeners) {
-      listener.onChanges(newChanges);
+      listener.onChanges(knownChanges, currentChanges);
     }
   }
 
@@ -72,6 +82,6 @@ public class GradleProjectStructureChangesModel extends AbstractProjectComponent
    */
   @NotNull
   public Set<GradleProjectStructureChange> getChanges() {
-    return myChanges;
+    return myChanges.get();
   }
 }
index 27fc80804c02ecf8a023b507b46d00636bba0a9d..b2d0a5ce81eb79bb76713140190f0d61fdb759c2 100644 (file)
@@ -8,6 +8,7 @@ import com.intellij.openapi.roots.ModuleRootManager;
 import com.intellij.openapi.roots.OrderEntry;
 import com.intellij.openapi.roots.RootPolicy;
 import com.intellij.ui.treeStructure.Tree;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.hash.HashMap;
 import com.intellij.util.ui.UIUtil;
 import org.jetbrains.annotations.NotNull;
@@ -25,6 +26,8 @@ import org.jetbrains.plugins.gradle.util.GradleConstants;
 import javax.swing.*;
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.MutableTreeNode;
+import javax.swing.tree.TreeNode;
 import java.awt.*;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -51,6 +54,9 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
    */
   private final Map<String, DefaultMutableTreeNode> myModuleDependencies = new HashMap<String, DefaultMutableTreeNode>();
   private final Map<String, DefaultMutableTreeNode> myModules            = new HashMap<String, DefaultMutableTreeNode>();
+
+  private final TreeNode[] myNodeHolder  = new TreeNode[1];
+  private final int[]      myIndexHolder = new int[1];
   
   private final GradleProjectStructureChangesModel myChangesModel;
 
@@ -62,11 +68,15 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
     myChangesModel = model;
     myChangesModel.addListener(new GradleProjectStructureChangeListener() {
       @Override
-      public void onChanges(@NotNull final Collection<GradleProjectStructureChange> changes) {
+      public void onChanges(@NotNull final Collection<GradleProjectStructureChange> oldChanges,
+                            @NotNull final Collection<GradleProjectStructureChange> currentChanges)
+      {
         UIUtil.invokeLaterIfNeeded(new Runnable() {
           @Override
           public void run() {
-            updateTree(changes); 
+            updateTree(currentChanges);
+            processObsoleteChanges(ContainerUtil.subtract(oldChanges, currentChanges));    
+
           }
         });
       }
@@ -84,6 +94,7 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
     constraints.fill = GridBagConstraints.BOTH;
     constraints.weightx = constraints.weighty = 1;
     myContent.add(tree, constraints);
+    myContent.setBackground(tree.getBackground());
     return treeModel;
   }
 
@@ -237,4 +248,83 @@ public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel {
       });
     }
   }
+
+  /**
+   * Updates the tree state considering that the given changes are obsolete.
+   * <p/>
+   * Example:
+   * <pre>
+   * <ol>
+   *   <li>There is a particular intellij-local library (change from the gradle project structure);</li>
+   *   <li>Corresponding node is shown at the current UI;</li>
+   *   <li>The library is removed, i.e. corresponding change has become obsolete;</li>
+   *   <li>This method is notified within the obsolete change and is expected to remove the corresponding node;</li>
+   * </ol>
+   * </pre>
+   */
+  private void processObsoleteChanges(Collection<GradleProjectStructureChange> changes) {
+    for (GradleProjectStructureChange change : changes) {
+      change.invite(new GradleProjectStructureChangeVisitor() {
+        @Override
+        public void visit(@NotNull GradleRenameChange change) {
+          // TODO den implement 
+        }
+
+        @Override
+        public void visit(@NotNull GradleProjectStructureChange change) {
+          // TODO den implement 
+        }
+
+        @Override
+        public void visit(@NotNull GradleModulePresenceChange change) {
+          // TODO den implement 
+        }
+
+        @Override
+        public void visit(@NotNull GradleLibraryDependencyPresenceChange change) {
+          // We need to remove the corresponding node then.
+          String moduleName;
+          Object library;
+          final GradleLibraryDependency gradleEntity = change.getGradleEntity();
+          final LibraryOrderEntry intellijEntity = change.getIntellijEntity();
+          assert gradleEntity != null || intellijEntity != null;
+          if (gradleEntity == null) {
+            moduleName = intellijEntity.getOwnerModule().getName();
+            library = intellijEntity;
+          }
+          else {
+            moduleName = gradleEntity.getOwnerModule().getName();
+            library = gradleEntity;
+          }
+          final DefaultMutableTreeNode holder = myModuleDependencies.get(moduleName);
+          if (holder == null) {
+            return;
+          }
+          for (DefaultMutableTreeNode node = holder.getFirstLeaf(); node != null; node = node.getNextSibling()) {
+            GradleProjectStructureNodeDescriptor<?> descriptor = (GradleProjectStructureNodeDescriptor<?>)node.getUserObject();
+            if (descriptor.getElement().equals(library)) {
+              removeNode(node);
+              return;
+            }
+          }
+        }
+      });
+    }
+  }
+
+  private void removeNode(@NotNull TreeNode node) {
+    final MutableTreeNode parent = (MutableTreeNode)node.getParent();
+    if (parent == null) {
+      return;
+    }
+    int i = parent.getIndex(node);
+    if (i <= 0) {
+      assert false : node;
+      return;
+    }
+    parent.remove(i);
+    myIndexHolder[0] = i;
+    myNodeHolder[0] = node;
+    myTreeModel.nodesWereRemoved(parent, myIndexHolder, myNodeHolder);
+  }
 }
index a8d7bd868f8e8b46ed94de5e65705de51d5742d1..ff8f163eb147bf0fc9e5675f52288a3a69ae6851 100644 (file)
@@ -43,7 +43,12 @@ public class GradleResolveProjectTask extends AbstractGradleTask {
       return;
     }
     final GradleProjectStructureChangesModel model = myIntellijProject.getComponent(GradleProjectStructureChangesModel.class);
-    model.update(project);
+    if (model != null) {
+      // This task may be called during the 'import from gradle' processing, hence, no project-level IoC is up.
+      // Model update is necessary for the correct tool window project structure diff showing but we don't have
+      // gradle tool window on this stage.
+      model.update(project);
+    }
   }
 
   @Nullable
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesModelTest.groovy
new file mode 100644 (file)
index 0000000..abe61ee
--- /dev/null
@@ -0,0 +1,76 @@
+package org.jetbrains.plugins.gradle.sync;
+
+
+import com.intellij.openapi.project.Project
+import org.jetbrains.plugins.gradle.testutil.ChangeBuilder
+import org.jetbrains.plugins.gradle.testutil.GradleProjectBuilder
+import org.jetbrains.plugins.gradle.testutil.IntellijProjectBuilder
+import org.junit.Before
+import org.junit.Test
+import org.picocontainer.defaults.DefaultPicoContainer
+import org.jetbrains.plugins.gradle.diff.*
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author Denis Zhdanov
+ * @since 01/25/2012
+ */
+public class GradleProjectStructureChangesModelTest {
+
+  private GradleProjectStructureChangesModel myModel;
+  def gradle;
+  def intellij;
+  def changes;
+  
+  @Before
+  public void setUp() {
+    gradle = new GradleProjectBuilder()
+    intellij = new IntellijProjectBuilder()
+    changes = new ChangeBuilder()
+    def container = new DefaultPicoContainer()
+    container.registerComponentInstance(Project, intellij.project)
+    container.registerComponentInstance(GradleProjectStructureHelper, intellij.projectStructureHelper as GradleProjectStructureHelper)
+    container.registerComponentImplementation(GradleProjectStructureChangesModel)
+    container.registerComponentImplementation(GradleStructureChangesCalculator, GradleProjectStructureChangesCalculator)
+    container.registerComponentImplementation(GradleModuleStructureChangesCalculator)
+    container.registerComponentImplementation(GradleLibraryDependencyStructureChangesCalculator)
+
+    myModel = container.getComponentInstance(GradleProjectStructureChangesModel.class) as GradleProjectStructureChangesModel
+  }
+  
+  @Test
+  public void mergeGradleLocalToIntellij() {
+    gradle {
+      module {
+        dependencies {
+          lib(name: "lib1")
+          lib(name: "lib2")
+    } } }
+    
+    intellij {
+      module {
+        dependencies {
+          lib(name: "lib1")
+    } } }
+    
+    myModel.update(gradle.project)
+    checkChanges {
+      presence {
+        lib(gradle: gradle.modules.dependencies.flatten().findAll { it.name == "lib2" })
+      }
+    }
+
+    gradle {
+      module {
+        dependencies {
+          lib(name: "lib1")
+    } } }
+    myModel.update(gradle.project)
+    assertEquals([].toSet(), myModel.changes)
+  }
+
+  private def checkChanges(c) {
+    c.delegate = changes
+    assertEquals(c(), myModel.changes)
+  }
+}
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/AbstractProjectBuilder.groovy
new file mode 100644 (file)
index 0000000..6e4d1f3
--- /dev/null
@@ -0,0 +1,68 @@
+package org.jetbrains.plugins.gradle.testutil
+
+import com.intellij.pom.java.LanguageLevel;
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/25/12 4:06 PM
+ */
+public abstract class AbstractProjectBuilder extends BuilderSupport {
+  
+  private static final def SAME_TOKEN = "same"
+  private static int COUNTER
+  
+  def project
+  def modules = []
+  def libraries = [:].withDefault { createLibrary(it.name?: same, it.paths?: [:]) }
+  def dependencies = [:].withDefault {[]}
+
+  @Override
+  protected void setParent(Object parent, Object child) {
+  }
+
+  @Override
+  protected Object createNode(Object name) {
+    switch (name) {
+      case "call": return createNode("project", [name: same])
+      case "module": return createNode(name, [name: same])
+      case "dependencies": return getCurrent() // Assuming that 'current' is a module object
+    }
+  }
+
+  @Override
+  protected Object createNode(Object name, Object value) {
+    return null
+  }
+
+  @Override
+  protected Object createNode(Object name, Map attributes) {
+    switch (name) {
+      case "project":
+        reset()
+        return project = createProject(attributes.name?: same, attributes.langLevel?: LanguageLevel.JDK_1_6)
+      case "module": def module = createModule(attributes.name?: same); modules << module; return module
+      case "lib":
+        def module = getCurrent()
+        def dep = createLibraryDependency(module, libraries[attributes])
+        dependencies[module] << dep
+        return dep
+    }
+  }
+
+  @Override
+  protected Object createNode(Object name, Map attributes, Object value) {
+    return null
+  }
+  
+  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 createLibraryDependency(module, library)
+
+  protected String getUnique() { "./${COUNTER++}" }
+  protected String getSame() { SAME_TOKEN }
+  
+  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
new file mode 100644 (file)
index 0000000..700bcac
--- /dev/null
@@ -0,0 +1,47 @@
+package org.jetbrains.plugins.gradle.testutil;
+
+
+import org.jetbrains.plugins.gradle.diff.GradleLibraryDependencyPresenceChange
+
+/**
+ * @author Denis Zhdanov
+ * @since 1/26/12 3:25 PM
+ */
+public class ChangeBuilder extends BuilderSupport {
+  
+  def changes = []
+  
+  @Override
+  protected void setParent(Object parent, Object child) {
+  }
+
+  @Override
+  protected Object createNode(Object name) {
+    if (current == null) {
+      changes = []
+    }
+    changes
+  }
+
+  @Override
+  protected Object createNode(Object name, Object value) { changes }
+
+  @Override
+  protected Object createNode(Object name, Map attributes) {
+    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
+    }
+  }
+
+  @Override
+  protected Object createNode(Object name, Map attributes, Object value) { changes }
+
+  @Override
+  protected Object postNodeCompletion(Object parent, Object node) {
+    parent == null ? changes.toSet() : node
+  }
+}
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/GradleProjectBuilder.groovy
new file mode 100644 (file)
index 0000000..e889543
--- /dev/null
@@ -0,0 +1,46 @@
+package org.jetbrains.plugins.gradle.testutil
+
+import org.jetbrains.plugins.gradle.model.GradleProject
+import org.jetbrains.plugins.gradle.model.GradleModule
+import org.jetbrains.plugins.gradle.model.GradleLibraryDependency
+import org.jetbrains.plugins.gradle.model.GradleLibrary
+import org.jetbrains.plugins.gradle.model.LibraryPathType
+import com.intellij.pom.java.LanguageLevel
+
+/** 
+ * @author Denis Zhdanov
+ * @since 1/25/12 1:29 PM
+ */
+class GradleProjectBuilder extends AbstractProjectBuilder {
+
+  @Override
+  protected createProject(String name, LanguageLevel languageLevel) {
+    def result = new GradleProject(same, same)
+    result.name = name
+    result.languageLevel = languageLevel
+    result
+  }
+
+  @Override
+  protected createModule(String name) {
+    def result = new GradleModule(name, unique)
+    project.addModule(result)
+    result
+  }
+
+  @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) }
+    result
+  }
+
+  @Override
+  protected createLibraryDependency(module, library) {
+    def result = new GradleLibraryDependency(module, library)
+    module.addDependency(result)
+    result
+  }
+}
diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/testutil/IntellijProjectBuilder.groovy
new file mode 100644 (file)
index 0000000..92fad07
--- /dev/null
@@ -0,0 +1,49 @@
+package org.jetbrains.plugins.gradle.testutil
+
+import com.intellij.pom.java.LanguageLevel
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.roots.RootPolicy
+import com.intellij.openapi.roots.libraries.Library
+import com.intellij.openapi.roots.LibraryOrderEntry
+import groovy.mock.interceptor.StubFor
+import com.intellij.openapi.project.Project
+
+/** 
+ * @author Denis Zhdanov
+ * @since 1/25/12 3:09 PM
+ */
+class IntellijProjectBuilder extends AbstractProjectBuilder {
+  
+  def projectStub = [:]
+  def project = projectStub as Project
+  def projectStructureHelper = [
+    getModules: { modules },
+    getOrderEntries: { dependencies[it] }
+  ]
+
+  @Override
+  protected createProject(String name, LanguageLevel languageLevel) {
+    projectStub.getName = { name }
+    projectStructureHelper.getLanguageLevel = { languageLevel }
+    project
+  }
+
+  @Override
+  protected createModule(String name) {
+    [ getName: { name } ] as Module
+  }
+
+  @Override
+  protected createLibrary(String name, Map paths) {
+    [ getName: { name } ] as Library
+  }
+
+  @Override
+  protected createLibraryDependency(module, library) {
+    def stub = [:]
+    def result = stub as LibraryOrderEntry
+    stub.accept = { policy, defaultValue -> policy.visitLibraryOrderEntry(result, defaultValue) }
+    stub.getLibraryName = { library.name }
+    result
+  }
+}