9a6a3bb4b859c50fe4baedf81b5fb76116dd2def
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / util / ExternalSystemUtil.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.externalSystem.util;
17
18 import com.intellij.execution.*;
19 import com.intellij.execution.configurations.ConfigurationType;
20 import com.intellij.execution.executors.DefaultDebugExecutor;
21 import com.intellij.execution.executors.DefaultRunExecutor;
22 import com.intellij.execution.process.ProcessAdapter;
23 import com.intellij.execution.process.ProcessEvent;
24 import com.intellij.execution.process.ProcessHandler;
25 import com.intellij.execution.rmi.RemoteUtil;
26 import com.intellij.execution.runners.ExecutionEnvironment;
27 import com.intellij.execution.runners.ProgramRunner;
28 import com.intellij.openapi.Disposable;
29 import com.intellij.openapi.application.*;
30 import com.intellij.openapi.application.ex.ApplicationEx;
31 import com.intellij.openapi.components.ServiceManager;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.extensions.Extensions;
34 import com.intellij.openapi.externalSystem.ExternalSystemManager;
35 import com.intellij.openapi.externalSystem.importing.ImportSpec;
36 import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder;
37 import com.intellij.openapi.externalSystem.model.*;
38 import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
39 import com.intellij.openapi.externalSystem.model.project.ModuleData;
40 import com.intellij.openapi.externalSystem.model.project.ProjectData;
41 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
42 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType;
43 import com.intellij.openapi.externalSystem.service.ImportCanceledException;
44 import com.intellij.openapi.externalSystem.service.execution.AbstractExternalSystemTaskConfigurationType;
45 import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration;
46 import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
47 import com.intellij.openapi.externalSystem.service.internal.ExternalSystemProcessingManager;
48 import com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask;
49 import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager;
50 import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
51 import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback;
52 import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
53 import com.intellij.openapi.externalSystem.service.project.manage.ContentRootDataService;
54 import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManagerImpl;
55 import com.intellij.openapi.externalSystem.service.project.manage.ExternalSystemTaskActivator;
56 import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManagerImpl;
57 import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemSettings;
58 import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings;
59 import com.intellij.openapi.externalSystem.task.TaskCallback;
60 import com.intellij.openapi.externalSystem.view.ExternalProjectsView;
61 import com.intellij.openapi.externalSystem.view.ExternalProjectsViewImpl;
62 import com.intellij.openapi.progress.EmptyProgressIndicator;
63 import com.intellij.openapi.progress.PerformInBackgroundOption;
64 import com.intellij.openapi.progress.ProgressIndicator;
65 import com.intellij.openapi.progress.Task;
66 import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
67 import com.intellij.openapi.project.Project;
68 import com.intellij.openapi.util.Disposer;
69 import com.intellij.openapi.util.Pair;
70 import com.intellij.openapi.util.Ref;
71 import com.intellij.openapi.util.UserDataHolderBase;
72 import com.intellij.openapi.util.io.FileUtil;
73 import com.intellij.openapi.util.text.StringUtil;
74 import com.intellij.openapi.vfs.LocalFileSystem;
75 import com.intellij.openapi.vfs.StandardFileSystems;
76 import com.intellij.openapi.vfs.VirtualFile;
77 import com.intellij.openapi.wm.ToolWindow;
78 import com.intellij.openapi.wm.ToolWindowEP;
79 import com.intellij.openapi.wm.ToolWindowManager;
80 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
81 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
82 import com.intellij.openapi.wm.impl.ToolWindowImpl;
83 import com.intellij.util.Consumer;
84 import com.intellij.util.DisposeAwareRunnable;
85 import com.intellij.util.concurrency.Semaphore;
86 import com.intellij.util.containers.ContainerUtil;
87 import com.intellij.util.containers.ContainerUtilRt;
88 import gnu.trove.TObjectHashingStrategy;
89 import org.jetbrains.annotations.NotNull;
90 import org.jetbrains.annotations.Nullable;
91
92 import java.io.File;
93 import java.io.IOException;
94 import java.util.Collection;
95 import java.util.Map;
96 import java.util.Set;
97
98 import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.doWriteAction;
99
100 /**
101  * @author Denis Zhdanov
102  * @since 4/22/13 9:36 AM
103  */
104 public class ExternalSystemUtil {
105
106   private static final Logger LOG = Logger.getInstance(ExternalSystemUtil.class);
107
108   @NotNull private static final Map<String, String> RUNNER_IDS = ContainerUtilRt.newHashMap();
109
110   public static final TObjectHashingStrategy<Pair<ProjectSystemId, File>> HASHING_STRATEGY =
111     new TObjectHashingStrategy<Pair<ProjectSystemId, File>>() {
112       @Override
113       public int computeHashCode(Pair<ProjectSystemId, File> object) {
114         return object.first.hashCode() + fileHashCode(object.second);
115       }
116
117       @Override
118       public boolean equals(Pair<ProjectSystemId, File> o1, Pair<ProjectSystemId, File> o2) {
119         return o1.first.equals(o2.first) && filesEqual(o1.second, o2.second);
120       }
121     };
122
123   static {
124     RUNNER_IDS.put(DefaultRunExecutor.EXECUTOR_ID, ExternalSystemConstants.RUNNER_ID);
125     RUNNER_IDS.put(DefaultDebugExecutor.EXECUTOR_ID, ExternalSystemConstants.DEBUG_RUNNER_ID);
126   }
127
128   private ExternalSystemUtil() {
129   }
130
131   public static int fileHashCode(@Nullable File file) {
132     int hash;
133     try {
134       hash = FileUtil.pathHashCode(file == null ? null : file.getCanonicalPath());
135     }
136     catch (IOException e) {
137       LOG.warn("unable to get canonical file path", e);
138       hash = FileUtil.fileHashCode(file);
139     }
140     return hash;
141   }
142
143   public static boolean filesEqual(@Nullable File file1, @Nullable File file2) {
144     try {
145       return FileUtil.pathsEqual(file1 == null ? null : file1.getCanonicalPath(), file2 == null ? null : file2.getCanonicalPath());
146     }
147     catch (IOException e) {
148       LOG.warn("unable to get canonical file path", e);
149     }
150     return FileUtil.filesEqual(file1, file2);
151   }
152
153   public static void ensureToolWindowInitialized(@NotNull Project project, @NotNull ProjectSystemId externalSystemId) {
154     try {
155       ToolWindowManager manager = ToolWindowManager.getInstance(project);
156       if (!(manager instanceof ToolWindowManagerEx)) {
157         return;
158       }
159       ToolWindowManagerEx managerEx = (ToolWindowManagerEx)manager;
160       String id = externalSystemId.getReadableName();
161       ToolWindow window = manager.getToolWindow(id);
162       if (window != null) {
163         return;
164       }
165       ToolWindowEP[] beans = Extensions.getExtensions(ToolWindowEP.EP_NAME);
166       for (final ToolWindowEP bean : beans) {
167         if (id.equals(bean.id)) {
168           managerEx.initToolWindow(bean);
169         }
170       }
171     }
172     catch (Exception e) {
173       LOG.error(String.format("Unable to initialize %s tool window", externalSystemId.getReadableName()), e);
174     }
175   }
176
177   @Nullable
178   public static ToolWindow ensureToolWindowContentInitialized(@NotNull Project project, @NotNull ProjectSystemId externalSystemId) {
179     final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
180     if (toolWindowManager == null) return null;
181
182     final ToolWindow toolWindow = toolWindowManager.getToolWindow(externalSystemId.getReadableName());
183     if (toolWindow == null) return null;
184
185     if (toolWindow instanceof ToolWindowImpl) {
186       ((ToolWindowImpl)toolWindow).ensureContentInitialized();
187     }
188     return toolWindow;
189   }
190
191   /**
192    * Asks to refresh all external projects of the target external system linked to the given ide project.
193    * <p/>
194    * 'Refresh' here means 'obtain the most up-to-date version and apply it to the ide'.
195    *
196    * @param project          target ide project
197    * @param externalSystemId target external system which projects should be refreshed
198    * @param force            flag which defines if external project refresh should be performed if it's config is up-to-date
199    * @deprecated use {@link  ExternalSystemUtil#refreshProjects(ImportSpecBuilder)}
200    */
201   @Deprecated
202   public static void refreshProjects(@NotNull final Project project, @NotNull final ProjectSystemId externalSystemId, boolean force) {
203     refreshProjects(project, externalSystemId, force, ProgressExecutionMode.IN_BACKGROUND_ASYNC);
204   }
205
206   /**
207    * Asks to refresh all external projects of the target external system linked to the given ide project.
208    * <p/>
209    * 'Refresh' here means 'obtain the most up-to-date version and apply it to the ide'.
210    *
211    * @param project          target ide project
212    * @param externalSystemId target external system which projects should be refreshed
213    * @param force            flag which defines if external project refresh should be performed if it's config is up-to-date
214    * @deprecated use {@link  ExternalSystemUtil#refreshProjects(ImportSpecBuilder)}
215    */
216   @Deprecated
217   public static void refreshProjects(@NotNull final Project project,
218                                      @NotNull final ProjectSystemId externalSystemId,
219                                      boolean force,
220                                      @NotNull final ProgressExecutionMode progressExecutionMode) {
221     refreshProjects(
222       new ImportSpecBuilder(project, externalSystemId)
223         .forceWhenUptodate(force)
224         .use(progressExecutionMode)
225     );
226   }
227
228   /**
229    * Asks to refresh all external projects of the target external system linked to the given ide project based on provided spec
230    *
231    * @param specBuilder import specification builder
232    */
233   public static void refreshProjects(@NotNull final ImportSpecBuilder specBuilder) {
234     refreshProjects(specBuilder.build());
235   }
236
237   /**
238    * Asks to refresh all external projects of the target external system linked to the given ide project based on provided spec
239    *
240    * @param spec import specification
241    */
242   public static void refreshProjects(@NotNull final ImportSpec spec) {
243     ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(spec.getExternalSystemId());
244     if (manager == null) {
245       return;
246     }
247     AbstractExternalSystemSettings<?, ?, ?> settings = manager.getSettingsProvider().fun(spec.getProject());
248     final Collection<? extends ExternalProjectSettings> projectsSettings = settings.getLinkedProjectsSettings();
249     if (projectsSettings.isEmpty()) {
250       return;
251     }
252
253     final ProjectDataManager projectDataManager = ServiceManager.getService(ProjectDataManager.class);
254
255     final ExternalProjectRefreshCallback callback;
256     if (spec.getCallback() == null) {
257       callback = new MyMultiExternalProjectRefreshCallback(spec.getProject(), projectDataManager, spec.getExternalSystemId());
258     }
259     else {
260       callback = spec.getCallback();
261     }
262
263     Set<String> toRefresh = ContainerUtilRt.newHashSet();
264     for (ExternalProjectSettings setting : projectsSettings) {
265       // don't refresh project when auto-import is disabled if such behavior needed (e.g. on project opening when auto-import is disabled)
266       if (!setting.isUseAutoImport() && spec.whenAutoImportEnabled()) continue;
267       toRefresh.add(setting.getExternalProjectPath());
268     }
269
270     if (!toRefresh.isEmpty()) {
271       ExternalSystemNotificationManager.getInstance(spec.getProject())
272         .clearNotifications(null, NotificationSource.PROJECT_SYNC, spec.getExternalSystemId());
273
274       for (String path : toRefresh) {
275         refreshProject(path, new ImportSpecBuilder(spec).callback(callback).build());
276       }
277     }
278   }
279
280   @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
281   @Nullable
282   private static String extractDetails(@NotNull Throwable e) {
283     final Throwable unwrapped = RemoteUtil.unwrap(e);
284     if (unwrapped instanceof ExternalSystemException) {
285       return ((ExternalSystemException)unwrapped).getOriginalReason();
286     }
287     return null;
288   }
289
290   public static void refreshProject(@NotNull final Project project,
291                                     @NotNull final ProjectSystemId externalSystemId,
292                                     @NotNull final String externalProjectPath,
293                                     final boolean isPreviewMode,
294                                     @NotNull final ProgressExecutionMode progressExecutionMode) {
295     refreshProject(project, externalSystemId, externalProjectPath, new ExternalProjectRefreshCallback() {
296       @Override
297       public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) {
298         if (externalProject == null) {
299           return;
300         }
301         final boolean synchronous = progressExecutionMode == ProgressExecutionMode.MODAL_SYNC;
302         ServiceManager.getService(ProjectDataManager.class).importData(externalProject, project, synchronous);
303       }
304
305       @Override
306       public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
307       }
308     }, isPreviewMode, progressExecutionMode, true);
309   }
310
311   /**
312    * TODO[Vlad]: refactor the method to use {@link ImportSpecBuilder}
313    * <p>
314    * Queries slave gradle process to refresh target gradle project.
315    *
316    * @param project             target intellij project to use
317    * @param externalProjectPath path of the target gradle project's file
318    * @param callback            callback to be notified on refresh result
319    * @param isPreviewMode       flag that identifies whether gradle libraries should be resolved during the refresh
320    * @return the most up-to-date gradle project (if any)
321    */
322   public static void refreshProject(@NotNull final Project project,
323                                     @NotNull final ProjectSystemId externalSystemId,
324                                     @NotNull final String externalProjectPath,
325                                     @NotNull final ExternalProjectRefreshCallback callback,
326                                     final boolean isPreviewMode,
327                                     @NotNull final ProgressExecutionMode progressExecutionMode) {
328     refreshProject(project, externalSystemId, externalProjectPath, callback, isPreviewMode, progressExecutionMode, true);
329   }
330
331   /**
332    * <p>
333    * Refresh target gradle project.
334    *
335    * @param project             target intellij project to use
336    * @param externalProjectPath path of the target external project
337    * @param callback            callback to be notified on refresh result
338    * @param isPreviewMode       flag that identifies whether libraries should be resolved during the refresh
339    * @param reportRefreshError  prevent to show annoying error notification, e.g. if auto-import mode used
340    */
341   public static void refreshProject(@NotNull final Project project,
342                                     @NotNull final ProjectSystemId externalSystemId,
343                                     @NotNull final String externalProjectPath,
344                                     @NotNull final ExternalProjectRefreshCallback callback,
345                                     final boolean isPreviewMode,
346                                     @NotNull final ProgressExecutionMode progressExecutionMode,
347                                     final boolean reportRefreshError) {
348     ImportSpecBuilder builder = new ImportSpecBuilder(project, externalSystemId).callback(callback).use(progressExecutionMode);
349     if (isPreviewMode) builder.usePreviewMode();
350     if (!reportRefreshError) builder.dontReportRefreshErrors();
351     refreshProject(externalProjectPath, builder.build());
352   }
353
354   public static void refreshProject(@NotNull final String externalProjectPath, @NotNull final ImportSpec importSpec) {
355     Project project = importSpec.getProject();
356     ProjectSystemId externalSystemId = importSpec.getExternalSystemId();
357     ExternalProjectRefreshCallback callback = importSpec.getCallback();
358     boolean isPreviewMode = importSpec.isPreviewMode();
359     ProgressExecutionMode progressExecutionMode = importSpec.getProgressExecutionMode();
360     boolean reportRefreshError = importSpec.isReportRefreshError();
361     String arguments = importSpec.getArguments();
362     String vmOptions = importSpec.getVmOptions();
363
364     File projectFile = new File(externalProjectPath);
365     final String projectName;
366     if (projectFile.isFile()) {
367       projectName = projectFile.getParentFile().getName();
368     }
369     else {
370       projectName = projectFile.getName();
371     }
372     final TaskUnderProgress refreshProjectStructureTask = new TaskUnderProgress() {
373       private final ExternalSystemResolveProjectTask myTask
374         = new ExternalSystemResolveProjectTask(externalSystemId, project, externalProjectPath, vmOptions, arguments, isPreviewMode);
375
376       @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"})
377       @Override
378       public void execute(@NotNull ProgressIndicator indicator) {
379         if (project.isDisposed()) return;
380
381         if (indicator instanceof ProgressIndicatorEx) {
382           ((ProgressIndicatorEx)indicator).addStateDelegate(new AbstractProgressIndicatorExBase() {
383             @Override
384             public void cancel() {
385               super.cancel();
386
387               ApplicationManager.getApplication().executeOnPooledThread(
388                 (Runnable)() -> myTask.cancel(ExternalSystemTaskNotificationListener.EP_NAME.getExtensions()));
389             }
390           });
391         }
392
393         ExternalSystemProcessingManager processingManager = ServiceManager.getService(ExternalSystemProcessingManager.class);
394         if (processingManager.findTask(ExternalSystemTaskType.RESOLVE_PROJECT, externalSystemId, externalProjectPath) != null) {
395           if (callback != null) {
396             callback.onFailure(ExternalSystemBundle.message("error.resolve.already.running", externalProjectPath), null);
397           }
398           return;
399         }
400
401         if (!(callback instanceof MyMultiExternalProjectRefreshCallback)) {
402           ExternalSystemNotificationManager.getInstance(project)
403             .clearNotifications(null, NotificationSource.PROJECT_SYNC, externalSystemId);
404         }
405
406         final ExternalSystemTaskActivator externalSystemTaskActivator = ExternalProjectsManagerImpl.getInstance(project).getTaskActivator();
407         if (!isPreviewMode && !externalSystemTaskActivator.runTasks(externalProjectPath, ExternalSystemTaskActivator.Phase.BEFORE_SYNC)) {
408           return;
409         }
410
411         myTask.execute(indicator, ExternalSystemTaskNotificationListener.EP_NAME.getExtensions());
412         if (project.isDisposed()) return;
413
414         final Throwable error = myTask.getError();
415         if (error == null) {
416           if (callback != null) {
417             DataNode<ProjectData> externalProject = myTask.getExternalProject();
418             if (externalProject != null && importSpec.shouldCreateDirectoriesForEmptyContentRoots()) {
419               externalProject.putUserData(ContentRootDataService.CREATE_EMPTY_DIRECTORIES, Boolean.TRUE);
420             }
421             callback.onSuccess(externalProject);
422           }
423           if (!isPreviewMode) {
424             externalSystemTaskActivator.runTasks(externalProjectPath, ExternalSystemTaskActivator.Phase.AFTER_SYNC);
425           }
426           return;
427         }
428         if (error instanceof ImportCanceledException) {
429           // stop refresh task
430           return;
431         }
432         String message = ExternalSystemApiUtil.buildErrorMessage(error);
433         if (StringUtil.isEmpty(message)) {
434           message = String.format(
435             "Can't resolve %s project at '%s'. Reason: %s", externalSystemId.getReadableName(), externalProjectPath, message
436           );
437         }
438
439         if (callback != null) {
440           callback.onFailure(message, extractDetails(error));
441         }
442
443         ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId);
444         if (manager == null) {
445           return;
446         }
447         AbstractExternalSystemSettings<?, ?, ?> settings = manager.getSettingsProvider().fun(project);
448         ExternalProjectSettings projectSettings = settings.getLinkedProjectSettings(externalProjectPath);
449         if (projectSettings == null || !reportRefreshError) {
450           return;
451         }
452
453         ExternalSystemNotificationManager.getInstance(project).processExternalProjectRefreshError(error, projectName, externalSystemId);
454       }
455     };
456
457     TransactionGuard.getInstance().assertWriteSafeContext(ModalityState.defaultModalityState());
458
459     final String title;
460     switch (progressExecutionMode) {
461       case NO_PROGRESS_SYNC:
462       case NO_PROGRESS_ASYNC:
463         throw new ExternalSystemException("Please, use progress for the project import!");
464       case MODAL_SYNC:
465         title = ExternalSystemBundle.message("progress.import.text", projectName, externalSystemId.getReadableName());
466         new Task.Modal(project, title, true) {
467           @Override
468           public void run(@NotNull ProgressIndicator indicator) {
469             refreshProjectStructureTask.execute(indicator);
470           }
471         }.queue();
472         break;
473       case IN_BACKGROUND_ASYNC:
474         title = ExternalSystemBundle.message("progress.refresh.text", projectName, externalSystemId.getReadableName());
475         new Task.Backgroundable(project, title) {
476           @Override
477           public void run(@NotNull ProgressIndicator indicator) {
478             refreshProjectStructureTask.execute(indicator);
479           }
480         }.queue();
481         break;
482       case START_IN_FOREGROUND_ASYNC:
483         title = ExternalSystemBundle.message("progress.refresh.text", projectName, externalSystemId.getReadableName());
484         new Task.Backgroundable(project, title, true, PerformInBackgroundOption.DEAF) {
485           @Override
486           public void run(@NotNull ProgressIndicator indicator) {
487             refreshProjectStructureTask.execute(indicator);
488           }
489         }.queue();
490     }
491   }
492
493   public static void runTask(@NotNull ExternalSystemTaskExecutionSettings taskSettings,
494                              @NotNull String executorId,
495                              @NotNull Project project,
496                              @NotNull ProjectSystemId externalSystemId) {
497     runTask(taskSettings, executorId, project, externalSystemId, null, ProgressExecutionMode.IN_BACKGROUND_ASYNC);
498   }
499
500   public static void runTask(@NotNull final ExternalSystemTaskExecutionSettings taskSettings,
501                              @NotNull final String executorId,
502                              @NotNull final Project project,
503                              @NotNull final ProjectSystemId externalSystemId,
504                              @Nullable final TaskCallback callback,
505                              @NotNull final ProgressExecutionMode progressExecutionMode) {
506     runTask(taskSettings, executorId, project, externalSystemId, callback, progressExecutionMode, true);
507   }
508
509   public static void runTask(@NotNull final ExternalSystemTaskExecutionSettings taskSettings,
510                              @NotNull final String executorId,
511                              @NotNull final Project project,
512                              @NotNull final ProjectSystemId externalSystemId,
513                              @Nullable final TaskCallback callback,
514                              @NotNull final ProgressExecutionMode progressExecutionMode,
515                              boolean activateToolWindowBeforeRun) {
516     runTask(taskSettings, executorId, project, externalSystemId, callback, progressExecutionMode, activateToolWindowBeforeRun, null);
517   }
518
519   public static void runTask(@NotNull final ExternalSystemTaskExecutionSettings taskSettings,
520                              @NotNull final String executorId,
521                              @NotNull final Project project,
522                              @NotNull final ProjectSystemId externalSystemId,
523                              @Nullable final TaskCallback callback,
524                              @NotNull final ProgressExecutionMode progressExecutionMode,
525                              boolean activateToolWindowBeforeRun,
526                              @Nullable UserDataHolderBase userData) {
527     ExecutionEnvironment environment = createExecutionEnvironment(project, externalSystemId, taskSettings, executorId);
528     if (environment == null) return;
529
530     RunnerAndConfigurationSettings runnerAndConfigurationSettings = environment.getRunnerAndConfigurationSettings();
531     assert runnerAndConfigurationSettings != null;
532     runnerAndConfigurationSettings.setActivateToolWindowBeforeRun(activateToolWindowBeforeRun);
533
534     if (userData != null) {
535       ExternalSystemRunConfiguration runConfiguration = (ExternalSystemRunConfiguration)runnerAndConfigurationSettings.getConfiguration();
536       userData.copyUserDataTo(runConfiguration);
537     }
538
539     final TaskUnderProgress task = new TaskUnderProgress() {
540       @Override
541       public void execute(@NotNull ProgressIndicator indicator) {
542         indicator.setIndeterminate(true);
543         final Semaphore targetDone = new Semaphore();
544         final Ref<Boolean> result = new Ref<>(false);
545         final Disposable disposable = Disposer.newDisposable();
546
547         project.getMessageBus().connect(disposable).subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() {
548           public void processStartScheduled(@NotNull final String executorIdLocal, @NotNull final ExecutionEnvironment environmentLocal) {
549             if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) {
550               targetDone.down();
551             }
552           }
553
554           public void processNotStarted(@NotNull final String executorIdLocal, @NotNull final ExecutionEnvironment environmentLocal) {
555             if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) {
556               targetDone.up();
557             }
558           }
559
560           public void processStarted(@NotNull final String executorIdLocal,
561                                      @NotNull final ExecutionEnvironment environmentLocal,
562                                      @NotNull final ProcessHandler handler) {
563             if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) {
564               handler.addProcessListener(new ProcessAdapter() {
565                 public void processTerminated(ProcessEvent event) {
566                   result.set(event.getExitCode() == 0);
567                   targetDone.up();
568                 }
569               });
570             }
571           }
572         });
573
574         try {
575           ApplicationManager.getApplication().invokeAndWait(() -> {
576             try {
577               environment.getRunner().execute(environment);
578             }
579             catch (ExecutionException e) {
580               targetDone.up();
581               LOG.error(e);
582             }
583           }, ModalityState.defaultModalityState());
584         }
585         catch (Exception e) {
586           LOG.error(e);
587           Disposer.dispose(disposable);
588           return;
589         }
590
591         targetDone.waitFor();
592         Disposer.dispose(disposable);
593
594         if (callback != null) {
595           if (result.get()) {
596             callback.onSuccess();
597           }
598           else {
599             callback.onFailure();
600           }
601         }
602       }
603     };
604
605     final String title = AbstractExternalSystemTaskConfigurationType.generateName(project, taskSettings);
606     switch (progressExecutionMode) {
607       case NO_PROGRESS_SYNC:
608         task.execute(new EmptyProgressIndicator());
609         break;
610       case MODAL_SYNC:
611         new Task.Modal(project, title, true) {
612           @Override
613           public void run(@NotNull ProgressIndicator indicator) {
614             task.execute(indicator);
615           }
616         }.queue();
617         break;
618       case NO_PROGRESS_ASYNC:
619         ApplicationManager.getApplication().executeOnPooledThread(() -> task.execute(new EmptyProgressIndicator()));
620         break;
621       case IN_BACKGROUND_ASYNC:
622         new Task.Backgroundable(project, title) {
623           @Override
624           public void run(@NotNull ProgressIndicator indicator) {
625             task.execute(indicator);
626           }
627         }.queue();
628         break;
629       case START_IN_FOREGROUND_ASYNC:
630         new Task.Backgroundable(project, title, true, PerformInBackgroundOption.DEAF) {
631           @Override
632           public void run(@NotNull ProgressIndicator indicator) {
633             task.execute(indicator);
634           }
635         }.queue();
636     }
637   }
638
639   @Nullable
640   public static ExecutionEnvironment createExecutionEnvironment(@NotNull Project project,
641                                                                 @NotNull ProjectSystemId externalSystemId,
642                                                                 @NotNull ExternalSystemTaskExecutionSettings taskSettings,
643                                                                 @NotNull String executorId) {
644     Executor executor = ExecutorRegistry.getInstance().getExecutorById(executorId);
645     if (executor == null) return null;
646
647     String runnerId = getRunnerId(executorId);
648     if (runnerId == null) return null;
649
650     ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(runnerId);
651     if (runner == null) return null;
652
653     RunnerAndConfigurationSettings settings = createExternalSystemRunnerAndConfigurationSettings(taskSettings, project, externalSystemId);
654     if (settings == null) return null;
655
656     return new ExecutionEnvironment(executor, runner, settings, project);
657   }
658
659   /**
660    * @deprecated to be removed in IDEA 2017, use {@link #createExecutionEnvironment}
661    */
662   @Nullable
663   public static Pair<ProgramRunner, ExecutionEnvironment> createRunner(@NotNull ExternalSystemTaskExecutionSettings taskSettings,
664                                                                        @NotNull String executorId,
665                                                                        @NotNull Project project,
666                                                                        @NotNull ProjectSystemId externalSystemId) {
667     ExecutionEnvironment executionEnvironment = createExecutionEnvironment(project, externalSystemId, taskSettings, executorId);
668     return executionEnvironment == null ? null : Pair.create(executionEnvironment.getRunner(), executionEnvironment);
669   }
670
671   @Nullable
672   public static RunnerAndConfigurationSettings createExternalSystemRunnerAndConfigurationSettings(@NotNull ExternalSystemTaskExecutionSettings taskSettings,
673                                                                                                   @NotNull Project project,
674                                                                                                   @NotNull ProjectSystemId externalSystemId) {
675     AbstractExternalSystemTaskConfigurationType configurationType = findConfigurationType(externalSystemId);
676     if (configurationType == null) return null;
677
678     String name = AbstractExternalSystemTaskConfigurationType.generateName(project, taskSettings);
679     RunnerAndConfigurationSettings settings = RunManager.getInstance(project).createRunConfiguration(name, configurationType.getFactory());
680     ExternalSystemRunConfiguration runConfiguration = (ExternalSystemRunConfiguration)settings.getConfiguration();
681     runConfiguration.getSettings().setExternalProjectPath(taskSettings.getExternalProjectPath());
682     runConfiguration.getSettings().setTaskNames(ContainerUtil.newArrayList(taskSettings.getTaskNames()));
683     runConfiguration.getSettings().setTaskDescriptions(ContainerUtil.newArrayList(taskSettings.getTaskDescriptions()));
684     runConfiguration.getSettings().setVmOptions(taskSettings.getVmOptions());
685     runConfiguration.getSettings().setScriptParameters(taskSettings.getScriptParameters());
686     runConfiguration.getSettings().setPassParentEnvs(taskSettings.isPassParentEnvs());
687     runConfiguration.getSettings().setEnv(ContainerUtil.newHashMap(taskSettings.getEnv()));
688     runConfiguration.getSettings().setExecutionName(taskSettings.getExecutionName());
689
690     return settings;
691   }
692
693   @Nullable
694   public static AbstractExternalSystemTaskConfigurationType findConfigurationType(@NotNull ProjectSystemId externalSystemId) {
695     for (ConfigurationType type : Extensions.getExtensions(ConfigurationType.CONFIGURATION_TYPE_EP)) {
696       if (type instanceof AbstractExternalSystemTaskConfigurationType) {
697         AbstractExternalSystemTaskConfigurationType candidate = (AbstractExternalSystemTaskConfigurationType)type;
698         if (externalSystemId.equals(candidate.getExternalSystemId())) {
699           return candidate;
700         }
701       }
702     }
703     return null;
704   }
705
706   @Nullable
707   public static String getRunnerId(@NotNull String executorId) {
708     return RUNNER_IDS.get(executorId);
709   }
710
711   /**
712    * Tries to obtain external project info implied by the given settings and link that external project to the given ide project.
713    *
714    * @param externalSystemId        target external system
715    * @param projectSettings         settings of the external project to link
716    * @param project                 target ide project to link external project to
717    * @param executionResultCallback it might take a while to resolve external project info, that's why it's possible to provide
718    *                                a callback to be notified on processing result. It receives <code>true</code> if an external
719    *                                project has been successfully linked to the given ide project;
720    *                                <code>false</code> otherwise (note that corresponding notification with error details is expected
721    *                                to be shown to the end-user then)
722    * @param isPreviewMode           flag which identifies if missing external project binaries should be downloaded
723    * @param progressExecutionMode   identifies how progress bar will be represented for the current processing
724    */
725   @SuppressWarnings("UnusedDeclaration")
726   public static void linkExternalProject(@NotNull final ProjectSystemId externalSystemId,
727                                          @NotNull final ExternalProjectSettings projectSettings,
728                                          @NotNull final Project project,
729                                          @Nullable final Consumer<Boolean> executionResultCallback,
730                                          boolean isPreviewMode,
731                                          @NotNull final ProgressExecutionMode progressExecutionMode) {
732     ExternalProjectRefreshCallback callback = new ExternalProjectRefreshCallback() {
733       @SuppressWarnings("unchecked")
734       @Override
735       public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) {
736         if (externalProject == null) {
737           if (executionResultCallback != null) {
738             executionResultCallback.consume(false);
739           }
740           return;
741         }
742         AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(project, externalSystemId);
743         Set<ExternalProjectSettings> projects = ContainerUtilRt.newHashSet(systemSettings.getLinkedProjectsSettings());
744         projects.add(projectSettings);
745         systemSettings.setLinkedProjectsSettings(projects);
746         ensureToolWindowInitialized(project, externalSystemId);
747         ServiceManager.getService(ProjectDataManager.class).importData(externalProject, project, true);
748         if (executionResultCallback != null) {
749           executionResultCallback.consume(true);
750         }
751       }
752
753       @Override
754       public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
755         if (executionResultCallback != null) {
756           executionResultCallback.consume(false);
757         }
758       }
759     };
760     refreshProject(project, externalSystemId, projectSettings.getExternalProjectPath(), callback, isPreviewMode, progressExecutionMode);
761   }
762
763   @Nullable
764   public static VirtualFile refreshAndFindFileByIoFile(@NotNull final File file) {
765     final Application app = ApplicationManager.getApplication();
766     if (app.isDispatchThread()) {
767       return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
768     }
769     else {
770       assert !((ApplicationEx)app).holdsReadLock();
771       return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
772     }
773   }
774
775   @Nullable
776   public static VirtualFile findLocalFileByPath(String path) {
777     VirtualFile result = StandardFileSystems.local().findFileByPath(path);
778     if (result != null) return result;
779
780     return !ApplicationManager.getApplication().isReadAccessAllowed()
781            ? findLocalFileByPathUnderWriteAction(path)
782            : findLocalFileByPathUnderReadAction(path);
783   }
784
785   @Nullable
786   private static VirtualFile findLocalFileByPathUnderWriteAction(final String path) {
787     return doWriteAction(() -> StandardFileSystems.local().refreshAndFindFileByPath(path));
788   }
789
790   @Nullable
791   private static VirtualFile findLocalFileByPathUnderReadAction(final String path) {
792     return ReadAction.compute(() -> StandardFileSystems.local().findFileByPath(path));
793   }
794
795   public static void scheduleExternalViewStructureUpdate(@NotNull final Project project, @NotNull final ProjectSystemId systemId) {
796     ExternalProjectsView externalProjectsView = ExternalProjectsManagerImpl.getInstance(project).getExternalProjectsView(systemId);
797     if (externalProjectsView instanceof ExternalProjectsViewImpl) {
798       ((ExternalProjectsViewImpl)externalProjectsView).scheduleStructureUpdate();
799     }
800   }
801
802   @Nullable
803   public static ExternalProjectInfo getExternalProjectInfo(@NotNull final Project project,
804                                                            @NotNull final ProjectSystemId projectSystemId,
805                                                            @NotNull final String externalProjectPath) {
806     final ExternalProjectSettings linkedProjectSettings =
807       ExternalSystemApiUtil.getSettings(project, projectSystemId).getLinkedProjectSettings(externalProjectPath);
808     if (linkedProjectSettings == null) return null;
809
810     return ProjectDataManagerImpl.getInstance().getExternalProjectData(
811       project, projectSystemId, linkedProjectSettings.getExternalProjectPath());
812   }
813
814
815   public static void invokeLater(Project p, Runnable r) {
816     invokeLater(p, ModalityState.defaultModalityState(), r);
817   }
818
819   public static void invokeLater(final Project p, final ModalityState state, final Runnable r) {
820     if (isNoBackgroundMode()) {
821       r.run();
822     }
823     else {
824       ApplicationManager.getApplication().invokeLater(DisposeAwareRunnable.create(r, p), state);
825     }
826   }
827
828   public static boolean isNoBackgroundMode() {
829     return (ApplicationManager.getApplication().isUnitTestMode()
830             || ApplicationManager.getApplication().isHeadlessEnvironment());
831   }
832
833   private interface TaskUnderProgress {
834     void execute(@NotNull ProgressIndicator indicator);
835   }
836
837   private static class MyMultiExternalProjectRefreshCallback implements ExternalProjectRefreshCallback {
838
839     @NotNull
840     private final Set<String> myExternalModulePaths;
841     private final Project myProject;
842     private final ProjectDataManager myProjectDataManager;
843     private final ProjectSystemId myExternalSystemId;
844
845     public MyMultiExternalProjectRefreshCallback(Project project,
846                                                  ProjectDataManager projectDataManager,
847                                                  ProjectSystemId externalSystemId) {
848       myProject = project;
849       myProjectDataManager = projectDataManager;
850       myExternalSystemId = externalSystemId;
851       myExternalModulePaths = ContainerUtilRt.newHashSet();
852     }
853
854     @Override
855     public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) {
856       if (externalProject == null) {
857         return;
858       }
859       Collection<DataNode<ModuleData>> moduleNodes = ExternalSystemApiUtil.findAllRecursively(externalProject, ProjectKeys.MODULE);
860       for (DataNode<ModuleData> node : moduleNodes) {
861         myExternalModulePaths.add(node.getData().getLinkedExternalProjectPath());
862       }
863
864       myProjectDataManager.importData(externalProject, myProject, true);
865     }
866
867     @Override
868     public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
869     }
870   }
871 }