@SuppressWarnings("unchecked")
 @Nullable
 public static <T> DataNode<T> find(@NotNull DataNode<?> node, @NotNull Key<T> key) {
   for (DataNode<?> child : node.getChildren()) {
     if (key.equals(child.getKey())) {
       return (DataNode<T>) child;
     }
   }
   return null;
 }
 @SuppressWarnings("unchecked")
 @Nullable
 public static <T> DataNode<T> find(
     @NotNull DataNode<?> node, @NotNull Key<T> key, BooleanFunction<DataNode<T>> predicate) {
   for (DataNode<?> child : node.getChildren()) {
     if (key.equals(child.getKey()) && predicate.fun((DataNode<T>) child)) {
       return (DataNode<T>) child;
     }
   }
   return null;
 }
 @SuppressWarnings("unchecked")
 @Nullable
 public static <T> DataNode<T> findParent(
     @NotNull DataNode<?> node,
     @NotNull Key<T> key,
     @Nullable BooleanFunction<DataNode<T>> predicate) {
   DataNode<?> parent = node.getParent();
   if (parent == null) return null;
   return key.equals(parent.getKey()) && (predicate == null || predicate.fun((DataNode<T>) parent))
       ? (DataNode<T>) parent
       : findParent(parent, key, predicate);
 }
 @SuppressWarnings("unchecked")
 @NotNull
 public static <T> Collection<DataNode<T>> findAll(
     @NotNull DataNode<?> parent, @NotNull Key<T> key) {
   Collection<DataNode<T>> result = null;
   for (DataNode<?> child : parent.getChildren()) {
     if (!key.equals(child.getKey())) {
       continue;
     }
     if (result == null) {
       result = ContainerUtilRt.newArrayList();
     }
     result.add((DataNode<T>) child);
   }
   return result == null ? Collections.<DataNode<T>>emptyList() : result;
 }
/**
 * Add necessary configuration information to an appengine modules identified by {@link
 * com.google.gct.idea.appengine.gradle.project.AppEngineGradleProjectResolver}
 */
public class AppEngineGradleProjectDataService
    implements ProjectDataService<IdeaAppEngineProject, Void> {
  private static final NotificationGroup LOGGING_NOTIFICATION =
      NotificationGroup.logOnlyGroup("Gradle sync");

  private static final Logger LOG = Logger.getInstance(AppEngineGradleProjectDataService.class);
  // TODO: move this somewhere else, maybe a keys file
  @NotNull
  public static final Key<IdeaAppEngineProject> IDE_APP_ENGINE_PROJECT =
      Key.create(IdeaAppEngineProject.class, ProjectKeys.PROJECT.getProcessingWeight() + 25);

  @NotNull
  @Override
  public Key<IdeaAppEngineProject> getTargetDataKey() {
    return IDE_APP_ENGINE_PROJECT;
  }

  /** Add facet to App Engine module on imported modules */
  @Override
  public void importData(
      @NotNull final Collection<DataNode<IdeaAppEngineProject>> toImport,
      @NotNull final Project project,
      boolean synchronous) {
    if (toImport.isEmpty()) {
      return;
    }

    RunResult result =
        new WriteCommandAction.Simple(project) {
          @Override
          protected void run() throws Throwable {
            Map<String, IdeaAppEngineProject> importModulesMap = indexByModuleName(toImport);
            for (Module module : ModuleManager.getInstance(project).getModules()) {
              if (importModulesMap.containsKey(module.getName())) {
                AppEngineGradleFacet facet =
                    addAppEngineGradleFacet(importModulesMap.get(module.getName()), module);
                addAppEngineRunConfiguration(module, facet);
                UsageTracker.getInstance()
                    .trackEvent(GctTracking.CATEGORY, GctTracking.GRADLE_IMPORT, null, null);
              }
            }
          }
        }.execute();
    Throwable error = result.getThrowable();
    if (error != null) {
      LOG.error(String.format("Failed to set up App Engine Gradle Modules"));
      syncFailed(project, error.getMessage());
    }
  }

  // TODO notify the GradleSyncState that this has failed.
  public void syncFailed(@NotNull final Project project, @NotNull final String message) {
    String logMsg = "Gradle sync failed";
    if (isNotEmpty(message)) {
      logMsg += String.format(": %1$s", message);
    }
    addToEventLog(project, logMsg, ERROR);
  }

  private void addToEventLog(
      @NotNull final Project project, @NotNull String message, @NotNull MessageType type) {
    LOGGING_NOTIFICATION.createNotification(message, type).notify(project);
  }

  @Override
  public void removeData(
      @NotNull Collection<? extends Void> toRemove,
      @NotNull Project project,
      boolean synchronous) {}

  @NotNull
  private static AppEngineGradleFacet addAppEngineGradleFacet(
      IdeaAppEngineProject ideaAppEngineProject, Module appEngineModule) {
    FacetManager facetManager = FacetManager.getInstance(appEngineModule);
    ModifiableFacetModel model = facetManager.createModifiableModel();
    AppEngineGradleFacet facet = AppEngineGradleFacet.getInstance(appEngineModule);
    if (facet == null) {
      // Module does not have AppEngine-Gradle facet. Create one and add it.
      try {
        facet =
            facetManager.createFacet(
                AppEngineGradleFacet.getFacetType(), AppEngineGradleFacet.NAME, null);
        model.addFacet(facet);
      } finally {
        model.commit();
      }
    }

    // deserialize state from ideaAppEngineProject into facet config.
    if (facet != null) {
      facet.getConfiguration().getState().APPENGINE_SDKROOT =
          ideaAppEngineProject.getDelegate().getAppEngineSdkRoot();
      facet.getConfiguration().getState().HTTP_ADDRESS =
          ideaAppEngineProject.getDelegate().getHttpAddress();
      facet.getConfiguration().getState().HTTP_PORT =
          ideaAppEngineProject.getDelegate().getHttpPort();
      facet.getConfiguration().getState().JVM_FLAGS.clear();
      for (String flag : ideaAppEngineProject.getDelegate().getJvmFlags()) {
        facet.getConfiguration().getState().JVM_FLAGS.add(flag);
      }
      facet.getConfiguration().getState().WAR_DIR =
          ideaAppEngineProject.getDelegate().getWarDir().getAbsolutePath();
      facet.getConfiguration().getState().WEB_APP_DIR =
          ideaAppEngineProject.getDelegate().getWebAppDir().getAbsolutePath();
      facet.getConfiguration().getState().DISABLE_UPDATE_CHECK =
          ideaAppEngineProject.getDelegate().isDisableUpdateCheck();
    }
    return facet;
  }

  private static void addAppEngineRunConfiguration(
      @NotNull Module appEngineModule, @NotNull AppEngineGradleFacet facet) {
    final RunManagerEx runManager = RunManagerEx.getInstanceEx(appEngineModule.getProject());
    for (RunConfiguration configuration : runManager.getAllConfigurationsList()) {
      if (configuration.getName().equals(appEngineModule.getName())) {
        // TODO, we might want to check if this module already has a run configuration configured
        // instead of the name
        return;
      }
    }
    final RunnerAndConfigurationSettings settings =
        runManager.createRunConfiguration(
            appEngineModule.getName(), AppEngineRunConfigurationType.getInstance().getFactory());
    settings.setSingleton(true);
    final AppEngineRunConfiguration configuration =
        (AppEngineRunConfiguration) settings.getConfiguration();
    configuration.setModule(appEngineModule);
    // pull configuration out of gradle
    configuration.setSyncWithGradle(true);
    runManager.addConfiguration(settings, false);
  }

  @NotNull
  private static Map<String, IdeaAppEngineProject> indexByModuleName(
      @NotNull Collection<DataNode<IdeaAppEngineProject>> dataNodes) {
    Map<String, IdeaAppEngineProject> index = Maps.newHashMap();
    for (DataNode<IdeaAppEngineProject> d : dataNodes) {
      IdeaAppEngineProject appEngineProject = d.getData();
      index.put(appEngineProject.getModuleName(), appEngineProject);
    }
    return index;
  }
}