Merge "Fixed bug where settings.gradle file was updated when project validation fails before a sync." into idea133-milestone
diff --git a/android/src/com/android/tools/idea/gradle/GradleSyncState.java b/android/src/com/android/tools/idea/gradle/GradleSyncState.java
index 00193d4..89065bd 100644
--- a/android/src/com/android/tools/idea/gradle/GradleSyncState.java
+++ b/android/src/com/android/tools/idea/gradle/GradleSyncState.java
@@ -17,7 +17,6 @@
 
 import com.android.SdkConstants;
 import com.android.tools.idea.gradle.project.GradleSyncListener;
-import com.android.tools.idea.gradle.project.ProjectValidator;
 import com.android.tools.idea.gradle.util.GradleUtil;
 import com.android.tools.idea.gradle.variant.view.BuildVariantView;
 import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
@@ -99,7 +98,6 @@
       @Override
       public void run() {
         myMessageBus.syncPublisher(GRADLE_SYNC_TOPIC).syncFailed(myProject, message);
-        ProjectValidator.mergeQueuedMessages(myProject);
       }
     });
   }
@@ -115,7 +113,6 @@
       @Override
       public void run() {
         myMessageBus.syncPublisher(GRADLE_SYNC_TOPIC).syncSucceeded(myProject);
-        ProjectValidator.mergeQueuedMessages(myProject);
       }
     });
   }
diff --git a/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java b/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java
index 654ff5b..a06635f 100644
--- a/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java
+++ b/android/src/com/android/tools/idea/gradle/actions/CleanImportProjectAction.java
@@ -77,7 +77,7 @@
         delete(filesToDelete, projectName);
         try {
           LOG.info(String.format("About to import project '%1$s'.", projectName));
-          GradleProjectImporter.getInstance().importNewlyCreatedProject(projectName, projectDir, null);
+          GradleProjectImporter.getInstance().importNewlyCreatedProject(projectName, projectDir, null, null, null);
           LOG.info(String.format("Done importing project '%1$s'.", projectName));
         }
         catch (Exception error) {
diff --git a/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java b/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java
index 8e10009..0b7fd98 100644
--- a/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java
+++ b/android/src/com/android/tools/idea/gradle/eclipse/AdtImportBuilder.java
@@ -180,7 +180,7 @@
       };
       final GradleProjectImporter importer = GradleProjectImporter.getInstance();
       if (myCreateProject) {
-        importer.importProject(project.getName(), destDir, callback, project);
+        importer.importProject(project.getName(), destDir, true, callback, project, null);
       } else {
         importer.requestProjectSync(project, true, callback);
       }
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java b/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java
index d913276..f0d1956 100644
--- a/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleBuildFileUpdater.java
@@ -16,22 +16,25 @@
 package com.android.tools.idea.gradle.project;
 
 import com.android.SdkConstants;
+import com.android.tools.idea.gradle.GradleSyncState;
 import com.android.tools.idea.gradle.facet.AndroidGradleFacet;
 import com.android.tools.idea.gradle.parser.GradleSettingsFile;
 import com.android.tools.idea.gradle.util.GradleUtil;
 import com.google.common.base.Joiner;
-import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.command.WriteCommandAction;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleUtilCore;
 import com.intellij.openapi.project.ModuleAdapter;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
 import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent;
+import com.intellij.psi.PsiFile;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.List;
@@ -41,8 +44,6 @@
  * sees.
  */
 public class GradleBuildFileUpdater extends ModuleAdapter implements BulkFileListener {
-  private static final Logger LOG = Logger.getInstance(GradleBuildFileUpdater.class);
-
   private final Project myProject;
 
   public GradleBuildFileUpdater(@NotNull Project project) {
@@ -51,18 +52,40 @@
 
   @Override
   public void moduleAdded(@NotNull final Project project, @NotNull final Module module) {
-    // The module has probably already been added to the settings file but let's call this to be safe.
-    GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
+    // Don't do anything if we are in the middle of a project sync.
+    if (GradleSyncState.getInstance(project).isSyncInProgress()) {
+      return;
+    }
+    final GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
     if (settingsFile != null) {
-      settingsFile.addModule(module);
+      // if settings.gradle does not have a module, we are in the middle of setting up a project.
+      final PsiFile psiFile = settingsFile.getPsiFile();
+      Module found = ModuleUtilCore.findModuleForPsiElement(psiFile);
+      if (found != null) {
+        new WriteCommandAction<Void>(project, "Update settings.gradle", psiFile) {
+          @Override
+          protected void run(@NotNull Result<Void> result) throws Throwable {
+            settingsFile.addModule(module);
+          }
+        }.execute();
+      }
     }
   }
 
   @Override
   public void moduleRemoved(@NotNull Project project, @NotNull final Module module) {
-    GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
+    // Don't do anything if we are in the middle of a project sync.
+    if (GradleSyncState.getInstance(project).isSyncInProgress()) {
+      return;
+    }
+    final GradleSettingsFile settingsFile = GradleSettingsFile.get(project);
     if (settingsFile != null) {
-      settingsFile.removeModule(module);
+      new WriteCommandAction<Void>(project, "Update settings.gradle", settingsFile.getPsiFile()) {
+        @Override
+        protected void run(@NotNull Result<Void> result) throws Throwable {
+          settingsFile.removeModule(module);
+        }
+      }.execute();
     }
   }
 
@@ -83,7 +106,7 @@
       if (!(event instanceof VFilePropertyChangeEvent)) {
         continue;
       }
-      VFilePropertyChangeEvent propChangeEvent = (VFilePropertyChangeEvent) event;
+      VFilePropertyChangeEvent propChangeEvent = (VFilePropertyChangeEvent)event;
       if (!(VirtualFile.PROP_NAME.equals(propChangeEvent.getPropertyName()))) {
         continue;
       }
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java b/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java
index 9399255..72b886b 100644
--- a/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleModuleImporter.java
@@ -15,21 +15,36 @@
  */
 package com.android.tools.idea.gradle.project;
 
+import com.android.SdkConstants;
+import com.android.tools.idea.gradle.parser.GradleSettingsFile;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Suppliers;
+import com.google.common.base.Throwables;
+import com.google.common.collect.*;
 import com.intellij.ide.util.projectWizard.ModuleWizardStep;
 import com.intellij.ide.util.projectWizard.WizardContext;
+import com.intellij.openapi.command.WriteCommandAction;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.options.ConfigurationException;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.HashSet;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.plugins.gradle.util.GradleConstants;
 
+import java.io.File;
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
+
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.base.Predicates.notNull;
 
 /**
  * Creates new project module from source files with Gradle configuration.
@@ -39,7 +54,6 @@
 
   @Nullable private final Project myProject;
   private final boolean myIsWizard;
-  private final GradleProjectImporter myImporter;
 
   public GradleModuleImporter(@NotNull WizardContext context) {
     this(context.getProject(), true);
@@ -52,7 +66,6 @@
   private GradleModuleImporter(@Nullable Project project, boolean isWizard) {
     myIsWizard = isWizard;
     myProject = project;
-    myImporter = GradleProjectImporter.getInstance();
   }
 
   public static boolean isGradleProject(VirtualFile importSource) {
@@ -73,7 +86,7 @@
   @Override
   public void importProjects(Map<String, VirtualFile> projects) {
     try {
-      myImporter.importModules(projects, myProject, null);
+      importModules(this, projects, myProject, null);
     }
     catch (IOException e) {
       LOG.error(e);
@@ -102,6 +115,200 @@
   @Override
   public Set<ModuleToImport> findModules(VirtualFile importSource) throws IOException {
     assert myProject != null;
-    return myImporter.getRelatedProjects(importSource, myProject);
+    return getRelatedProjects(importSource, myProject);
+  }
+
+  /**
+   * Find related modules that should be imported into Android Studio together with the project user chose so it could be built.
+   * <p/>
+   * Top-level use-cases:
+   * 1. If the user selects top-level project (e.g. the one with settings.gradle) Android Studio will import all its sub-projects.
+   * 2. For leaf projects (ones with build.gradle), Android Studio will import selected project and the projects it depends on.
+   *
+   * @param sourceProject      the destinationProject that user wants to import
+   * @param destinationProject destination destinationProject
+   * @return mapping from module name to {@code VirtualFile} containing module contents. Values will be null if the module location was not
+   * found.
+   */
+  @NotNull
+  public static Set<ModuleToImport> getRelatedProjects(@NotNull VirtualFile sourceProject, @NotNull Project destinationProject) {
+    VirtualFile settingsGradle = sourceProject.findFileByRelativePath(SdkConstants.FN_SETTINGS_GRADLE);
+    if (settingsGradle != null) {
+      return buildModulesSet(getSubProjects(settingsGradle, destinationProject),
+                             GradleProjectDependencyParser.newInstance(destinationProject));
+    }
+    else {
+      return getRequiredProjects(sourceProject, destinationProject);
+    }
+  }
+
+  /**
+   * Find direct and transitive dependency projects.
+   */
+  @NotNull
+  private static Set<ModuleToImport> getRequiredProjects(VirtualFile sourceProject, Project destinationProject) {
+    GradleSiblingLookup subProjectLocations = new GradleSiblingLookup(sourceProject, destinationProject);
+    Function<VirtualFile, Iterable<String>> parser = GradleProjectDependencyParser.newInstance(destinationProject);
+    Map<String, VirtualFile> modules = Maps.newHashMap();
+    List<VirtualFile> toAnalyze = Lists.newLinkedList();
+    toAnalyze.add(sourceProject);
+
+    while (!toAnalyze.isEmpty()) {
+      Set<String> dependencies = Sets.newHashSet(Iterables.concat(Iterables.transform(toAnalyze, parser)));
+      Iterable<String> notAnalyzed = Iterables.filter(dependencies, not(in(modules.keySet())));
+      // Turns out, Maps#toMap does not allow null values...
+      Map<String, VirtualFile> dependencyToLocation = Maps.newHashMap();
+      for (String dependency : notAnalyzed) {
+        dependencyToLocation.put(dependency, subProjectLocations.apply(dependency));
+      }
+      modules.putAll(dependencyToLocation);
+      toAnalyze = FluentIterable.from(dependencyToLocation.values()).filter(notNull()).toList();
+    }
+    modules.put(subProjectLocations.getPrimaryProjectName(), sourceProject);
+    return buildModulesSet(modules, parser);
+  }
+
+  private static Set<ModuleToImport> buildModulesSet(Map<String, VirtualFile> modules, Function<VirtualFile, Iterable<String>> parser) {
+    Set<ModuleToImport> modulesSet = new HashSet<ModuleToImport>(modules.size());
+    for (Map.Entry<String, VirtualFile> entry : modules.entrySet()) {
+      modulesSet
+        .add(new ModuleToImport(entry.getKey(), entry.getValue(), Suppliers.compose(parser, Suppliers.ofInstance(entry.getValue()))));
+    }
+    return modulesSet;
+  }
+
+  @NotNull
+  public static Map<String, VirtualFile> getSubProjects(@NotNull final VirtualFile settingsGradle, Project destinationProject) {
+    final GradleSettingsFile settingsFile = new GradleSettingsFile(settingsGradle, destinationProject);
+    Map<String, File> allProjects = settingsFile.getModulesWithLocation();
+    return Maps.transformValues(allProjects, new ResolvePath(VfsUtilCore.virtualToIoFile(settingsGradle.getParent())));
+  }
+
+  /**
+   * Import set of gradle modules into existing Android Studio project. Note that no validation will be performed, modules will
+   * be copied as is and settings.xml will be updated for the imported modules. It is callers' responsibility to ensure content
+   * can be copied to the target directory and that module list is valid.
+   *
+   * @param modules  mapping between module names and locations on the filesystem. Neither name nor location should be null
+   * @param project  project to import the modules to
+   * @param listener optional object that gets notified of operation success or failure
+   */
+  @VisibleForTesting
+  static void importModules(@NotNull final Object requestor,
+                            @NotNull final Map<String, VirtualFile> modules,
+                            @Nullable final Project project,
+                            @Nullable final GradleSyncListener listener) throws IOException, ConfigurationException {
+    String error = validateProjectsForImport(modules);
+    if (error != null) {
+      if (listener != null && project != null) {
+        listener.syncFailed(project, error);
+        return;
+      }
+      else {
+        throw new IOException(error);
+      }
+    }
+
+    assert project != null;
+    Throwable throwable = new WriteCommandAction.Simple(project) {
+      @Override
+      protected void run() throws Throwable {
+        copyAndRegisterModule(requestor, modules, project, listener);
+      }
+
+      @Override
+      public boolean isSilentExecution() {
+        return true;
+      }
+    }.execute().getThrowable();
+    rethrowAsProperlyTypedException(throwable);
+  }
+
+  /**
+   * Ensures that we know paths for all projects we are trying to import.
+   *
+   * @return message string if import is not possible or <code>null</code> otherwise
+   */
+  @Nullable
+  private static String validateProjectsForImport(@NotNull Map<String, VirtualFile> modules) {
+    Set<String> projects = new TreeSet<String>();
+    for (Map.Entry<String, VirtualFile> mapping : modules.entrySet()) {
+      if (mapping.getValue() == null) {
+        projects.add(mapping.getKey());
+      }
+    }
+    if (projects.isEmpty()) {
+      return null;
+    }
+    else if (projects.size() == 1) {
+      return String.format("Sources for module '%1$s' were not found", Iterables.getFirst(projects, null));
+    }
+    else {
+      String projectsList = Joiner.on("', '").join(projects);
+      return String.format("Sources were not found for modules '%1$s'", projectsList);
+    }
+  }
+
+  /**
+   * Recover actual type of the exception.
+   */
+  private static void rethrowAsProperlyTypedException(Throwable throwable) throws IOException, ConfigurationException {
+    if (throwable != null) {
+      Throwables.propagateIfPossible(throwable, IOException.class, ConfigurationException.class);
+      throw new IllegalStateException(throwable);
+    }
+  }
+
+  /**
+   * Copy modules and adds it to settings.gradle
+   */
+  private static void copyAndRegisterModule(@NotNull Object requestor,
+                                            @NotNull Map<String, VirtualFile> modules,
+                                            @NotNull Project project,
+                                            @Nullable GradleSyncListener listener) throws IOException, ConfigurationException {
+    VirtualFile projectRoot = project.getBaseDir();
+    if (projectRoot.findChild(SdkConstants.FN_SETTINGS_GRADLE) == null) {
+      projectRoot.createChildData(requestor, SdkConstants.FN_SETTINGS_GRADLE);
+    }
+    GradleSettingsFile gradleSettingsFile = GradleSettingsFile.get(project);
+    assert gradleSettingsFile != null : "File should have been created";
+    for (Map.Entry<String, VirtualFile> module : modules.entrySet()) {
+      String name = module.getKey();
+      File targetFile = GradleUtil.getDefaultSubprojectLocation(projectRoot, name);
+      VirtualFile moduleSource = module.getValue();
+      if (moduleSource != null) {
+        if (!VfsUtilCore.isAncestor(projectRoot, moduleSource, true)) {
+          VirtualFile target = VfsUtil.createDirectoryIfMissing(targetFile.getAbsolutePath());
+          if (target == null) {
+            throw new IOException(String.format("Unable to create directory %1$s", targetFile));
+          }
+          moduleSource.copy(requestor, target.getParent(), target.getName());
+        }
+        else {
+          targetFile = VfsUtilCore.virtualToIoFile(moduleSource);
+        }
+      }
+      gradleSettingsFile.addModule(name, targetFile);
+    }
+    GradleProjectImporter.getInstance().requestProjectSync(project, false, listener);
+  }
+
+  /**
+   * Resolves paths that may be either relative to provided directory or absolute.
+   */
+  private static class ResolvePath implements Function<File, VirtualFile> {
+    private final File mySourceDir;
+
+    public ResolvePath(File sourceDir) {
+      mySourceDir = sourceDir;
+    }
+
+    @Override
+    public VirtualFile apply(File path) {
+      if (!path.isAbsolute()) {
+        path = new File(mySourceDir, path.getPath());
+      }
+      return VfsUtil.findFileByIoFile(path, true);
+    }
   }
 }
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 928a225..e46b0a4 100755
--- a/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleProjectImporter.java
@@ -18,22 +18,16 @@
 import com.android.SdkConstants;
 import com.android.tools.idea.gradle.GradleSyncState;
 import com.android.tools.idea.gradle.invoker.GradleInvoker;
-import com.android.tools.idea.gradle.parser.GradleSettingsFile;
 import com.android.tools.idea.gradle.util.FilePaths;
 import com.android.tools.idea.gradle.util.GradleUtil;
 import com.android.tools.idea.gradle.util.LocalProperties;
 import com.android.tools.idea.gradle.util.Projects;
 import com.android.tools.idea.startup.AndroidStudioSpecificInitializer;
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Suppliers;
-import com.google.common.base.Throwables;
-import com.google.common.collect.*;
+import com.google.common.collect.ImmutableList;
 import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.command.CommandProcessor;
-import com.intellij.openapi.command.WriteCommandAction;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.externalSystem.model.DataNode;
@@ -66,13 +60,11 @@
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.io.FileUtilRt;
 import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VfsUtilCore;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.pom.java.LanguageLevel;
 import com.intellij.ui.AppUIUtil;
 import com.intellij.util.SystemProperties;
-import com.intellij.util.containers.HashSet;
 import com.intellij.util.ui.UIUtil;
 import org.jetbrains.android.facet.AndroidFacet;
 import org.jetbrains.annotations.NotNull;
@@ -85,10 +77,10 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.util.*;
+import java.util.Collection;
 
-import static com.google.common.base.Predicates.*;
 import static com.intellij.notification.NotificationType.ERROR;
+import static com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode.MODAL_SYNC;
 import static org.jetbrains.plugins.gradle.util.GradleUtil.getLastUsedGradleHome;
 import static org.jetbrains.plugins.gradle.util.GradleUtil.isGradleDefaultWrapperFilesExist;
 
@@ -172,111 +164,6 @@
     createProjectFileForGradleProject(selectedFile, null);
   }
 
-  /**
-   * Import set of gradle modules into existing Android Studio project. Note that no validation will be performed, modules will
-   * be copied as is and settings.xml will be updated for the imported modules. It is callers' responsibility to ensure content
-   * can be copied to the target directory and that module list is valid.
-   *
-   * @param modules  mapping between module names and locations on the filesystem. Neither name nor location should be null
-   * @param project  project to import the modules to
-   * @param listener optional object that gets notified of operation success or failure
-   */
-  public void importModules(@NotNull final Map<String, VirtualFile> modules,
-                            @Nullable final Project project,
-                            @Nullable final GradleSyncListener listener) throws IOException, ConfigurationException {
-    String error = validateProjectsForImport(modules);
-    if (error != null) {
-      if (listener != null && project != null) {
-        listener.syncFailed(project, error);
-        return;
-      }
-      else {
-        throw new IOException(error);
-      }
-    }
-
-    assert project != null;
-    Throwable throwable = new WriteCommandAction.Simple(project) {
-      @Override
-      protected void run() throws Throwable {
-        copyAndRegisterModule(modules, project, listener);
-      }
-
-      @Override
-      public boolean isSilentExecution() {
-        return true;
-      }
-    }.execute().getThrowable();
-    rethrowAsProperlyTypedException(throwable);
-  }
-
-  /**
-   * Ensures that we know paths for all projects we are trying to import.
-   *
-   * @return message string if import is not possible or <code>null</code> otherwise
-   */
-  @Nullable
-  private static String validateProjectsForImport(@NotNull Map<String, VirtualFile> modules) {
-    Set<String> projects = new TreeSet<String>();
-    for (Map.Entry<String, VirtualFile> mapping : modules.entrySet()) {
-      if (mapping.getValue() == null) {
-        projects.add(mapping.getKey());
-      }
-    }
-    if (projects.isEmpty()) {
-      return null;
-    }
-    else if (projects.size() == 1) {
-      return String.format("Sources for module '%1$s' were not found", Iterables.getFirst(projects, null));
-    }
-    else {
-      String projectsList = Joiner.on("', '").join(projects);
-      return String.format("Sources were not found for modules '%1$s'", projectsList);
-    }
-  }
-
-  /**
-   * Recover actual type of the exception.
-   */
-  private static void rethrowAsProperlyTypedException(Throwable throwable) throws IOException, ConfigurationException {
-    if (throwable != null) {
-      Throwables.propagateIfPossible(throwable, IOException.class, ConfigurationException.class);
-      throw new IllegalStateException(throwable);
-    }
-  }
-
-  /**
-   * Copy modules and adds it to settings.gradle
-   */
-  private void copyAndRegisterModule(@NotNull Map<String, VirtualFile> modules,
-                                     @NotNull Project project,
-                                     @Nullable GradleSyncListener listener) throws IOException, ConfigurationException {
-    VirtualFile projectRoot = project.getBaseDir();
-    if (projectRoot.findChild(SdkConstants.FN_SETTINGS_GRADLE) == null) {
-      projectRoot.createChildData(this, SdkConstants.FN_SETTINGS_GRADLE);
-    }
-    GradleSettingsFile gradleSettingsFile = GradleSettingsFile.get(project);
-    assert gradleSettingsFile != null : "File should have been created";
-    for (Map.Entry<String, VirtualFile> module : modules.entrySet()) {
-      String name = module.getKey();
-      File targetFile = GradleUtil.getDefaultSubprojectLocation(projectRoot, name);
-      VirtualFile moduleSource = module.getValue();
-      if (moduleSource != null) {
-        if (!VfsUtilCore.isAncestor(projectRoot, moduleSource, true)) {
-          VirtualFile target = VfsUtil.createDirectoryIfMissing(targetFile.getAbsolutePath());
-          if (target == null) {
-            throw new IOException(String.format("Unable to create directory %1$s", targetFile));
-          }
-          moduleSource.copy(this, target.getParent(), target.getName());
-        }
-        else {
-          targetFile = VfsUtilCore.virtualToIoFile(moduleSource);
-        }
-      }
-      gradleSettingsFile.addModule(name, targetFile);
-    }
-    requestProjectSync(project, false, listener);
-  }
 
   /**
    * Creates IntelliJ project file in the root of the project directory.
@@ -288,12 +175,12 @@
     VirtualFile projectDir = selectedFile.isDirectory() ? selectedFile : selectedFile.getParent();
     File projectDirPath = VfsUtilCore.virtualToIoFile(projectDir);
     try {
-      importProject(projectDir.getName(), projectDirPath, new NewProjectImportGradleSyncListener() {
+      importProject(projectDir.getName(), projectDirPath, true, new NewProjectImportGradleSyncListener() {
         @Override
         public void syncSucceeded(@NotNull Project project) {
           activateProjectView(project);
         }
-      }, parentProject);
+      }, parentProject, null);
     }
     catch (Exception e) {
       if (ApplicationManager.getApplication().isUnitTestMode()) {
@@ -347,7 +234,7 @@
       @Override
       public void run() {
         try {
-          doRequestSync(project, ProgressExecutionMode.MODAL_SYNC, generateSourcesOnSuccess, listener);
+          doRequestSync(project, MODAL_SYNC, generateSourcesOnSuccess, listener);
         }
         catch (ConfigurationException e) {
           Messages.showErrorDialog(project, e.getMessage(), e.getTitle());
@@ -365,7 +252,7 @@
       FileDocumentManager.getInstance().saveAllDocuments();
       setUpGradleSettings(project);
       resetProject(project);
-      doImport(project, true /* validate */, false /* existing project */, progressExecutionMode, generateSourcesOnSuccess, listener);
+      doImport(project, false /* existing project */, progressExecutionMode, generateSourcesOnSuccess, listener);
     }
     else {
       Runnable notificationTask = new Runnable() {
@@ -428,32 +315,22 @@
    * Imports and opens an Android project that has been created with the "New Project" wizard. This method does not perform any project
    * validation before importing the project (assuming that the wizard properly created the new project.)
    *
-   * @param projectName    name of the project.
-   * @param projectRootDir root directory of the project.
-   * @param listener       called after the project has been imported.
+   * @param projectName          name of the project.
+   * @param projectRootDir       root directory of the project.
+   * @param listener             called after the project has been imported.
+   * @param project              the given project. This method does nothing if the project is not an Android-Gradle project.
+   * @param initialLanguageLevel when creating a new project, sets the language level to the given version early on (this is because you
+   *                             cannot set a language level later on in the process without telling the user that the language level
+   *                             has changed and to re-open the project)
    * @throws IOException            if any file I/O operation fails (e.g. creating the '.idea' directory.)
    * @throws ConfigurationException if any required configuration option is missing (e.g. Gradle home directory path.)
    */
-  public void importNewlyCreatedProject(@NotNull String projectName, @NotNull File projectRootDir, @Nullable GradleSyncListener listener)
-    throws IOException, ConfigurationException {
-    doImport(projectName, projectRootDir, false /* do not validate */, true, listener, null, null);
-  }
-
-  /**
-   * Imports and opens an Android project.
-   *
-   * @param projectName    name of the project.
-   * @param projectRootDir root directory of the project.
-   * @param listener       called after the project has been imported.
-   * @param project        the given project. This method does nothing if the project is not an Android-Gradle project.
-   * @throws IOException            if any file I/O operation fails (e.g. creating the '.idea' directory.)
-   * @throws ConfigurationException if any required configuration option is missing (e.g. Gradle home directory path.)
-   */
-  public void importProject(@NotNull String projectName,
-                            @NotNull File projectRootDir,
-                            @Nullable GradleSyncListener listener,
-                            @Nullable Project project) throws IOException, ConfigurationException {
-    importProject(projectName, projectRootDir, true, listener, project, null);
+  public void importNewlyCreatedProject(@NotNull String projectName,
+                                        @NotNull File projectRootDir,
+                                        @Nullable GradleSyncListener listener,
+                                        @Nullable Project project,
+                                        @Nullable LanguageLevel initialLanguageLevel) throws IOException, ConfigurationException {
+    doImport(projectName, projectRootDir, true, listener, project, initialLanguageLevel);
   }
 
   /**
@@ -476,12 +353,11 @@
                             @Nullable GradleSyncListener listener,
                             @Nullable Project project,
                             @Nullable LanguageLevel initialLanguageLevel) throws IOException, ConfigurationException {
-    doImport(projectName, projectRootDir, true, generateSourcesOnSuccess, listener, project, initialLanguageLevel);
+    doImport(projectName, projectRootDir, generateSourcesOnSuccess, listener, project, initialLanguageLevel);
   }
 
   private void doImport(@NotNull String projectName,
                         @NotNull File projectRootDir,
-                        boolean validateBeforeImporting,
                         boolean generateSourcesOnSuccess,
                         @Nullable GradleSyncListener listener,
                         @Nullable Project project,
@@ -496,8 +372,7 @@
       newProject.save();
     }
 
-    doImport(newProject, validateBeforeImporting, true /* new project */, ProgressExecutionMode.MODAL_SYNC /* synchronous import */,
-             generateSourcesOnSuccess, listener);
+    doImport(newProject, true /* new project */, MODAL_SYNC /* synchronous import */, generateSourcesOnSuccess, listener);
   }
 
   private static void createTopLevelBuildFileIfNotExisting(@NotNull File projectRootDir) throws IOException {
@@ -599,20 +474,11 @@
   }
 
   private void doImport(@NotNull final Project project,
-                        boolean validateBeforeImporting,
                         final boolean newProject,
                         @NotNull final ProgressExecutionMode progressExecutionMode,
                         boolean generateSourcesOnSuccess,
                         @Nullable final GradleSyncListener listener) throws ConfigurationException {
-    // temporarily disable to check whether this is the cause for broken tests
-    if (validateBeforeImporting && !ApplicationManager.getApplication().isUnitTestMode()) {
-      if (!ProjectValidator.validate(project, new File(project.getBasePath()))) {
-        // The project failed validation. Bail out on Gradle import, but create a top-level module so that the entire project directory
-        // contents will show up in the project window and the user can edit files to fix the validation problems.
-        NewProjectImportGradleSyncListener.createTopLevelProjectAndOpen(project);
-        return;
-      }
-    }
+    PreSyncChecks.check(project);
 
     if (AndroidStudioSpecificInitializer.isAndroidStudio() && Projects.isDirectGradleInvocationEnabled(project)) {
       // We cannot do the same when using JPS. We don't have access to the contents of the Message view used by JPS.
@@ -710,70 +576,6 @@
     });
   }
 
-  /**
-   * Find related modules that should be imported into Android Studio together with the project user chose so it could be built.
-   * <p/>
-   * Top-level use-cases:
-   * 1. If the user selects top-level project (e.g. the one with settings.gradle) Android Studio will import all its subprojects.
-   * 2. For leaf projects (ones with build.gradle), Android Studio will import selected project and the projects it depends on.
-   *
-   * @param sourceProject      the destinationProject that user wants to import
-   * @param destinationProject destination destinationProject
-   * @return mapping from module name to {@link com.intellij.openapi.vfs.VirtualFile} containing module contents. Values will be null
-   * if the module location was not found.
-   */
-  @NotNull
-  public Set<ModuleToImport> getRelatedProjects(@NotNull VirtualFile sourceProject, @NotNull Project destinationProject) {
-    VirtualFile settingsGradle = sourceProject.findFileByRelativePath(SdkConstants.FN_SETTINGS_GRADLE);
-    if (settingsGradle != null) {
-      return buildModulesSet(getSubprojects(settingsGradle, destinationProject),
-                             GradleProjectDependencyParser.newInstance(destinationProject));
-    }
-    else {
-      return getRequiredProjects(sourceProject, destinationProject);
-    }
-  }
-
-  /**
-   * Find direct and transitive dependency projects.
-   */
-  @NotNull
-  private static Set<ModuleToImport> getRequiredProjects(VirtualFile sourceProject, Project destinationProject) {
-    GradleSiblingLookup subprojectLocations = new GradleSiblingLookup(sourceProject, destinationProject);
-    Function<VirtualFile, Iterable<String>> parser = GradleProjectDependencyParser.newInstance(destinationProject);
-    Map<String, VirtualFile> modules = Maps.newHashMap();
-    List<VirtualFile> toAnalyze = Lists.newLinkedList();
-    toAnalyze.add(sourceProject);
-
-    while (!toAnalyze.isEmpty()) {
-      Set<String> dependencies = Sets.newHashSet(Iterables.concat(Iterables.transform(toAnalyze, parser)));
-      Iterable<String> notAnalyzed = Iterables.filter(dependencies, not(in(modules.keySet())));
-      // Turns out, Maps#toMap does not allow null values...
-      Map<String, VirtualFile> dependencyToLocation = Maps.newHashMap();
-      for (String dependency : notAnalyzed) {
-        dependencyToLocation.put(dependency, subprojectLocations.apply(dependency));
-      }
-      modules.putAll(dependencyToLocation);
-      toAnalyze = FluentIterable.from(dependencyToLocation.values()).filter(notNull()).toList();
-    }
-    modules.put(subprojectLocations.getPrimaryProjectName(), sourceProject);
-    return buildModulesSet(modules, parser);
-  }
-
-  private static Set<ModuleToImport> buildModulesSet(Map<String, VirtualFile> modules, Function<VirtualFile, Iterable<String>> parser) {
-    Set<ModuleToImport> modulesSet = new HashSet<ModuleToImport>(modules.size());
-    for (Map.Entry<String, VirtualFile> entry : modules.entrySet()) {
-      modulesSet.add(new ModuleToImport(entry.getKey(), entry.getValue(), Suppliers.compose(parser, Suppliers.ofInstance(entry.getValue()))));
-    }
-    return modulesSet;
-  }
-
-  @NotNull
-  public static Map<String, VirtualFile> getSubprojects(@NotNull final VirtualFile settingsGradle, Project destinationProject) {
-    final GradleSettingsFile settingsFile = new GradleSettingsFile(settingsGradle, destinationProject);
-    Map<String, File> allProjects = settingsFile.getModulesWithLocation();
-    return Maps.transformValues(allProjects, new ResolvePath(VfsUtilCore.virtualToIoFile(settingsGradle.getParent())));
-  }
 
   // Makes it possible to mock invocations to the Gradle Tooling API.
   static class ImporterDelegate {
@@ -791,23 +593,4 @@
       }
     }
   }
-
-  /**
-   * Functor that resolves paths that may be either relative to provided directory or absolute.
-   */
-  private static class ResolvePath implements Function<File, VirtualFile> {
-    private final File mySourceDir;
-
-    public ResolvePath(File sourceDir) {
-      mySourceDir = sourceDir;
-    }
-
-    @Override
-    public VirtualFile apply(File path) {
-      if (!path.isAbsolute()) {
-        path = new File(mySourceDir, path.getPath());
-      }
-      return VfsUtil.findFileByIoFile(path, true);
-    }
-  }
 }
diff --git a/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java b/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java
index d775193..297e0f7 100644
--- a/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java
+++ b/android/src/com/android/tools/idea/gradle/project/GradleSiblingLookup.java
@@ -58,7 +58,7 @@
         return findSiblings(directory.getParent(), project, seen);
       }
       else {
-        return GradleProjectImporter.getSubprojects(settings, project);
+        return GradleModuleImporter.getSubProjects(settings, project);
       }
     }
   }
diff --git a/android/src/com/android/tools/idea/gradle/project/PreSyncChecks.java b/android/src/com/android/tools/idea/gradle/project/PreSyncChecks.java
new file mode 100644
index 0000000..8b20072
--- /dev/null
+++ b/android/src/com/android/tools/idea/gradle/project/PreSyncChecks.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.gradle.project;
+
+import com.android.SdkConstants;
+import com.android.sdklib.repository.FullRevision;
+import com.android.tools.idea.gradle.util.GradleUtil;
+import com.android.tools.idea.gradle.util.PropertiesUtil;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Processor;
+import org.gradle.wrapper.WrapperExecutor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.gradle.settings.DistributionType;
+import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class PreSyncChecks {
+  private static final Pattern ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN = Pattern.compile("['\"]com.android.tools.build:gradle:(.+)['\"]");
+  private static final Pattern GRADLE_DISTRIBUTION_URL_PATTERN =
+    Pattern.compile("http://services\\.gradle\\.org/distributions/gradle-(.+)-(.+)\\.zip");
+
+  private static final Logger LOG = Logger.getInstance(PreSyncChecks.class);
+
+  private static final String MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13 = "2.0";
+  private static final FullRevision MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13 =
+    FullRevision.parseRevision(MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13);
+
+  private PreSyncChecks() {
+  }
+
+  static void check(@NotNull Project project) {
+    VirtualFile baseDir = project.getBaseDir();
+    if (baseDir == null) {
+      return;
+    }
+    final List<File> filesToProcess = Lists.newArrayList();
+    VfsUtil.processFileRecursivelyWithoutIgnored(baseDir, new Processor<VirtualFile>() {
+      @Override
+      public boolean process(VirtualFile virtualFile) {
+        if (SdkConstants.FN_BUILD_GRADLE.equals(virtualFile.getName())) {
+          filesToProcess.add(VfsUtilCore.virtualToIoFile(virtualFile));
+        }
+        return true;
+      }
+    });
+
+    attemptToUpdateGradleVersionIfApplicable(project, filesToProcess);
+  }
+
+  private static void attemptToUpdateGradleVersionIfApplicable(@NotNull Project project, @NotNull List<File> gradleFiles) {
+    String originalPluginVersion = null;
+    for (File fileToCheck : gradleFiles) {
+      if (SdkConstants.FN_BUILD_GRADLE.equals(fileToCheck.getName())) {
+        try {
+          String contents = Files.toString(fileToCheck, Charsets.UTF_8);
+          Matcher matcher = ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN.matcher(contents);
+          if (matcher.find()) {
+            originalPluginVersion = matcher.group(1);
+            if (!StringUtil.isEmpty(originalPluginVersion)) {
+              break;
+            }
+          }
+        }
+        catch (IOException e) {
+          LOG.warn("Failed to read contents of " + fileToCheck.getPath());
+        }
+      }
+    }
+    if (StringUtil.isEmpty(originalPluginVersion)) {
+      // Could not obtain plug-in version. Continue with sync.
+      return;
+    }
+    String pluginVersion = originalPluginVersion.replace('+', '0');
+    FullRevision pluginRevision = null;
+    try {
+      pluginRevision = FullRevision.parseRevision(pluginVersion);
+    }
+    catch (NumberFormatException e) {
+      LOG.warn("Failed to parse '" + pluginVersion + "'");
+    }
+    if (pluginRevision == null || (pluginRevision.getMajor() == 0 && pluginRevision.getMinor() <= 12)) {
+      // Unable to parse the plug-in version, or the plug-in is version 0.12 or older. Continue with sync.
+      return;
+    }
+
+    GradleProjectSettings gradleSettings = GradleUtil.getGradleProjectSettings(project);
+    File wrapperPropertiesFile = GradleUtil.findWrapperPropertiesFile(project);
+
+    DistributionType distributionType = gradleSettings != null ? gradleSettings.getDistributionType() : null;
+
+    boolean usingWrapper = distributionType == DistributionType.DEFAULT_WRAPPED && wrapperPropertiesFile != null;
+    if (usingWrapper) {
+      attemptToUpdateGradleVersionInWrapper(wrapperPropertiesFile, originalPluginVersion, project);
+    }
+    else if (distributionType == DistributionType.LOCAL) {
+      attemptToUseSupportedLocalGradle(gradleSettings);
+    }
+  }
+
+  private static void attemptToUpdateGradleVersionInWrapper(@NotNull final File wrapperPropertiesFile,
+                                                            @NotNull String pluginVersion,
+                                                            @NotNull Project project) {
+    Properties wrapperProperties = null;
+    try {
+      wrapperProperties = PropertiesUtil.getProperties(wrapperPropertiesFile);
+    }
+    catch (IOException e) {
+      LOG.warn("Failed to read file " + wrapperPropertiesFile.getPath());
+    }
+
+    if (wrapperProperties == null) {
+      // There is a wrapper, but the Gradle version could not be read. Continue with sync.
+      return;
+    }
+    String url = wrapperProperties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY);
+    Matcher matcher = GRADLE_DISTRIBUTION_URL_PATTERN.matcher(url);
+    if (!matcher.matches()) {
+      // Could not get URL of Gradle distribution. Continue with sync.
+      return;
+    }
+    String gradleVersion = matcher.group(1);
+    FullRevision gradleRevision = FullRevision.parseRevision(gradleVersion);
+
+    if (!isSupportedGradleVersion(gradleRevision)) {
+      String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
+      String msg = "Version " + pluginVersion + " of the Android Gradle plug-in requires Gradle " + newGradleVersion + " or newer.\n\n" +
+                   "Click 'OK' to automatically update the Gradle version in the Gradle wrapper and continue.";
+      Messages.showMessageDialog(project, msg, "Gradle Sync", Messages.getQuestionIcon());
+      try {
+        GradleUtil.updateGradleDistributionUrl(newGradleVersion, wrapperPropertiesFile);
+      }
+      catch (IOException e) {
+        LOG.warn("Failed to update Gradle wrapper file to Gradle version " + newGradleVersion);
+      }
+    }
+  }
+
+  private static void attemptToUseSupportedLocalGradle(@NotNull GradleProjectSettings gradleSettings) {
+    String gradleHome = gradleSettings.getGradleHome();
+    if (StringUtil.isEmpty(gradleHome)) {
+      // Unable to obtain the path of the Gradle local installation. Continue with sync.
+      return;
+    }
+    File gradleHomePath = new File(gradleHome);
+    FullRevision gradleVersion = GradleUtil.getGradleVersion(gradleHomePath);
+
+    if (gradleVersion == null) {
+      // Unable to obtain the path of the Gradle local installation. Continue with sync.
+      return;
+    }
+
+    if (!isSupportedGradleVersion(gradleVersion)) {
+      String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
+      ChooseGradleHomeDialog dialog = new ChooseGradleHomeDialog(newGradleVersion);
+      dialog.setTitle(String.format("Please select the location of a Gradle distribution version %1$s or newer", newGradleVersion));
+      if (dialog.showAndGet()) {
+        String enteredGradleHomePath = dialog.getEnteredGradleHomePath();
+        gradleSettings.setGradleHome(enteredGradleHomePath);
+      }
+    }
+  }
+
+  private static boolean isSupportedGradleVersion(@NotNull FullRevision gradleVersion) {
+    // Plug-in v0.13.+ supports Gradle 2.0+ only.
+    return gradleVersion.compareTo(MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13) >= 0;
+  }
+}
diff --git a/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java b/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
deleted file mode 100644
index 381f5a2..0000000
--- a/android/src/com/android/tools/idea/gradle/project/ProjectValidator.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tools.idea.gradle.project;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.sdklib.repository.FullRevision;
-import com.android.tools.idea.gradle.messages.Message;
-import com.android.tools.idea.gradle.messages.ProjectSyncMessages;
-import com.android.tools.idea.gradle.util.GradleUtil;
-import com.android.tools.idea.gradle.util.PropertiesUtil;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.client.api.LintRequest;
-import com.android.tools.lint.detector.api.*;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.io.Files;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VfsUtil;
-import com.intellij.openapi.vfs.VfsUtilCore;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiDocumentManager;
-import com.intellij.psi.PsiFile;
-import com.intellij.psi.PsiManager;
-import com.intellij.util.Processor;
-import org.gradle.wrapper.WrapperExecutor;
-import org.jetbrains.android.inspections.lint.IntellijLintClient;
-import org.jetbrains.android.inspections.lint.IntellijLintIssueRegistry;
-import org.jetbrains.android.inspections.lint.IntellijLintRequest;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.plugins.gradle.settings.DistributionType;
-import org.jetbrains.plugins.gradle.settings.GradleProjectSettings;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.Properties;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class ProjectValidator {
-  private static final Pattern ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN = Pattern.compile("['\"]com.android.tools.build:gradle:(.+)['\"]");
-  private static final Pattern GRADLE_DISTRIBUTION_URL_PATTERN =
-    Pattern.compile("http://services\\.gradle\\.org/distributions/gradle-(.+)-(.+)\\.zip");
-
-  private static final Logger LOG = Logger.getInstance(ProjectValidator.class);
-
-  public static final Key<List<Message>> VALIDATION_MESSAGES = Key.create("gradle.validation.messages");
-
-  private static final Set<String> FILES_TO_PROCESS =
-    ImmutableSet.of(SdkConstants.FN_SETTINGS_GRADLE, SdkConstants.FN_BUILD_GRADLE, SdkConstants.FN_LOCAL_PROPERTIES,
-                    SdkConstants.FN_GRADLE_WRAPPER_PROPERTIES);
-
-  private static final String MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13 = "2.0";
-  private static final FullRevision MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13 =
-    FullRevision.parseRevision(MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13);
-
-  private ProjectValidator() {
-  }
-
-  /**
-   * Runs lint checks on all the Gradle files found underneath the given root directory and reports messages to the
-   * {@link ProjectSyncMessages} message window. Returns true if there are no significant problems, or false if there are critical errors.
-   * Any errors or warnings that are generated are saved, and are send to the Messages window upon a subsequent call to the
-   * {@link #mergeQueuedMessages(com.intellij.openapi.project.Project)} method.
-   */
-  public static boolean validate(@NotNull Project project, @NotNull File rootDir) {
-    VirtualFile file = VfsUtil.findFileByIoFile(rootDir, true);
-    if (file == null) {
-      return false;
-    }
-    VirtualFile rootDirectory = file.isDirectory() ? file : file.getParent();
-
-    final List<File> filesToProcess = Lists.newArrayList();
-    VfsUtil.processFileRecursivelyWithoutIgnored(rootDirectory, new Processor<VirtualFile>() {
-      @Override
-      public boolean process(VirtualFile virtualFile) {
-        if (FILES_TO_PROCESS.contains(virtualFile.getName().toLowerCase())) {
-          filesToProcess.add(VfsUtilCore.virtualToIoFile(virtualFile));
-        }
-        return true;
-      }
-    });
-
-    attemptToUpdateGradleVersionIfApplicable(project, filesToProcess);
-
-    if (project.isInitialized()) {
-      // Lint requires the project to be initialized.
-      MyLintClient lintClient = new MyLintClient(project);
-      ImmutableList<Module> modules = ImmutableList.of();
-      LintRequest request = new IntellijLintRequest(lintClient, project, null, modules, false) {
-        @NonNull
-        @Override
-        public List<File> getFiles() {
-          return filesToProcess;
-        }
-      };
-      LintDriver lintDriver = new LintDriver(new IntellijLintIssueRegistry(), lintClient);
-      lintDriver.analyze(request);
-      return !lintClient.hasFatalError();
-    }
-
-    return true;
-  }
-
-  private static void attemptToUpdateGradleVersionIfApplicable(@NotNull Project project, @NotNull List<File> gradleFiles) {
-    String originalPluginVersion = null;
-    for (File fileToCheck : gradleFiles) {
-      if (SdkConstants.FN_BUILD_GRADLE.equals(fileToCheck.getName())) {
-        try {
-          String contents = Files.toString(fileToCheck, Charsets.UTF_8);
-          Matcher matcher = ANDROID_GRADLE_PLUGIN_DEPENDENCY_PATTERN.matcher(contents);
-          if (matcher.find()) {
-            originalPluginVersion = matcher.group(1);
-            if (!StringUtil.isEmpty(originalPluginVersion)) {
-              break;
-            }
-          }
-        }
-        catch (IOException e) {
-          LOG.warn("Failed to read contents of " + fileToCheck.getPath());
-        }
-      }
-    }
-    if (StringUtil.isEmpty(originalPluginVersion)) {
-      // Could not obtain plug-in version. Continue with sync.
-      return;
-    }
-    String pluginVersion = originalPluginVersion.replace('+', '0');
-    FullRevision pluginRevision = null;
-    try {
-      pluginRevision = FullRevision.parseRevision(pluginVersion);
-    }
-    catch (NumberFormatException e) {
-      LOG.warn("Failed to parse '" + pluginVersion + "'");
-    }
-    if (pluginRevision == null || (pluginRevision.getMajor() == 0 && pluginRevision.getMinor() <= 12)) {
-      // Unable to parse the plug-in version, or the plug-in is version 0.12 or older. Continue with sync.
-      return;
-    }
-
-    GradleProjectSettings gradleSettings = GradleUtil.getGradleProjectSettings(project);
-    File wrapperPropertiesFile = GradleUtil.findWrapperPropertiesFile(project);
-
-    DistributionType distributionType = gradleSettings != null ? gradleSettings.getDistributionType() : null;
-
-    boolean usingWrapper = distributionType == DistributionType.DEFAULT_WRAPPED && wrapperPropertiesFile != null;
-    if (usingWrapper) {
-      attemptToUpdateGradleVersionInWrapper(wrapperPropertiesFile, originalPluginVersion, project);
-    }
-    else if (distributionType == DistributionType.LOCAL) {
-      attemptToUseSupportedLocalGradle(gradleSettings);
-    }
-  }
-
-  private static void attemptToUpdateGradleVersionInWrapper(@NotNull final File wrapperPropertiesFile,
-                                                            @NotNull String pluginVersion,
-                                                            @NotNull Project project) {
-    Properties wrapperProperties = null;
-    try {
-      wrapperProperties = PropertiesUtil.getProperties(wrapperPropertiesFile);
-    }
-    catch (IOException e) {
-      LOG.warn("Failed to read file " + wrapperPropertiesFile.getPath());
-    }
-
-    if (wrapperProperties == null) {
-      // There is a wrapper, but the Gradle version could not be read. Continue with sync.
-      return;
-    }
-    String url = wrapperProperties.getProperty(WrapperExecutor.DISTRIBUTION_URL_PROPERTY);
-    Matcher matcher = GRADLE_DISTRIBUTION_URL_PATTERN.matcher(url);
-    if (!matcher.matches()) {
-      // Could not get URL of Gradle distribution. Continue with sync.
-      return;
-    }
-    String gradleVersion = matcher.group(1);
-    FullRevision gradleRevision = FullRevision.parseRevision(gradleVersion);
-
-    if (!isSupportedGradleVersion(gradleRevision)) {
-      String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
-      String msg = "Version " + pluginVersion + " of the Android Gradle plug-in requires Gradle " + newGradleVersion + " or newer.\n\n" +
-                   "Click 'OK' to automatically update the Gradle version in the Gradle wrapper and continue.";
-      Messages.showMessageDialog(project, msg, "Gradle Sync", Messages.getQuestionIcon());
-      try {
-        GradleUtil.updateGradleDistributionUrl(newGradleVersion, wrapperPropertiesFile);
-      }
-      catch (IOException e) {
-        LOG.warn("Failed to update Gradle wrapper file to Gradle version " + newGradleVersion);
-      }
-    }
-  }
-
-  private static void attemptToUseSupportedLocalGradle(@NotNull GradleProjectSettings gradleSettings) {
-    String gradleHome = gradleSettings.getGradleHome();
-    if (StringUtil.isEmpty(gradleHome)) {
-      // Unable to obtain the path of the Gradle local installation. Continue with sync.
-      return;
-    }
-    File gradleHomePath = new File(gradleHome);
-    FullRevision gradleVersion = GradleUtil.getGradleVersion(gradleHomePath);
-
-    if (gradleVersion == null) {
-      // Unable to obtain the path of the Gradle local installation. Continue with sync.
-      return;
-    }
-
-    if (!isSupportedGradleVersion(gradleVersion)) {
-      String newGradleVersion = MINIMUM_SUPPORTED_GRADLE_VERSION_FOR_PLUGIN_0_13;
-      ChooseGradleHomeDialog dialog = new ChooseGradleHomeDialog(newGradleVersion);
-      dialog.setTitle(String.format("Please select the location of a Gradle distribution version %1$s or newer", newGradleVersion));
-      if (dialog.showAndGet()) {
-        String enteredGradleHomePath = dialog.getEnteredGradleHomePath();
-        gradleSettings.setGradleHome(enteredGradleHomePath);
-      }
-    }
-  }
-
-  private static boolean isSupportedGradleVersion(@NotNull FullRevision gradleVersion) {
-    // Plug-in v0.13.+ supports Gradle 2.0+ only.
-    return gradleVersion.compareTo(MINIMUM_SUPPORTED_GRADLE_REVISION_FOR_PLUGIN_0_13) >= 0;
-  }
-
-  /**
-   * Takes any accumulated validation errors that have been stored with the project and shows them in the Messages tool window.
-   */
-  public static void mergeQueuedMessages(@NotNull Project project) {
-    List<Message> messages = project.getUserData(VALIDATION_MESSAGES);
-    if (messages == null) {
-      return;
-    }
-    ProjectSyncMessages projectSyncMessages = ProjectSyncMessages.getInstance(project);
-    for (Message message : messages) {
-      projectSyncMessages.add(message);
-    }
-    project.putUserData(VALIDATION_MESSAGES, null);
-  }
-
-  private static class MyLintClient extends IntellijLintClient {
-    private static final String GROUP_NAME = "Project import";
-    private boolean myFatalError = false;
-    private final List<Message> myMessages = Lists.newArrayList();
-
-    protected MyLintClient(@NonNull Project project) {
-      super(project);
-      project.putUserData(VALIDATION_MESSAGES, myMessages);
-    }
-
-    @Override
-    public void report(@NonNull Context context,
-                       @NonNull Issue issue,
-                       @NonNull Severity severity,
-                       @Nullable Location location,
-                       @NonNull String message,
-                       @Nullable Object data) {
-      myFatalError |= severity.compareTo(Severity.ERROR) <= 0;
-
-      File file = location != null ? location.getFile() : null;
-      VirtualFile virtualFile = file != null ? LocalFileSystem.getInstance().findFileByIoFile(file) : null;
-      Position start = location != null ? location.getStart() : null;
-      Message.Type type = convertSeverity(severity);
-      if (virtualFile != null && start != null) {
-        try {
-          int line = start.getLine();
-          int column = start.getColumn();
-          int offset = start.getOffset();
-          if (line == -1 && offset >= 0) {
-            PsiManager psiManager = PsiManager.getInstance(myProject);
-            PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(myProject);
-            PsiFile psiFile = psiManager.findFile(virtualFile);
-            if (psiFile != null) {
-              Document document = psiDocumentManager.getDocument(psiFile);
-              if (document != null) {
-                line = document.getLineNumber(offset);
-                column = offset - document.getLineStartOffset(line);
-              }
-            }
-          }
-          myMessages.add(new Message(myProject, GROUP_NAME, type, virtualFile, line, column, message));
-        }
-        catch (Exception e) {
-          // There are cases where the offset lookup is wrong; e.g.
-          //   java.lang.IndexOutOfBoundsException: Wrong offset: 312. Should be in range: [0, 16]
-          // in this case, just report the message without a location
-          myMessages.add(new Message(GROUP_NAME, type, message));
-        }
-      }
-      else {
-        myMessages.add(new Message(GROUP_NAME, type, message));
-      }
-    }
-
-    @Override
-    public void log(@NonNull Severity severity, @Nullable Throwable exception, @Nullable String format, @Nullable Object... args) {
-      myMessages.add(new Message(GROUP_NAME, convertSeverity(severity), format != null ? String.format(format, args) : ""));
-      if (exception != null) {
-        LOG.warn("Exception occurred during validation of project", exception);
-      }
-    }
-
-    @NonNull
-    private static Message.Type convertSeverity(@NonNull Severity severity) {
-      switch (severity) {
-        case ERROR:
-        case FATAL:
-          return Message.Type.ERROR;
-        case IGNORE:
-          return Message.Type.INFO;
-        default:
-        case INFORMATIONAL:
-          return Message.Type.INFO;
-        case WARNING:
-          return Message.Type.WARNING;
-      }
-    }
-
-    public boolean hasFatalError() {
-      return myFatalError;
-    }
-
-    @NonNull
-    @Override
-    protected List<Issue> getIssues() {
-      return new IntellijLintIssueRegistry().getIssues();
-    }
-
-    @Nullable
-    @Override
-    protected Module getModule() {
-      return null;
-    }
-
-    @Override
-    public boolean isProjectDirectory(@NonNull File dir) {
-      return new File(dir, SdkConstants.FN_BUILD_GRADLE).exists();
-    }
-  }
-}
diff --git a/android/src/com/android/tools/idea/wizard/NewProjectWizard.java b/android/src/com/android/tools/idea/wizard/NewProjectWizard.java
index 3b01d6e..69bd51a 100644
--- a/android/src/com/android/tools/idea/wizard/NewProjectWizard.java
+++ b/android/src/com/android/tools/idea/wizard/NewProjectWizard.java
@@ -186,7 +186,7 @@
         if (version != null) {
           initialLanguageLevel = LanguageLevel.parse(version.toString());
         }
-        projectImporter.importProject(projectName, projectRoot, true, new NewProjectImportGradleSyncListener() {
+        projectImporter.importNewlyCreatedProject(projectName, projectRoot, new NewProjectImportGradleSyncListener() {
           @Override
           public void syncSucceeded(@NotNull final Project project) {
             // Open files -- but wait until the Android facets are available, otherwise for example
diff --git a/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java b/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java
index 90edf80..18973e3 100644
--- a/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java
+++ b/android/src/com/android/tools/idea/wizard/NewProjectWizardDynamic.java
@@ -158,7 +158,7 @@
         private boolean openTemplateFiles(Project project) {
           return TemplateUtils.openEditors(project, myFilesToOpen, true);
         }
-      });
+      }, null, null);
     }
     catch (IOException e) {
       Messages.showErrorDialog(e.getMessage(), ERROR_MSG_TITLE);
diff --git a/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java b/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java
index 22fc357..d6973e1 100644
--- a/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/project/GradleModuleImportTest.java
@@ -216,7 +216,7 @@
    */
   public void testImportSimpleGradleProject() throws IOException, ConfigurationException {
     VirtualFile moduleRoot = createGradleProjectToImport(dir, MODULE_NAME);
-    GradleProjectImporter.getInstance().importModules(Collections.singletonMap(moduleRoot.getName(), moduleRoot), getProject(), null);
+    GradleModuleImporter.importModules(this, Collections.singletonMap(moduleRoot.getName(), moduleRoot), getProject(), null);
     assertModuleImported(getProject(), MODULE_NAME, moduleRoot);
   }
 
@@ -227,14 +227,13 @@
     String[] paths = {module(1), module(2), SAMPLE_PROJECT_PATH};
 
     VirtualFile projectRoot = createProjectWithSubprojects(projectsWithDefaultLocations(paths));
-    GradleProjectImporter importer = GradleProjectImporter.getInstance();
-    Map<String, VirtualFile> toImport = moduleListToMap(importer.getRelatedProjects(projectRoot, getProject()));
+    Map<String, VirtualFile> toImport = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject()));
     assertEquals(paths.length, toImport.size());
     for (String path : paths) {
       assertEquals(projectRoot.findFileByRelativePath(path), toImport.get(pathToGradleName(path)));
     }
 
-    importer.importModules(toImport, getProject(), null);
+    GradleModuleImporter.importModules(this, toImport, getProject(), null);
 
     for (String path : paths) {
       VirtualFile moduleRoot = projectRoot.findFileByRelativePath(path);
@@ -246,17 +245,16 @@
   }
 
   /**
-   * Missing submodule will be on the list but with a <code>null</code> path. It is up to client code to decide what to do with it.
+   * Missing sub-module will be on the list but with a <code>null</code> path. It is up to client code to decide what to do with it.
    */
-  public void testImportSubprojectsWithMissingSubmodule() throws IOException, ConfigurationException {
+  public void testImportSubProjectsWithMissingSubModule() throws IOException, ConfigurationException {
     VirtualFile projectRoot = createProjectWithSubprojects(projectsWithDefaultLocations(module(1)), module(2));
-    GradleProjectImporter importer = GradleProjectImporter.getInstance();
-    Map<String, VirtualFile> toImport = moduleListToMap(importer.getRelatedProjects(projectRoot, getProject()));
+    Map<String, VirtualFile> toImport = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject()));
     assertEquals(2, toImport.size());
     assertModuleRequiredButNotFound(module(2), toImport);
 
     try {
-      importer.importModules(toImport, getProject(), null);
+      GradleModuleImporter.importModules(this, toImport, getProject(), null);
       fail();
     }
     catch (IOException e) {
@@ -267,17 +265,16 @@
   /**
    * Verify discovery of projects with non-default locations
    */
-  public void testImportSubprojectWithCustomLocation() throws IOException, ConfigurationException {
+  public void testImportSubProjectWithCustomLocation() throws IOException, ConfigurationException {
     VirtualFile projectRoot =
       createProjectWithSubprojects(Collections.singletonMap(pathToGradleName(SAMPLE_PROJECT_NAME), SAMPLE_PROJECT_PATH));
-    GradleProjectImporter importer = GradleProjectImporter.getInstance();
-    Map<String, VirtualFile> subprojects = moduleListToMap(importer.getRelatedProjects(projectRoot, getProject()));
-    assertEquals(1, subprojects.size());
+    Map<String, VirtualFile> subProjects = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject()));
+    assertEquals(1, subProjects.size());
     VirtualFile moduleLocation = projectRoot.findFileByRelativePath(SAMPLE_PROJECT_PATH);
     assert moduleLocation != null;
-    assertEquals(moduleLocation, subprojects.get(pathToGradleName(SAMPLE_PROJECT_NAME)));
+    assertEquals(moduleLocation, subProjects.get(pathToGradleName(SAMPLE_PROJECT_NAME)));
 
-    importer.importModules(subprojects, getProject(), null);
+    GradleModuleImporter.importModules(this, subProjects, getProject(), null);
     assertModuleImported(getProject(), SAMPLE_PROJECT_NAME, moduleLocation);
   }
 
@@ -298,7 +295,7 @@
     assert project1 != null && project2 != null : "Something wrong with the setup";
     configureTopLevelProject(dir, Arrays.asList(module(1), module(2)), Collections.<String>emptySet());
 
-    Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project2, getProject()));
+    Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project2, getProject()));
     assertEquals(2, projects.size());
     assertEquals(project1, projects.get(pathToGradleName(module(1))));
     assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -312,7 +309,7 @@
     assert project2 != null : "Something wrong with the setup";
     configureTopLevelProject(dir, Arrays.asList(module(1), module(2)), Collections.<String>emptySet());
 
-    Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project2, getProject()));
+    Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project2, getProject()));
     assertEquals(2, projects.size());
     assertModuleRequiredButNotFound(module(1), projects);
     assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -325,7 +322,7 @@
     VirtualFile module = createGradleProjectToImport(dir, module(1), module(2));
     assert module != null;
 
-    Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(module, getProject()));
+    Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(module, getProject()));
     assertEquals(2, projects.size());
     assertModuleRequiredButNotFound(module(2), projects);
     assertEquals(module, projects.get(pathToGradleName(module(1))));
@@ -340,7 +337,7 @@
     VirtualFile project3 = createGradleProjectToImport(dir, module(3), module(2));
     configureTopLevelProject(dir, Arrays.asList(module(1), module(2), module(3)), Collections.<String>emptySet());
 
-    Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project3, getProject()));
+    Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project3, getProject()));
     assertEquals(3, projects.size());
     assertEquals(project1, projects.get(pathToGradleName(module(1))));
     assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -356,7 +353,7 @@
     VirtualFile project3 = createGradleProjectToImport(dir, module(3), module(2));
     configureTopLevelProject(dir, Arrays.asList(module(1), module(2), module(3)), Collections.<String>emptySet());
 
-    Map<String, VirtualFile> projects = moduleListToMap(GradleProjectImporter.getInstance().getRelatedProjects(project3, getProject()));
+    Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project3, getProject()));
     assertEquals(3, projects.size());
     assertEquals(project1, projects.get(pathToGradleName(module(1))));
     assertEquals(project2, projects.get(pathToGradleName(module(2))));
@@ -385,12 +382,11 @@
         return moduleWithDependency;
       }
     });
-    GradleProjectImporter importer = GradleProjectImporter.getInstance();
-    Map<String, VirtualFile> importInput = moduleListToMap(importer.getRelatedProjects(module, getProject()));
+    Map<String, VirtualFile> importInput = moduleListToMap(GradleModuleImporter.getRelatedProjects(module, getProject()));
     assertEquals(2, importInput.size());
     assertEquals(module, importInput.get(pathToGradleName(module(2))));
 
-    importer.importModules(importInput, getProject(), null);
+    GradleModuleImporter.importModules(this, importInput, getProject(), null);
 
     GradleSettingsFile settingsFile = GradleSettingsFile.get(getProject());
     assertNotNull(settingsFile);
diff --git a/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java b/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java
index 93055c7..e357916 100644
--- a/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java
+++ b/android/testSrc/com/android/tools/idea/gradle/project/GradleProjectImporterTest.java
@@ -87,7 +87,7 @@
 
   public void testImportNewlyCreatedProject() throws Exception {
     MyGradleSyncListener callback = new MyGradleSyncListener();
-    myImporter.importNewlyCreatedProject(myProjectName, myProjectRootDir, callback);
+    myImporter.importNewlyCreatedProject(myProjectName, myProjectRootDir, callback, null, null);
   }
 
   private class MyGradleSyncListener extends GradleSyncListener.Adapter {