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