a bugfix on variables
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / externaltools / model / ToolUtil.java
1 package net.sourceforge.phpdt.externaltools.model;
2
3 /**********************************************************************
4 Copyright (c) 2002 IBM Corp. and others. All rights reserved.
5 This file is made available under the terms of the Common Public License v1.0
6 which accompanies this distribution, and is available at
7 http://www.eclipse.org/legal/cpl-v10.html
8  
9 Contributors:
10 **********************************************************************/
11
12 import java.util.ArrayList;
13
14 import org.eclipse.core.runtime.IPath;
15 import org.eclipse.core.runtime.MultiStatus;
16 import net.sourceforge.phpdt.externaltools.internal.model.ExternalToolsPlugin;
17 import net.sourceforge.phpdt.externaltools.internal.model.ToolMessages;
18 import net.sourceforge.phpdt.externaltools.internal.registry.ArgumentVariable;
19 import net.sourceforge.phpdt.externaltools.internal.registry.ArgumentVariableRegistry;
20 import net.sourceforge.phpdt.externaltools.internal.registry.PathLocationVariable;
21 import net.sourceforge.phpdt.externaltools.internal.registry.PathLocationVariableRegistry;
22 import net.sourceforge.phpdt.externaltools.variable.ExpandVariableContext;
23
24 /**
25  * General utility class dealing with external tools
26  */
27 public final class ToolUtil {
28         /**
29          * Argument parsing constants
30          */
31         private static final char ARG_DELIMITER = ' '; //$NON-NLS-1$
32         private static final char ARG_DBL_QUOTE = '"'; //$NON-NLS-1$
33         
34         /**
35          * Variable tag indentifiers
36          */
37         private static final char VAR_TAG_START_CHAR1 = '$'; //$NON-NLS-1$
38         private static final char VAR_TAG_START_CHAR2 = '{'; //$NON-NLS-1$
39         private static final char VAR_TAG_END_CHAR1 = '}'; //$NON-NLS-1$
40         private static final String VAR_TAG_START = "${"; //$NON-NLS-1$
41         private static final String VAR_TAG_END = "}"; //$NON-NLS-1$
42         private static final String VAR_TAG_SEP = ":"; //$NON-NLS-1$
43
44         /**
45          * No instances allowed
46          */
47         private ToolUtil() {
48                 super();
49         }
50
51         /**
52          * Builds a variable tag that will be auto-expanded before
53          * the tool is run.
54          * 
55          * @param varName the name of a known variable (one of the VAR_* constants for instance)
56          * @param varArgument an optional argument for the variable, <code>null</code> if none
57          */
58         public static String buildVariableTag(String varName, String varArgument) {
59                 StringBuffer buf = new StringBuffer();
60                 buildVariableTag(varName,varArgument, buf);
61                 return buf.toString();
62         }
63         
64         /**
65          * Builds a variable tag that will be auto-expanded before
66          * the tool is run.
67          * 
68          * @param varName the name of a known variable (one of the VAR_* constants for instance)
69          * @param varArgument an optional argument for the variable, <code>null</code> if none
70          * @param buffer the buffer to write the constructed variable tag
71          */
72         public static void buildVariableTag(String varName, String varArgument, StringBuffer buffer) {
73                 buffer.append(VAR_TAG_START);
74                 buffer.append(varName);
75                 if (varArgument != null && varArgument.length() > 0) {
76                         buffer.append(VAR_TAG_SEP);
77                         buffer.append(varArgument);
78                 }
79                 buffer.append(VAR_TAG_END);
80         }
81         
82         /**
83          * Expands all the variables found in an individual
84          * argument text.
85          * 
86          * @param argument one of the argument text in the list of arguments
87          * @param context the context to use for expanding variables
88          * @param status multi status to report any problems expanding variables
89          * @return the argument text with all variables expanded, or <code>null</code> if not possible
90          */
91         public static String expandArgument(String argument, ExpandVariableContext context, MultiStatus status) {
92                 StringBuffer buffer = new StringBuffer();
93                 
94                 int start = 0;
95                 while (true) {
96                         VariableDefinition varDef = extractVariableTag(argument, start);
97                         
98                         // No more variables found...
99                         if (varDef.start == -1) {
100                                 if (start == 0)
101                                         buffer.append(argument);
102                                 else
103                                         buffer.append(argument.substring(start));
104                                 break;
105                         }
106
107                         // Invalid variable format
108                         if (varDef.end == -1 || varDef.name == null || varDef.name.length() == 0) {
109                                 String msg = ToolMessages.getString("ToolUtil.argumentVarFormatWrong"); //$NON-NLS-1$
110                                 status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
111                                 return null;
112                         }
113
114                         // Copy text between start and variable.                        
115                         if (varDef.start > start)
116                                 buffer.append(argument.substring(start, varDef.start));
117                         start = varDef.end;
118                         
119                         // Lookup the variable if it exist
120                         ArgumentVariableRegistry registry;
121                         registry = ExternalToolsPlugin.getDefault().getArgumentVariableRegistry();
122                         ArgumentVariable variable = registry.getArgumentVariable(varDef.name);
123                         if (variable == null) {
124                                 String msg = ToolMessages.format("ToolUtil.argumentVarMissing", new Object[] {varDef.name}); //$NON-NLS-1$
125                                 status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
126                                 return null;
127                         }
128                         
129                         // Expand the variable as text if possible
130                         String text = variable.getExpander().getText(varDef.name, varDef.argument, context);
131                         if (text == null) {
132                                 String msg = ToolMessages.format("ToolUtil.argumentVarExpandFailed", new Object[] {varDef.name}); //$NON-NLS-1$
133                                 status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
134                                 return null;
135                         }
136                         buffer.append(text);
137                 }
138                 
139                 return buffer.toString();
140         }
141         
142         /**
143          * Returns a list of individual arguments where all
144          * variables have been expanded.
145          * 
146          * @param arguments the arguments with leading and trailing
147          *              spaces already removed.
148          * @param context the context used to expand the variable(s)
149          * @param status multi status to report any problems expanding variables
150          * @return the list of individual arguments where some elements in the
151          *              list maybe <code>null</code> if problems expanding variable(s).
152          */
153         public static String[] expandArguments(String arguments, ExpandVariableContext context, MultiStatus status) {
154                 if (arguments == null || arguments.length() == 0)
155                         return new String[0];
156
157                 String[] argList = parseArgumentsIntoList(arguments);
158                 for (int i = 0; i < argList.length; i++)
159                         argList[i] = expandArgument(argList[i], context, status);
160                 
161                 return argList;
162         }
163         
164         /**
165          * Returns the expanded directory location if represented by a
166          * directory variable. Otherwise, the directory location given is
167          * return unless an unknown variable was detected.
168          * 
169          * @param dirLocation a directory location either as a path or a variable
170          *              with leading and trailing spaces already removed.
171          * @param context the context used to expand the variable
172          * @param status multi status to report any problems expanding variables
173          * @return the directory location as a string or <code>null</code> if not possible
174          */
175         public static String expandDirectoryLocation(String dirLocation, ExpandVariableContext context, MultiStatus status) {
176                 if (dirLocation == null || dirLocation.length() == 0)
177                         return ""; //$NON-NLS-1$
178
179                 VariableDefinition varDef = extractVariableTag(dirLocation, 0);
180                 // Return if no variable found
181                 if (varDef.start < 0)
182                         return dirLocation;
183                 
184                 // Disallow text before/after variable
185                 if (varDef.start != 0 || (varDef.end < dirLocation.length() && varDef.end != -1)) {
186                         String msg = ToolMessages.getString("ToolUtil.dirLocVarBetweenText"); //$NON-NLS-1$
187                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
188                         return null;
189                 }
190                 
191                 // Invalid variable format
192                 if (varDef.name == null || varDef.name.length() == 0 || varDef.end == -1) {
193                         String msg = ToolMessages.getString("ToolUtil.dirLocVarFormatWrong"); //$NON-NLS-1$
194                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
195                         return null;
196                 }
197                 
198                 // Lookup the variable if it exist
199                 PathLocationVariableRegistry registry;
200                 registry = ExternalToolsPlugin.getDefault().getDirectoryLocationVariableRegistry();
201                 PathLocationVariable variable = registry.getPathLocationVariable(varDef.name);
202                 if (variable == null) {
203                         String msg = ToolMessages.format("ToolUtil.dirLocVarMissing", new Object[] {varDef.name}); //$NON-NLS-1$
204                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
205                         return null;
206                 }
207                 
208                 // Expand the variable into a IPath if possible
209                 IPath path = variable.getExpander().getPath(varDef.name, varDef.argument, context);
210                 if (path == null) {
211                         String msg = ToolMessages.format("ToolUtil.dirLocVarExpandFailed", new Object[] {varDef.name}); //$NON-NLS-1$
212                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
213                         return null;
214                 }
215                 
216                 return path.toOSString();
217         }
218         
219         /**
220          * Returns the expanded file location if represented by a
221          * file variable. Otherwise, the file location given is
222          * return unless an unknown variable was detected.
223          * 
224          * @param fileLocation a file location either as a path or a variable
225          *              with leading and trailing spaces already removed.
226          * @param context the context used to expand the variable
227          * @param status multi status to report any problems expanding variables
228          * @return the file location as a string or <code>null</code> if not possible
229          */
230         public static String expandFileLocation(String fileLocation, ExpandVariableContext context, MultiStatus status) {
231                 if (fileLocation == null || fileLocation.length() == 0)
232                         return ""; //$NON-NLS-1$
233
234                 VariableDefinition varDef = extractVariableTag(fileLocation, 0);
235                 // Return if no variable found
236                 if (varDef.start < 0)
237                         return fileLocation;
238                 
239                 // Disallow text before/after variable
240                 if (varDef.start != 0 || (varDef.end < fileLocation.length() && varDef.end != -1)) {
241                         String msg = ToolMessages.getString("ToolUtil.fileLocVarBetweenText"); //$NON-NLS-1$
242                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
243                         return null;
244                 }
245                 
246                 // Invalid variable format
247                 if (varDef.name == null || varDef.name.length() == 0 || varDef.end == -1) {
248                         String msg = ToolMessages.getString("ToolUtil.fileLocVarFormatWrong"); //$NON-NLS-1$
249                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
250                         return null;
251                 }
252                 
253                 // Lookup the variable if it exist
254                 PathLocationVariableRegistry registry;
255                 registry = ExternalToolsPlugin.getDefault().getFileLocationVariableRegistry();
256                 PathLocationVariable variable = registry.getPathLocationVariable(varDef.name);
257                 if (variable == null) {
258                         String msg = ToolMessages.format("ToolUtil.fileLocVarMissing", new Object[] {varDef.name}); //$NON-NLS-1$
259                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
260                         return null;
261                 }
262                 
263                 // Expand the variable into a IPath if possible
264                 IPath path = variable.getExpander().getPath(varDef.name, varDef.argument, context);
265                 if (path == null) {
266                         String msg = ToolMessages.format("The variable {0} with argument {1} could not be expanded to a valid path.", new Object[] {varDef.name, varDef.argument});
267                         status.merge(ExternalToolsPlugin.newErrorStatus(msg, null));
268                         return null;
269                 }
270                 
271                 return path.toString();
272         }
273         
274         /**
275          * Extracts from the source text the variable tag's name
276          * and argument.
277          * 
278          * @param text the source text to parse for a variable tag
279          * @param start the index in the string to start the search
280          * @return the variable definition
281          */
282         public static VariableDefinition extractVariableTag(String text, int start) {
283                 VariableDefinition varDef = new VariableDefinition();
284                 
285                 varDef.start = text.indexOf(VAR_TAG_START, start);
286                 if (varDef.start < 0)
287                         return varDef;
288                 start = varDef.start + VAR_TAG_START.length();
289                 
290                 int end = text.indexOf(VAR_TAG_END, start);
291                 if (end < 0)
292                         return varDef;
293                 varDef.end = end + VAR_TAG_END.length();
294                 if (end == start)
295                         return varDef;
296         
297                 int mid = text.indexOf(VAR_TAG_SEP, start);
298                 if (mid < 0 || mid > end) {
299                         varDef.name = text.substring(start, end);
300                 } else {
301                         if (mid > start)
302                                 varDef.name = text.substring(start, mid);
303                         mid = mid + VAR_TAG_SEP.length();
304                         if (mid < end)
305                                 varDef.argument = text.substring(mid, end);
306                 }
307                 
308                 return varDef;
309         }
310         
311         /**
312          * Parses the argument text into an array of individual
313          * arguments using the space character as the delimiter.
314          * An individual argument containing spaces must have a
315          * double quote (") at the start and end. Two double 
316          * quotes together is taken to mean an embedded double
317          * quote in the argument text. Variables are treated as
318          * a single unit and therefore spaces and double quotes
319          * inside a variable are copied as is and not parsed.
320          * 
321          * @param arguments the arguments as one string
322          * @return the array of arguments
323          */
324         public static String[] parseArgumentsIntoList(String arguments) {
325                 if (arguments == null || arguments.length() == 0)
326                         return new String[0];
327                 
328                 ArrayList list = new ArrayList(10);
329                 boolean inQuotes = false;
330                 boolean inVar = false;
331                 int start = 0;
332                 int end = arguments.length();
333                 StringBuffer buffer = new StringBuffer(end);
334                 
335                 while (start < end) {
336                         char ch = arguments.charAt(start);
337                         start++;
338                         
339                         switch (ch) {
340                                 case ARG_DELIMITER :
341                                         if (inQuotes || inVar) {
342                                                 buffer.append(ch);
343                                         } else {
344                                                 if (buffer.length() > 0) {
345                                                         list.add(buffer.toString());
346                                                         buffer.setLength(0);
347                                                 }
348                                         }
349                                         break;
350
351                                 case ARG_DBL_QUOTE :
352                                         if (inVar) {
353                                                 buffer.append(ch);
354                                         } else {
355                                                 if (start < end) {
356                                                         if (arguments.charAt(start) == ARG_DBL_QUOTE) {
357                                                                 // Two quotes together represents one quote
358                                                                 buffer.append(ch);
359                                                                 start++;
360                                                         } else {
361                                                                 inQuotes = !inQuotes;
362                                                         }
363                                                 } else {
364                                                         // A lone quote at the end, just drop it.
365                                                         inQuotes = false;
366                                                 }
367                                         }
368                                         break;
369                                         
370                                 case VAR_TAG_START_CHAR1 :
371                                         buffer.append(ch);
372                                         if (!inVar && start < end) {
373                                                 if (arguments.charAt(start) == VAR_TAG_START_CHAR2) {
374                                                         buffer.append(VAR_TAG_START_CHAR2);
375                                                         inVar = true;
376                                                         start++;
377                                                 }
378                                         }
379                                         break;
380
381                                 case VAR_TAG_END_CHAR1 :
382                                         buffer.append(ch);
383                                         inVar = false;
384                                         break;
385
386                                 default :
387                                         buffer.append(ch);
388                                         break;
389                         }
390                         
391                 }
392                 
393                 if (buffer.length() > 0)
394                         list.add(buffer.toString());
395                         
396                 String[] results = new String[list.size()];
397                 list.toArray(results);
398                 return results;
399         }
400
401
402         /**
403          * Structure to represent a variable definition within a
404          * source string.
405          */
406         public static final class VariableDefinition {
407                 /**
408                  * Index in the source text where the variable started
409                  * or <code>-1</code> if no valid variable start tag 
410                  * identifier found.
411                  */
412                 public int start = -1;
413                 
414                 /**
415                  * Index in the source text of the character following
416                  * the end of the variable or <code>-1</code> if no 
417                  * valid variable end tag found.
418                  */
419                 public int end = -1;
420                 
421                 /**
422                  * The variable's name found in the source text, or
423                  * <code>null</code> if no valid variable found.
424                  */
425                 public String name = null;
426                 
427                 /**
428                  * The variable's argument found in the source text, or
429                  * <code>null</code> if no valid variable found or if
430                  * the variable did not specify an argument
431                  */
432                 public String argument = null;
433                 
434                 /**
435                  * Create an initialized variable definition.
436                  */
437                 private VariableDefinition() {
438                         super();
439                 }
440                 
441                 /**
442                  * Create an initialized variable definition.
443                  */
444                 private VariableDefinition(int start, int end) {
445                         super();
446                         this.start = start;
447                         this.end = end;
448                 }
449         }
450 }