Package abbot.script

Source Code of abbot.script.Launch$ThreadedLaunchListener

package abbot.script;

import java.awt.Window;
import java.lang.reflect.*;
import java.util.Map;
import java.util.Iterator;

import abbot.finder.Hierarchy;
import abbot.*;
import abbot.i18n.Strings;

/**
* Provides scripted static method invocation.  Usage:<br>
* <blockquote><code>
*  &lt;launch class="package.class" method="methodName" args="..."
* classpath="..." [threaded=true]&gt;<br>
* </code></blockquote><p>
* The args attribute is a comma-separated list of arguments to pass to the
* class method, and may use square brackets to denote an array,
* e.g. "[one,two,three]" will be interpreted as an array length 3
* of String.  The square brackets may be escaped ('\[' or '\]') to include
* them literally in an argument.
* <p>
* The class path attribute may use either colon or semicolon as a path
* separator, but should preferably use relative paths to avoid making the
* containing script platform- and location-dependent.<p>
* In most cases, the classes under test will <i>only</i> be found under the
* custom class path, and so the parent class loader will fail to find them.
* If this is the case then the classes under test will be properly discarded
* on each launch when a new class loader is created.
* <p>
* The 'threaded' attribute is provided in case your code under test requires
* GUI event processing prior to returning from its invoked method.  An
* example might be a main method which invokes dialog and waits for the
* response before continuing.  In general, it's better to refactor the code
* if possible so that the main method turns over control to the event
* dispatch thread as soon as possible.  Otherwise, if the application under
* test is background threaded by the Launch step, any runtime exceptions
* thrown from the launch code will cause errors in the launch step out of
* sequence with the other script steps.  While this won't cause any problems
* for the Abbot framework, it can be very confusing for the user.<p>
* Note that if the "reload" attribute is set true (i.e. Abbot's class loader
* is used to reload code under test), ComponentTester extensions must also be
* loaded by that class loader, so the path to extensions should be included
* in the Launch class path.<p>
*/

public class Launch extends Call {

    private String classpath = null;
    private boolean threaded = false;
    private transient AppClassLoader classLoader = null;
    private transient ThreadedLaunchListener listener = null;

    private static final String USAGE =
        "<launch class=\"...\" method=\"...\" args=\"...\" "
        + "[threaded=true]>";

    public Launch(Resolver resolver, Map attributes) {
        super(resolver, attributes);
        classpath = (String)attributes.get(TAG_CLASSPATH);
        String thr = (String)attributes.get(TAG_THREADED);
        if (thr != null)
            threaded = Boolean.valueOf(thr).booleanValue();
    }

    public Launch(Resolver resolver, String description,
                  String className, String methodName, String[] args) {
        this(resolver, description, className, methodName, args, null, false);
    }

    public Launch(Resolver resolver, String description,
                  String className, String methodName, String[] args,
                  String classpath, boolean threaded) {
        super(resolver, description, className, methodName, args);
        this.classpath = classpath;
        this.threaded = threaded;
    }

    public String getClasspath() {
        return classpath;
    }

    public void setClasspath(String cp) {
        classpath = cp;
        // invalidate class loader
        classLoader = null;
    }

    public boolean isThreaded() {
        return threaded;
    }

    public void setThreaded(boolean thread) {
        threaded = thread;
    }

    protected AppClassLoader createClassLoader() {
        return new AppClassLoader(classpath);
    }

    /** Install the class loader context for the code being launched.  The
     * context class loader for the current thread is modified.
     */
    public void installContext() {

        // Use a custom class loader so that we can provide additional
        // classpath and also optionally reload the class on each run.
        // FIXME maybe classpath should be relative to the script?  In this
        // case, it's relative to user.dir
        AppClassLoader cl = createClassLoader();

        // Everything else loaded on the same thread as this
        // launch should be loaded by this custom loader. 
        cl.install();
        classLoader = cl;
    }

    protected void launch() throws Throwable {
        // A bug in pre-1.4 VMs locks the toolkit prior to notifying AWT event
        // listeners.  This causes a deadlock when the main method invokes
        // "show" on a component which triggers AWT events for which there are
        // listeners.  To avoid this, grab the toolkit lock first so that the
        // locks are acquired in the same order by either sequence.
        // (Unfortunately, some swing code locks the tree prior to
        // grabbing the toolkit lock, so there's still opportunity for
        // deadlock).  One alternative (although very heavyweight) is to
        // always fork a separate VM.
        //
        // If threaded, take the danger of deadlock over the possibility that
        // the main method will never return and leave the lock forever held.
        // NOTE: this is guaranteed to deadlock if "main" calls
        // EventQueue.invokeAndWait.
        if (Platform.JAVA_VERSION < Platform.JAVA_1_4
            && !isThreaded()) {
            synchronized(java.awt.Toolkit.getDefaultToolkit()) {
                super.runStep();
            }
        }
        else {
            super.runStep();
        }
    }

    /** The default termination disposes of all extant windows in the current
     * Hierarchy.
     */
    protected void terminate() {
        Hierarchy h = getResolver().getHierarchy();
        Iterator iter = h.getRoots().iterator();
        while (iter.hasNext())
            h.dispose((Window)iter.next());
        uninstallContext();
    }

    public void runStep()
        throws Throwable {
        uninstallContext();
        installContext();
        System.setProperty("abbot.framework.launched", "true");
        if (isThreaded()) {
            Thread threaded = new Thread("Threaded " + toString()) {
                public void run() {
                    try {
                        launch();
                    }
                    catch(AssertionFailedError e) {
                        if (listener != null)
                            listener.stepFailure(Launch.this, e);
                    }
                    catch(Throwable t) {
                        if (listener != null)
                            listener.stepError(Launch.this, t);
                    }
                }
            };
            threaded.setDaemon(true);
            threaded.setContextClassLoader(classLoader);
            threaded.start();
        }
        else {
            launch();
        }
    }

    /** Overrides the default implementation to always use the class loader
     * defined by this step.  This works in cases where the Launch step has
     * not yet been added to a Script; otherwise the Script will provide an
     * implementation equivalent to this one.
     */
    public Class resolveClass(String className) throws ClassNotFoundException {
        return Class.forName(className, true, getClassLoader());
    }

    /** Return the class loader that uses the classpath defined in this
     * step.
     */
    public ClassLoader getClassLoader() {
        if (classLoader == null) {
            classLoader = createClassLoader();
        }
        return classLoader;
    }

    /** Remove the class loader context.  May be invoked from any thread. */
    public void uninstallContext() {
        if (classLoader != null) {
            classLoader.uninstall();
            classLoader = null;
        }
    }

    public Class getTargetClass() throws ClassNotFoundException {
        Class cls = resolveClass(getTargetClassName());
        Log.debug("Target class is " + cls.getName());
        return cls;
    }

    /** Return the target for the method invocation.  All launch invocations
     * must be static, so this always returns null.
     */
    protected Object getTarget(Method m) {
        return null;
    }

    /** Return the method to be used for invocation. */
    public Method getMethod()
        throws ClassNotFoundException, NoSuchMethodException {
        return resolveMethod(getMethodName(), getTargetClass(), null);
    }

    public Map getAttributes() {
        Map map = super.getAttributes();
        if (classpath != null) {
            map.put(TAG_CLASSPATH, classpath);
        }
        if (threaded) {
            map.put(TAG_THREADED, "true");
        }
        return map;
    }

    public String getDefaultDescription() {
        String desc = Strings.get("launch.desc",
                                  new Object[] { getTargetClassName()
                                                 + "." + getMethodName()
                                                 + "(" + getEncodedArguments()
                                                 + ")"});
        return desc;
    }

    public String getUsage() { return USAGE; }

    public String getXMLTag() { return TAG_LAUNCH; }

    /** Set a listener to respond to events when the launch step is
     * threaded.
     */
    public void setThreadedLaunchListener(ThreadedLaunchListener l) {
        listener = l;
    }

    public interface ThreadedLaunchListener {
        public void stepFailure(Launch launch, AssertionFailedError error);
        public void stepError(Launch launch, Throwable throwable);
    }
}
TOP

Related Classes of abbot.script.Launch$ThreadedLaunchListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.