WEB-20882 Preserve setting `toggle auto-test` between IDEA restarts
[idea/community.git] / platform / testRunner / src / com / intellij / execution / testframework / autotest / AutoTestManager.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.testframework.autotest;
17
18 import com.intellij.execution.*;
19 import com.intellij.execution.configurations.RunConfiguration;
20 import com.intellij.execution.configurations.RunProfile;
21 import com.intellij.execution.impl.RunManagerImpl;
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.process.ProcessListener;
26 import com.intellij.execution.runners.ExecutionEnvironment;
27 import com.intellij.execution.runners.ExecutionUtil;
28 import com.intellij.execution.ui.RunContentDescriptor;
29 import com.intellij.execution.ui.RunContentManager;
30 import com.intellij.ide.DataManager;
31 import com.intellij.ide.scratch.ScratchFileService;
32 import com.intellij.ide.util.PropertiesComponent;
33 import com.intellij.openapi.actionSystem.LangDataKeys;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.application.ModalityState;
36 import com.intellij.openapi.components.*;
37 import com.intellij.openapi.fileEditor.FileEditorManager;
38 import com.intellij.openapi.project.Project;
39 import com.intellij.openapi.util.Condition;
40 import com.intellij.openapi.util.Key;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.ui.content.Content;
43 import com.intellij.util.Consumer;
44 import com.intellij.util.ObjectUtils;
45 import com.intellij.util.containers.ContainerUtil;
46 import com.intellij.util.xmlb.annotations.AbstractCollection;
47 import com.intellij.util.xmlb.annotations.Attribute;
48 import com.intellij.util.xmlb.annotations.Tag;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51
52 import javax.swing.*;
53 import java.util.List;
54 import java.util.Set;
55
56 /**
57  * @author yole
58  */
59 @State(
60   name = "AutoTestManager",
61   storages = {@Storage(StoragePathMacros.WORKSPACE_FILE)}
62 )
63 public class AutoTestManager implements PersistentStateComponent<AutoTestManager.State> {
64   private static final String AUTO_TEST_MANAGER_DELAY = "auto.test.manager.delay";
65   private static final int AUTO_TEST_MANAGER_DELAY_DEFAULT = 3000;
66
67   private static final Key<ProcessListener> ON_TERMINATION_RESTARTER_KEY = Key.create("auto.test.manager.on.termination.restarter");
68
69   private final Project myProject;
70   private int myDelayMillis;
71   private DelayedDocumentWatcher myDocumentWatcher;
72   private final Set<RunProfile> myEnabledRunProfiles = ContainerUtil.newHashSet();
73
74   @NotNull
75   public static AutoTestManager getInstance(Project project) {
76     return ServiceManager.getService(project, AutoTestManager.class);
77   }
78
79   public AutoTestManager(@NotNull Project project) {
80     myProject = project;
81     myDelayMillis = PropertiesComponent.getInstance(project).getInt(AUTO_TEST_MANAGER_DELAY, AUTO_TEST_MANAGER_DELAY_DEFAULT);
82     myDocumentWatcher = createWatcher();
83   }
84
85   @NotNull
86   private DelayedDocumentWatcher createWatcher() {
87     return new DelayedDocumentWatcher(myProject, myDelayMillis, new Consumer<Integer>() {
88       @Override
89       public void consume(Integer modificationStamp) {
90         restartAllAutoTests(modificationStamp);
91       }
92     }, new Condition<VirtualFile>() {
93       @Override
94       public boolean value(VirtualFile file) {
95         if (ScratchFileService.getInstance().getRootType(file) != null) {
96           return false;
97         }
98         // Vladimir.Krivosheev - I don't know, why AutoTestManager checks it, but old behavior is preserved
99         return FileEditorManager.getInstance(myProject).isFileOpen(file);
100       }
101     });
102   }
103
104   public void setAutoTestEnabled(@NotNull RunContentDescriptor descriptor, @NotNull ExecutionEnvironment environment, boolean enabled) {
105     Content content = descriptor.getAttachedContent();
106     if (content != null) {
107       if (enabled) {
108         myEnabledRunProfiles.add(environment.getRunProfile());
109         myDocumentWatcher.activate();
110       }
111       else {
112         myEnabledRunProfiles.remove(environment.getRunProfile());
113         if (!hasEnabledAutoTests()) {
114           myDocumentWatcher.deactivate();
115         }
116         ProcessHandler processHandler = descriptor.getProcessHandler();
117         if (processHandler != null) {
118           clearRestarterListener(processHandler);
119         }
120       }
121     }
122   }
123
124   private boolean hasEnabledAutoTests() {
125     RunContentManager contentManager = ExecutionManager.getInstance(myProject).getContentManager();
126     for (RunContentDescriptor descriptor : contentManager.getAllDescriptors()) {
127       if (isAutoTestEnabledForDescriptor(descriptor)) {
128         return true;
129       }
130     }
131     return false;
132   }
133
134   public boolean isAutoTestEnabled(@NotNull RunContentDescriptor descriptor) {
135     return isAutoTestEnabledForDescriptor(descriptor);
136   }
137
138   private boolean isAutoTestEnabledForDescriptor(@NotNull RunContentDescriptor descriptor) {
139     Content content = descriptor.getAttachedContent();
140     if (content != null) {
141       ExecutionEnvironment environment = getCurrentEnvironment(content);
142       return environment != null && myEnabledRunProfiles.contains(environment.getRunProfile());
143     }
144     return false;
145   }
146
147   @Nullable
148   private static ExecutionEnvironment getCurrentEnvironment(@NotNull Content content) {
149     JComponent component = content.getComponent();
150     if (component == null) {
151       return null;
152     }
153     return LangDataKeys.EXECUTION_ENVIRONMENT.getData(DataManager.getInstance().getDataContext(component));
154   }
155
156   private static void clearRestarterListener(@NotNull ProcessHandler processHandler) {
157     ProcessListener restarterListener = ON_TERMINATION_RESTARTER_KEY.get(processHandler, null);
158     if (restarterListener != null) {
159       processHandler.removeProcessListener(restarterListener);
160       ON_TERMINATION_RESTARTER_KEY.set(processHandler, null);
161     }
162   }
163
164   private void restartAllAutoTests(int modificationStamp) {
165     RunContentManager contentManager = ExecutionManager.getInstance(myProject).getContentManager();
166     boolean active = false;
167     for (RunContentDescriptor descriptor : contentManager.getAllDescriptors()) {
168       if (isAutoTestEnabledForDescriptor(descriptor)) {
169         restartAutoTest(descriptor, modificationStamp, myDocumentWatcher);
170         active = true;
171       }
172     }
173     if (!active) {
174       myDocumentWatcher.deactivate();
175     }
176   }
177
178   private void restartAutoTest(@NotNull RunContentDescriptor descriptor,
179                                int modificationStamp,
180                                @NotNull DelayedDocumentWatcher documentWatcher) {
181     ProcessHandler processHandler = descriptor.getProcessHandler();
182     if (processHandler != null && !processHandler.isProcessTerminated()) {
183       scheduleRestartOnTermination(descriptor, processHandler, modificationStamp, documentWatcher);
184     }
185     else {
186       restart(descriptor);
187     }
188   }
189
190   private void scheduleRestartOnTermination(@NotNull final RunContentDescriptor descriptor,
191                                             @NotNull final ProcessHandler processHandler,
192                                             final int modificationStamp,
193                                             @NotNull final DelayedDocumentWatcher documentWatcher) {
194     ProcessListener restarterListener = ON_TERMINATION_RESTARTER_KEY.get(processHandler);
195     if (restarterListener != null) {
196       clearRestarterListener(processHandler);
197     }
198     restarterListener = new ProcessAdapter() {
199       @Override
200       public void processTerminated(ProcessEvent event) {
201         clearRestarterListener(processHandler);
202         ApplicationManager.getApplication().invokeLater(new Runnable() {
203           @Override
204           public void run() {
205             if (isAutoTestEnabledForDescriptor(descriptor) && documentWatcher.isUpToDate(modificationStamp)) {
206               restart(descriptor);
207             }
208           }
209         }, ModalityState.any());
210       }
211     };
212     ON_TERMINATION_RESTARTER_KEY.set(processHandler, restarterListener);
213     processHandler.addProcessListener(restarterListener);
214   }
215
216   private static void restart(@NotNull RunContentDescriptor descriptor) {
217     descriptor.setActivateToolWindowWhenAdded(false);
218     descriptor.setReuseToolWindowActivation(true);
219     ExecutionUtil.restart(descriptor);
220   }
221
222   int getDelay() {
223     return myDelayMillis;
224   }
225
226   void setDelay(int delay) {
227     myDelayMillis = delay;
228     myDocumentWatcher.deactivate();
229     myDocumentWatcher = createWatcher();
230     if (hasEnabledAutoTests()) {
231       myDocumentWatcher.activate();
232     }
233     PropertiesComponent.getInstance(myProject).setValue(AUTO_TEST_MANAGER_DELAY, myDelayMillis, AUTO_TEST_MANAGER_DELAY_DEFAULT);
234   }
235
236   @Nullable
237   @Override
238   public State getState() {
239     State state = new State();
240     for (RunProfile profile : myEnabledRunProfiles) {
241       RunConfiguration runConfiguration = ObjectUtils.tryCast(profile, RunConfiguration.class);
242       if (runConfiguration != null) {
243         RunConfigurationDescriptor descriptor = new RunConfigurationDescriptor();
244         descriptor.myType = runConfiguration.getType().getId();
245         descriptor.myName = runConfiguration.getName();
246         state.myEnabledRunConfigurations.add(descriptor);
247       }
248     }
249     return state;
250   }
251
252   @Override
253   public void loadState(State state) {
254     List<RunConfiguration> configurations = ContainerUtil.newArrayList();
255     RunManagerImpl runManager = RunManagerImpl.getInstanceImpl(myProject);
256     for (RunConfigurationDescriptor descriptor : state.myEnabledRunConfigurations) {
257       RunnerAndConfigurationSettings settings = runManager.findConfigurationByTypeAndName(descriptor.myType,
258                                                                                           descriptor.myName);
259       RunConfiguration configuration = settings != null ? settings.getConfiguration() : null;
260       if (configuration != null) {
261         configurations.add(configuration);
262       }
263     }
264     myEnabledRunProfiles.clear();
265     myEnabledRunProfiles.addAll(configurations);
266   }
267
268   static class State {
269     @Tag("enabled-run-configurations")
270     @AbstractCollection(surroundWithTag = false)
271     List<RunConfigurationDescriptor> myEnabledRunConfigurations = ContainerUtil.newArrayList();
272   }
273
274   @Tag("run-configuration")
275   static class RunConfigurationDescriptor {
276     @Attribute("type")
277     String myType;
278
279     @Attribute("name")
280     String myName;
281   }
282 }