package abbot.script;
import java.awt.Component;
import java.awt.Frame;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import javax.swing.SwingUtilities;
import abbot.Log;
import abbot.NoExitSecurityManager;
import abbot.util.AWT;
import abbot.finder.*;
import abbot.finder.matchers.*;
import abbot.i18n.Strings;
import abbot.tester.Robot;
import abbot.tester.ComponentTester;
import abbot.tester.WindowTracker;
/**
* Provides applet launch capability. Usage:<br>
* <blockquote><code>
* <applet code="..." [codebase="..."] [params="..."]
* [archive="..."]><br>
* </code></blockquote><p>
* The attributes are equivalent to those provided in the HTML
* <code>applet</code> tag. The <code>params</code> attribute is a
* comma-separated list of <code>name=value</code> pairs, which will be passed
* to the applet within the <code>applet</code> tag as <code>param</code>
* elements.
* <p>
* <em>WARNING: Closing the appletviewer window from the window manager
* close button will result applet-spawned event dispatch threads being left
* running. To avoid this situation, always use the appletviewer <b>Quit</b>
* menu item or use the {@link #terminate()} method of this class.</em>
*/
public class Appletviewer extends Launch {
private String code;
private Map params;
private String codebase;
private String archive;
private String width;
private String height;
private ClassLoader appletClassLoader;
private Frame appletViewerFrame;
private transient SecurityManager oldSM;
private transient boolean terminating;
private static final String DEFAULT_WIDTH = "100";
private static final String DEFAULT_HEIGHT = "100";
private static final String CLASS_NAME = "sun.applet.Main";
private static final String METHOD_NAME = "main";
private static final int LAUNCH_TIMEOUT = 30000;
private static final String USAGE =
"<appletviewer code=\"...\" [params=\"name1=value1,...\"] "
+ "[codebase=\"...\"] [archive=\"...\"]>";
private static void quitApplet(final Frame frame) {
new ComponentTester().selectAWTMenuItem(frame, "Applet|Quit");
}
private static Map patchAttributes(Map map) {
map.put(TAG_CLASS, CLASS_NAME);
map.put(TAG_METHOD, METHOD_NAME);
return map;
}
/** Create an applet-launching step. */
public Appletviewer(Resolver resolver, Map attributes) {
super(resolver, patchAttributes(attributes));
code = (String)attributes.get(TAG_CODE);
params = parseParams((String)attributes.get(TAG_PARAMS));
codebase = (String)attributes.get(TAG_CODEBASE);
archive = (String)attributes.get(TAG_ARCHIVE);
width = (String)attributes.get(TAG_WIDTH);
height = (String)attributes.get(TAG_HEIGHT);
}
/** Create an applet-launching step. */
public Appletviewer(Resolver resolver, String description,
String code, Map params,
String codebase, String archive, String classpath) {
super(resolver, description,
CLASS_NAME, METHOD_NAME, null,
classpath, false);
this.code = code;
this.params = params;
this.codebase = codebase;
this.archive = archive;
}
/** Run this step. Causes the appropriate HTML file to be written for use
by appletviewer and its path used as the sole argument.
*/
public void runStep() throws Throwable {
File dir = new File(System.getProperty("user.dir"));
File htmlFile = File.createTempFile("abbot-applet", ".html", dir);
htmlFile.deleteOnExit();
try {
FileOutputStream os = new FileOutputStream(htmlFile);
os.write(generateHTML().getBytes());
os.close();
setArguments(new String[] { "[" + htmlFile.getName() + "]" });
super.runStep();
// Wait for the applet to become visible
long start = System.currentTimeMillis();
ComponentFinder finder =
new BasicFinder(getResolver().getHierarchy());
Matcher matcher = new ClassMatcher(Applet.class, true);
while (true) {
try {
Component c = finder.find(matcher);
appletViewerFrame = (Frame)
SwingUtilities.getWindowAncestor(c);
addCloseListener(appletViewerFrame);
appletClassLoader = c.getClass().getClassLoader();
break;
}
catch(ComponentSearchException e) {
}
if (System.currentTimeMillis() - start > LAUNCH_TIMEOUT) {
throw new RuntimeException(Strings.get("step.appletviewer.launch_timed_out"));
}
Thread.sleep(200);
}
}
finally {
htmlFile.delete();
}
}
private void addCloseListener(final Frame frame) {
// Workaround for lockup when applet is closed via WM close button and
// then relaunched. Can't figure out how to kill those extant threads.
// This avoids the lockup, but leaves threads around.
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
// Invoke the frame's quit menu item
quitApplet(frame);
}
});
}
/** Generate HTML suitable for launching this applet. */
protected String generateHTML() {
StringBuffer html = new StringBuffer();
html.append("<html><applet code=\"" + getCode() + "\"");
html.append(" width=\"" + getWidth() + "\""
+ " height=\"" + getHeight() + "\"");
if (getCodebase() != null) {
html.append(" codebase=\"" + getCodebase() + "\"");
}
if (getArchive() != null) {
html.append(" archive=\"" + getArchive() + "\"");
}
html.append(">");
Iterator iter = params.keySet().iterator();
while (iter.hasNext()) {
String key = (String)iter.next();
String value = (String)params.get(key);
html.append("<param name=\""
+ key + "\" value=\"" + value + "\">");
}
html.append("</applet></html>");
return html.toString();
}
public void setTargetClassName(String name) {
if (CLASS_NAME.equals(name))
super.setTargetClassName(name);
else
throw new IllegalArgumentException(Strings.get("step.call.immutable_class"));
}
public void setMethodName(String name) {
if (METHOD_NAME.equals(name))
super.setMethodName(name);
else
throw new IllegalArgumentException(Strings.get("step.call.immutable_method"));
}
public void setCode(String code) {
this.code = code;
}
public String getCode() { return code; }
public void setCodebase(String codebase) {
this.codebase = codebase;
}
public String getCodebase() { return codebase; }
public void setArchive(String archive) {
this.archive = archive;
}
public String getArchive() { return archive; }
public String getWidth() {
return width != null ? width : DEFAULT_WIDTH;
}
public void setWidth(String width) {
this.width = width;
try { Integer.parseInt(width); }
catch(NumberFormatException e) { this.width = null; }
}
public String getHeight() {
return height != null ? height : DEFAULT_HEIGHT;
}
public void setHeight(String height) {
this.height = height;
try { Integer.parseInt(height); }
catch(NumberFormatException e) { this.height = null; }
}
public Map getParams() {
return params;
}
public void setParams(Map params) {
this.params = params;
}
protected Map parseParams(String attribute) {
Map map = new HashMap();
if (attribute != null) {
String[] list = ArgumentParser.parseArgumentList(attribute);
for (int i=0;i < list.length;i++) {
String p = list[i];
int eq = p.indexOf("=");
map.put(p.substring(0, eq), p.substring(eq + 1));
}
}
return map;
}
public String[] getParamsAsArray() {
ArrayList list = new ArrayList();
// Ensure we always get a consistent order
Iterator iter = new TreeMap(params).keySet().iterator();
while (iter.hasNext()) {
String key = (String)iter.next();
String value = (String)params.get(key);
list.add(key + "=" + value);
}
return (String[])list.toArray(new String[list.size()]);
}
public String getParamsAttribute() {
return ArgumentParser.encodeArguments(getParamsAsArray());
}
public Map getAttributes() {
Map map = super.getAttributes();
map.put(TAG_CODE, getCode());
if (params.size() > 0)
map.put(TAG_PARAMS, getParamsAttribute());
if (getCodebase() != null)
map.put(TAG_CODEBASE, getCodebase());
if (getArchive() != null)
map.put(TAG_ARCHIVE, getArchive());
if (!DEFAULT_WIDTH.equals(getWidth()))
map.put(TAG_WIDTH, getWidth());
if (!DEFAULT_HEIGHT.equals(getHeight()))
map.put(TAG_HEIGHT, getHeight());
// don't need to store these
map.remove(TAG_CLASS);
map.remove(TAG_METHOD);
map.remove(TAG_THREADED);
map.remove(TAG_ARGS);
return map;
}
public String getDefaultDescription() {
String desc = Strings.get("step.appletviewer",
new Object[] { getCode() });
return desc;
}
public String getUsage() { return USAGE; }
public String getXMLTag() { return TAG_APPLETVIEWER; }
/** Returns the applet class loader. */
public ClassLoader getContextClassLoader() {
// Maybe use codebase/archive to have an alternative classpath?
ClassLoader cl = super.getContextClassLoader();
return appletClassLoader != null
? appletClassLoader : cl;
}
protected void install() {
super.install();
// Appletviewer expects the security manager to be an instance of
// AppletSecurity. Use the custom class loader, *not* the one for the
// applet.
installAppletSecurityManager(super.getContextClassLoader());
}
/** Install a security manager if there is none; this is a workaround
* to prevent sun's applet viewer's security manager from preventing
* any classes from being loaded.
*/
private void installAppletSecurityManager(ClassLoader cl) {
oldSM = System.getSecurityManager();
if (oldSM == null || (oldSM instanceof NoExitSecurityManager)) {
Log.debug("install security manager");
// NOTE: the security manager *must* be loaded with the same class
// loader as the appletviewer.
try {
Class cls = Class.forName("abbot.script.AppletSecurityManager",
true, cl);
Constructor ctor = cls.getConstructor(new Class[] {
SecurityManager.class
});
SecurityManager sm = (SecurityManager)
ctor.newInstance(new Object[] { oldSM });
System.setSecurityManager(sm);
}
catch(Exception exc) {
Log.warn(exc);
}
}
else {
Log.debug("old sm=" + oldSM);
}
}
/** To properly terminate, we need to invoke AppletViewer's appletQuit()
* method (protected, but accessible).
*/
public void terminate() {
synchronized(this) {
// Avoid recursion, since we'll return here when the applet
// invokes System.exit.
if (terminating)
return;
terminating = true;
}
Frame frame = appletViewerFrame;
appletViewerFrame = null;
try {
// FIXME: figure out why closing the appletviewer window causes an
// EDT hangup. (maybe need to post this to the applet's event
// queue?)
// Also figure out who's creating all the extra EDTs and dispose of
// them properly, but it's probably not worth the effort.
if (frame != null) {
quitApplet(frame);
}
// Now clean up normally
super.terminate();
if (oldSM != null) {
Log.debug("restore sm=" + oldSM);
System.setSecurityManager(oldSM);
}
appletClassLoader = null;
}
finally {
synchronized(this) {
terminating = false;
}
}
}
}