Clover coverage report - clover
Coverage timestamp: Sat Oct 8 2005 22:54:17 EDT
file stats: LOC: 354   Methods: 25
NCLOC: 234   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
Step.java 61.1% 68.7% 80% 68.8%
coverage coverage
 1   
 package abbot.script;
 2   
 
 3   
 import java.awt.Component;
 4   
 import java.io.*;
 5   
 import java.lang.reflect.*;
 6   
 import java.util.*;
 7   
 
 8   
 import org.jdom.*;
 9   
 import org.jdom.input.SAXBuilder;
 10   
 import org.jdom.output.XMLOutputter;
 11   
 
 12   
 import abbot.Log;
 13   
 import abbot.finder.*;
 14   
 import abbot.i18n.Strings;
 15   
 import abbot.tester.ComponentTester;
 16   
 
 17   
 /**
 18   
  * Provides access to one step (line) from a script.  A Step is the basic
 19   
  * unit of execution.  
 20   
  * <b>Custom Step classes</b><p>
 21   
  * All custom {@link Step} classes must supply a {@link Constructor} with the
 22   
  * signature <code>&lt;init&gt;(${link Resolver}, {@link Map})</code>.  If 
 23   
  * the step has contents (e.g. {@link Sequence}), then it should also
 24   
  * provide a {@link Constructor} with the signature
 25   
  * <code>&lt;init&gt;({@link Resolver}, {@link Element}, {@link Map})</code>.
 26   
  * <p>
 27   
  * The XML tag for a given {@link Step} will be used to auto-generate the
 28   
  * {@link Step} class name, e.g. the tag &lt;aphrodite/&gt; causes the
 29   
  * parser to create an instance of class <code>abbot.script.Aphrodite</code>,
 30   
  * using one of the {@link Constructor}s described above. 
 31   
  * <p>
 32   
  * All derived classes should include an entry in the
 33   
  * <a href={@docRoot}/../abbot.xsd>schema</a>, or validation must be turned
 34   
  * off by setting the System property
 35   
  * <code>abbot.script.validate=false</code>. 
 36   
  * <p>
 37   
  * You can make the custom <code>Aphrodite</code> step do just about anything
 38   
  * by overriding the {@link #runStep()} method.  
 39   
  * <p>
 40   
  * See the source for any {@link Step} implementation in this package for
 41   
  * examples. 
 42   
  */
 43   
 public abstract class Step implements XMLConstants, XMLifiable, Serializable {
 44   
 
 45   
     private String description = null;
 46   
     private Resolver resolver;
 47   
     /** Error encountered on parse. */
 48   
     private Throwable invalidScriptError = null;
 49   
 
 50  674
     public Step(Resolver resolver, Map attributes) {
 51  674
         this(resolver, "");
 52  674
         Log.debug("Instantiating " + getClass());
 53  674
         if (Log.expectDebugOutput) {
 54  0
             Iterator iter = attributes.keySet().iterator();
 55  0
             while (iter.hasNext()) {
 56  0
                 String key = (String)iter.next();
 57  0
                 Log.debug(key + "=" + attributes.get(key));
 58   
             }
 59   
         }
 60  674
         parseStepAttributes(attributes);
 61   
     }
 62   
 
 63  1283
     public Step(Resolver resolver, String description) {
 64   
         // Kind of a hack; a Script is its own resolver
 65  1283
         if (resolver == null) {
 66  617
             if (!(this instanceof Resolver)) {
 67  0
                 throw new Error("Resolver must be provided");
 68   
             }
 69  617
             resolver = (Resolver)this;
 70   
         }
 71  666
         else if (this instanceof Resolver) {
 72  36
             resolver = (Resolver)this;
 73   
         }
 74  1283
         this.resolver = resolver;
 75  1283
         if ("".equals(description))
 76  676
             description = null;
 77  1283
         this.description = description;
 78   
     }
 79   
     
 80   
     /** Only exposed so that Script may invoke it on load from disk. */
 81  689
     protected final void parseStepAttributes(Map attributes) {
 82  689
         Log.debug("Parsing attributes for " + getClass());
 83  689
         description = (String)attributes.get(TAG_DESC);
 84   
     }
 85   
 
 86   
     /** Main run method.  Should <b>never</b> be run on the event dispatch
 87   
      * thread, although no check is explicitly done here.
 88   
      */
 89  33
     public final void run() throws Throwable {
 90  33
         if (invalidScriptError != null)
 91  0
             throw invalidScriptError;
 92  33
         Log.debug("Running " + toString());
 93  33
         runStep();
 94   
     }
 95   
 
 96   
     /** Implement the step's behavior here. */
 97   
     protected abstract void runStep() throws Throwable;
 98   
 
 99  1720
     public String getDescription() { 
 100  1720
         return description != null ? description : getDefaultDescription();
 101   
     }
 102  55
     public void setDescription(String desc) { 
 103  55
         description = desc;
 104   
     }
 105   
 
 106   
     /** Define the XML tag to use for this script step. */
 107   
     public abstract String getXMLTag();
 108   
 
 109   
     /** Provide a usage String for this step. */
 110   
     public abstract String getUsage();
 111   
 
 112   
     /** Return a reasonable default description for this script step.
 113   
         This value is used in the absence of an explicit description. 
 114   
      */
 115   
     public abstract String getDefaultDescription();
 116   
 
 117   
     /** For use by subclasses when an error is encountered during parsing. 
 118   
      * Should only be used by the XML parsing ctors.
 119   
      */
 120  20
     protected void setScriptError(Throwable thr) {
 121  20
         if (invalidScriptError == null) {
 122  20
             invalidScriptError = thr;
 123   
         }
 124   
         else {
 125  0
             Log.warn("More than one script error encountered: " + thr);
 126  0
             Log.warn("Already have: " + invalidScriptError);
 127   
         }
 128   
     }
 129   
 
 130   
     /** Throw an invalid script exception describing the proper script
 131   
      * usage.  This should be used by derived classes whenever parsing
 132   
      * indicates invalid input.
 133   
      */ 
 134  0
     protected void usage() {
 135  0
         usage(null);
 136   
     }
 137   
 
 138   
     /** Store an invalid script exception describing the proper script
 139   
      * usage.  This should be used by derived classes whenever parsing
 140   
      * indicates invalid input.  
 141   
      */ 
 142  0
     protected void usage(String details) {
 143  0
         String msg = getUsage();
 144  0
         if (details != null) {
 145  0
             msg = Strings.get("step.usage", new Object[] { msg, details });
 146   
         }
 147  0
         setScriptError(new InvalidScriptException(msg));
 148   
     }
 149   
 
 150   
     /** Attributes to save in script. */
 151  785
     public Map getAttributes() {
 152  785
         Map map = new HashMap();
 153  785
         if (description != null
 154   
             && !description.equals(getDefaultDescription()))
 155  56
             map.put(TAG_DESC, description);
 156  785
         return map;
 157   
     }
 158   
 
 159  59063
     public Resolver getResolver() { return resolver; }
 160   
 
 161   
     /** Override if the step actually has some contents.  In most cases, it
 162   
      * won't.
 163   
      */
 164  44
     protected Element addContent(Element el) { 
 165  44
         return el;
 166   
     }
 167   
 
 168   
     /** Add an attribute to the given XML Element.  Attributes are kept in
 169   
         alphabetical order. */
 170  807
     protected Element addAttributes(Element el) {
 171   
         // Use a TreeMap to keep the attributes sorted on output
 172  807
         Map atts = new TreeMap(getAttributes());
 173  807
         Iterator iter = atts.keySet().iterator();
 174  807
         while (iter.hasNext()) {
 175  218
             String key = (String)iter.next();
 176  218
             String value = (String)atts.get(key);
 177  218
             if (value == null) {
 178  0
                 Log.warn("Attribute '" + key + "' value was null in step "
 179   
                          + getXMLTag());
 180  0
                 value = "";
 181   
             }
 182  218
             el.setAttribute(key, value);
 183   
         }
 184  807
         return el;
 185   
     }
 186   
 
 187   
     /** Convert this Step into a String suitable for editing.  The default is
 188   
         the XML representation of the Step. */
 189  0
     public String toEditableString() {
 190  0
         return toXMLString(this);
 191   
     }
 192   
 
 193   
     /** Provide a one-line XML string representation. */
 194  2837
     public static String toXMLString(XMLifiable obj) {
 195   
         // Comments are the only things that aren't actually elements...
 196  2837
         if (obj instanceof Comment) {
 197  0
             return "<!-- " + ((Comment)obj).getDescription() + " -->";
 198   
         }
 199  2837
         Element el = obj.toXML();
 200  2837
         StringWriter writer = new StringWriter();
 201  2837
         try {
 202  2837
             XMLOutputter outputter = new XMLOutputter();
 203  2837
             outputter.output(el, writer);
 204   
         }
 205   
         catch(IOException io) {
 206  0
             Log.warn(io);
 207   
         }
 208  2837
         return writer.toString();
 209   
     }
 210   
 
 211   
     /** Convert the object to XML.  */
 212  807
     public Element toXML() {
 213  807
         return addAttributes(addContent(new Element(getXMLTag())));
 214   
     }
 215   
 
 216   
     /** Create a new step from an in-line XML string. */
 217  2
     public static Step createStep(Resolver resolver, String str) 
 218   
         throws InvalidScriptException, IOException {
 219  2
         StringReader reader = new StringReader(str);
 220  2
         try {
 221  2
             SAXBuilder builder = new SAXBuilder();
 222  2
             Document doc = builder.build(reader);
 223  2
             Element el = doc.getRootElement();
 224  2
             return createStep(resolver, el);
 225   
         }
 226   
         catch(JDOMException e) {
 227  0
             throw new InvalidScriptException(e.getMessage());
 228   
         }
 229   
     }
 230   
 
 231   
     /** Convert the attributes in the given XML Element into a Map of
 232   
         name/value pairs. */
 233  38
     protected static Map createAttributeMap(Element el) {
 234  38
         Log.debug("Creating attribute map for " + el);
 235  38
         Map attributes = new HashMap();
 236  38
         Iterator iter = el.getAttributes().iterator();
 237  38
         while (iter.hasNext()) {
 238  26
             Attribute att = (Attribute)iter.next();
 239  26
             attributes.put(att.getName(), att.getValue());
 240   
         }
 241  38
         return attributes;
 242   
     }
 243   
 
 244   
     /**
 245   
      * Factory method, equivalent to a "fromXML" for step creation.  Looks for
 246   
      * a class with the same name as the XML tag, with the first letter
 247   
      * capitalized.  For example, &lt;call /&gt; is abbot.script.Call.
 248   
      */
 249  23
     public static Step createStep(Resolver resolver, Element el) 
 250   
         throws InvalidScriptException {
 251  23
         String tag = el.getName();
 252  23
         Map attributes = createAttributeMap(el);
 253  23
         String name = tag.substring(0, 1).toUpperCase() + tag.substring(1);
 254  23
         if (tag.equals(TAG_WAIT)) {
 255  0
             attributes.put(TAG_WAIT, "true");
 256  0
             name = "Assert";
 257   
         }
 258  23
         try {
 259  23
             name = "abbot.script." + name;
 260  23
             Log.debug("Instantiating " + name);
 261  23
             Class cls = Class.forName(name);
 262  23
             try {
 263   
                 // Steps with contents require access to the XML element
 264  23
                 Class[] argTypes = new Class[] {
 265   
                     Resolver.class, Element.class, Map.class
 266   
                 };
 267  23
                 Constructor ctor = cls.getConstructor(argTypes);
 268  7
                 return (Step)ctor.newInstance(new Object[] {
 269   
                     resolver, el, attributes
 270   
                 });
 271   
             }
 272   
             catch(NoSuchMethodException nsm) {
 273   
                 // All steps must support this ctor
 274  16
                 Class[] argTypes = new Class[] { 
 275   
                     Resolver.class, Map.class
 276   
                 };
 277  16
                 Constructor ctor = cls.getConstructor(argTypes);
 278  16
                 return (Step)ctor.newInstance(new Object[] {
 279   
                     resolver, attributes
 280   
                 });
 281   
             }
 282   
         }
 283   
         catch(ClassNotFoundException cnf) {
 284  0
             String msg = Strings.get("step.unknown_tag", new Object[] { tag });
 285  0
             throw new InvalidScriptException(msg);
 286   
         }
 287   
         catch(InvocationTargetException ite) {
 288  0
             Log.warn(ite);
 289  0
             throw new InvalidScriptException(ite.getTargetException().
 290   
                                              getMessage());
 291   
         }
 292   
         catch(Exception exc) {
 293  0
             Log.warn(exc);
 294  0
             throw new InvalidScriptException(exc.getMessage());
 295   
         }
 296   
     }
 297   
 
 298  96
     protected String simpleClassName(Class cls) {
 299  96
         return ComponentTester.simpleClassName(cls);
 300   
     }
 301   
 
 302   
     /** Return a description of this script step. */
 303  1270
     public String toString() {
 304  1270
         return getDescription();
 305   
     }
 306   
 
 307   
     /** Returns the Class corresponding to the given class name.  Provides
 308   
      * just-in-time classname resolution to ensure loading by the proper class
 309   
      * loader. <p>
 310   
      */
 311  197
     public Class resolveClass(String className) throws ClassNotFoundException {
 312  197
         ClassLoader cl = getResolver().getContextClassLoader();
 313  197
         return Class.forName(className, true, cl);
 314   
     }
 315   
 
 316   
     /** Look up an appropriate ComponentTester given an arbitrary
 317   
      * Component-derived class.
 318   
      * If the class is derived from abbot.tester.ComponentTester, instantiate
 319   
      * one; if it is derived from java.awt.Component, return a matching Tester.
 320   
      * Otherwise return abbot.tester.ComponentTester.<p>
 321   
      * @throws ClassNotFoundException If the given class can't be found.
 322   
      * @throws IllegalArgumentException If the tester cannot be instantiated.
 323   
      */
 324  7
     protected ComponentTester resolveTester(String className) 
 325   
         throws ClassNotFoundException {
 326  7
         Class testedClass = resolveClass(className);
 327  7
         if (Component.class.isAssignableFrom(testedClass))
 328  7
             return ComponentTester.getTester(testedClass);
 329  0
         else if (ComponentTester.class.isAssignableFrom(testedClass)) {
 330  0
             try {
 331  0
                 return (ComponentTester)testedClass.newInstance();
 332   
             }
 333   
             catch(Exception e) {
 334  0
                 String msg = "Custom ComponentTesters must provide "
 335   
                     + "an accessible no-args Constructor: " + e.getMessage();
 336  0
                 throw new IllegalArgumentException(msg);
 337   
             }
 338   
         }
 339  0
         String msg = "The given class '" + className
 340   
             + "' is neither a Component nor a ComponentTester";
 341  0
         throw new IllegalArgumentException(msg);
 342   
     }
 343   
 
 344  0
     private void writeObject(ObjectOutputStream out) {
 345   
         // NOTE: this is only to avoid drag/drop errors
 346  0
         out = null;
 347   
     }
 348   
 
 349  0
     private void readObject(ObjectInputStream in) {
 350   
         // NOTE: this is only to avoid drag/drop errors
 351  0
         in = null;
 352   
     }
 353   
 }
 354