Remote interpreters: helpers copying, debugging, packaging.
[idea/community.git] / platform / platform-api / src / com / intellij / execution / configurations / ParametersList.java
1 /*
2  * Copyright 2000-2011 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.configurations;
17
18 import com.intellij.openapi.application.Application;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.PathMacros;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.util.ArrayUtil;
24 import com.intellij.util.EnvironmentUtil;
25 import com.intellij.util.StringBuilderSpinAllocator;
26 import com.intellij.util.containers.CollectionFactory;
27 import com.intellij.util.containers.ContainerUtil;
28 import gnu.trove.THashMap;
29 import org.jetbrains.annotations.NonNls;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.util.*;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36
37 public class ParametersList implements Cloneable {
38   private static final Logger LOG = Logger.getInstance("#com.intellij.execution.configurations.ParametersList");
39
40   private static final Pattern PROPERTY_PATTERN = Pattern.compile("-D(\\S+?)=(.+)");
41
42   private List<String> myParameters = new ArrayList<String>();
43   private Map<String, String> myMacroMap = null;
44   private List<ParamsGroup> myGroups = new ArrayList<ParamsGroup>();
45
46   public boolean hasParameter(@NonNls final String param) {
47     return myParameters.contains(param);
48   }
49
50   public boolean hasProperty(@NonNls final String name) {
51     for (@NonNls String parameter : myParameters) {
52       if (StringUtil.startsWithConcatenationOf(parameter, "-D" + name, "=")) return true;
53     }
54     return false;
55   }
56
57   @Nullable
58   public String getPropertyValue(@NotNull @NonNls final String name) {
59     final String prefix = "-D" + name + "=";
60     for (String parameter : myParameters) {
61       if (parameter.startsWith(prefix)) {
62         return parameter.substring(prefix.length());
63       }
64     }
65     return null;
66   }
67
68   @NotNull
69   public Map<String, String> getProperties() {
70     Map<String, String> result = new THashMap<String, String>();
71     for (String parameter : myParameters) {
72       Matcher matcher = PROPERTY_PATTERN.matcher(parameter);
73       if (matcher.matches()) {
74         result.put(matcher.group(1), matcher.group(2));
75       }
76     }
77     return result;
78   }
79
80   @NotNull
81   public String getParametersString() {
82     return join(getList());
83   }
84
85   @NotNull
86   public String[] getArray() {
87     return ArrayUtil.toStringArray(getList());
88   }
89
90   @NotNull
91   public List<String> getList() {
92     if (myGroups.isEmpty()) {
93       return Collections.unmodifiableList(myParameters);
94     }
95
96     final List<String> params = new ArrayList<String>();
97     params.addAll(myParameters);
98     for (ParamsGroup group : myGroups) {
99       params.addAll(group.getParameters());
100     }
101     return Collections.unmodifiableList(params);
102   }
103
104   public void clearAll() {
105     myParameters.clear();
106     myGroups.clear();
107   }
108
109   public void prepend(@NonNls final String parameter) {
110     addAt(0, parameter);
111   }
112
113   public void prependAll(@NonNls final String... parameter) {
114     for (int i = parameter.length - 1; i >= 0; i--) {
115       addAt(0, parameter[i]);
116     }
117   }
118
119   public void addParametersString(final String parameters) {
120     if (parameters != null) {
121       final String[] split = parse(parameters);
122       for (String param : split) {
123         add(param);
124       }
125     }
126   }
127
128   public void add(@NonNls final String parameter) {
129     myParameters.add(expandMacros(parameter));
130   }
131
132   public ParamsGroup addParamsGroup(@NotNull final String groupId) {
133     return addParamsGroup(new ParamsGroup(groupId));
134   }
135
136   public ParamsGroup addParamsGroup(@NotNull final ParamsGroup group) {
137     myGroups.add(group);
138     return group;
139   }
140
141   public ParamsGroup addParamsGroupAt(final int index, @NotNull final ParamsGroup group) {
142     myGroups.add(index, group);
143     return group;
144   }
145
146   public ParamsGroup addParamsGroupAt(final int index, @NotNull final String groupId) {
147     final ParamsGroup group = new ParamsGroup(groupId);
148     myGroups.add(index, group);
149     return group;
150   }
151
152   public int getParamsGroupsCount() {
153     return myGroups.size();
154   }
155
156   public List<String> getParameters() {
157     return Collections.unmodifiableList(myParameters);
158   }
159
160   public List<ParamsGroup> getParamsGroups() {
161     return Collections.unmodifiableList(myGroups);
162   }
163
164   public ParamsGroup getParamsGroupAt(final int index) {
165     return myGroups.get(index);
166   }
167
168   @Nullable
169   public ParamsGroup getParamsGroup(@NotNull final String name) {
170     for (ParamsGroup group : myGroups) {
171       if (name.equals(group.getId())) return group;
172     }
173     return null;
174   }
175
176   public ParamsGroup removeParamsGroup(final int index) {
177     return myGroups.remove(index);
178   }
179
180   public void addAt(final int index, @NotNull final String parameter) {
181     myParameters.add(index, expandMacros(parameter));
182   }
183
184   public void defineProperty(@NonNls final String propertyName, @NonNls final String propertyValue) {
185     addProperty(propertyName, propertyValue);
186   }
187
188   public void addProperty(@NonNls final String propertyName, @NonNls final String propertyValue) {
189     //noinspection HardCodedStringLiteral
190     myParameters.add("-D" + propertyName + "=" + propertyValue);
191   }
192
193   public void replaceOrAppend(final @NonNls String parameterPrefix, final @NonNls String replacement) {
194     replaceOrAdd(parameterPrefix, replacement, myParameters.size());
195   }
196
197   private void replaceOrAdd(final @NonNls String parameterPrefix, final @NonNls String replacement, final int position) {
198     for (ListIterator<String> iterator = myParameters.listIterator(); iterator.hasNext(); ) {
199       final String param = iterator.next();
200       if (param.startsWith(parameterPrefix)) {
201         if ("".equals(replacement)) {
202           iterator.remove();
203         }
204         else {
205           iterator.set(replacement);
206         }
207         return;
208       }
209     }
210     if (!"".equals(replacement)) {
211       myParameters.add(position, replacement);
212     }
213   }
214
215   public void replaceOrPrepend(final @NonNls String parameter, final @NonNls String replacement) {
216     replaceOrAdd(parameter, replacement, 0);
217   }
218   
219   public void set(int ind, final @NonNls String value) {
220     myParameters.set(ind, value);
221   }
222
223   public void add(@NonNls final String name, @NonNls final String value) {
224     add(name);
225     add(value);
226   }
227
228   public void addAll(final String... parameters) {
229     ContainerUtil.addAll(myParameters, parameters);
230   }
231
232   public void addAll(final List<String> parameters) {
233     myParameters.addAll(parameters);
234   }
235
236   @Override
237   public ParametersList clone() {
238     try {
239       final ParametersList clone = (ParametersList)super.clone();
240       clone.myParameters = new ArrayList<String>(myParameters);
241       clone.myGroups = new ArrayList<ParamsGroup>(myGroups.size() + 1);
242       for (ParamsGroup group : myGroups) {
243         clone.myGroups.add(group.clone());
244       }
245       return clone;
246     }
247     catch (CloneNotSupportedException e) {
248       LOG.error(e);
249       return null;
250     }
251   }
252
253   /**
254    * <p>Joins list of parameters into single string, which may be then parsed back into list by {@link #parse(String)}.</p>
255    * <p/>
256    * <p>
257    * <strong>Conversion rules:</strong>
258    * <ul>
259    * <li>double quotes are escaped by backslash (<code>&#92;</code>);</li>
260    * <li>empty parameters parameters and parameters with spaces inside are surrounded with double quotes (<code>"</code>);</li>
261    * <li>parameters are separated by single whitespace.</li>
262    * </ul>
263    * </p>
264    * <p/>
265    * <p><strong>Examples:</strong></p>
266    * <p>
267    * <code>['a', 'b'] => 'a  b'</code><br/>
268    * <code>['a="1 2"', 'b'] => '"a &#92;"1 2&#92;"" b'</code>
269    * </p>
270    *
271    * @param parameters a list of parameters to join.
272    * @return a string with parameters.
273    */
274   @NotNull
275   public static String join(@NotNull final List<String> parameters) {
276     return ParametersTokenizer.encode(parameters);
277   }
278
279   @NotNull
280   public static String join(final String... parameters) {
281     return ParametersTokenizer.encode(Arrays.asList(parameters));
282   }
283
284   /**
285    * <p>Converts single parameter string (as created by {@link #join(java.util.List)}) into list of parameters.</p>
286    * <p/>
287    * <p>
288    * <strong>Conversion rules:</strong>
289    * <ul>
290    * <li>starting/whitespaces are trimmed;</li>
291    * <li>parameters are split by whitespaces, whitespaces itself are dropped</li>
292    * <li>parameters inside double quotes (<code>"a b"</code>) are kept as single one;</li>
293    * <li>double quotes are dropped, escaped double quotes (<code>&#92;"</code>) are un-escaped.</li>
294    * </ul>
295    * </p>
296    * <p/>
297    * <p><strong>Examples:</strong></p>
298    * <p>
299    * <code>' a  b ' => ['a', 'b']</code><br/>
300    * <code>'a="1 2" b' => ['a=1 2', 'b']</code><br/>
301    * <code>'a " " b' => ['a', ' ', 'b']</code><br/>
302    * <code>'"a &#92;"1 2&#92;"" b' => ['a="1 2"', 'b']</code>
303    * </p>
304    *
305    * @param string parameter string to split.
306    * @return array of parameters.
307    */
308   @NotNull
309   public static String[] parse(@NotNull final String string) {
310     final List<String> params = ParametersTokenizer.decode(string);
311     return ArrayUtil.toStringArray(params);
312   }
313
314   public String expandMacros(String text) {
315     final Map<String, String> macroMap = getMacroMap();
316     final Set<String> set = macroMap.keySet();
317     for (final String from : set) {
318       final String to = macroMap.get(from);
319       text = StringUtil.replace(text, from, to, true);
320     }
321     return text;
322   }
323
324   private Map<String, String> getMacroMap() {
325     if (myMacroMap == null) {
326       // the insertion order is important for later iterations, so LinkedHashMap is used
327       myMacroMap = new LinkedHashMap<String, String>();
328
329       // ApplicationManager.getApplication() will return null if executed in ParameterListTest
330       final Application application = ApplicationManager.getApplication();
331       if (application != null) {
332         final PathMacros pathMacros = PathMacros.getInstance();
333         final Set<String> names = pathMacros.getAllMacroNames();
334         for (String name : names) {
335           myMacroMap.put("${" + name + "}", pathMacros.getValue(name));
336         }
337         final Map<String, String> env = EnvironmentUtil.getEnviromentProperties();
338         for (String name : env.keySet()) {
339           final String key = "${" + name + "}";
340           if (!myMacroMap.containsKey(key)) {
341             myMacroMap.put(key, env.get(name));
342           }
343         }
344       }
345     }
346     return myMacroMap;
347   }
348
349   @Override
350   public String toString() {
351     return myParameters.toString();
352   }
353
354   private static class ParametersTokenizer {
355     private ParametersTokenizer() {
356     }
357
358     @NotNull
359     public static String encode(@NotNull final List<String> parameters) {
360       final StringBuilder buffer = new StringBuilder();
361       for (final String parameter : parameters) {
362         if (buffer.length() > 0) {
363           buffer.append(' ');
364         }
365         buffer.append(encode(parameter));
366       }
367       return buffer.toString();
368     }
369
370     @NotNull
371     public static String encode(@NotNull String parameter) {
372       final StringBuilder builder = StringBuilderSpinAllocator.alloc();
373       try {
374         builder.append(parameter);
375         StringUtil.escapeQuotes(builder);
376         if (builder.length() == 0 || StringUtil.indexOf(builder, ' ') >= 0 || StringUtil.indexOf(builder, '|') >= 0) {
377           StringUtil.quote(builder);
378         }
379         return builder.toString();
380       }
381       finally {
382         StringBuilderSpinAllocator.dispose(builder);
383       }
384     }
385
386     @NotNull
387     public static List<String> decode(@NotNull String parameterString) {
388       parameterString = parameterString.trim();
389
390       final ArrayList<String> params = CollectionFactory.arrayList();
391       final StringBuilder token = new StringBuilder(128);
392       boolean inQuotes = false;
393       boolean escapedQuote = false;
394       boolean nonEmpty = false;
395
396       for (int i = 0; i < parameterString.length(); i++) {
397         final char ch = parameterString.charAt(i);
398
399         if (ch == '\"') {
400           if (!escapedQuote) {
401             inQuotes = !inQuotes;
402             nonEmpty = true;
403             continue;
404           }
405           escapedQuote = false;
406         }
407         else if (Character.isWhitespace(ch)) {
408           if (!inQuotes) {
409             if (token.length() > 0 || nonEmpty) {
410               params.add(token.toString());
411               token.setLength(0);
412               nonEmpty = false;
413             }
414             continue;
415           }
416         }
417         else if (ch == '\\') {
418           if (i < parameterString.length() - 1 && parameterString.charAt(i + 1) == '"') {
419             escapedQuote = true;
420             continue;
421           }
422         }
423
424         token.append(ch);
425       }
426
427       if (token.length() > 0 || nonEmpty) {
428         params.add(token.toString());
429       }
430
431       return params;
432     }
433   }
434 }