a68dea0503bc25a3d6f49106c77c62b466d91f24
[idea/community.git] / platform / lang-api / src / com / intellij / execution / actions / RunConfigurationProducer.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.actions;
17
18 import com.intellij.execution.*;
19 import com.intellij.execution.configurations.ConfigurationFactory;
20 import com.intellij.execution.configurations.ConfigurationType;
21 import com.intellij.execution.configurations.RunConfiguration;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.extensions.ExtensionPointName;
24 import com.intellij.openapi.extensions.Extensions;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.util.Ref;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.util.containers.ContainerUtil;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import java.util.*;
33
34 /**
35  * Supports creating run configurations from context (by right-clicking a code element in the source editor or the project view). Typically,
36  * run configurations that can be created from context should extend the {@link com.intellij.execution.configurations.LocatableConfigurationBase} class.
37  *
38  * @since 13
39  * @author yole
40  */
41 public abstract class RunConfigurationProducer<T extends RunConfiguration> {
42   public static final ExtensionPointName<RunConfigurationProducer> EP_NAME = ExtensionPointName.create("com.intellij.runConfigurationProducer");
43   private static final Logger LOG = Logger.getInstance("#" + RunConfigurationProducer.class.getName());
44
45   @NotNull
46   public static List<RunConfigurationProducer<?>> getProducers(@NotNull Project project) {
47     RunConfigurationProducerService runConfigurationProducerService = RunConfigurationProducerService.getInstance(project);
48     RunConfigurationProducer[] allProducers = Extensions.getExtensions(EP_NAME);
49
50     List<RunConfigurationProducer<?>> result = ContainerUtil.newArrayListWithCapacity(allProducers.length);
51     for (RunConfigurationProducer producer : allProducers) {
52       if (!runConfigurationProducerService.isIgnored(producer)) {
53         result.add(producer);
54       }
55     }
56
57     return result;
58   }
59
60   private final ConfigurationFactory myConfigurationFactory;
61
62   protected RunConfigurationProducer(final ConfigurationFactory configurationFactory) {
63     myConfigurationFactory = configurationFactory;
64   }
65
66   protected RunConfigurationProducer(final ConfigurationType configurationType) {
67     myConfigurationFactory = configurationType.getConfigurationFactories()[0];
68   }
69
70   public ConfigurationFactory getConfigurationFactory() {
71     return myConfigurationFactory;
72   }
73
74   public ConfigurationType getConfigurationType() {
75     return myConfigurationFactory.getType();
76   }
77
78   /**
79    * Creates a run configuration from the context.
80    *
81    * @param context contains the information about a location in the source code.
82    * @return a container with a prepared run configuration and the context element from which it was created, or null if the context is
83    * not applicable to this run configuration producer.
84    */
85   @Nullable
86   public ConfigurationFromContext createConfigurationFromContext(ConfigurationContext context) {
87     final RunnerAndConfigurationSettings settings = cloneTemplateConfiguration(context);
88     Ref<PsiElement> ref = new Ref<PsiElement>(context.getPsiLocation());
89     try {
90       if (!setupConfigurationFromContext((T)settings.getConfiguration(), context, ref)) {
91        return null;
92      }
93     }
94     catch (ClassCastException e) {
95       LOG.error(myConfigurationFactory + " produced wrong type", e);
96       return null;
97     }
98     return new ConfigurationFromContextImpl(this, settings, ref.get());
99   }
100
101   /**
102    * Sets up a configuration based on the specified context.
103    *
104    * @param configuration a clone of the template run configuration of the specified type
105    * @param context       contains the information about a location in the source code.
106    * @param sourceElement a reference to the source element for the run configuration (by default contains the element at caret,
107    *                      can be updated by the producer to point to a higher-level element in the tree).
108    *
109    * @return true if the context is applicable to this run configuration producer, false if the context is not applicable and the
110    * configuration should be discarded.
111    */
112   protected abstract boolean setupConfigurationFromContext(T configuration, ConfigurationContext context, Ref<PsiElement> sourceElement);
113
114   /**
115    * Checks if the specified configuration was created from the specified context.
116    * @param configuration a configuration instance.
117    * @param context       contains the information about a location in the source code.
118    * @return true if this configuration was created from the specified context, false otherwise.
119    */
120   public abstract boolean isConfigurationFromContext(T configuration, ConfigurationContext context);
121
122   /**
123    * When two configurations are created from the same context by two different producers, checks if the configuration created by
124    * this producer should be discarded in favor of the other one.
125    *
126    * @param self  a configuration created by this producer.
127    * @param other a configuration created by another producer.
128    * @return true if the configuration created by this producer is at least as good as the other one; false if this configuration
129    * should be discarded and the other one should be used instead.
130    * @see #shouldReplace(ConfigurationFromContext, ConfigurationFromContext)
131    */
132   public boolean isPreferredConfiguration(ConfigurationFromContext self, ConfigurationFromContext other) {
133     return true;
134   }
135
136   /**
137    * When two configurations are created from the same context by two different producers, checks if the configuration created by
138    * this producer should replace the other one, that is if the other one should be discarded.
139    *
140    * <p>This is the same relationship as {@link #isPreferredConfiguration(ConfigurationFromContext, ConfigurationFromContext)} but
141    * specified from the "replacement" side.
142    *
143    * @param self  a configuration created by this producer.
144    * @param other a configuration created by another producer.
145    * @return true if the other configuration should be discarded, false otherwise.
146    * @see #isPreferredConfiguration(ConfigurationFromContext, ConfigurationFromContext)
147    */
148   public boolean shouldReplace(ConfigurationFromContext self, ConfigurationFromContext other) {
149     return false;
150   }
151
152   /**
153    * Called before a configuration created from context by this producer is first executed. Can be used to show additional UI for
154    * customizing the created configuration.
155    *
156    * @param configuration a configuration created by this producer.
157    * @param context       the context
158    * @param startRunnable the runnable that needs to be called after additional customization is complete.
159    */
160   public void onFirstRun(ConfigurationFromContext configuration, ConfigurationContext context, Runnable startRunnable) {
161     startRunnable.run();
162   }
163
164   /**
165    * Searches the list of existing run configurations to find one created from this context. Returns one if found, or tries to create
166    * a new configuration from this context if not found.
167    *
168    * @param context contains the information about a location in the source code.
169    * @return a configuration (new or existing) matching the context, or null if the context is not applicable to this producer.
170    */
171   @Nullable
172   public ConfigurationFromContext findOrCreateConfigurationFromContext(ConfigurationContext context) {
173     Location location = context.getLocation();
174     if (location == null) {
175       return null;
176     }
177
178     ConfigurationFromContext fromContext = createConfigurationFromContext(context);
179     if (fromContext != null) {
180       final PsiElement psiElement = fromContext.getSourceElement();
181       final Location<PsiElement> _location = PsiLocation.fromPsiElement(psiElement, location.getModule());
182       if (_location != null) {
183         // replace with existing configuration if any
184         final RunManager runManager = RunManager.getInstance(context.getProject());
185         final ConfigurationType type = fromContext.getConfigurationType();
186         final List<RunnerAndConfigurationSettings> configurations = runManager.getConfigurationSettingsList(type);
187         final RunnerAndConfigurationSettings settings = findExistingConfiguration(context);
188         if (settings != null) {
189           fromContext.setConfigurationSettings(settings);
190         } else {
191           final ArrayList<String> currentNames = new ArrayList<String>();
192           for (RunnerAndConfigurationSettings configurationSettings : configurations) {
193             currentNames.add(configurationSettings.getName());
194           }
195           RunConfiguration configuration = fromContext.getConfiguration();
196           String name = configuration.getName();
197           if (name == null) {
198             LOG.error(configuration);
199             name = "Unnamed";
200           }
201           configuration.setName(RunManager.suggestUniqueName(name, currentNames));
202         }
203       }
204     }
205
206     return fromContext;
207   }
208
209   /**
210    * Searches the list of existing run configurations to find one created from this context. Returns one if found.
211    *
212    * @param context contains the information about a location in the source code.
213    * @return an existing configuration matching the context, or null if no such configuration is found.
214    */
215   @Nullable
216   public RunnerAndConfigurationSettings findExistingConfiguration(ConfigurationContext context) {
217     final RunManager runManager = RunManager.getInstance(context.getProject());
218     final List<RunnerAndConfigurationSettings> configurations = runManager.getConfigurationSettingsList(myConfigurationFactory.getType());
219     for (RunnerAndConfigurationSettings configurationSettings : configurations) {
220       if (isConfigurationFromContext((T) configurationSettings.getConfiguration(), context)) {
221         return configurationSettings;
222       }
223     }
224     return null;
225   }
226
227   protected RunnerAndConfigurationSettings cloneTemplateConfiguration(@NotNull final ConfigurationContext context) {
228     final RunConfiguration original = context.getOriginalConfiguration(myConfigurationFactory.getType());
229     if (original != null) {
230       return RunManager.getInstance(context.getProject()).createConfiguration(original.clone(), myConfigurationFactory);
231     }
232     return RunManager.getInstance(context.getProject()).createRunConfiguration("", myConfigurationFactory);
233   }
234
235   @NotNull
236   public static <T extends RunConfigurationProducer> T getInstance(Class<? extends T> aClass) {
237     for (RunConfigurationProducer producer : Extensions.getExtensions(EP_NAME)) {
238       if (aClass.isInstance(producer)) {
239         return (T)producer;
240       }
241     }
242     assert false : aClass;
243     return null;
244   }
245
246   @Nullable
247   public RunConfiguration createLightConfiguration(@NotNull final ConfigurationContext context) {
248     RunConfiguration configuration = myConfigurationFactory.createTemplateConfiguration(context.getProject());
249     final Ref<PsiElement> ref = new Ref<PsiElement>(context.getPsiLocation());
250     try {
251       if (!setupConfigurationFromContext((T)configuration, context, ref)) {
252         return null;
253       }
254     }
255     catch (ClassCastException e) {
256       LOG.error(myConfigurationFactory + " produced wrong type", e);
257       return null;
258     }
259     return configuration;
260   }
261 }