refactor "suggestUniqueName"
[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.List;
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 RunnerAndConfigurationSettings settings = findExistingConfiguration(context);
187         if (settings != null) {
188           fromContext.setConfigurationSettings(settings);
189         } else {
190           runManager.setUniqueNameIfNeed(fromContext.getConfiguration());
191         }
192       }
193     }
194
195     return fromContext;
196   }
197
198   /**
199    * Searches the list of existing run configurations to find one created from this context. Returns one if found.
200    *
201    * @param context contains the information about a location in the source code.
202    * @return an existing configuration matching the context, or null if no such configuration is found.
203    */
204   @Nullable
205   public RunnerAndConfigurationSettings findExistingConfiguration(ConfigurationContext context) {
206     final RunManager runManager = RunManager.getInstance(context.getProject());
207     final List<RunnerAndConfigurationSettings> configurations = runManager.getConfigurationSettingsList(myConfigurationFactory.getType());
208     for (RunnerAndConfigurationSettings configurationSettings : configurations) {
209       if (isConfigurationFromContext((T) configurationSettings.getConfiguration(), context)) {
210         return configurationSettings;
211       }
212     }
213     return null;
214   }
215
216   protected RunnerAndConfigurationSettings cloneTemplateConfiguration(@NotNull final ConfigurationContext context) {
217     final RunConfiguration original = context.getOriginalConfiguration(myConfigurationFactory.getType());
218     if (original != null) {
219       return RunManager.getInstance(context.getProject()).createConfiguration(original.clone(), myConfigurationFactory);
220     }
221     return RunManager.getInstance(context.getProject()).createRunConfiguration("", myConfigurationFactory);
222   }
223
224   @NotNull
225   public static <T extends RunConfigurationProducer> T getInstance(Class<? extends T> aClass) {
226     for (RunConfigurationProducer producer : Extensions.getExtensions(EP_NAME)) {
227       if (aClass.isInstance(producer)) {
228         return (T)producer;
229       }
230     }
231     assert false : aClass;
232     return null;
233   }
234
235   @Nullable
236   public RunConfiguration createLightConfiguration(@NotNull final ConfigurationContext context) {
237     RunConfiguration configuration = myConfigurationFactory.createTemplateConfiguration(context.getProject());
238     final Ref<PsiElement> ref = new Ref<PsiElement>(context.getPsiLocation());
239     try {
240       if (!setupConfigurationFromContext((T)configuration, context, ref)) {
241         return null;
242       }
243     }
244     catch (ClassCastException e) {
245       LOG.error(myConfigurationFactory + " produced wrong type", e);
246       return null;
247     }
248     return configuration;
249   }
250 }