package org.kde.kjas.server; import java.applet.*; import java.util.*; import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.JFrame; import java.security.PrivilegedAction; import java.security.AccessController; import java.security.AccessControlContext; import java.security.ProtectionDomain; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * The stub used by Applets to communicate with their environment. * */ public final class KJASAppletStub implements AppletStub { private KJASAppletContext context; // The containing context. private Hashtable params; // Maps parameter names to values private URL codeBase; // The URL directory where files are private URL docBase; // The document that referenced the applet private boolean active; // Is the applet active? private String appletName; // The name of this applet instance private String appletID; // The id of this applet- for use in callbacks private Dimension appletSize; private String windowName; private String className; private Class appletClass; private JFrame frame; /** * out of bounds applet state :-), perform an action */ public static final int ACTION = -1; /** * applet state unknown */ public static final int UNKNOWN = 0; /** * the applet class has been loaded */ public static final int CLASS_LOADED = 1; /** * the applet has been instanciated */ public static final int INSTANCIATED = 2; /** * the applet has been initialized */ public static final int INITIALIZED = 3; /** * the applet has been started */ public static final int STARTED = 4; /** * the applet has been stopped */ public static final int STOPPED = 5; /** * the applet has been destroyed */ public static final int DESTROYED = 6; /** * request for termination of the applet thread */ private static final int TERMINATE = 7; /** * like TERMINATE, an end-point state */ private static final int FAILED = 8; //private KJASAppletClassLoader loader; KJASAppletClassLoader loader; private KJASAppletPanel panel; private Applet app; KJASAppletStub me; /** * Interface for so called LiveConnect actions, put-, get- and callMember */ // keep this in sync with KParts::LiveConnectExtension::Type private final static int JError = -1; private final static int JVoid = 0; private final static int JBoolean = 1; private final static int JFunction = 2; private final static int JNumber = 3; private final static int JObject = 4; final static int JString = 5; interface AppletAction { void apply(); void fail(); } private class RunThread extends Thread { private int request_state = CLASS_LOADED; private int current_state = UNKNOWN; private Vector actions = new Vector(); private AccessControlContext acc = null; RunThread() { super("KJAS-AppletStub-" + appletID + "-" + appletName); setContextClassLoader(loader); } /** * Ask applet to go to the next state */ synchronized void requestState(int nstate) { if (nstate > current_state) { request_state = nstate; notifyAll(); } } synchronized void requestAction(AppletAction action) { actions.add(action); notifyAll(); } /** * Get the asked state */ synchronized private int getRequestState() { while (request_state == current_state) { if (!actions.isEmpty()) { if (current_state >= INITIALIZED && current_state < STOPPED) return ACTION; else { AppletAction action = (AppletAction) actions.remove(0); action.fail(); } } else { try { wait (); } catch(InterruptedException ie) { } } } if (request_state == DESTROYED && current_state == STARTED) return current_state + 1; // make sure we don't skip stop() return request_state; } /** * Get the current state */ synchronized int getAppletState() { return current_state; } /** * Set the current state */ synchronized private void setState(int nstate) { current_state = nstate; } /** * Put applet in asked state * Note, kjavaapletviewer asks for create/start/stop/destroy, the * missing states instance/init/terminate, we do automatically */ private void doState(int nstate) throws ClassNotFoundException, IllegalAccessException, InstantiationException { switch (nstate) { case CLASS_LOADED: appletClass = loader.loadClass( className ); requestState(INSTANCIATED); break; case INSTANCIATED: { Object object = null; try { object = appletClass.newInstance(); app = (Applet) object; } catch ( ClassCastException e ) { if ( object != null && object instanceof java.awt.Component) { app = new Applet(); app.setLayout(new BorderLayout()); app.add( (Component) object, BorderLayout.CENTER); } else throw e; } acc = new AccessControlContext(new ProtectionDomain[] {app.getClass().getProtectionDomain()}); requestState(INITIALIZED); break; } case INITIALIZED: app.setStub( me ); app.setVisible(false); panel.setApplet( app ); if (appletSize.getWidth() > 0) app.setBounds( 0, 0, appletSize.width, appletSize.height ); else app.setBounds( 0, 0, panel.getSize().width, panel.getSize().height ); app.init(); loader.removeStatusListener(panel); // stop the loading... animation panel.stopAnimation(); app.setVisible(true); break; case STARTED: active = true; app.start(); frame.validate(); app.repaint(); break; case STOPPED: active = false; app.stop(); if (Main.java_version > 1.399) { // kill the windowClosing listener(s) WindowListener[] l = frame.getWindowListeners(); for (int i = 0; l != null && i < l.length; i++) frame.removeWindowListener(l[i]); } frame.setVisible(false); break; case DESTROYED: if (app != null) app.destroy(); frame.dispose(); app = null; requestState(TERMINATE); break; default: return; } } /** * RunThread run(), loop until state is TERMINATE */ public void run() { while (true) { int nstate = getRequestState(); if (nstate >= TERMINATE) return; if (nstate == ACTION) { AccessController.doPrivileged( new PrivilegedAction() { public Object run() { AppletAction action = (AppletAction) actions.remove(0); try { action.apply(); } catch (Exception ex) { Main.debug("Error during action " + ex); action.fail(); } return null; } }, acc); } else { // move to nstate try { doState(nstate); } catch (Exception ex) { Main.kjas_err("Error during state " + nstate, ex); if (nstate < INITIALIZED) { setState(FAILED); setFailed(ex.toString()); return; } } catch (Throwable tr) { setState(FAILED); setFailed(tr.toString()); return; } setState(nstate); stateChange(nstate); } } } } private RunThread runThread = null; /** * Create an AppletStub for the specified applet. The stub will be in * the specified context and will automatically attach itself to the * passed applet. */ public KJASAppletStub( KJASAppletContext _context, String _appletID, URL _codeBase, URL _docBase, String _appletName, String _className, Dimension _appletSize, Hashtable _params, String _windowName, KJASAppletClassLoader _loader ) { context = _context; appletID = _appletID; codeBase = _codeBase; docBase = _docBase; active = false; appletName = _appletName; className = _className.replace( '/', '.' ); appletSize = _appletSize; params = _params; windowName = _windowName; loader = _loader; String fixedClassName = _className; if (_className.endsWith(".class") || _className.endsWith(".CLASS")) { fixedClassName = _className.substring(0, _className.length()-6); } else if (_className.endsWith(".java")|| _className.endsWith(".JAVA")) { fixedClassName = _className.substring(0, _className.length()-5); } className = fixedClassName.replace('/', '.'); appletClass = null; me = this; } private void stateChange(int newState) { Main.protocol.sendAppletStateNotification( context.getID(), appletID, newState); } private void setFailed(String why) { loader.removeStatusListener(panel); panel.stopAnimation(); panel.showFailed(); Main.protocol.sendAppletFailed(context.getID(), appletID, why); } void createApplet() { panel = new KJASAppletPanel(); frame = new JFrame(windowName); // under certain circumstances, it may happen that the // applet is not embedded but shown in a separate window. // think of konqueror running under fvwm or gnome. // than, the user should have the ability to close the window. frame.addWindowListener ( new WindowAdapter() { public void windowClosing(WindowEvent e) { me.destroyApplet(); } } ); frame.getContentPane().add( panel, BorderLayout.CENTER ); try { if (Main.java_version > 1.399) frame.setUndecorated(true); } catch(java.awt.IllegalComponentStateException e) { // This happens with gcj 4.0.1, ignore for now... } frame.setLocation( 0, 0 ); frame.pack(); // resize frame for j2sdk1.5beta1.. if (appletSize.getWidth() > 0) frame.setBounds( 0, 0, appletSize.width, appletSize.height ); else frame.setBounds( 0, 0, 50, 50 ); frame.setVisible(true); loader.addStatusListener(panel); runThread = new RunThread(); runThread.start(); } /** * starts the applet managed by this stub by calling the applets start() method. * Also marks this stub as active. * @see java.applet.Applet#start() * @see java.applet.AppletStub#isActive() * */ void startApplet() { runThread.requestState(STARTED); } /** * stops the applet managed by this stub by calling the applets stop() method. * Also marks this stub as inactive. * @see java.applet.Applet#stop() * @see java.applet.AppletStub#isActive() * */ void stopApplet() { runThread.requestState(STOPPED); } /** * initialize the applet managed by this stub by calling the applets init() method. * @see java.applet.Applet#init() */ void initApplet() { runThread.requestState(INITIALIZED); } /** * destroys the applet managed by this stub by calling the applets destroy() method. * Also marks the the applet as inactive. * @see java.applet.Applet#init() */ synchronized void destroyApplet() { runThread.requestState(DESTROYED); } static void waitForAppletThreads() { Thread [] ts = new Thread[Thread.activeCount() + 5]; int len = Thread.enumerate(ts); for (int i = 0; i < len; i++) { try { if (ts[i].getName() != null && ts[i].getName().startsWith("KJAS-AppletStub-")) { try { ((RunThread) ts[i]).requestState(TERMINATE); ts[i].join(10000); } catch (InterruptedException ie) {} } } catch (Exception e) {} } } /** * get the Applet managed by this stub. * @return the Applet or null if the applet could not be loaded * or instanciated. */ Applet getApplet() { if (runThread != null && runThread.getAppletState() > CLASS_LOADED) return app; return null; } /** * get a parameter value given in the <APPLET> tag * @param name the name of the parameter * @return the value or null if no parameter with this name exists. */ public String getParameter( String name ) { return (String) params.get( name.toUpperCase() ); } /** * implements the isActive method of the AppletStub interface. * @return if the applet managed by this stub is currently active. * @see java.applet.AppletStub#isActive() */ public boolean isActive() { return active; } /** * determines if the applet has been loaded and instanciated * and can hence be used. * @return true if the applet has been completely loaded. */ boolean isLoaded() { if (runThread == null) return false; int state = runThread.getAppletState(); return (state >= INSTANCIATED && state < DESTROYED); } public void appletResize( int width, int height ) { if( active ) { if ( (width >= 0) && (height >= 0)) { Main.debug( "Applet #" + appletID + ": appletResize to : (" + width + ", " + height + ")" ); Main.protocol.sendResizeAppletCmd( context.getID(), appletID, width, height ); appletSize = new Dimension( width, height ); //pack(); } } } /** * converts Object arg into an object of class cl. * @param arg Object to convert * @param cl Destination class * @return An Object of the specified class with the value specified * in arg */ private static final Object cast(Object arg, Class cl) throws NumberFormatException { Object ret = arg; if (arg == null) { ret = null; } else if (cl.isAssignableFrom(arg.getClass())) { return arg; } else if (arg instanceof String) { String s = (String)arg; Main.debug("Argument String: \"" + s + "\""); if (cl == Boolean.TYPE || cl == Boolean.class) { ret = new Boolean(s); } else if (cl == Integer.TYPE || cl == Integer.class) { ret = new Integer(s); } else if (cl == Long.TYPE || cl == Long.class) { ret = new Long(s); } else if (cl == Float.TYPE || cl == Float.class) { ret = new Float(s); } else if (cl == Double.TYPE || cl == Double.class) { ret = new Double(s); } else if (cl == Short.TYPE || cl == Short.class) { ret = new Short(s); } else if (cl == Byte.TYPE || cl == Byte.class) { ret = new Byte(s); } else if (cl == Character.TYPE || cl == Character.class) { ret = new Character(s.charAt(0)); } } return ret; } private Method findMethod(Class c, String name, Class [] argcls) { try { Method[] methods = c.getMethods(); for (int i = 0; i < methods.length; i++) { Method m = methods[i]; if (m.getName().equals(name)) { Main.debug("Candidate: " + m); Class [] parameterTypes = m.getParameterTypes(); if (argcls == null) { if (parameterTypes.length == 0) { return m; } } else { if (argcls.length == parameterTypes.length) { for (int j = 0; j < argcls.length; j++) { // Main.debug("Parameter " + j + " " + parameterTypes[j]); argcls[j] = parameterTypes[j]; } return m; } } } } } catch (Exception e) { e.printStackTrace(); } return null; } private int[] getJSTypeValue(Hashtable jsRefs, Object obj, int objid, StringBuffer value) { String val = obj.toString(); int[] rettype = { JError, objid }; String type = obj.getClass().getName(); if (type.equals("boolean") || type.equals("java.lang.Boolean")) rettype[0] = JBoolean; else if (type.equals("int") || type.equals("long") || type.equals("float") || type.equals("double") || type.equals("byte") || obj instanceof java.lang.Number) rettype[0] = JNumber; else if (type.equals("java.lang.String")) rettype[0] = JString; else if (!type.startsWith("org.kde.kjas.server") && !(obj instanceof java.lang.Class && ((Class)obj).getName().startsWith("org.kde.kjas.server"))) { rettype[0] = JObject; rettype[1] = obj.hashCode(); jsRefs.put(new Integer(rettype[1]), obj); } value.insert(0, val); return rettype; } private class PutAction implements AppletAction { int call_id; int objid; String name; String value; PutAction(int cid, int oid, String n, String v) { call_id = cid; objid = oid; name = n; value = v; } public void apply() { Hashtable jsRefs = loader.getJSReferencedObjects(); Object o = objid==0 ? getApplet() : jsRefs.get(new Integer(objid)); if (o == null) { Main.debug("Error in putValue: object " + objid + " not found"); fail(); return; } Field f; try { f = o.getClass().getField(name); } catch (Exception e) { fail(); return; } if (f == null) { Main.debug("Error in putValue: " + name + " not found"); fail(); return; } try { String type = f.getType().getName(); Main.debug("putValue: (" + type + ")" + name + "=" + value); if (type.equals("boolean")) f.setBoolean(o, Boolean.getBoolean(value)); else if (type.equals("java.lang.Boolean")) f.set(o, Boolean.valueOf(value)); else if (type.equals("int")) f.setInt(o, Integer.parseInt(value)); else if (type.equals("java.lang.Integer")) f.set(o, Integer.valueOf(value)); else if (type.equals("byte")) f.setByte(o, Byte.parseByte(value)); else if (type.equals("java.lang.Byte")) f.set(o, Byte.valueOf(value)); else if (type.equals("char")) f.setChar(o, value.charAt(0)); else if (type.equals("java.lang.Character")) f.set(o, new Character(value.charAt(0))); else if (type.equals("double")) f.setDouble(o, Double.parseDouble(value)); else if (type.equals("java.lang.Double")) f.set(o, Double.valueOf(value)); else if (type.equals("float")) f.setFloat(o, Float.parseFloat(value)); else if (type.equals("java.lang.Float")) f.set(o, Float.valueOf(value)); else if (type.equals("long")) f.setLong(o, Long.parseLong(value)); else if (type.equals("java.lang.Long")) f.set(o, Long.valueOf(value)); else if (type.equals("short")) f.setShort(o, Short.parseShort(value)); else if (type.equals("java.lang.Short")) f.set(o, Short.valueOf(value)); else if (type.equals("java.lang.String")) f.set(o, value); else { Main.debug("Error putValue: unsupported type: " + type); fail(); return; } } catch (Exception e) { Main.debug("Exception in putValue: " + e.getMessage()); fail(); return; } Main.protocol.sendPutMember( context.getID(), call_id, true ); } public void fail() { Main.protocol.sendPutMember( context.getID(), call_id, false ); } } private class GetAction implements AppletAction { int call_id; int objid; String name; GetAction(int cid, int oid, String n) { call_id = cid; objid = oid; name = n; } public void apply() { Main.debug("getMember: " + name); StringBuffer value = new StringBuffer(); int ret[] = { JError, objid }; Hashtable jsRefs = loader.getJSReferencedObjects(); Object o = objid==0 ? getApplet() : jsRefs.get(new Integer(objid)); if (o == null) { fail(); return; } Class c = o.getClass(); try { Field field = c.getField(name); ret = getJSTypeValue(jsRefs, field.get(o), objid, value); } catch (Exception ex) { Method [] m = c.getMethods(); for (int i = 0; i < m.length; i++) if (m[i].getName().equals(name)) { ret[0] = JFunction; break; } } Main.protocol.sendMemberValue(context.getID(), KJASProtocolHandler.GetMember, call_id, ret[0], ret[1], value.toString()); } public void fail() { Main.protocol.sendMemberValue(context.getID(), KJASProtocolHandler.GetMember, call_id, -1, 0, ""); } } private class CallAction implements AppletAction { int call_id; int objid; String name; java.util.List args; CallAction(int cid, int oid, String n, java.util.List a) { call_id = cid; objid = oid; name = n; args = a; } public void apply() { StringBuffer value = new StringBuffer(); Hashtable jsRefs = loader.getJSReferencedObjects(); int [] ret = { JError, objid }; Object o = objid==0 ? getApplet() : jsRefs.get(new Integer(objid)); if (o == null) { fail(); return; } try { Main.debug("callMember: " + name); Object obj; Class c = o.getClass(); String type; Class [] argcls = new Class[args.size()]; for (int i = 0; i < args.size(); i++) argcls[i] = name.getClass(); // String for now, will be updated by findMethod Method m = findMethod(c, (String) name, argcls); Main.debug("Found Method: " + m); if (m != null) { Object [] argobj = new Object[args.size()]; for (int i = 0; i < args.size(); i++) { argobj[i] = cast(args.get(i), argcls[i]); } Object retval = m.invoke(o, argobj); if (retval == null) ret[0] = JVoid; else ret = getJSTypeValue(jsRefs, retval, objid, value); } } catch (Exception e) { Main.debug("callMember threw exception: " + e.toString()); } Main.protocol.sendMemberValue(context.getID(), KJASProtocolHandler.CallMember, call_id, ret[0], ret[1], value.toString()); } public void fail() { Main.protocol.sendMemberValue(context.getID(), KJASProtocolHandler.CallMember, call_id, -1, 0, ""); } } boolean putMember(int callid, int objid, String name, String val) { if (runThread == null) return false; runThread.requestAction( new PutAction( callid, objid, name, val) ); return true; } boolean getMember(int cid, int oid, String name) { if (runThread == null) return false; runThread.requestAction( new GetAction( cid, oid, name) ); return true; } boolean callMember(int cid, int oid, String name, java.util.List args) { if (runThread == null) return false; runThread.requestAction( new CallAction( cid, oid, name, args) ); return true; } /************************************************************************* ********************** AppletStub Interface ***************************** *************************************************************************/ /** * implements the getAppletContext method of the AppletStub interface. * @return the AppletContext to which this stub belongs. * @see java.applet.AppletStub#getAppletContext() */ public AppletContext getAppletContext() { return context; } /** * implements the getCodeBase method of the AppletStub interface. * @return the code base of the applet as given in the <APPLET> tag. * @see java.applet.AppletStub#getCodeBase() */ public URL getCodeBase() { return codeBase; } /** * implements the getDocumentBase method of the AppletStub interface. * @return the code base of the applet as given in the * <APPLET> tag or determined by the containing page. * @see java.applet.AppletStub#getDocumentBase() */ public URL getDocumentBase() { return docBase; } /** * get the applet's name * @return the name of the applet as given in the * <APPLET> tag or determined by the code parameter. */ public String getAppletName() { return appletName; } }