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