Merge "Fixed how project sync handles invalid cached models." into studio-1.2-release
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/GradleSyncTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/GradleSyncTest.java
index 80ceb66..a3f2ee4 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/GradleSyncTest.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/gradle/GradleSyncTest.java
@@ -38,6 +38,9 @@
 import com.intellij.openapi.command.WriteCommandAction;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.externalSystem.model.DataNode;
+import com.intellij.openapi.externalSystem.model.project.ModuleData;
+import com.intellij.openapi.externalSystem.model.project.ProjectData;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.Project;
@@ -50,6 +53,7 @@
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.SystemProperties;
 import junit.framework.Assert;
+import org.fest.reflect.reference.TypeRef;
 import org.fest.swing.core.GenericTypeMatcher;
 import org.fest.swing.core.matcher.DialogMatcher;
 import org.fest.swing.data.TableCell;
@@ -60,6 +64,7 @@
 import org.fest.swing.fixture.JButtonFixture;
 import org.fest.swing.fixture.JTableFixture;
 import org.fest.swing.timing.Condition;
+import org.jetbrains.android.AndroidPlugin.GuiTestSuiteState;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -100,6 +105,7 @@
 import static com.intellij.util.SystemProperties.getLineSeparator;
 import static junit.framework.Assert.*;
 import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.reflect.core.Reflection.field;
 import static org.fest.swing.core.matcher.DialogMatcher.withTitle;
 import static org.fest.swing.core.matcher.JButtonMatcher.withText;
 import static org.fest.swing.data.TableCell.row;
@@ -108,13 +114,17 @@
 import static org.fest.swing.timing.Pause.pause;
 import static org.fest.util.Strings.quote;
 import static org.jetbrains.android.AndroidPlugin.GRADLE_SYNC_COMMAND_LINE_OPTIONS_KEY;
+import static org.jetbrains.android.AndroidPlugin.getGuiTestSuiteState;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 public class GradleSyncTest extends GuiTestCase {
   @Before
-  public void disableSourceGenTask() {
+  public void resetIdeState() {
     GradleExperimentalSettings.getInstance().SKIP_SOURCE_GEN_ON_PROJECT_SYNC = true;
+    GuiTestSuiteState state = getGuiTestSuiteState();
+    assertNotNull(state);
+    state.setUseCachedGradleModelOnly(false);
   }
 
   @After
@@ -917,6 +927,40 @@
     assertThat(moduleDependency.getModuleName()).isEqualTo("library2");
   }
 
+  // Verifies that if syncing using cached model, and if the cached model is missing data, we fall back to a full Gradle sync.
+  // See: https://code.google.com/p/android/issues/detail?id=160899
+  @Test @IdeGuiTest
+  public void testWithCacheMissingModules() throws IOException {
+    IdeFrameFixture projectFrame = openSimpleApplication();
+
+    // Remove a module from the cache.
+    Project project = projectFrame.getProject();
+    DataNode<ProjectData> cache = getCachedProjectData(project);
+    assertNotNull(cache);
+
+    List<DataNode<?>> cachedChildren = field("myChildren").ofType(new TypeRef<List<DataNode<?>>>(){}).in(cache).get();
+    assertThat(cachedChildren.size()).isGreaterThan(1);
+    DataNode<?> toRemove = null;
+    for (DataNode<?> child : cachedChildren) {
+      if (child.getData() instanceof ModuleData) {
+        toRemove = child;
+        break;
+      }
+    }
+    assertNotNull(toRemove);
+    cachedChildren.remove(toRemove);
+
+    // Force the IDE to use cache for sync.
+    GuiTestSuiteState state = getGuiTestSuiteState();
+    assertNotNull(state);
+    state.setUseCachedGradleModelOnly(true);
+
+    // Sync again, and a full sync should occur, since the cache is missing modules.
+    // 'waitForGradleProjectSyncToFinish' will never finish and test will time out and fail if the IDE never gets notified that the sync
+    // finished.
+    projectFrame.requestProjectSync().waitForGradleProjectSyncToFinish();
+  }
+
   @NotNull
   private static String getUnsupportedGradleHome() {
     return getGradleHomeFromSystemProperty(UNSUPPORTED_GRADLE_HOME_PROPERTY, "2.1");
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
index 5616f40..a3a179b 100755
--- a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
@@ -65,6 +65,7 @@
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.pom.java.LanguageLevel;
 import com.intellij.util.SystemProperties;
+import org.jetbrains.android.AndroidPlugin.GuiTestSuiteState;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.android.newProject.AndroidModuleBuilder;
 import org.jetbrains.annotations.NotNull;
@@ -98,6 +99,8 @@
 import static com.intellij.openapi.util.io.FileUtilRt.createIfNotExists;
 import static com.intellij.ui.AppUIUtil.invokeLaterIfProjectAlive;
 import static com.intellij.util.ui.UIUtil.invokeAndWaitIfNeeded;
+import static org.jetbrains.android.AndroidPlugin.getGuiTestSuiteState;
+import static org.jetbrains.android.AndroidPlugin.isGuiTestingMode;
 
 /**
  * Imports an Android-Gradle project without showing the "Import Project" Wizard UI.
@@ -107,7 +110,8 @@
   private static final ProjectSystemId SYSTEM_ID = GradleConstants.SYSTEM_ID;
 
   // When this system property is set, the sync operation always tries to use the cached project data unless any gradle files are modified.
-  private static final boolean alwaysSyncWithCachedProjectData = Boolean.getBoolean("studio.sync.with.cached.project.data");
+  private static final boolean SYNC_WITH_CACHED_MODEL_ONLY =
+    SystemProperties.getBooleanProperty("studio.sync.with.cached.model.only", false);
 
   private final ImporterDelegate myDelegate;
 
@@ -533,21 +537,19 @@
     setHasSyncErrors(project, false);
     setHasWrongJdk(project, false);
 
-    if (alwaysSyncWithCachedProjectData || options.useCachedProjectData) {
+    if (forceSyncWithCachedModel() || options.useCachedProjectData) {
       GradleProjectSyncData syncData = GradleProjectSyncData.getInstance((project));
       if (syncData != null && syncData.canUseCachedProjectData()) {
         DataNode<ProjectData> cache = getCachedProjectData(project);
-        if (cache != null) {
+        if (cache != null && !isCacheMissingModels(cache, project)) {
           PostProjectSetupTasksExecutor executor = PostProjectSetupTasksExecutor.getInstance(project);
           executor.setGenerateSourcesAfterSync(false);
           executor.setUsingCachedProjectData(true);
           executor.setLastSyncTimestamp(syncData.getLastGradleSyncTimestamp());
 
-          if (!isCacheMissingModels(cache, project)) {
-            ProjectSetUpTask setUpTask = new ProjectSetUpTask(project, newProject, options.importingExistingProject, true, listener);
-            setUpTask.onSuccess(cache);
-            return;
-          }
+          ProjectSetUpTask setUpTask = new ProjectSetUpTask(project, newProject, options.importingExistingProject, true, listener);
+          setUpTask.onSuccess(cache);
+          return;
         }
       }
     }
@@ -561,6 +563,18 @@
     myDelegate.importProject(project, setUpTask, progressExecutionMode);
   }
 
+  private static boolean forceSyncWithCachedModel() {
+    if (SYNC_WITH_CACHED_MODEL_ONLY) {
+      return true;
+    }
+    if (isGuiTestingMode()) {
+      GuiTestSuiteState state = getGuiTestSuiteState();
+      assert state != null;
+      return state.syncWithCachedModelOnly();
+    }
+    return false;
+  }
+
   @NotNull
   private static String getProjectBasePath(Project project) {
     String projectBasePath = toCanonicalPath(project.getBasePath());
@@ -577,11 +591,16 @@
       ModuleManager moduleManager = ModuleManager.getInstance(project);
       for (Module module : moduleManager.getModules()) {
         DataNode<ModuleData> moduleDataNode = moduleDataNodesByName.get(module.getName());
-        if (moduleDataNode != null) {
-          if (isCacheMissingModels(moduleDataNode, module)) {
+        if (moduleDataNode == null) {
+          // When a Gradle facet is present, there should be a cache node for the module.
+          AndroidGradleFacet gradleFacet = AndroidGradleFacet.getInstance(module);
+          if (gradleFacet != null) {
             return true;
           }
         }
+        else if (isCacheMissingModels(moduleDataNode, module)) {
+          return true;
+        }
       }
       return false;
     }
diff --git a/android/src/org/jetbrains/android/AndroidPlugin.java b/android/src/org/jetbrains/android/AndroidPlugin.java
index 51462be..fa62876 100644
--- a/android/src/org/jetbrains/android/AndroidPlugin.java
+++ b/android/src/org/jetbrains/android/AndroidPlugin.java
@@ -67,6 +67,7 @@
   public static class GuiTestSuiteState {
     private boolean myOpenProjectWizardAlreadyTested;
     private boolean myImportProjectWizardAlreadyTested;
+    private boolean myUseCachedGradleModelOnly;
 
     public boolean isOpenProjectWizardAlreadyTested() {
       return myOpenProjectWizardAlreadyTested;
@@ -83,5 +84,13 @@
     public void setImportProjectWizardAlreadyTested(boolean importProjectWizardAlreadyTested) {
       myImportProjectWizardAlreadyTested = importProjectWizardAlreadyTested;
     }
+
+    public boolean syncWithCachedModelOnly() {
+      return myUseCachedGradleModelOnly;
+    }
+
+    public void setUseCachedGradleModelOnly(boolean useCachedGradleModelOnly) {
+      myUseCachedGradleModelOnly = useCachedGradleModelOnly;
+    }
   }
 }