Clover coverage report - clover
Coverage timestamp: Sat Oct 8 2005 22:54:17 EDT
file stats: LOC: 454   Methods: 20
NCLOC: 360   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
ArgumentParser.java 72.6% 83.3% 85% 79.9%
coverage coverage
 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