package abbot.script.swt; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.text.MessageFormat; import java.util.HashMap; import java.util.Iterator; import org.eclipse.swt.widgets.Widget; import org.jdom.Attribute; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.XMLOutputter; import abbot.Log; import abbot.finder.matchers.swt.NameMatcher; import abbot.finder.swt.BasicFinder; import abbot.finder.swt.MultipleWidgetsFoundException; import abbot.finder.swt.WidgetNotFoundException; import abbot.i18n.Strings; import abbot.script.InvalidScriptException; import abbot.script.NoSuchReferenceException; import abbot.script.XMLConstants; import abbot.script.XMLifiable; import abbot.swt.Resolver; import abbot.finder.swt.WidgetFinder; import abbot.tester.swt.WidgetTester; /** * Provides access to one step (line) from a script. A Step is the basic * unit of execution. All derived classes should have a tag "sampleStep" with * a corresponding class abbot.script.SampleStep. The class must supply at * least a Constructor with the signature SampleStep(Resolver, HashMap). If * the step has contents (e.g. Sequence), then it should also provide * SampleStep(Resolver, Element, HashMap). */ public abstract class Step implements XMLConstants, XMLifiable, Serializable { public static final String copyright = "Licensed Materials -- Property of IBM\n"+ "(c) Copyright International Business Machines Corporation, 2003\nUS Government "+ "Users Restricted Rights - Use, duplication or disclosure restricted by GSA "+ "ADP Schedule Contract with IBM Corp."; private String description = null; private Resolver resolver; private WidgetTester tester; /** Error encountered on parse. */ private Throwable invalidScriptError = null; public Step(Resolver resolver, HashMap attributes) { this(resolver, ""); Log.debug("Instantiating " + getClass()); if (Log.expectDebugOutput) { Iterator iter = attributes.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); Log.debug(key + "=" + attributes.get(key)); } } parseStepAttributes(attributes); } public Step(Resolver resolver, String description) { // Kind of a hack; a Script is its own resolver if (resolver == null) { throw new Error("Resolver must be provided"); // TODO CREATE NEW RESOLVER HERE } this.resolver = resolver; if ("".equals(description)) description = null; this.description = description; } protected final void parseStepAttributes(HashMap attributes) { Log.debug("Parsing attributes for " + getClass()); description = (String)attributes.get(TAG_DESC); } /** Main run method. Should <b>never</b> be run on the event dispatch * thread, although no check is explicitly done here. */ public final void run() throws Throwable { if (invalidScriptError != null) throw invalidScriptError; Log.debug("Running " + toString()); runStep(); } /** Implement the step's behavior here. */ protected abstract void runStep() throws Throwable; public String getDescription() { return description != null ? description : getDefaultDescription(); } public void setDescription(String desc) { description = desc; } /** Define the XML tag to use for this script step. */ public abstract String getXMLTag(); /** Provide a usage String for this step. */ public abstract String getUsage(); /** Return a reasonable default description for this script step. This value is used in the absence of an explicit description. */ protected abstract String getDefaultDescription(); /** For use by subclasses when an error is encountered during parsing. * Should only be used by the XML parsing ctors. */ protected void setScriptError(Throwable thr) { if (invalidScriptError != null) invalidScriptError = thr; else Log.warn("More than one script error encountered: " + thr); } /** Throw an invalid script exception describing the proper script * usage. This should be used by derived classes whenever parsing * indicates invalid input. */ protected void usage() { usage(null); } /** Store an invalid script exception describing the proper script * usage. This should be used by derived classes whenever parsing * indicates invalid input. */ protected void usage(String details) { String msg = getUsage(); if (details != null) { MessageFormat mf = new MessageFormat(Strings.get("UsageDetails")); msg = mf.format(new Object[] { msg, details }); } setScriptError(new InvalidScriptException(msg)); } /** Attributes to save in script. */ public HashMap getAttributes() { HashMap map = new HashMap(); if (description != null && !description.equals(getDefaultDescription())) map.put(TAG_DESC, description); return map; } /** Resolve the given name into a Widget. */ protected Widget resolve(String name) throws NoSuchReferenceException, WidgetNotFoundException, MultipleWidgetsFoundException { //WidgetReference ref = resolver.getWidgetReference(name); // TODO: Make sure this actually works if (name != null && name !="") { return getFinder().find(new NameMatcher(name)); } throw new NoSuchReferenceException(name); } public WidgetFinder getFinder() { return BasicFinder.getDefault(); } public Resolver getResolver() { return resolver; } /** Override if the step actually has some contents. In most cases, it * won't. */ protected Element addContent(Element el) { return el; } protected Element addAttributes(Element el) { HashMap atts = getAttributes(); Iterator iter = atts.keySet().iterator(); while (iter.hasNext()) { String key = (String)iter.next(); String value = (String)atts.get(key); el.setAttribute(key, value); } return el; } public String toEditableString() { return toXMLString(this); } /** Provide a one-line XML string representation. */ public static String toXMLString(XMLifiable obj) { // Comments are the only things that aren't actually elements... if (obj instanceof Comment) { return "<!--" + ((Comment)obj).getDescription() + "-->"; } Element el = obj.toXML(); StringWriter writer = new StringWriter(); try { XMLOutputter outputter = new XMLOutputter(); outputter.output(el, writer); } catch(IOException io) { Log.warn(io); } return writer.toString(); } /** Convert the object to XML. */ public Element toXML() { return addAttributes(addContent(new Element(getXMLTag()))); } /** Create a new step from an in-line XML string. */ public static Step createStep(Resolver resolver, String str) throws InvalidScriptException { StringReader reader = new StringReader(str); try { SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(reader); Element el = doc.getRootElement(); return createStep(resolver, el); } catch(JDOMException e) { throw new InvalidScriptException(e.getMessage()); } catch (IOException e) { /* Added due to changes in abbot; exception handler * not implemented */ throw new InvalidScriptException(e.getMessage()); } } protected static HashMap createAttributeMap(Element el) { Log.debug("Creating attribute map for " + el); HashMap attributes = new HashMap(); Iterator iter = el.getAttributes().iterator(); while (iter.hasNext()) { Attribute att = (Attribute)iter.next(); attributes.put(att.getName(), att.getValue()); } return attributes; } /** * Factory method, equivalent to a "fromXML" for step creation. Looks for * a class with the same name as the XML tag, with the first letter * capitalized. For example, <call /> is abbot.script.Call. */ public static Step createStep(Resolver resolver, Element el) throws InvalidScriptException { String tag = el.getName(); HashMap attributes = createAttributeMap(el); String name = tag.substring(0, 1).toUpperCase() + tag.substring(1); if (tag.equals(TAG_WAIT)) { attributes.put(TAG_WAIT, "true"); name = "Assert"; } try { Class cls = Class.forName("abbot.script." + name, true, Thread.currentThread(). getContextClassLoader()); try { // Steps with contents require access to the XML element Class[] argTypes = new Class[] { Resolver.class, Element.class, HashMap.class }; Constructor ctor = cls.getConstructor(argTypes); return (Step)ctor.newInstance(new Object[] { resolver, el, attributes }); } catch(NoSuchMethodException nsm) { // All steps must support this ctor Class[] argTypes = new Class[] { Resolver.class, HashMap.class }; Constructor ctor = cls.getConstructor(argTypes); return (Step)ctor.newInstance(new Object[] { resolver, attributes }); } } catch(ClassNotFoundException cnf) { MessageFormat mf = new MessageFormat(Strings.get("UnknownTag")); throw new InvalidScriptException(mf.format(new Object[] { tag })); } catch(Exception exc) { throw new InvalidScriptException(exc.getMessage()); } } protected String simpleClassName(Class cls) { return WidgetTester.simpleClassName(cls); } /** Return a description of this script step. */ // public String toString() { // return getDescription(); // } /** Returns the Class corresponding to the given class name. Provides * just-in-time classname resolution to ensure loading by the proper class * loader. <p> * NOTE: only works if the app under test has been launched. */ public Class resolveClass(String className) throws InvalidScriptException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); try { return Class.forName(className, true, cl); } catch(ClassNotFoundException cnf) { // Unrecoverable; script must be fixed MessageFormat mf = new MessageFormat(Strings.get("ClassNotFound")); throw new InvalidScriptException(mf.format(new Object[] { className })); } } /** Look up an appropriate WidgetTester given an arbitrary class. * If the class is derived from abbot.swt.tester.WidgetTester, instantiate * one; if it is derived from java.awt.Widget, return a matching Tester. * Otherwise return abbot.swt.tester.WidgetTester.<p> * The class is looked up based on the appropriate context for the Step. */ protected WidgetTester resolveTester(String compClassName) throws InvalidScriptException { Class testedClass = resolveClass(compClassName); WidgetTester tester = WidgetTester.getTester(org.eclipse.swt.widgets.Widget.class); if (WidgetTester.class.isAssignableFrom(testedClass)) { try { tester = (WidgetTester)testedClass.newInstance(); } catch(Exception e) { throw new InvalidScriptException(e.getMessage()); } } else if (org.eclipse.swt.widgets.Widget.class.isAssignableFrom(testedClass)) { tester = WidgetTester.getTester(testedClass); } Log.debug("Tester for " + testedClass.getName() + " is " + tester.getClass() + " (" + tester.getClass().getClassLoader() + ")"); return tester; } private void writeObject(ObjectOutputStream out) throws IOException { // NOTE: this is only to avoid drag/drop errors } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // NOTE: this is only to avoid drag/drop errors } }