|
|||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
ArgumentParser.java | 72.6% | 83.3% | 85% | 79.9% |
|
1 |
package abbot.script;
|
|
2 |
|
|
3 |
import java.awt.Component;
|
|
4 |
import java.lang.reflect.Array;
|
|
5 |
import java.util.*;
|
|
6 |
|
|
7 |
import abbot.*;
|
|
8 |
import abbot.Log;
|
|
9 |
import abbot.i18n.Strings;
|
|
10 |
import abbot.finder.*;
|
|
11 |
import abbot.script.parsers.Parser;
|
|
12 |
import abbot.tester.*;
|
|
13 |
|
|
14 |
/** Provide parsing of a String into an array of appropriately typed
|
|
15 |
* arguments. Arrays are indicated by square brackets, and arguments are
|
|
16 |
* separated by commas, e.g.<br>
|
|
17 |
* <ul>
|
|
18 |
* <li>An empty String array (length zero): "[]"
|
|
19 |
* <li>Three arguments "one,two,three"
|
|
20 |
* <li>An array of length three: "[one,two,three]"
|
|
21 |
* <li>A single-element array of integer: "[1]"
|
|
22 |
* <li>A single null argument: "null"
|
|
23 |
* <li>An array of two strings: "[one,two]"
|
|
24 |
* <li>Commas must be escaped when they would otherwise be interpreted as an
|
|
25 |
* argument separator:<br>
|
|
26 |
* "one,two%2ctwo,three" (2nd argument is "two,two")
|
|
27 |
*/
|
|
28 |
|
|
29 |
public class ArgumentParser { |
|
30 | 0 |
private ArgumentParser() { }
|
31 |
|
|
32 |
private static final String ESC_ESC_COMMA = "%%2C"; |
|
33 |
public static final String ESC_COMMA = "%2c"; |
|
34 |
public static final String NULL = "null"; |
|
35 |
public static final String DEFAULT_TOSTRING = "<default-tostring>"; |
|
36 |
|
|
37 |
/** Maps class names to their corresponding string parsers. */
|
|
38 |
private static Map parsers = new HashMap(); |
|
39 |
|
|
40 | 3 |
private static boolean isExtension(String name) { |
41 | 3 |
return name.indexOf(".extensions.") != -1; |
42 |
} |
|
43 |
|
|
44 | 3 |
private static Parser findParser(String name, Class targetClass) { |
45 | 3 |
Log.debug("Trying " + name + " for " + targetClass); |
46 | 3 |
try {
|
47 | 3 |
Class cvtClass = isExtension(name) |
48 |
? Class.forName(name, true, targetClass.getClassLoader())
|
|
49 |
: Class.forName(name); |
|
50 | 3 |
Parser parser = (Parser)cvtClass.newInstance(); |
51 | 3 |
if (cvtClass.getName().indexOf(".extensions.") == -1) |
52 | 3 |
parsers.put(targetClass, parser); |
53 | 3 |
return parser;
|
54 |
} |
|
55 |
catch(InstantiationException ie) {
|
|
56 | 0 |
Log.debug(ie); |
57 |
} |
|
58 |
catch(IllegalAccessException iae) {
|
|
59 | 0 |
Log.debug(iae); |
60 |
} |
|
61 |
catch(ClassNotFoundException cnf) {
|
|
62 | 0 |
Log.debug(cnf); |
63 |
} |
|
64 | 0 |
return null; |
65 |
} |
|
66 |
|
|
67 |
/** Set the parser for a given class. Returns the old one, if any. */
|
|
68 | 34 |
public static Parser setParser(Class cls, Parser parser) { |
69 | 34 |
Parser old = (Parser)parsers.get(cls); |
70 | 34 |
parsers.put(cls, parser); |
71 | 34 |
return old;
|
72 |
} |
|
73 |
|
|
74 |
/** Find a string parser for the given class. Returns null if none
|
|
75 |
* found.
|
|
76 |
*/
|
|
77 | 3 |
public static Parser getParser(Class cls) { |
78 | 3 |
Parser parser = (Parser)parsers.get(cls); |
79 |
// Load core testers with the current framework's class loader
|
|
80 |
// context, and anything else in the context of the code under test
|
|
81 | 3 |
if (parser == null) { |
82 | 3 |
String base = ComponentTester.simpleClassName(cls); |
83 | 3 |
String pkg = Parser.class.getPackage().getName();
|
84 | 3 |
parser = findParser(pkg + "." + base + "Parser", cls); |
85 | 3 |
if (parser == null) { |
86 | 0 |
parser = findParser(pkg + ".extensions."
|
87 |
+ base + "Parser", cls);
|
|
88 |
} |
|
89 |
} |
|
90 | 3 |
return parser;
|
91 |
} |
|
92 |
|
|
93 | 1538 |
private static boolean isBounded(String s) { |
94 | 1538 |
return s.startsWith("[") && s.endsWith("]") |
95 |
|| s.startsWith("\"") && s.endsWith("\"") |
|
96 |
|| s.startsWith("'") && s.endsWith("'"); |
|
97 |
} |
|
98 |
|
|
99 | 1241 |
private static String escapeCommas(String s) { |
100 | 1241 |
return replace(replace(s, ESC_COMMA, ESC_ESC_COMMA), ",", ESC_COMMA); |
101 |
} |
|
102 |
|
|
103 | 57 |
private static String unescapeCommas(String s) { |
104 | 57 |
return replace(replace(s, ESC_COMMA, ","), ESC_ESC_COMMA, ESC_COMMA); |
105 |
} |
|
106 |
|
|
107 | 971 |
public static String encodeArguments(String[] args) { |
108 | 971 |
StringBuffer sb = new StringBuffer();
|
109 | 971 |
if (args.length > 0) {
|
110 | 881 |
if (isBounded(args[0])) {
|
111 | 165 |
sb.append(args[0]); |
112 |
} |
|
113 |
else {
|
|
114 | 716 |
sb.append(escapeCommas(args[0])); |
115 |
} |
|
116 | 881 |
for (int i=1;i < args.length;i++) { |
117 | 657 |
sb.append(",");
|
118 | 657 |
if (isBounded(args[i])) {
|
119 | 132 |
sb.append(args[i]); |
120 |
} |
|
121 |
else {
|
|
122 | 525 |
sb.append(escapeCommas(args[i])); |
123 |
} |
|
124 |
} |
|
125 |
} |
|
126 | 971 |
return sb.toString();
|
127 |
} |
|
128 |
|
|
129 |
private static class Tokenizer extends ArrayList { |
|
130 | 32 |
public Tokenizer(String input) {
|
131 | 32 |
while (true) { |
132 | 78 |
int index = input.indexOf(","); |
133 | 78 |
if (index == -1) {
|
134 | 32 |
add(input); |
135 | 32 |
break;
|
136 |
} |
|
137 | 46 |
add(input.substring(0, index)); |
138 | 46 |
input = input.substring(index + 1); |
139 |
} |
|
140 |
} |
|
141 |
} |
|
142 |
|
|
143 |
/** Convert the given encoded String into an array of Strings.
|
|
144 |
* Interprets strings of the format "[el1,el2,el3]" to be a single (array)
|
|
145 |
* argument (such commas do not need escaping). <p>
|
|
146 |
* Explicit commas and square brackets in arguments must be escaped by
|
|
147 |
* preceding the character with a backslash ('\'). The strings
|
|
148 |
* '(null)' and 'null' are interpreted as the value null.<p>
|
|
149 |
* Explicit spaces should be protected by double quotes, e.g.
|
|
150 |
* " an argument bounded by spaces ".
|
|
151 |
*/
|
|
152 | 38 |
public static String[] parseArgumentList(String encodedArgs) { |
153 | 38 |
ArrayList alist = new ArrayList();
|
154 | 38 |
if (encodedArgs == null || "".equals(encodedArgs)) |
155 | 6 |
return new String[0]; |
156 |
// handle old method of escaped commas
|
|
157 | 32 |
encodedArgs = replace(encodedArgs, "\\,", ESC_COMMA);
|
158 | 32 |
Iterator iter = new Tokenizer(encodedArgs).iterator();
|
159 | 32 |
while (iter.hasNext()) {
|
160 | 70 |
String str = (String)iter.next(); |
161 |
|
|
162 | 70 |
if (str.trim().startsWith("[") |
163 |
&& !str.trim().endsWith("]")) {
|
|
164 | 5 |
while (iter.hasNext()) {
|
165 | 5 |
String next = (String)iter.next(); |
166 | 5 |
str += "," + next;
|
167 | 5 |
if (next.trim().endsWith("]")) { |
168 | 5 |
break;
|
169 |
} |
|
170 |
} |
|
171 |
} |
|
172 | 65 |
else if (str.trim().startsWith("\"") |
173 |
&& !str.trim().endsWith("\"")) {
|
|
174 | 3 |
while (iter.hasNext()) {
|
175 | 3 |
String next = (String)iter.next(); |
176 | 3 |
str += "," + next;
|
177 | 3 |
if (next.trim().endsWith("\"")) { |
178 | 2 |
break;
|
179 |
} |
|
180 |
} |
|
181 |
} |
|
182 | 63 |
else if (str.trim().startsWith("'") |
183 |
&& !str.trim().endsWith("'")) {
|
|
184 | 0 |
while (iter.hasNext()) {
|
185 | 0 |
String next = (String)iter.next(); |
186 | 0 |
str += "," + next;
|
187 | 0 |
if (next.trim().endsWith("'")) { |
188 | 0 |
break;
|
189 |
} |
|
190 |
} |
|
191 |
} |
|
192 |
|
|
193 | 70 |
if (NULL.equals(str.trim())) {
|
194 | 5 |
alist.add(null);
|
195 |
} |
|
196 |
else {
|
|
197 |
// If it's an array, don't unescape the commas yet
|
|
198 | 65 |
if (!str.startsWith("[")) { |
199 | 57 |
str = unescapeCommas(str); |
200 |
} |
|
201 | 65 |
alist.add(str); |
202 |
} |
|
203 |
} |
|
204 | 32 |
return (String[])alist.toArray(new String[alist.size()]); |
205 |
} |
|
206 |
|
|
207 |
/** Performs property substitutions on the argument priort to evaluating
|
|
208 |
* it. Substitutions are not recursive.
|
|
209 |
*/
|
|
210 | 11905 |
public static String substitute(Resolver resolver, String arg) { |
211 | 11905 |
if (arg == null) { |
212 | 0 |
return arg;
|
213 |
} |
|
214 |
|
|
215 | 11905 |
int i = 0;
|
216 | 11905 |
int marker = 0;
|
217 | 11905 |
StringBuffer sb = new StringBuffer();
|
218 | ? |
while ((i = arg.indexOf("${", marker)) != -1) { |
219 | 13 |
if (marker < i) {
|
220 | 4 |
sb.append(arg.substring(marker, i)); |
221 | 4 |
marker = i; |
222 |
} |
|
223 | 13 |
int end = arg.indexOf("}", i); |
224 | 13 |
if (end == -1) {
|
225 | 2 |
break;
|
226 |
} |
|
227 | 11 |
String name = arg.substring(i + 2, end); |
228 | 11 |
Object value = resolver.getProperty(name); |
229 | 11 |
if (value == null) { |
230 | 1 |
value = System.getProperty(name); |
231 |
} |
|
232 | 11 |
if (value == null) { |
233 | 1 |
value = arg.substring(i, end + 1); |
234 |
} |
|
235 | 11 |
sb.append(toString(value)); |
236 | 11 |
marker = end + 1; |
237 |
} |
|
238 | 11905 |
sb.append(arg.substring(marker)); |
239 | 11905 |
return sb.toString();
|
240 |
} |
|
241 |
|
|
242 |
/** Convert the given string into the given class, if possible,
|
|
243 |
* using any available parsers if conversion to basic types fails.
|
|
244 |
* The Resolver could be a parser, but it would need to adapt
|
|
245 |
* automatically to whatever is the current context.<p>
|
|
246 |
* Performs property substitution on the argument prior to evaluating it.
|
|
247 |
* Spaces are only trimmed from the argument if spaces have no meaning for
|
|
248 |
* the target class.
|
|
249 |
*/
|
|
250 | 40 |
public static Object eval(Resolver resolver, String arg, Class cls) |
251 |
throws IllegalArgumentException,
|
|
252 |
NoSuchReferenceException, |
|
253 |
ComponentSearchException { |
|
254 |
// Perform property substitution
|
|
255 | 40 |
arg = substitute(resolver, arg); |
256 |
|
|
257 | 40 |
Parser parser; |
258 | 40 |
Object result = null;
|
259 | 40 |
try {
|
260 | 40 |
if (arg == null || arg.equals(NULL)) { |
261 | 4 |
result = null;
|
262 |
} |
|
263 | 36 |
else if (cls.equals(Boolean.class) |
264 |
|| cls.equals(boolean.class)) { |
|
265 | 1 |
result = Boolean.valueOf(arg.trim()); |
266 |
} |
|
267 | 35 |
else if (cls.equals(Short.class) |
268 |
|| cls.equals(short.class)) { |
|
269 | 0 |
result = Short.valueOf(arg.trim()); |
270 |
} |
|
271 | 35 |
else if (cls.equals(Integer.class) |
272 |
|| cls.equals(int.class)) { |
|
273 | 2 |
result = Integer.valueOf(arg.trim()); |
274 |
} |
|
275 | 33 |
else if (cls.equals(Long.class) |
276 |
|| cls.equals(long.class)) { |
|
277 | 0 |
result = Long.valueOf(arg.trim()); |
278 |
} |
|
279 | 33 |
else if (cls.equals(Float.class) |
280 |
|| cls.equals(float.class)) { |
|
281 | 0 |
result = Float.valueOf(arg.trim()); |
282 |
} |
|
283 | 33 |
else if (cls.equals(Double.class) |
284 |
|| cls.equals(double.class)) { |
|
285 | 0 |
result = Double.valueOf(arg.trim()); |
286 |
} |
|
287 | 33 |
else if (cls.equals(ComponentReference.class)) { |
288 | 0 |
ComponentReference ref = |
289 |
resolver.getComponentReference(arg.trim()); |
|
290 | 0 |
if (ref == null) |
291 | 0 |
throw new NoSuchReferenceException("The resolver " |
292 |
+ resolver |
|
293 |
+ " has no reference '"
|
|
294 |
+ arg + "'");
|
|
295 | 0 |
result = ref; |
296 |
} |
|
297 | 33 |
else if (Component.class.isAssignableFrom(cls)) { |
298 | 16 |
ComponentReference ref = |
299 |
resolver.getComponentReference(arg.trim()); |
|
300 | 16 |
if (ref == null) |
301 | 0 |
throw new NoSuchReferenceException("The resolver " |
302 |
+ resolver |
|
303 |
+ " has no reference '"
|
|
304 |
+ arg + "'");
|
|
305 |
// Avoid requiring the user to wait for a component to become
|
|
306 |
// available, in most cases. In those cases where the
|
|
307 |
// component creation is particularly slow, an explicit wait
|
|
308 |
// can be added.
|
|
309 |
// Note that this is not necessarily a wait for the component
|
|
310 |
// to become visible, since menu items are not normally
|
|
311 |
// visible even if they're available.
|
|
312 | 16 |
result = waitForComponentAvailable(ref); |
313 |
} |
|
314 | 17 |
else if (cls.equals(String.class)) { |
315 | 7 |
result = arg; |
316 |
} |
|
317 | 10 |
else if (cls.isArray() && arg.trim().startsWith("[")) { |
318 | 8 |
arg = arg.trim(); |
319 | 8 |
String[] args = |
320 |
parseArgumentList(arg.substring(1, arg.length()-1)); |
|
321 | 8 |
Class base = cls.getComponentType(); |
322 | 8 |
Object arr = Array.newInstance(base, args.length); |
323 | 8 |
for (int i=0;i < args.length;i++) { |
324 | 3 |
Object obj = eval(resolver, args[i], base); |
325 | 3 |
Array.set(arr, i, obj); |
326 |
} |
|
327 | 8 |
result = arr; |
328 |
} |
|
329 | ? |
else if ((parser = getParser(cls)) != null) { |
330 | 2 |
result = parser.parse(arg.trim()); |
331 |
} |
|
332 |
else {
|
|
333 | 0 |
String msg = Strings.get("parser.conversion_error",
|
334 |
new Object[] {
|
|
335 |
arg.trim(), |
|
336 |
cls.getName() |
|
337 |
}); |
|
338 | 0 |
throw new IllegalArgumentException(msg); |
339 |
} |
|
340 | 39 |
return result;
|
341 |
} |
|
342 |
catch(NumberFormatException nfe) {
|
|
343 | 0 |
String msg = Strings.get("parser.conversion_error",
|
344 |
new Object[] {
|
|
345 |
arg.trim(), |
|
346 |
cls.getName() |
|
347 |
}); |
|
348 | 0 |
throw new IllegalArgumentException(msg); |
349 |
} |
|
350 |
} |
|
351 |
|
|
352 |
/** Evaluate the given set of arguments into the given set of types. */
|
|
353 | 0 |
public static Object[] eval(Resolver resolver, |
354 |
String[] args, Class[] params) |
|
355 |
throws IllegalArgumentException,
|
|
356 |
NoSuchReferenceException, |
|
357 |
ComponentSearchException { |
|
358 | 0 |
Object[] plist = new Object[params.length];
|
359 | 0 |
for(int i=0;i < plist.length;i++) { |
360 | 0 |
plist[i] = eval(resolver, args[i], params[i]); |
361 |
} |
|
362 | 0 |
return plist;
|
363 |
} |
|
364 |
|
|
365 |
/** Replace all instances in the given String of s1 with s2. */
|
|
366 | 3394 |
public static String replace(String str, String s1, String s2) { |
367 | 3394 |
StringBuffer sb = new StringBuffer(str);
|
368 | 3394 |
int index = 0;
|
369 | ? |
while ((index = sb.toString().indexOf(s1, index)) != -1) {
|
370 | 109 |
sb.delete(index, index + s1.length()); |
371 | 109 |
sb.insert(index, s2); |
372 | 109 |
index += s2.length(); |
373 |
} |
|
374 | 3394 |
return sb.toString();
|
375 |
} |
|
376 |
|
|
377 |
// TODO: move this somewhere more appropriate; make public static, maybe
|
|
378 |
// in ComponentReference
|
|
379 | 16 |
private static Component waitForComponentAvailable(final ComponentReference ref) |
380 |
throws ComponentSearchException {
|
|
381 | 16 |
try {
|
382 | 16 |
ComponentTester tester = |
383 |
ComponentTester.getTester(Component.class);
|
|
384 |
|
|
385 | 16 |
tester.wait(new Condition() {
|
386 | 16 |
public boolean test() { |
387 | 16 |
try { ref.getComponent(); }
|
388 | 0 |
catch(ComponentNotFoundException e) { return false; } |
389 |
catch(MultipleComponentsFoundException m) { }
|
|
390 | 16 |
return true; |
391 |
} |
|
392 | 0 |
public String toString() {
|
393 | 0 |
return ref + " to become available"; |
394 |
} |
|
395 |
}, ComponentTester.componentDelay); |
|
396 |
} |
|
397 |
catch(WaitTimedOutError wto) {
|
|
398 | 0 |
String msg = "Could not find " + ref + ": " |
399 |
+ Step.toXMLString(ref); |
|
400 | 0 |
throw new ComponentNotFoundException(msg); |
401 |
} |
|
402 | 16 |
return ref.getComponent();
|
403 |
} |
|
404 |
|
|
405 |
/** Convert a value into a String representation. Handles null values and
|
|
406 |
arrays. Returns null if the String representation is the default
|
|
407 |
class@pointer format.
|
|
408 |
*/
|
|
409 | 47 |
public static String toString(Object value) { |
410 | 47 |
if (value == null) |
411 | 2 |
return NULL;
|
412 | 45 |
if (value.getClass().isArray()) {
|
413 | 4 |
StringBuffer sb = new StringBuffer();
|
414 | 4 |
sb.append("[");
|
415 | 4 |
for (int i=0;i < Array.getLength(value);i++) { |
416 | 12 |
Object o = Array.get(value, i); |
417 | 12 |
if (i > 0)
|
418 | 8 |
sb.append(",");
|
419 | 12 |
sb.append(toString(o)); |
420 |
} |
|
421 | 4 |
sb.append("]");
|
422 | 4 |
return sb.toString();
|
423 |
} |
|
424 | 41 |
String s = value.toString(); |
425 | 41 |
if (s == null) |
426 | 1 |
return NULL;
|
427 |
|
|
428 | 40 |
if (isDefaultToString(s))
|
429 | 2 |
return DEFAULT_TOSTRING;
|
430 | 38 |
return s;
|
431 |
} |
|
432 |
|
|
433 |
/** Returns whether the given String is the default toString()
|
|
434 |
* implementation for the given Object.
|
|
435 |
*/
|
|
436 | 319 |
public static boolean isDefaultToString(String s) { |
437 | 319 |
if (s == null) |
438 | 1 |
return false; |
439 |
|
|
440 | 318 |
int at = s.indexOf("@"); |
441 | 318 |
if (at != -1) {
|
442 | 4 |
String hash = s.substring(at + 1, s.length()); |
443 | 4 |
try {
|
444 | 4 |
Integer.parseInt(hash, 16); |
445 | 4 |
return true; |
446 |
} |
|
447 |
catch(NumberFormatException e) {
|
|
448 |
} |
|
449 |
} |
|
450 | 314 |
return false; |
451 |
} |
|
452 |
|
|
453 |
} |
|
454 |
|
|