001: package abbot.script;
002:
003: import java.awt.Component;
004: import java.lang.reflect.Array;
005: import java.util.*;
006:
007: import abbot.*;
008: import abbot.i18n.Strings;
009: import abbot.finder.*;
010: import abbot.script.parsers.Parser;
011: import abbot.tester.*;
012: import abbot.util.Condition;
013:
014: /** Provide parsing of a String into an array of appropriately typed
015: * arguments. Arrays are indicated by square brackets, and arguments are
016: * separated by commas, e.g.<br>
017: * <ul>
018: * <li>An empty String array (length zero): "[]"
019: * <li>Three arguments "one,two,three"
020: * <li>An array of length three: "[one,two,three]"
021: * <li>A single-element array of integer: "[1]"
022: * <li>A single null argument: "null"
023: * <li>An array of two strings: "[one,two]"
024: * <li>Commas must be escaped when they would otherwise be interpreted as an
025: * argument separator:<br>
026: * "one,two%2ctwo,three" (2nd argument is "two,two")
027: */
028:
029: public class ArgumentParser {
030: private ArgumentParser() {
031: }
032:
033: private static final String ESC_ESC_COMMA = "%%2C";
034: public static final String ESC_COMMA = "%2c";
035: public static final String NULL = "null";
036: public static final String DEFAULT_TOSTRING = "<default-tostring>";
037:
038: /** Maps class names to their corresponding string parsers. */
039: private static Map parsers = new HashMap();
040:
041: private static boolean isExtension(String name) {
042: return name.indexOf(".extensions.") != -1;
043: }
044:
045: private static Parser findParser(String name, Class targetClass) {
046: Log.debug("Trying " + name + " for " + targetClass);
047: try {
048: Class cvtClass = isExtension(name) ? Class.forName(name,
049: true, targetClass.getClassLoader()) : Class
050: .forName(name);
051: Parser parser = (Parser) cvtClass.newInstance();
052: if (cvtClass.getName().indexOf(".extensions.") == -1)
053: parsers.put(targetClass, parser);
054: return parser;
055: } catch (InstantiationException ie) {
056: Log.debug(ie);
057: } catch (IllegalAccessException iae) {
058: Log.debug(iae);
059: } catch (ClassNotFoundException cnf) {
060: Log.debug(cnf);
061: }
062: return null;
063: }
064:
065: /** Set the parser for a given class. Returns the old one, if any. */
066: public static Parser setParser(Class cls, Parser parser) {
067: Parser old = (Parser) parsers.get(cls);
068: parsers.put(cls, parser);
069: return old;
070: }
071:
072: /** Find a string parser for the given class. Returns null if none
073: * found.
074: */
075: public static Parser getParser(Class cls) {
076: Parser parser = (Parser) parsers.get(cls);
077: // Load core testers with the current framework's class loader
078: // context, and anything else in the context of the code under test
079: if (parser == null) {
080: String base = ComponentTester.simpleClassName(cls);
081: String pkg = Parser.class.getPackage().getName();
082: parser = findParser(pkg + "." + base + "Parser", cls);
083: if (parser == null) {
084: parser = findParser(pkg + ".extensions." + base
085: + "Parser", cls);
086: }
087: }
088: return parser;
089: }
090:
091: private static boolean isBounded(String s) {
092: return s.startsWith("[") && s.endsWith("]")
093: || s.startsWith("\"") && s.endsWith("\"")
094: || s.startsWith("'") && s.endsWith("'");
095: }
096:
097: private static String escapeCommas(String s) {
098: return replace(replace(s, ESC_COMMA, ESC_ESC_COMMA), ",",
099: ESC_COMMA);
100: }
101:
102: private static String unescapeCommas(String s) {
103: return replace(replace(s, ESC_COMMA, ","), ESC_ESC_COMMA,
104: ESC_COMMA);
105: }
106:
107: public static String encodeArguments(String[] args) {
108: StringBuffer sb = new StringBuffer();
109: if (args.length > 0) {
110: if (isBounded(args[0])) {
111: sb.append(args[0]);
112: } else {
113: sb.append(escapeCommas(args[0]));
114: }
115: for (int i = 1; i < args.length; i++) {
116: sb.append(",");
117: if (isBounded(args[i])) {
118: sb.append(args[i]);
119: } else {
120: sb.append(escapeCommas(args[i]));
121: }
122: }
123: }
124: return sb.toString();
125: }
126:
127: private static class Tokenizer extends ArrayList {
128: public Tokenizer(String input) {
129: while (true) {
130: int index = input.indexOf(",");
131: if (index == -1) {
132: add(input);
133: break;
134: }
135: add(input.substring(0, index));
136: input = input.substring(index + 1);
137: }
138: }
139: }
140:
141: /** Convert the given encoded String into an array of Strings.
142: * Interprets strings of the format "[el1,el2,el3]" to be a single (array)
143: * argument (such commas do not need escaping). <p>
144: * Explicit commas and square brackets in arguments must be escaped by
145: * preceding the character with a backslash ('\'). The strings
146: * '(null)' and 'null' are interpreted as the value null.<p>
147: * Explicit spaces should be protected by double quotes, e.g.
148: * " an argument bounded by spaces ".
149: */
150: public static String[] parseArgumentList(String encodedArgs) {
151: ArrayList alist = new ArrayList();
152: if (encodedArgs == null || "".equals(encodedArgs))
153: return new String[0];
154: // handle old method of escaped commas
155: encodedArgs = replace(encodedArgs, "\\,", ESC_COMMA);
156: Iterator iter = new Tokenizer(encodedArgs).iterator();
157: while (iter.hasNext()) {
158: String str = (String) iter.next();
159:
160: if (str.trim().startsWith("[") && !str.trim().endsWith("]")) {
161: while (iter.hasNext()) {
162: String next = (String) iter.next();
163: str += "," + next;
164: if (next.trim().endsWith("]")) {
165: break;
166: }
167: }
168: } else if (str.trim().startsWith("\"")
169: && !str.trim().endsWith("\"")) {
170: while (iter.hasNext()) {
171: String next = (String) iter.next();
172: str += "," + next;
173: if (next.trim().endsWith("\"")) {
174: break;
175: }
176: }
177: } else if (str.trim().startsWith("'")
178: && !str.trim().endsWith("'")) {
179: while (iter.hasNext()) {
180: String next = (String) iter.next();
181: str += "," + next;
182: if (next.trim().endsWith("'")) {
183: break;
184: }
185: }
186: }
187:
188: if (NULL.equals(str.trim())) {
189: alist.add(null);
190: } else {
191: // If it's an array, don't unescape the commas yet
192: if (!str.startsWith("[")) {
193: str = unescapeCommas(str);
194: }
195: alist.add(str);
196: }
197: }
198: return (String[]) alist.toArray(new String[alist.size()]);
199: }
200:
201: /** Performs property substitutions on the argument priort to evaluating
202: * it. Substitutions are not recursive.
203: */
204: public static String substitute(Resolver resolver, String arg) {
205: if (arg == null) {
206: return arg;
207: }
208:
209: int i = 0;
210: int marker = 0;
211: StringBuffer sb = new StringBuffer();
212: while ((i = arg.indexOf("${", marker)) != -1) {
213: if (marker < i) {
214: sb.append(arg.substring(marker, i));
215: marker = i;
216: }
217: int end = arg.indexOf("}", i);
218: if (end == -1) {
219: break;
220: }
221: String name = arg.substring(i + 2, end);
222: Object value = resolver.getProperty(name);
223: if (value == null) {
224: value = System.getProperty(name);
225: }
226: if (value == null) {
227: value = arg.substring(i, end + 1);
228: }
229: sb.append(toString(value));
230: marker = end + 1;
231: }
232: sb.append(arg.substring(marker));
233: return sb.toString();
234: }
235:
236: /** Convert the given string into the given class, if possible,
237: * using any available parsers if conversion to basic types fails.
238: * The Resolver could be a parser, but it would need to adapt
239: * automatically to whatever is the current context.<p>
240: * Performs property substitution on the argument prior to evaluating it.
241: * Spaces are only trimmed from the argument if spaces have no meaning for
242: * the target class.
243: */
244: public static Object eval(Resolver resolver, String arg, Class cls)
245: throws IllegalArgumentException, NoSuchReferenceException,
246: ComponentSearchException {
247: // Perform property substitution
248: arg = substitute(resolver, arg);
249:
250: Parser parser;
251: Object result = null;
252: try {
253: if (arg == null || arg.equals(NULL)) {
254: result = null;
255: } else if (cls.equals(Boolean.class)
256: || cls.equals(boolean.class)) {
257: result = Boolean.valueOf(arg.trim());
258: } else if (cls.equals(Short.class)
259: || cls.equals(short.class)) {
260: result = Short.valueOf(arg.trim());
261: } else if (cls.equals(Integer.class)
262: || cls.equals(int.class)) {
263: result = Integer.valueOf(arg.trim());
264: } else if (cls.equals(Long.class) || cls.equals(long.class)) {
265: result = Long.valueOf(arg.trim());
266: } else if (cls.equals(Float.class)
267: || cls.equals(float.class)) {
268: result = Float.valueOf(arg.trim());
269: } else if (cls.equals(Double.class)
270: || cls.equals(double.class)) {
271: result = Double.valueOf(arg.trim());
272: } else if (cls.equals(ComponentReference.class)) {
273: ComponentReference ref = resolver
274: .getComponentReference(arg.trim());
275: if (ref == null)
276: throw new NoSuchReferenceException("The resolver "
277: + resolver + " has no reference '" + arg
278: + "'");
279: result = ref;
280: } else if (Component.class.isAssignableFrom(cls)) {
281: ComponentReference ref = resolver
282: .getComponentReference(arg.trim());
283: if (ref == null)
284: throw new NoSuchReferenceException("The resolver "
285: + resolver + " has no reference '" + arg
286: + "'");
287: // Avoid requiring the user to wait for a component to become
288: // available, in most cases. In those cases where the
289: // component creation is particularly slow, an explicit wait
290: // can be added.
291: // Note that this is not necessarily a wait for the component
292: // to become visible, since menu items are not normally
293: // visible even if they're available.
294: result = waitForComponentAvailable(ref);
295: } else if (cls.equals(String.class)) {
296: result = arg;
297: } else if (cls.isArray() && arg.trim().startsWith("[")) {
298: arg = arg.trim();
299: String[] args = parseArgumentList(arg.substring(1, arg
300: .length() - 1));
301: Class base = cls.getComponentType();
302: Object arr = Array.newInstance(base, args.length);
303: for (int i = 0; i < args.length; i++) {
304: Object obj = eval(resolver, args[i], base);
305: Array.set(arr, i, obj);
306: }
307: result = arr;
308: } else if ((parser = getParser(cls)) != null) {
309: result = parser.parse(arg.trim());
310: } else {
311: String msg = Strings.get("parser.conversion_error",
312: new Object[] { arg.trim(), cls.getName() });
313: throw new IllegalArgumentException(msg);
314: }
315: return result;
316: } catch (NumberFormatException nfe) {
317: String msg = Strings.get("parser.conversion_error",
318: new Object[] { arg.trim(), cls.getName() });
319: throw new IllegalArgumentException(msg);
320: }
321: }
322:
323: /** Evaluate the given set of arguments into the given set of types. */
324: public static Object[] eval(Resolver resolver, String[] args,
325: Class[] params) throws IllegalArgumentException,
326: NoSuchReferenceException, ComponentSearchException {
327: Object[] plist = new Object[params.length];
328: for (int i = 0; i < plist.length; i++) {
329: plist[i] = eval(resolver, args[i], params[i]);
330: }
331: return plist;
332: }
333:
334: /** Replace all instances in the given String of s1 with s2. */
335: public static String replace(String str, String s1, String s2) {
336: StringBuffer sb = new StringBuffer(str);
337: int index = 0;
338: while ((index = sb.toString().indexOf(s1, index)) != -1) {
339: sb.delete(index, index + s1.length());
340: sb.insert(index, s2);
341: index += s2.length();
342: }
343: return sb.toString();
344: }
345:
346: // TODO: move this somewhere more appropriate; make public static, maybe
347: // in ComponentReference
348: private static Component waitForComponentAvailable(
349: final ComponentReference ref)
350: throws ComponentSearchException {
351: try {
352: ComponentTester tester = ComponentTester
353: .getTester(Component.class);
354:
355: tester.wait(new Condition() {
356: public boolean test() {
357: try {
358: ref.getComponent();
359: } catch (ComponentNotFoundException e) {
360: return false;
361: } catch (MultipleComponentsFoundException m) {
362: }
363: return true;
364: }
365:
366: public String toString() {
367: return ref + " to become available";
368: }
369: }, ComponentTester.componentDelay);
370: } catch (WaitTimedOutError wto) {
371: String msg = "Could not find " + ref + ": "
372: + Step.toXMLString(ref);
373: throw new ComponentNotFoundException(msg);
374: }
375: return ref.getComponent();
376: }
377:
378: /** Convert a value into a String representation. Handles null values and
379: arrays. Returns null if the String representation is the default
380: class@pointer format.
381: */
382: public static String toString(Object value) {
383: if (value == null)
384: return NULL;
385: if (value.getClass().isArray()) {
386: StringBuffer sb = new StringBuffer();
387: sb.append("[");
388: for (int i = 0; i < Array.getLength(value); i++) {
389: Object o = Array.get(value, i);
390: if (i > 0)
391: sb.append(",");
392: sb.append(toString(o));
393: }
394: sb.append("]");
395: return sb.toString();
396: }
397: String s = value.toString();
398: if (s == null)
399: return NULL;
400:
401: if (isDefaultToString(s))
402: return DEFAULT_TOSTRING;
403: return s;
404: }
405:
406: /** Returns whether the given String is the default toString()
407: * implementation for the given Object.
408: */
409: public static boolean isDefaultToString(String s) {
410: if (s == null)
411: return false;
412:
413: int at = s.indexOf("@");
414: if (at != -1) {
415: String hash = s.substring(at + 1, s.length());
416: try {
417: Integer.parseInt(hash, 16);
418: return true;
419: } catch (NumberFormatException e) {
420: }
421: }
422: return false;
423: }
424:
425: }
|