IDEA-136705 (delay post-execution refresh to allow FS changes to reach the IDE)
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / impl / ExecutionManagerImpl.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.execution.impl;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.execution.*;
20 import com.intellij.execution.configuration.CompatibilityAwareRunProfile;
21 import com.intellij.execution.configurations.RunConfiguration;
22 import com.intellij.execution.configurations.RunProfile;
23 import com.intellij.execution.configurations.RunProfileState;
24 import com.intellij.execution.process.ProcessAdapter;
25 import com.intellij.execution.process.ProcessEvent;
26 import com.intellij.execution.process.ProcessHandler;
27 import com.intellij.execution.runners.ExecutionEnvironment;
28 import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
29 import com.intellij.execution.runners.ExecutionUtil;
30 import com.intellij.execution.runners.ProgramRunner;
31 import com.intellij.execution.ui.RunContentDescriptor;
32 import com.intellij.execution.ui.RunContentManager;
33 import com.intellij.execution.ui.RunContentManagerImpl;
34 import com.intellij.ide.SaveAndSyncHandler;
35 import com.intellij.openapi.Disposable;
36 import com.intellij.openapi.actionSystem.DataContext;
37 import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.diagnostic.Logger;
40 import com.intellij.openapi.project.DumbService;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.ui.DialogWrapper;
43 import com.intellij.openapi.ui.Messages;
44 import com.intellij.openapi.util.Condition;
45 import com.intellij.openapi.util.Disposer;
46 import com.intellij.openapi.util.Key;
47 import com.intellij.openapi.util.Trinity;
48 import com.intellij.openapi.util.text.StringUtil;
49 import com.intellij.ui.docking.DockManager;
50 import com.intellij.util.Alarm;
51 import com.intellij.util.SmartList;
52 import com.intellij.util.containers.ContainerUtil;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import javax.swing.*;
57 import java.util.Collections;
58 import java.util.List;
59
60 public class ExecutionManagerImpl extends ExecutionManager implements Disposable {
61   public static final Key<Object> EXECUTION_SESSION_ID_KEY = Key.create("EXECUTION_SESSION_ID_KEY");
62
63   private static final Logger LOG = Logger.getInstance(ExecutionManagerImpl.class);
64   private static final ProcessHandler[] EMPTY_PROCESS_HANDLERS = new ProcessHandler[0];
65
66   private final Project myProject;
67
68   private RunContentManagerImpl myContentManager;
69   private final Alarm awaitingTerminationAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
70   private final List<Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor>> myRunningConfigurations =
71     ContainerUtil.createLockFreeCopyOnWriteList();
72
73   ExecutionManagerImpl(@NotNull Project project) {
74     myProject = project;
75   }
76
77   @Override
78   public void dispose() {
79     for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) {
80       Disposer.dispose(trinity.first);
81     }
82     myRunningConfigurations.clear();
83   }
84
85   @NotNull
86   @Override
87   public RunContentManager getContentManager() {
88     if (myContentManager == null) {
89       myContentManager = new RunContentManagerImpl(myProject, DockManager.getInstance(myProject));
90       Disposer.register(myProject, myContentManager);
91     }
92     return myContentManager;
93   }
94
95   @NotNull
96   @Override
97   public ProcessHandler[] getRunningProcesses() {
98     if (myContentManager == null) return EMPTY_PROCESS_HANDLERS;
99     List<ProcessHandler> handlers = null;
100     for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) {
101       ProcessHandler processHandler = descriptor.getProcessHandler();
102       if (processHandler != null) {
103         if (handlers == null) {
104           handlers = new SmartList<ProcessHandler>();
105         }
106         handlers.add(processHandler);
107       }
108     }
109     return handlers == null ? EMPTY_PROCESS_HANDLERS : handlers.toArray(new ProcessHandler[handlers.size()]);
110   }
111
112   @Override
113   public void compileAndRun(@NotNull final Runnable startRunnable,
114                             @NotNull final ExecutionEnvironment environment,
115                             @Nullable final RunProfileState state,
116                             @Nullable final Runnable onCancelRunnable) {
117     long id = environment.getExecutionId();
118     if (id == 0) {
119       id = environment.assignNewExecutionId();
120     }
121
122     RunProfile profile = environment.getRunProfile();
123     if (!(profile instanceof RunConfiguration)) {
124       startRunnable.run();
125       return;
126     }
127
128     final RunConfiguration runConfiguration = (RunConfiguration)profile;
129     final List<BeforeRunTask> beforeRunTasks = RunManagerEx.getInstanceEx(myProject).getBeforeRunTasks(runConfiguration);
130     if (beforeRunTasks.isEmpty()) {
131       startRunnable.run();
132     }
133     else {
134       DataContext context = environment.getDataContext();
135       final DataContext projectContext = context != null ? context : SimpleDataContext.getProjectContext(myProject);
136       final long finalId = id;
137       final Long executionSessionId = new Long(id);
138       ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
139         /** @noinspection SSBasedInspection*/
140         @Override
141         public void run() {
142           for (BeforeRunTask task : beforeRunTasks) {
143             if (myProject.isDisposed()) {
144               return;
145             }
146             @SuppressWarnings("unchecked")
147             BeforeRunTaskProvider<BeforeRunTask> provider = BeforeRunTaskProvider.getProvider(myProject, task.getProviderId());
148             if (provider == null) {
149               LOG.warn("Cannot find BeforeRunTaskProvider for id='" + task.getProviderId() + "'");
150               continue;
151             }
152             ExecutionEnvironment taskEnvironment = new ExecutionEnvironmentBuilder(environment).contentToReuse(null).build();
153             taskEnvironment.setExecutionId(finalId);
154             EXECUTION_SESSION_ID_KEY.set(taskEnvironment, executionSessionId);
155             if (!provider.executeTask(projectContext, runConfiguration, taskEnvironment, task)) {
156               if (onCancelRunnable != null) {
157                 SwingUtilities.invokeLater(onCancelRunnable);
158               }
159               return;
160             }
161           }
162           // important! Do not use DumbService.smartInvokeLater here because it depends on modality state
163           // and execution of startRunnable could be skipped if modality state check fails
164           SwingUtilities.invokeLater(new Runnable() {
165             @Override
166             public void run() {
167               if (!myProject.isDisposed()) {
168                 DumbService.getInstance(myProject).runWhenSmart(startRunnable);
169               }
170             }
171           });
172         }
173       });
174     }
175   }
176
177   @Override
178   public void startRunProfile(@NotNull final RunProfileStarter starter,
179                               @NotNull final RunProfileState state,
180                               @NotNull final ExecutionEnvironment environment) {
181     final Project project = environment.getProject();
182     RunContentDescriptor reuseContent = getContentManager().getReuseContent(environment);
183     if (reuseContent != null) {
184       reuseContent.setExecutionId(environment.getExecutionId());
185       environment.setContentToReuse(reuseContent);
186     }
187
188     final Executor executor = environment.getExecutor();
189     project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStartScheduled(executor.getId(), environment);
190
191     Runnable startRunnable = new Runnable() {
192       @Override
193       public void run() {
194         if (project.isDisposed()) {
195           return;
196         }
197
198         RunProfile profile = environment.getRunProfile();
199         boolean started = false;
200         try {
201           project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarting(executor.getId(), environment);
202
203           final RunContentDescriptor descriptor = starter.execute(project, executor, state, environment.getContentToReuse(), environment);
204           if (descriptor != null) {
205             environment.setContentToReuse(descriptor);
206             final Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity =
207               Trinity.create(descriptor, environment.getRunnerAndConfigurationSettings(), executor);
208             myRunningConfigurations.add(trinity);
209             Disposer.register(descriptor, new Disposable() {
210               @Override
211               public void dispose() {
212                 myRunningConfigurations.remove(trinity);
213               }
214             });
215             getContentManager().showRunContent(executor, descriptor, environment.getContentToReuse());
216             final ProcessHandler processHandler = descriptor.getProcessHandler();
217             if (processHandler != null) {
218               if (!processHandler.isStartNotified()) {
219                 processHandler.startNotify();
220               }
221               project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processStarted(executor.getId(), environment, processHandler);
222               started = true;
223               processHandler.addProcessListener(new ProcessExecutionListener(project, profile, processHandler));
224             }
225           }
226         }
227         catch (ExecutionException e) {
228           ExecutionUtil.handleExecutionError(project, executor.getToolWindowId(), profile, e);
229           LOG.info(e);
230         }
231         finally {
232           if (!started) {
233             project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
234           }
235         }
236       }
237     };
238
239     if (ApplicationManager.getApplication().isUnitTestMode()) {
240       startRunnable.run();
241     }
242     else {
243       compileAndRun(startRunnable, environment, state, new Runnable() {
244         @Override
245         public void run() {
246           if (!project.isDisposed()) {
247             project.getMessageBus().syncPublisher(EXECUTION_TOPIC).processNotStarted(executor.getId(), environment);
248           }
249         }
250       });
251     }
252   }
253
254   @Override
255   public void restartRunProfile(@NotNull Project project,
256                                 @NotNull Executor executor,
257                                 @NotNull ExecutionTarget target,
258                                 @Nullable RunnerAndConfigurationSettings configuration,
259                                 @Nullable ProcessHandler processHandler) {
260     ExecutionEnvironmentBuilder builder = createEnvironmentBuilder(project, executor, configuration);
261     if (processHandler != null) {
262       for (RunContentDescriptor descriptor : getContentManager().getAllDescriptors()) {
263         if (descriptor.getProcessHandler() == processHandler) {
264           builder.contentToReuse(descriptor);
265           break;
266         }
267       }
268     }
269     restartRunProfile(builder.target(target).build());
270   }
271
272   @NotNull
273   private static ExecutionEnvironmentBuilder createEnvironmentBuilder(@NotNull Project project, @NotNull Executor executor, @Nullable RunnerAndConfigurationSettings configuration) {
274     ExecutionEnvironmentBuilder builder = new ExecutionEnvironmentBuilder(project, executor);
275
276     ProgramRunner runner = RunnerRegistry.getInstance().getRunner(executor.getId(), configuration != null ? configuration.getConfiguration() : null);
277     if (runner == null && configuration != null) {
278       LOG.error("Cannot find runner for " + configuration.getName());
279     }
280     else if (runner != null) {
281       assert configuration != null;
282       builder.runnerAndSettings(runner, configuration);
283     }
284     return builder;
285   }
286
287   @Override
288   public void restartRunProfile(@NotNull Project project,
289                                 @NotNull Executor executor,
290                                 @NotNull ExecutionTarget target,
291                                 @Nullable RunnerAndConfigurationSettings configuration,
292                                 @Nullable RunContentDescriptor currentDescriptor) {
293     ExecutionEnvironmentBuilder builder = createEnvironmentBuilder(project, executor, configuration);
294     restartRunProfile(builder.target(target).contentToReuse(currentDescriptor).build());
295   }
296
297   @Override
298   public void restartRunProfile(@Nullable ProgramRunner runner,
299                                 @NotNull ExecutionEnvironment environment,
300                                 @Nullable RunContentDescriptor currentDescriptor) {
301     ExecutionEnvironmentBuilder builder = new ExecutionEnvironmentBuilder(environment).contentToReuse(currentDescriptor);
302     if (runner != null) {
303       builder.runner(runner);
304     }
305     restartRunProfile(builder.build());
306   }
307
308   public static boolean isProcessRunning(@Nullable RunContentDescriptor descriptor) {
309     ProcessHandler processHandler = descriptor == null ? null : descriptor.getProcessHandler();
310     return processHandler != null && !processHandler.isProcessTerminated();
311   }
312
313   @Override
314   public void restartRunProfile(@NotNull final ExecutionEnvironment environment) {
315     RunnerAndConfigurationSettings configuration = environment.getRunnerAndConfigurationSettings();
316
317     List<RunContentDescriptor> runningIncompatible;
318     if (configuration == null) {
319       runningIncompatible = Collections.emptyList();
320     }
321     else {
322       runningIncompatible = getIncompatibleRunningDescriptors(configuration);
323     }
324
325     RunContentDescriptor contentToReuse = environment.getContentToReuse();
326     final List<RunContentDescriptor> runningOfTheSameType = new SmartList<RunContentDescriptor>();
327     if (configuration != null && configuration.isSingleton()) {
328       runningOfTheSameType.addAll(getRunningDescriptorsOfTheSameConfigType(configuration));
329     }
330     else if (isProcessRunning(contentToReuse)) {
331       runningOfTheSameType.add(contentToReuse);
332     }
333
334     List<RunContentDescriptor> runningToStop = ContainerUtil.concat(runningOfTheSameType, runningIncompatible);
335     if (!runningToStop.isEmpty()) {
336       if (configuration != null) {
337         if (!runningOfTheSameType.isEmpty()
338             && (runningOfTheSameType.size() > 1 || contentToReuse == null || runningOfTheSameType.get(0) != contentToReuse) &&
339             !userApprovesStopForSameTypeConfigurations(environment.getProject(), configuration.getName(), runningOfTheSameType.size())) {
340           return;
341         }
342         if (!runningIncompatible.isEmpty()
343             && !userApprovesStopForIncompatibleConfigurations(myProject, configuration.getName(), runningIncompatible)) {
344           return;
345         }
346       }
347
348       for (RunContentDescriptor descriptor : runningToStop) {
349         stop(descriptor);
350       }
351     }
352
353     awaitingTerminationAlarm.addRequest(new Runnable() {
354       @Override
355       public void run() {
356         if (ExecutorRegistry.getInstance().isStarting(environment)) {
357           awaitingTerminationAlarm.addRequest(this, 100);
358           return;
359         }
360
361         for (RunContentDescriptor descriptor : runningOfTheSameType) {
362           ProcessHandler processHandler = descriptor.getProcessHandler();
363           if (processHandler != null && !processHandler.isProcessTerminated()) {
364             awaitingTerminationAlarm.addRequest(this, 100);
365             return;
366           }
367         }
368         start(environment);
369       }
370     }, 50);
371   }
372
373   private static void start(@NotNull ExecutionEnvironment environment) {
374     RunnerAndConfigurationSettings settings = environment.getRunnerAndConfigurationSettings();
375     ProgramRunnerUtil.executeConfiguration(environment, settings != null && settings.isEditBeforeRun(), true);
376   }
377
378   private static boolean userApprovesStopForSameTypeConfigurations(Project project, String configName, int instancesCount) {
379     RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project);
380     final RunManagerConfig config = runManager.getConfig();
381     if (!config.isRestartRequiresConfirmation()) return true;
382
383     DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() {
384       @Override
385       public boolean isToBeShown() {
386         return config.isRestartRequiresConfirmation();
387       }
388
389       @Override
390       public void setToBeShown(boolean value, int exitCode) {
391         config.setRestartRequiresConfirmation(value);
392       }
393
394       @Override
395       public boolean canBeHidden() {
396         return true;
397       }
398
399       @Override
400       public boolean shouldSaveOptionsOnCancel() {
401         return false;
402       }
403
404       @NotNull
405       @Override
406       public String getDoNotShowMessage() {
407         return CommonBundle.message("dialog.options.do.not.show");
408       }
409     };
410     return Messages.showOkCancelDialog(
411       project,
412       ExecutionBundle.message("rerun.singleton.confirmation.message", configName, instancesCount),
413       ExecutionBundle.message("process.is.running.dialog.title", configName),
414       ExecutionBundle.message("rerun.confirmation.button.text"),
415       CommonBundle.message("button.cancel"),
416       Messages.getQuestionIcon(), option) == Messages.OK;
417   }
418
419   private static boolean userApprovesStopForIncompatibleConfigurations(Project project,
420                                                                        String configName,
421                                                                        List<RunContentDescriptor> runningIncompatibleDescriptors) {
422     RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(project);
423     final RunManagerConfig config = runManager.getConfig();
424     if (!config.isStopIncompatibleRequiresConfirmation()) return true;
425
426     DialogWrapper.DoNotAskOption option = new DialogWrapper.DoNotAskOption() {
427       @Override
428       public boolean isToBeShown() {
429         return config.isStopIncompatibleRequiresConfirmation();
430       }
431
432       @Override
433       public void setToBeShown(boolean value, int exitCode) {
434         config.setStopIncompatibleRequiresConfirmation(value);
435       }
436
437       @Override
438       public boolean canBeHidden() {
439         return true;
440       }
441
442       @Override
443       public boolean shouldSaveOptionsOnCancel() {
444         return false;
445       }
446
447       @NotNull
448       @Override
449       public String getDoNotShowMessage() {
450         return CommonBundle.message("dialog.options.do.not.show");
451       }
452     };
453
454     final StringBuilder names = new StringBuilder();
455     for (final RunContentDescriptor descriptor : runningIncompatibleDescriptors) {
456       String name = descriptor.getDisplayName();
457       if (names.length() > 0) {
458         names.append(", ");
459       }
460       names.append(StringUtil.isEmpty(name) ? ExecutionBundle.message("run.configuration.no.name")
461                                                        : String.format("'%s'", name));
462     }
463
464     //noinspection DialogTitleCapitalization
465     return Messages.showOkCancelDialog(
466       project,
467       ExecutionBundle.message("stop.incompatible.confirmation.message",
468                               configName, names.toString(), runningIncompatibleDescriptors.size()),
469       ExecutionBundle.message("incompatible.configuration.is.running.dialog.title", runningIncompatibleDescriptors.size()),
470       ExecutionBundle.message("stop.incompatible.confirmation.button.text"),
471       CommonBundle.message("button.cancel"),
472       Messages.getQuestionIcon(), option) == Messages.OK;
473   }
474
475   @NotNull
476   private List<RunContentDescriptor> getRunningDescriptorsOfTheSameConfigType(@NotNull final RunnerAndConfigurationSettings configurationAndSettings) {
477     return getRunningDescriptors(new Condition<RunnerAndConfigurationSettings>() {
478       @Override
479       public boolean value(@Nullable RunnerAndConfigurationSettings runningConfigurationAndSettings) {
480         return configurationAndSettings == runningConfigurationAndSettings;
481       }
482     });
483   }
484
485   @NotNull
486   private List<RunContentDescriptor> getIncompatibleRunningDescriptors(@NotNull RunnerAndConfigurationSettings configurationAndSettings) {
487     final RunConfiguration configurationToCheckCompatibility = configurationAndSettings.getConfiguration();
488     return getRunningDescriptors(new Condition<RunnerAndConfigurationSettings>() {
489       @Override
490       public boolean value(@Nullable RunnerAndConfigurationSettings runningConfigurationAndSettings) {
491         RunConfiguration runningConfiguration = runningConfigurationAndSettings == null ? null : runningConfigurationAndSettings.getConfiguration();
492         if (runningConfiguration == null || !(runningConfiguration instanceof CompatibilityAwareRunProfile)) {
493           return false;
494         }
495         return ((CompatibilityAwareRunProfile)runningConfiguration).mustBeStoppedToRun(configurationToCheckCompatibility);
496       }
497     });
498   }
499
500   @NotNull
501   private List<RunContentDescriptor> getRunningDescriptors(@NotNull Condition<RunnerAndConfigurationSettings> condition) {
502     List<RunContentDescriptor> result = new SmartList<RunContentDescriptor>();
503     for (Trinity<RunContentDescriptor, RunnerAndConfigurationSettings, Executor> trinity : myRunningConfigurations) {
504       if (condition.value(trinity.getSecond())) {
505         ProcessHandler processHandler = trinity.getFirst().getProcessHandler();
506         if (processHandler != null && !processHandler.isProcessTerminating() && !processHandler.isProcessTerminated()) {
507           result.add(trinity.getFirst());
508         }
509       }
510     }
511     return result;
512   }
513
514   private static void stop(@Nullable RunContentDescriptor descriptor) {
515     ProcessHandler processHandler = descriptor != null ? descriptor.getProcessHandler() : null;
516     if (processHandler == null) {
517       return;
518     }
519
520     if (processHandler instanceof KillableProcess && processHandler.isProcessTerminating()) {
521       ((KillableProcess)processHandler).killProcess();
522       return;
523     }
524
525     if (!processHandler.isProcessTerminated()) {
526       if (processHandler.detachIsDefault()) {
527         processHandler.detachProcess();
528       }
529       else {
530         processHandler.destroyProcess();
531       }
532     }
533   }
534
535   private static class ProcessExecutionListener extends ProcessAdapter {
536     private final Project myProject;
537     private final RunProfile myProfile;
538     private final ProcessHandler myProcessHandler;
539
540     public ProcessExecutionListener(Project project, RunProfile profile, ProcessHandler processHandler) {
541       myProject = project;
542       myProfile = profile;
543       myProcessHandler = processHandler;
544     }
545
546     @Override
547     public void processTerminated(ProcessEvent event) {
548       if (myProject.isDisposed()) return;
549
550       myProject.getMessageBus().syncPublisher(EXECUTION_TOPIC).processTerminated(myProfile, myProcessHandler);
551
552       SaveAndSyncHandler.getInstance().scheduleRefresh();
553     }
554
555     @Override
556     public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
557       if (myProject.isDisposed()) return;
558
559       myProject.getMessageBus().syncPublisher(EXECUTION_TOPIC).processTerminating(myProfile, myProcessHandler);
560     }
561   }
562 }