9579c9630be5365adff8b166cca951c872788506
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / util / ExternalSystemUtil.java
1 /*
2  * Copyright 2000-2014 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.actionSystem.DataKey;
30 import com.intellij.openapi.actionSystem.DataProvider;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.application.ModalityState;
33 import com.intellij.openapi.components.ServiceManager;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.extensions.Extensions;
36 import com.intellij.openapi.externalSystem.ExternalSystemManager;
37 import com.intellij.openapi.externalSystem.importing.ImportSpec;
38 import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder;
39 import com.intellij.openapi.externalSystem.model.*;
40 import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
41 import com.intellij.openapi.externalSystem.model.project.ModuleData;
42 import com.intellij.openapi.externalSystem.model.project.ProjectData;
43 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener;
44 import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType;
45 import com.intellij.openapi.externalSystem.service.ImportCanceledException;
46 import com.intellij.openapi.externalSystem.service.execution.AbstractExternalSystemTaskConfigurationType;
47 import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration;
48 import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode;
49 import com.intellij.openapi.externalSystem.service.internal.ExternalSystemProcessingManager;
50 import com.intellij.openapi.externalSystem.service.internal.ExternalSystemResolveProjectTask;
51 import com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationManager;
52 import com.intellij.openapi.externalSystem.service.notification.NotificationSource;
53 import com.intellij.openapi.externalSystem.service.project.ExternalProjectRefreshCallback;
54 import com.intellij.openapi.externalSystem.service.project.PlatformFacade;
55 import com.intellij.openapi.externalSystem.service.project.ProjectStructureHelper;
56 import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManager;
57 import com.intellij.openapi.externalSystem.service.project.manage.ModuleDataService;
58 import com.intellij.openapi.externalSystem.service.project.manage.ProjectDataManager;
59 import com.intellij.openapi.externalSystem.service.settings.ExternalSystemConfigLocator;
60 import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemSettings;
61 import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings;
62 import com.intellij.openapi.externalSystem.task.TaskCallback;
63 import com.intellij.openapi.externalSystem.view.ExternalProjectsView;
64 import com.intellij.openapi.module.Module;
65 import com.intellij.openapi.progress.PerformInBackgroundOption;
66 import com.intellij.openapi.progress.ProgressIndicator;
67 import com.intellij.openapi.progress.Task;
68 import com.intellij.openapi.progress.util.AbstractProgressIndicatorExBase;
69 import com.intellij.openapi.project.Project;
70 import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
71 import com.intellij.openapi.roots.libraries.Library;
72 import com.intellij.openapi.roots.libraries.LibraryTable;
73 import com.intellij.openapi.ui.DialogWrapper;
74 import com.intellij.openapi.util.Computable;
75 import com.intellij.openapi.util.Disposer;
76 import com.intellij.openapi.util.Pair;
77 import com.intellij.openapi.util.Ref;
78 import com.intellij.openapi.util.text.StringUtil;
79 import com.intellij.openapi.vfs.StandardFileSystems;
80 import com.intellij.openapi.vfs.VirtualFile;
81 import com.intellij.openapi.wm.ToolWindow;
82 import com.intellij.openapi.wm.ToolWindowEP;
83 import com.intellij.openapi.wm.ToolWindowManager;
84 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
85 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
86 import com.intellij.openapi.wm.impl.ToolWindowImpl;
87 import com.intellij.ui.CheckBoxList;
88 import com.intellij.ui.IdeBorderFactory;
89 import com.intellij.ui.components.JBScrollPane;
90 import com.intellij.ui.content.Content;
91 import com.intellij.ui.content.ContentManager;
92 import com.intellij.util.Consumer;
93 import com.intellij.util.Function;
94 import com.intellij.util.concurrency.Semaphore;
95 import com.intellij.util.containers.ContainerUtil;
96 import com.intellij.util.containers.ContainerUtilRt;
97 import com.intellij.util.ui.UIUtil;
98 import org.jetbrains.annotations.NotNull;
99 import org.jetbrains.annotations.Nullable;
100
101 import javax.swing.*;
102 import java.awt.*;
103 import java.io.File;
104 import java.util.*;
105 import java.util.List;
106
107 import static com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil.executeOnEdtUnderWriteAction;
108
109 /**
110  * @author Denis Zhdanov
111  * @since 4/22/13 9:36 AM
112  */
113 public class ExternalSystemUtil {
114
115   private static final Logger LOG = Logger.getInstance("#" + ExternalSystemUtil.class.getName());
116
117   @NotNull private static final Map<String, String> RUNNER_IDS = ContainerUtilRt.newHashMap();
118
119   static {
120     RUNNER_IDS.put(DefaultRunExecutor.EXECUTOR_ID, ExternalSystemConstants.RUNNER_ID);
121     RUNNER_IDS.put(DefaultDebugExecutor.EXECUTOR_ID, ExternalSystemConstants.DEBUG_RUNNER_ID);
122   }
123
124   private ExternalSystemUtil() {
125   }
126
127   public static void ensureToolWindowInitialized(@NotNull Project project, @NotNull ProjectSystemId externalSystemId) {
128     ToolWindowManager manager = ToolWindowManager.getInstance(project);
129     if (!(manager instanceof ToolWindowManagerEx)) {
130       return;
131     }
132     ToolWindowManagerEx managerEx = (ToolWindowManagerEx)manager;
133     String id = externalSystemId.getReadableName();
134     ToolWindow window = manager.getToolWindow(id);
135     if (window != null) {
136       return;
137     }
138     ToolWindowEP[] beans = Extensions.getExtensions(ToolWindowEP.EP_NAME);
139     for (final ToolWindowEP bean : beans) {
140       if (id.equals(bean.id)) {
141         managerEx.initToolWindow(bean);
142       }
143     }
144   }
145
146   @Nullable
147   public static <T> T getToolWindowElement(@NotNull Class<T> clazz,
148                                            @NotNull Project project,
149                                            @NotNull DataKey<T> key,
150                                            @NotNull ProjectSystemId externalSystemId) {
151     if (project.isDisposed() || !project.isOpen()) {
152       return null;
153     }
154     final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
155     if (toolWindowManager == null) {
156       return null;
157     }
158     final ToolWindow toolWindow = ensureToolWindowContentInitialized(project, externalSystemId);
159     if (toolWindow == null) {
160       return null;
161     }
162
163     final ContentManager contentManager = toolWindow.getContentManager();
164     if (contentManager == null) {
165       return null;
166     }
167
168     for (Content content : contentManager.getContents()) {
169       final JComponent component = content.getComponent();
170       if (component instanceof DataProvider) {
171         final Object data = ((DataProvider)component).getData(key.getName());
172         if (data != null && clazz.isInstance(data)) {
173           //noinspection unchecked
174           return (T)data;
175         }
176       }
177     }
178     return null;
179   }
180
181   @Nullable
182   public static ToolWindow ensureToolWindowContentInitialized(@NotNull Project project, @NotNull ProjectSystemId externalSystemId) {
183     final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
184     if (toolWindowManager == null) return null;
185
186     final ToolWindow toolWindow = toolWindowManager.getToolWindow(externalSystemId.getReadableName());
187     if (toolWindow == null) return null;
188
189     if (toolWindow instanceof ToolWindowImpl) {
190       ((ToolWindowImpl)toolWindow).ensureContentInitialized();
191     }
192     return toolWindow;
193   }
194
195   /**
196    * Asks to refresh all external projects of the target external system linked to the given ide project.
197    * <p/>
198    * 'Refresh' here means 'obtain the most up-to-date version and apply it to the ide'.
199    *
200    * @param project          target ide project
201    * @param externalSystemId target external system which projects should be refreshed
202    * @param force            flag which defines if external project refresh should be performed if it's config is up-to-date
203    * @deprecated use {@link  ExternalSystemUtil#refreshProjects(com.intellij.openapi.externalSystem.importing.ImportSpecBuilder)}
204    */
205   @Deprecated
206   public static void refreshProjects(@NotNull final Project project, @NotNull final ProjectSystemId externalSystemId, boolean force) {
207     refreshProjects(project, externalSystemId, force, ProgressExecutionMode.IN_BACKGROUND_ASYNC);
208   }
209
210   /**
211    * Asks to refresh all external projects of the target external system linked to the given ide project.
212    * <p/>
213    * 'Refresh' here means 'obtain the most up-to-date version and apply it to the ide'.
214    *
215    * @param project           target ide project
216    * @param externalSystemId  target external system which projects should be refreshed
217    * @param force             flag which defines if external project refresh should be performed if it's config is up-to-date
218    *
219    * @deprecated use {@link  ExternalSystemUtil#refreshProjects(com.intellij.openapi.externalSystem.importing.ImportSpecBuilder)}
220    */
221   @Deprecated
222   public static void refreshProjects(@NotNull final Project project, @NotNull final ProjectSystemId externalSystemId, boolean force, @NotNull final ProgressExecutionMode progressExecutionMode) {
223     refreshProjects(
224       new ImportSpecBuilder(project, externalSystemId)
225         .forceWhenUptodate(force)
226         .use(progressExecutionMode)
227     );
228   }
229
230   /**
231    * Asks to refresh all external projects of the target external system linked to the given ide project based on provided spec
232    *
233    * @param specBuilder import specification builder
234    */
235   public static void refreshProjects(@NotNull final ImportSpecBuilder specBuilder) {
236     ImportSpec spec = specBuilder.build();
237
238     ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(spec.getExternalSystemId());
239     if (manager == null) {
240       return;
241     }
242     AbstractExternalSystemSettings<?, ?, ?> settings = manager.getSettingsProvider().fun(spec.getProject());
243     final Collection<? extends ExternalProjectSettings> projectsSettings = settings.getLinkedProjectsSettings();
244     if (projectsSettings.isEmpty()) {
245       return;
246     }
247
248     final ProjectDataManager projectDataManager = ServiceManager.getService(ProjectDataManager.class);
249     final int[] counter = new int[1];
250
251     final ExternalProjectRefreshCallback callback;
252     if (spec.getCallback() == null) {
253       callback = new MyMultiExternalProjectRefreshCallback(spec.getProject(), projectDataManager, counter, spec.getExternalSystemId());
254     }
255     else {
256       callback = spec.getCallback();
257     }
258
259     Map<String, Long> modificationStamps =
260       manager.getLocalSettingsProvider().fun(spec.getProject()).getExternalConfigModificationStamps();
261     Set<String> toRefresh = ContainerUtilRt.newHashSet();
262     for (ExternalProjectSettings setting : projectsSettings) {
263
264       // don't refresh project when auto-import is disabled if such behavior needed (e.g. on project opening when auto-import is disabled)
265       if (!setting.isUseAutoImport() && spec.isWhenAutoImportEnabled()) continue;
266
267       if (spec.isForceWhenUptodate()) {
268         toRefresh.add(setting.getExternalProjectPath());
269       }
270       else {
271         Long oldModificationStamp = modificationStamps.get(setting.getExternalProjectPath());
272         long currentModificationStamp = getTimeStamp(setting, spec.getExternalSystemId());
273         if (oldModificationStamp == null || oldModificationStamp < currentModificationStamp) {
274           toRefresh.add(setting.getExternalProjectPath());
275         }
276       }
277     }
278
279     if (!toRefresh.isEmpty()) {
280       ExternalSystemNotificationManager.getInstance(spec.getProject())
281         .clearNotifications(null, NotificationSource.PROJECT_SYNC, spec.getExternalSystemId());
282
283       counter[0] = toRefresh.size();
284       for (String path : toRefresh) {
285         refreshProject(
286           spec.getProject(), spec.getExternalSystemId(), path, callback, false, spec.getProgressExecutionMode());
287       }
288     }
289   }
290
291   private static long getTimeStamp(@NotNull ExternalProjectSettings externalProjectSettings, @NotNull ProjectSystemId externalSystemId) {
292     long timeStamp = 0;
293     for (ExternalSystemConfigLocator locator : ExternalSystemConfigLocator.EP_NAME.getExtensions()) {
294       if (!externalSystemId.equals(locator.getTargetExternalSystemId())) {
295         continue;
296       }
297       for (VirtualFile virtualFile : locator.findAll(externalProjectSettings)) {
298         timeStamp += virtualFile.getTimeStamp();
299       }
300     }
301     return timeStamp;
302   }
303
304   public static void ruleOrphanModules(@NotNull final List<Module> orphanModules,
305                                        @NotNull final Project project,
306                                        @NotNull final ProjectSystemId externalSystemId) {
307     //noinspection unchecked
308     ruleOrphanModules(orphanModules, project, externalSystemId, Consumer.EMPTY_CONSUMER);
309   }
310
311   /**
312    * There is a possible case that an external module has been un-linked from ide project. There are two ways to process
313    * ide modules which correspond to that external project:
314    * <pre>
315    * <ol>
316    *   <li>Remove them from ide project as well;</li>
317    *   <li>Keep them at ide project as well;</li>
318    * </ol>
319    * </pre>
320    * This method handles that situation, i.e. it asks a user what should be done and acts accordingly.
321    *
322    * @param orphanModules     modules which correspond to the un-linked external project
323    * @param project           current ide project
324    * @param externalSystemId  id of the external system which project has been un-linked from ide project
325    */
326   public static void ruleOrphanModules(@NotNull final List<Module> orphanModules,
327                                        @NotNull final Project project,
328                                        @NotNull final ProjectSystemId externalSystemId,
329                                        @NotNull final Consumer<Boolean> result)
330   {
331     UIUtil.invokeLaterIfNeeded(new Runnable() {
332       @Override
333       public void run() {
334
335         final JPanel content = new JPanel(new GridBagLayout());
336         content.add(new JLabel(ExternalSystemBundle.message("orphan.modules.text", externalSystemId.getReadableName())),
337                     ExternalSystemUiUtil.getFillLineConstraints(0));
338
339         final CheckBoxList<Module> orphanModulesList = new CheckBoxList<Module>();
340         orphanModulesList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
341         orphanModulesList.setItems(orphanModules, new Function<Module, String>() {
342           @Override
343           public String fun(Module module) {
344             return module.getName();
345           }
346         });
347         for (Module module : orphanModules) {
348           orphanModulesList.setItemSelected(module, true);
349         }
350         orphanModulesList.setBorder(IdeBorderFactory.createEmptyBorder(8));
351         content.add(orphanModulesList, ExternalSystemUiUtil.getFillLineConstraints(0));
352         content.setBorder(IdeBorderFactory.createEmptyBorder(0, 0, 8, 0));
353
354         DialogWrapper dialog = new DialogWrapper(project) {
355
356           {
357             setTitle(ExternalSystemBundle.message("import.title", externalSystemId.getReadableName()));
358             init();
359           }
360
361           @Nullable
362           @Override
363           protected JComponent createCenterPanel() {
364             return new JBScrollPane(content);
365           }
366         };
367         boolean ok = dialog.showAndGet();
368         result.consume(ok);
369         if (!ok) {
370           return;
371         }
372
373         List<Module> toRemove = ContainerUtilRt.newArrayList();
374         for (int i = 0; i < orphanModules.size(); i++) {
375           Module module = orphanModules.get(i);
376           if (orphanModulesList.isItemSelected(i)) {
377             toRemove.add(module);
378           }
379           else {
380             ModuleDataService.unlinkModuleFromExternalSystem(module);
381           }
382         }
383
384         if (!toRemove.isEmpty()) {
385           ServiceManager.getService(ProjectDataManager.class).removeData(ProjectKeys.MODULE, toRemove, project, true);
386         }
387       }
388     });
389   }
390
391   @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
392   @Nullable
393   private static String extractDetails(@NotNull Throwable e) {
394     final Throwable unwrapped = RemoteUtil.unwrap(e);
395     if (unwrapped instanceof ExternalSystemException) {
396       return ((ExternalSystemException)unwrapped).getOriginalReason();
397     }
398     return null;
399   }
400
401   /**
402    * TODO[Vlad]: refactor the method to use {@link com.intellij.openapi.externalSystem.importing.ImportSpecBuilder}
403    *
404    * Queries slave gradle process to refresh target gradle project.
405    *
406    * @param project               target intellij project to use
407    * @param externalProjectPath   path of the target gradle project's file
408    * @param callback              callback to be notified on refresh result
409    * @param isPreviewMode         flag that identifies whether gradle libraries should be resolved during the refresh
410    * @return the most up-to-date gradle project (if any)
411    */
412   public static void refreshProject(@NotNull final Project project,
413                                     @NotNull final ProjectSystemId externalSystemId,
414                                     @NotNull final String externalProjectPath,
415                                     @NotNull final ExternalProjectRefreshCallback callback,
416                                     final boolean isPreviewMode,
417                                     @NotNull final ProgressExecutionMode progressExecutionMode) {
418     refreshProject(project, externalSystemId, externalProjectPath, callback, isPreviewMode, progressExecutionMode, true);
419   }
420
421   /**
422    * TODO[Vlad]: refactor the method to use {@link com.intellij.openapi.externalSystem.importing.ImportSpecBuilder}
423    *
424    * Queries slave gradle process to refresh target gradle project.
425    *
426    * @param project               target intellij project to use
427    * @param externalProjectPath   path of the target gradle project's file
428    * @param callback              callback to be notified on refresh result
429    * @param isPreviewMode         flag that identifies whether gradle libraries should be resolved during the refresh
430    * @param reportRefreshError    prevent to show annoying error notification, e.g. if auto-import mode used
431    * @return the most up-to-date gradle project (if any)
432    */
433   public static void refreshProject(@NotNull final Project project,
434                                     @NotNull final ProjectSystemId externalSystemId,
435                                     @NotNull final String externalProjectPath,
436                                     @NotNull final ExternalProjectRefreshCallback callback,
437                                     final boolean isPreviewMode,
438                                     @NotNull final ProgressExecutionMode progressExecutionMode,
439                                     final boolean reportRefreshError)
440   {
441     File projectFile = new File(externalProjectPath);
442     final String projectName;
443     if (projectFile.isFile()) {
444       projectName = projectFile.getParentFile().getName();
445     }
446     else {
447       projectName = projectFile.getName();
448     }
449     final TaskUnderProgress refreshProjectStructureTask = new TaskUnderProgress() {
450       private final ExternalSystemResolveProjectTask myTask
451         = new ExternalSystemResolveProjectTask(externalSystemId, project, externalProjectPath, isPreviewMode);
452
453       @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "IOResourceOpenedButNotSafelyClosed"})
454       @Override
455       public void execute(@NotNull ProgressIndicator indicator) {
456         if(project.isDisposed()) return;
457
458         if (indicator instanceof ProgressIndicatorEx) {
459           ((ProgressIndicatorEx)indicator).addStateDelegate(new AbstractProgressIndicatorExBase() {
460             @Override
461             public void cancel() {
462               super.cancel();
463
464               ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
465                 @Override
466                 public void run() {
467                   myTask.cancel(ExternalSystemTaskNotificationListener.EP_NAME.getExtensions());
468                 }
469               });
470             }
471           });
472         }
473
474         ExternalSystemProcessingManager processingManager = ServiceManager.getService(ExternalSystemProcessingManager.class);
475         if (processingManager.findTask(ExternalSystemTaskType.RESOLVE_PROJECT, externalSystemId, externalProjectPath) != null) {
476           callback.onFailure(ExternalSystemBundle.message("error.resolve.already.running", externalProjectPath), null);
477           return;
478         }
479
480         if (!(callback instanceof MyMultiExternalProjectRefreshCallback)) {
481           ExternalSystemNotificationManager.getInstance(project)
482             .clearNotifications(null, NotificationSource.PROJECT_SYNC, externalSystemId);
483         }
484
485         myTask.execute(indicator, ExternalSystemTaskNotificationListener.EP_NAME.getExtensions());
486         if(project.isDisposed()) return;
487
488         final Throwable error = myTask.getError();
489         if (error == null) {
490           ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId);
491           assert manager != null;
492           DataNode<ProjectData> externalProject = myTask.getExternalProject();
493
494           if(externalProject != null) {
495             Set<String> externalModulePaths = ContainerUtil.newHashSet();
496             Collection<DataNode<ModuleData>> moduleNodes = ExternalSystemApiUtil.findAll(externalProject, ProjectKeys.MODULE);
497             for (DataNode<ModuleData> node : moduleNodes) {
498               externalModulePaths.add(node.getData().getLinkedExternalProjectPath());
499             }
500
501             String projectPath = externalProject.getData().getLinkedExternalProjectPath();
502             ExternalProjectSettings linkedProjectSettings = manager.getSettingsProvider().fun(project).getLinkedProjectSettings(projectPath);
503             if (linkedProjectSettings != null) {
504               linkedProjectSettings.setModules(externalModulePaths);
505
506               long stamp = getTimeStamp(linkedProjectSettings, externalSystemId);
507               if (stamp > 0) {
508                 manager.getLocalSettingsProvider().fun(project).getExternalConfigModificationStamps().put(externalProjectPath, stamp);
509               }
510             }
511           }
512
513           callback.onSuccess(externalProject);
514           return;
515         }
516         if(error instanceof ImportCanceledException) {
517           // stop refresh task
518           return;
519         }
520         String message = ExternalSystemApiUtil.buildErrorMessage(error);
521         if (StringUtil.isEmpty(message)) {
522           message = String.format(
523             "Can't resolve %s project at '%s'. Reason: %s", externalSystemId.getReadableName(), externalProjectPath, message
524           );
525         }
526
527         callback.onFailure(message, extractDetails(error));
528
529         ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId);
530         if(manager == null) {
531           return;
532         }
533         AbstractExternalSystemSettings<?, ?, ?> settings = manager.getSettingsProvider().fun(project);
534         ExternalProjectSettings projectSettings = settings.getLinkedProjectSettings(externalProjectPath);
535         if (projectSettings == null || !reportRefreshError) {
536           return;
537         }
538
539         ExternalSystemNotificationManager.getInstance(project).processExternalProjectRefreshError(error, projectName, externalSystemId);
540       }
541     };
542
543     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
544       @Override
545       public void run() {
546         final String title;
547         switch (progressExecutionMode) {
548           case MODAL_SYNC:
549             title = ExternalSystemBundle.message("progress.import.text", projectName, externalSystemId.getReadableName());
550             new Task.Modal(project, title, true) {
551               @Override
552               public void run(@NotNull ProgressIndicator indicator) {
553                 refreshProjectStructureTask.execute(indicator);
554               }
555             }.queue();
556             break;
557           case IN_BACKGROUND_ASYNC:
558             title = ExternalSystemBundle.message("progress.refresh.text", projectName, externalSystemId.getReadableName());
559             new Task.Backgroundable(project, title) {
560               @Override
561               public void run(@NotNull ProgressIndicator indicator) {
562                 refreshProjectStructureTask.execute(indicator);
563               }
564             }.queue();
565             break;
566           case START_IN_FOREGROUND_ASYNC:
567             title = ExternalSystemBundle.message("progress.refresh.text", projectName, externalSystemId.getReadableName());
568             new Task.Backgroundable(project, title, true, PerformInBackgroundOption.DEAF) {
569               @Override
570               public void run(@NotNull ProgressIndicator indicator) {
571                 refreshProjectStructureTask.execute(indicator);
572               }
573             }.queue();
574         }
575       }
576     });
577   }
578
579   public static void runTask(@NotNull ExternalSystemTaskExecutionSettings taskSettings,
580                              @NotNull String executorId,
581                              @NotNull Project project,
582                              @NotNull ProjectSystemId externalSystemId) {
583     runTask(taskSettings, executorId, project, externalSystemId, null, ProgressExecutionMode.IN_BACKGROUND_ASYNC);
584   }
585
586   public static void runTask(@NotNull final ExternalSystemTaskExecutionSettings taskSettings,
587                              @NotNull final String executorId,
588                              @NotNull final Project project,
589                              @NotNull final ProjectSystemId externalSystemId,
590                              @Nullable final TaskCallback callback,
591                              @NotNull final ProgressExecutionMode progressExecutionMode) {
592     final Pair<ProgramRunner, ExecutionEnvironment> pair = createRunner(taskSettings, executorId, project, externalSystemId);
593     if (pair == null) return;
594
595     final ProgramRunner runner = pair.first;
596     final ExecutionEnvironment environment = pair.second;
597
598     final TaskUnderProgress task = new TaskUnderProgress() {
599       @Override
600       public void execute(@NotNull ProgressIndicator indicator) {
601         final Semaphore targetDone = new Semaphore();
602         final Ref<Boolean> result = new Ref<Boolean>(false);
603         final Disposable disposable = Disposer.newDisposable();
604
605         project.getMessageBus().connect(disposable).subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionAdapter() {
606           public void processStartScheduled(final String executorIdLocal, final ExecutionEnvironment environmentLocal) {
607             if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) {
608               targetDone.down();
609             }
610           }
611
612           public void processNotStarted(final String executorIdLocal, @NotNull final ExecutionEnvironment environmentLocal) {
613             if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) {
614               targetDone.up();
615             }
616           }
617
618           public void processStarted(final String executorIdLocal,
619                                      @NotNull final ExecutionEnvironment environmentLocal,
620                                      @NotNull final ProcessHandler handler) {
621             if (executorId.equals(executorIdLocal) && environment.equals(environmentLocal)) {
622               handler.addProcessListener(new ProcessAdapter() {
623                 public void processTerminated(ProcessEvent event) {
624                   result.set(event.getExitCode() == 0);
625                   targetDone.up();
626                 }
627               });
628             }
629           }
630         });
631
632         try {
633           ApplicationManager.getApplication().invokeAndWait(new Runnable() {
634             @Override
635             public void run() {
636               try {
637                 runner.execute(environment);
638               }
639               catch (ExecutionException e) {
640                 targetDone.up();
641                 LOG.error(e);
642               }
643             }
644           }, ModalityState.NON_MODAL);
645         }
646         catch (Exception e) {
647           LOG.error(e);
648           Disposer.dispose(disposable);
649           return;
650         }
651
652         targetDone.waitFor();
653         Disposer.dispose(disposable);
654
655         if (callback != null) {
656           if (result.get()) {
657             callback.onSuccess();
658           }
659           else {
660             callback.onFailure();
661           }
662         }
663       }
664     };
665
666     UIUtil.invokeAndWaitIfNeeded(new Runnable() {
667       @Override
668       public void run() {
669         final String title = AbstractExternalSystemTaskConfigurationType.generateName(project, taskSettings);
670         switch (progressExecutionMode) {
671           case MODAL_SYNC:
672             new Task.Modal(project, title, true) {
673               @Override
674               public void run(@NotNull ProgressIndicator indicator) {
675                 task.execute(indicator);
676               }
677             }.queue();
678             break;
679           case IN_BACKGROUND_ASYNC:
680             new Task.Backgroundable(project, title) {
681               @Override
682               public void run(@NotNull ProgressIndicator indicator) {
683                 task.execute(indicator);
684               }
685             }.queue();
686             break;
687           case START_IN_FOREGROUND_ASYNC:
688             new Task.Backgroundable(project, title, true, PerformInBackgroundOption.DEAF) {
689               @Override
690               public void run(@NotNull ProgressIndicator indicator) {
691                 task.execute(indicator);
692               }
693             }.queue();
694         }
695       }
696     });
697   }
698
699   @Nullable
700   public static Pair<ProgramRunner, ExecutionEnvironment> createRunner(@NotNull ExternalSystemTaskExecutionSettings taskSettings,
701                                                                        @NotNull String executorId,
702                                                                        @NotNull Project project,
703                                                                        @NotNull ProjectSystemId externalSystemId) {
704     Executor executor = ExecutorRegistry.getInstance().getExecutorById(executorId);
705     if (executor == null) return null;
706
707     String runnerId = getRunnerId(executorId);
708     if (runnerId == null) return null;
709
710     ProgramRunner runner = RunnerRegistry.getInstance().findRunnerById(runnerId);
711     if (runner == null) return null;
712
713     RunnerAndConfigurationSettings settings = createExternalSystemRunnerAndConfigurationSettings(taskSettings, project, externalSystemId);
714     if (settings == null) return null;
715
716     return Pair.create(runner, new ExecutionEnvironment(executor, runner, settings, project));
717   }
718
719   @Nullable
720   public static RunnerAndConfigurationSettings createExternalSystemRunnerAndConfigurationSettings(@NotNull ExternalSystemTaskExecutionSettings taskSettings,
721                                                                                                   @NotNull Project project,
722                                                                                                   @NotNull ProjectSystemId externalSystemId) {
723     AbstractExternalSystemTaskConfigurationType configurationType = findConfigurationType(externalSystemId);
724     if (configurationType == null) return null;
725
726     String name = AbstractExternalSystemTaskConfigurationType.generateName(project, taskSettings);
727     RunnerAndConfigurationSettings settings = RunManager.getInstance(project).createRunConfiguration(name, configurationType.getFactory());
728     ExternalSystemRunConfiguration runConfiguration = (ExternalSystemRunConfiguration)settings.getConfiguration();
729     runConfiguration.getSettings().setExternalProjectPath(taskSettings.getExternalProjectPath());
730     runConfiguration.getSettings().setTaskNames(ContainerUtil.newArrayList(taskSettings.getTaskNames()));
731     runConfiguration.getSettings().setTaskDescriptions(ContainerUtil.newArrayList(taskSettings.getTaskDescriptions()));
732     runConfiguration.getSettings().setVmOptions(taskSettings.getVmOptions());
733     runConfiguration.getSettings().setScriptParameters(taskSettings.getScriptParameters());
734     runConfiguration.getSettings().setExecutionName(taskSettings.getExecutionName());
735
736     return settings;
737   }
738
739   @Nullable
740   public static AbstractExternalSystemTaskConfigurationType findConfigurationType(@NotNull ProjectSystemId externalSystemId) {
741     for (ConfigurationType type : Extensions.getExtensions(ConfigurationType.CONFIGURATION_TYPE_EP)) {
742       if (type instanceof AbstractExternalSystemTaskConfigurationType) {
743         AbstractExternalSystemTaskConfigurationType candidate = (AbstractExternalSystemTaskConfigurationType)type;
744         if (externalSystemId.equals(candidate.getExternalSystemId())) {
745           return candidate;
746         }
747       }
748     }
749     return null;
750   }
751
752   @Nullable
753   public static String getRunnerId(@NotNull String executorId) {
754     return RUNNER_IDS.get(executorId);
755   }
756
757   /**
758    * Allows to answer if given ide project has 1-1 mapping with the given external project, i.e. the ide project has been
759    * imported from external system and no other external projects have been added.
760    * <p/>
761    * This might be necessary in a situation when project-level setting is changed (e.g. project name). We don't want to rename
762    * ide project if it doesn't completely corresponds to the given ide project then.
763    *
764    * @param ideProject       target ide project
765    * @param externalProject  target external project
766    * @return                 <code>true</code> if given ide project has 1-1 mapping to the given external project;
767    *                         <code>false</code> otherwise
768    */
769   public static boolean isOneToOneMapping(@NotNull Project ideProject, @NotNull DataNode<ProjectData> externalProject) {
770     String linkedExternalProjectPath = null;
771     for (ExternalSystemManager<?, ?, ?, ?, ?> manager : ExternalSystemApiUtil.getAllManagers()) {
772       ProjectSystemId externalSystemId = manager.getSystemId();
773       AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(ideProject, externalSystemId);
774       Collection projectsSettings = systemSettings.getLinkedProjectsSettings();
775       int linkedProjectsNumber = projectsSettings.size();
776       if (linkedProjectsNumber > 1) {
777         // More than one external project of the same external system type is linked to the given ide project.
778         return false;
779       }
780       else if (linkedProjectsNumber == 1) {
781         if (linkedExternalProjectPath == null) {
782           // More than one external project of different external system types is linked to the current ide project.
783           linkedExternalProjectPath = ((ExternalProjectSettings)projectsSettings.iterator().next()).getExternalProjectPath();
784         }
785         else {
786           return false;
787         }
788       }
789     }
790     
791     ProjectData projectData = externalProject.getData();
792     if (linkedExternalProjectPath != null && !linkedExternalProjectPath.equals(projectData.getLinkedExternalProjectPath())) {
793       // New external project is being linked.
794       return false;
795     }
796
797     Set<String> externalModulePaths = ContainerUtilRt.newHashSet();
798     for (DataNode<ModuleData> moduleNode : ExternalSystemApiUtil.findAll(externalProject, ProjectKeys.MODULE)) {
799       externalModulePaths.add(moduleNode.getData().getLinkedExternalProjectPath());
800     }
801     externalModulePaths.remove(linkedExternalProjectPath);
802     
803     PlatformFacade platformFacade = ServiceManager.getService(PlatformFacade.class);
804     for (Module module : platformFacade.getModules(ideProject)) {
805       String path = module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY);
806       if (!StringUtil.isEmpty(path) && !externalModulePaths.remove(path)) {
807         return false;
808       }
809     }
810     return externalModulePaths.isEmpty();
811   }
812
813   /**
814    * Tries to obtain external project info implied by the given settings and link that external project to the given ide project. 
815    * 
816    * @param externalSystemId         target external system
817    * @param projectSettings          settings of the external project to link
818    * @param project                  target ide project to link external project to
819    * @param executionResultCallback  it might take a while to resolve external project info, that's why it's possible to provide
820    *                                 a callback to be notified on processing result. It receives <code>true</code> if an external
821    *                                 project has been successfully linked to the given ide project;
822    *                                 <code>false</code> otherwise (note that corresponding notification with error details is expected
823    *                                 to be shown to the end-user then)
824    * @param isPreviewMode            flag which identifies if missing external project binaries should be downloaded
825    * @param progressExecutionMode         identifies how progress bar will be represented for the current processing
826    */
827   @SuppressWarnings("UnusedDeclaration")
828   public static void linkExternalProject(@NotNull final ProjectSystemId externalSystemId,
829                                          @NotNull final ExternalProjectSettings projectSettings,
830                                          @NotNull final Project project,
831                                          @Nullable final Consumer<Boolean> executionResultCallback,
832                                          boolean isPreviewMode,
833                                          @NotNull final ProgressExecutionMode progressExecutionMode)
834   {
835     ExternalProjectRefreshCallback callback = new ExternalProjectRefreshCallback() {
836       @SuppressWarnings("unchecked")
837       @Override
838       public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) {
839         if (externalProject == null) {
840           if (executionResultCallback != null) {
841             executionResultCallback.consume(false);
842           }
843           return;
844         }
845         AbstractExternalSystemSettings systemSettings = ExternalSystemApiUtil.getSettings(project, externalSystemId);
846         Set<ExternalProjectSettings> projects = ContainerUtilRt.newHashSet(systemSettings.getLinkedProjectsSettings());
847         projects.add(projectSettings);
848         systemSettings.setLinkedProjectsSettings(projects);
849         ensureToolWindowInitialized(project, externalSystemId);
850         ExternalSystemApiUtil.executeProjectChangeAction(new DisposeAwareProjectChange(project) {
851           @Override
852           public void execute() {
853             ProjectRootManagerEx.getInstanceEx(project).mergeRootsChangesDuring(new Runnable() {
854               @Override
855               public void run() {
856                 ProjectDataManager dataManager = ServiceManager.getService(ProjectDataManager.class);
857                 dataManager.importData(externalProject.getKey(), Collections.singleton(externalProject), project, true);
858               }
859             });
860           }
861         });
862         if (executionResultCallback != null) {
863           executionResultCallback.consume(true);
864         }
865       }
866
867       @Override
868       public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
869         if (executionResultCallback != null) {
870           executionResultCallback.consume(false);
871         }
872       }
873     };
874     refreshProject(project, externalSystemId, projectSettings.getExternalProjectPath(), callback, isPreviewMode, progressExecutionMode);
875   }
876
877   @Nullable
878   public static VirtualFile findLocalFileByPath(String path) {
879     VirtualFile result = StandardFileSystems.local().findFileByPath(path);
880     if (result != null) return result;
881
882     return !ApplicationManager.getApplication().isReadAccessAllowed()
883            ? findLocalFileByPathUnderWriteAction(path)
884            : findLocalFileByPathUnderReadAction(path);
885   }
886
887   @Nullable
888   private static VirtualFile findLocalFileByPathUnderWriteAction(final String path) {
889     return executeOnEdtUnderWriteAction(new Computable<VirtualFile>() {
890       @Override
891       public VirtualFile compute() {
892         return StandardFileSystems.local().refreshAndFindFileByPath(path);
893       }
894     });
895   }
896
897   @Nullable
898   private static VirtualFile findLocalFileByPathUnderReadAction(final String path) {
899     return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
900       @Override
901       public VirtualFile compute() {
902         return StandardFileSystems.local().findFileByPath(path);
903       }
904     });
905   }
906
907   public static void scheduleExternalViewStructureUpdate(final Project project, final ProjectSystemId systemId) {
908     ExternalProjectsView externalProjectsView = ExternalProjectsManager.getInstance(project).getExternalProjectsView(systemId);
909     if (externalProjectsView != null) {
910       externalProjectsView.scheduleStructureUpdate();
911     }
912   }
913
914   @Nullable
915   public static ExternalProjectInfo getExternalProjectInfo(@NotNull final Project project,
916                                                            @NotNull final ProjectSystemId projectSystemId,
917                                                            @NotNull final String externalProjectPath) {
918     final ExternalProjectSettings linkedProjectSettings =
919       ExternalSystemApiUtil.getSettings(project, projectSystemId).getLinkedProjectSettings(externalProjectPath);
920     if (linkedProjectSettings == null) return null;
921
922     return ProjectDataManager.getInstance().getExternalProjectData(
923       project, projectSystemId, linkedProjectSettings.getExternalProjectPath());
924   }
925
926
927   private interface TaskUnderProgress {
928     void execute(@NotNull ProgressIndicator indicator);
929   }
930
931   private static class MyMultiExternalProjectRefreshCallback implements ExternalProjectRefreshCallback {
932
933     @NotNull
934     private final Set<String> myExternalModulePaths;
935     private final Project myProject;
936     private final ProjectDataManager myProjectDataManager;
937     private final int[] myCounter;
938     private final ProjectSystemId myExternalSystemId;
939
940     public MyMultiExternalProjectRefreshCallback(Project project,
941                                                  ProjectDataManager projectDataManager,
942                                                  int[] counter,
943                                                  ProjectSystemId externalSystemId) {
944       myProject = project;
945       myProjectDataManager = projectDataManager;
946       myCounter = counter;
947       myExternalSystemId = externalSystemId;
948       myExternalModulePaths = ContainerUtilRt.newHashSet();
949     }
950
951     @Override
952     public void onSuccess(@Nullable final DataNode<ProjectData> externalProject) {
953       if (externalProject == null) {
954         return;
955       }
956       Collection<DataNode<ModuleData>> moduleNodes = ExternalSystemApiUtil.findAll(externalProject, ProjectKeys.MODULE);
957       for (DataNode<ModuleData> node : moduleNodes) {
958         myExternalModulePaths.add(node.getData().getLinkedExternalProjectPath());
959       }
960       ExternalSystemApiUtil.executeProjectChangeAction(true, new DisposeAwareProjectChange(myProject) {
961         @Override
962         public void execute() {
963           ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(new Runnable() {
964             @Override
965             public void run() {
966               myProjectDataManager.importData(externalProject.getKey(), Collections.singleton(externalProject), myProject, true);
967             }
968           });
969
970           processOrphanProjectLibraries();
971         }
972       });
973       if (--myCounter[0] <= 0) {
974         processOrphanModules();
975       }
976     }
977
978     @Override
979     public void onFailure(@NotNull String errorMessage, @Nullable String errorDetails) {
980       myCounter[0] = Integer.MAX_VALUE; // Don't process orphan modules if there was an error on refresh.
981     }
982
983     private void processOrphanModules() {
984       if(myProject.isDisposed()) return;
985       if(ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) {
986         LOG.info(String.format(
987           "Checking for orphan modules. External paths returned by external system: '%s'", myExternalModulePaths
988         ));
989       }
990       PlatformFacade platformFacade = ServiceManager.getService(PlatformFacade.class);
991       List<Module> orphanIdeModules = ContainerUtilRt.newArrayList();
992       String externalSystemIdAsString = myExternalSystemId.toString();
993
994       for (Module module : platformFacade.getModules(myProject)) {
995         String s = module.getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY);
996         String p = module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY);
997         if(ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) {
998           LOG.info(String.format(
999             "IDE module: EXTERNAL_SYSTEM_ID_KEY - '%s', LINKED_PROJECT_PATH_KEY - '%s'.", s, p
1000           ));
1001         }
1002         if (externalSystemIdAsString.equals(s) && !myExternalModulePaths.contains(p)) {
1003           orphanIdeModules.add(module);
1004           if(ExternalSystemDebugEnvironment.DEBUG_ORPHAN_MODULES_PROCESSING) {
1005             LOG.info(String.format(
1006               "External paths doesn't contain IDE module LINKED_PROJECT_PATH_KEY anymore => add to orphan IDE modules."
1007             ));
1008           }
1009         }
1010       }
1011
1012       if (!orphanIdeModules.isEmpty()) {
1013         ruleOrphanModules(orphanIdeModules, myProject, myExternalSystemId);
1014       }
1015     }
1016
1017     private void processOrphanProjectLibraries() {
1018       PlatformFacade platformFacade = ServiceManager.getService(PlatformFacade.class);
1019       List<Library> orphanIdeLibraries = ContainerUtilRt.newArrayList();
1020
1021       LibraryTable projectLibraryTable = platformFacade.getProjectLibraryTable(myProject);
1022       for (Library library : projectLibraryTable.getLibraries()) {
1023         if (!ExternalSystemApiUtil.isExternalSystemLibrary(library, myExternalSystemId)) continue;
1024         if (ProjectStructureHelper.isOrphanProjectLibrary(library, platformFacade.getModules(myProject))) {
1025           orphanIdeLibraries.add(library);
1026         }
1027       }
1028       for (Library orphanIdeLibrary : orphanIdeLibraries) {
1029         projectLibraryTable.removeLibrary(orphanIdeLibrary);
1030       }
1031     }
1032   }
1033 }