diff options
author | Sven Gothel <[email protected]> | 2014-01-30 10:39:16 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-01-30 10:39:16 +0100 |
commit | 64e7dcc21339ae56841f10131a4f8a462454dec4 (patch) | |
tree | a62d2fbafc6fbe412d2d5ad599cd992bdf97ddbe /netx | |
parent | 98c9d6e1ea22db18913b531b8056fbdc5465eb18 (diff) |
Experimental Applet without AWT (Applet3)
DISCLAIMER:
- Only new Applet3 applets are supported under X11 for now
- AWT Applet are disabled
- Namespace com.jogamp.* and jogamp.* is only chosen
to indicate new AWT-less code
- Applet3 code path does not invoke any AWT function
- JNLP code path still utilizes AWT/Swing (UIs, ..)
TODO:
- Refactor AWT dependencies properly via UI interfaces ?
- Decide whether we shall merge netx and plugin namespace ?
IMHO the right thing to do, jumping hoops due to separation.
- Add support for Windows, OSX, Wayland, ..
Applet3:
- New AWT-less Applet3 interfaces are:
- com.jogamp.plugin.applet.Applet3
- User implements
- com.jogamp.plugin.applet.Applet3Context
- Plugin implements
- com.jogamp.plugin.ui.NativeWindowUpstream
- Plugin window, aka browser parent of Applet3
- com.jogamp.plugin.ui.NativeWindowDownstream
- Applet3 user window
- User interfaces are exported as:
- plugin3-public.jar
- plugin3-public-src.zip
Diffstat (limited to 'netx')
21 files changed, 5788 insertions, 53 deletions
diff --git a/netx/com/jogamp/plugin/applet/Applet3.java b/netx/com/jogamp/plugin/applet/Applet3.java new file mode 100644 index 0000000..b285852 --- /dev/null +++ b/netx/com/jogamp/plugin/applet/Applet3.java @@ -0,0 +1,150 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.plugin.applet; + +import java.util.Locale; + +import com.jogamp.plugin.ui.NativeWindowDownstream; +import com.jogamp.plugin.ui.NativeWindowUpstream; + +/** + * Implemented by user. + * <a name="lifecycle"><h5>Applet3 Lifecycle</h5></a> + * <p> + * <ul> + * <li>{@link #createNativeWindow(Applet3Context, NativeWindowUpstream)}</li> + * <li>{@link #init(Applet3Context)}</li> + * <li>{@link #start()}</li> + * <li>{@link #stop()}</li> + * <li>{@link #destroy()}</li> + * </ul> + * </p> + */ +public interface Applet3 { + + /** + * Returns applet information or <code>null</code>. + * <p> + * Implementation may utilize this method to + * return information about the author, version, and copyright of the applet. + * </p> + */ + String getAppletInfo(); + + /** + * Returns a custom locale of the applet or <code>null</code>. + */ + Locale getLocale(); + + /** + * Returns a description of parameters as used by this applet, or <code>null</code>. + * <p> + * The returned string array is arranged to + * contain the <code>name</code>, the <code>type</code>, and a <code>description</code>. + * Example: + * <pre> + * String pinfo[][] = { + * {"gl_profile", "GLProfile String", "The GLProfile to be used, i.e. GL2ES2"}, + * {"gl_swap_interval", "0-4", "The swap interval for vertical sync, i.e. 0 or 1"}, + * {"gl_debug", "boolean", "Enable JOGL debugging"} + * }; + * </pre> + */ + String[][] getParameterInfo(); + + /** + * Implementation creates a native child window, allowing to be controlled by the plugin. + * <p> + * The applet's child window is destroyed by the plugin after it has called {@link #destroy()}. + * </p> + * <p> + * Note that the returned {@link NativeWindowDownstream} instance's {@link NativeWindowDownstream#getParent()} + * must match the given <code>parent</code> instance, otherwise the applet will be aborted. + * </p> + * <p> + * See <a href="#lifecycle">Applet Lifecycle</a>. + * </p> + * @param context the {@link Applet3Context} + * @param parent the parent {@link NativeWindowUpstream}, reflecting the plugin's native applet window. + * @return {@link NativeWindowDownstream} users native child window. + */ + NativeWindowDownstream createNativeWindow(Applet3Context context, NativeWindowUpstream parent); + + /** + * Initialize the applet. Implementation may allocate resources. + * <p> + * See <a href="#lifecycle">Applet Lifecycle</a>. + * </p> + * + * @see #createNativeWindow(Applet3Context, NativeWindowUpstream) + * @see #destroy() + * @see #start() + * @see #stop() + */ + void init(Applet3Context context); + + /** + * Start the applet. + * <p> + * See <a href="#lifecycle">Applet Lifecycle</a>. + * </p> + * @see #createNativeWindow(Applet3Context, NativeWindowUpstream) + * @see #destroy() + * @see #start() + * @see #stop() + */ + void start(); + + /** + * Stop the applet. + * <p> + * See <a href="#lifecycle">Applet Lifecycle</a>. + * </p> + * @see #createNativeWindow(Applet3Context, NativeWindowUpstream) + * @see #destroy() + * @see #start() + * @see #stop() + */ + void stop(); + + /** + * Destroy the applet and all it's resources. Implementation shall release resources as allocated via {@link #init(Applet3Context)}. + * <p> + * Note that the applet's child window {@link #createNativeWindow(Applet3Context, NativeWindowUpstream) created by this implementation} + * is still valid and may be destroyed here. + * </p> + * <p> + * See <a href="#lifecycle">Applet Lifecycle</a>. + * </p> + * @see #createNativeWindow(Applet3Context, NativeWindowUpstream) + * @see #destroy() + * @see #start() + * @see #stop() + */ + void destroy(); +} diff --git a/netx/com/jogamp/plugin/applet/Applet3Context.java b/netx/com/jogamp/plugin/applet/Applet3Context.java new file mode 100644 index 0000000..04528e6 --- /dev/null +++ b/netx/com/jogamp/plugin/applet/Applet3Context.java @@ -0,0 +1,87 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.plugin.applet; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Provided by Plugin implementation. + */ +public interface Applet3Context { + /** + * Returns the {@link Applet3} bound to this context + */ + Applet3 getApplet(); + + String getAppletName(); + + boolean isActive(); + String getParameter(String name); + URL getDocumentBase(); + URL getCodeBase(); + + /** + * Requests that this applet be resized. + * + * @param width the new requested width for the applet. + * @param height the new requested height for the applet. + */ + void resize(int width, int height); + + /** + * Returns the {@link Applet3Context} within this context domain, + * referenced by the document, codebase and the given <code>name</code>. + * <p> + * The <code>name</code> can be set in the HTML tag by setting the <code>name</code> attribute. + * </p> + */ + Applet3Context getAppletContext(String name); + + /** + * Returns all {@link Applet3Context} within this context domain, + * referenced by the document and codebase. + */ + Enumeration<Applet3Context> getAllAppletContexts(); + + void showDocument(URL url); + + void showDocument(URL url, String target); + + void showStatus(String status); + + void setStream(String key, InputStream stream)throws IOException; + + InputStream getStream(String key); + + Iterator<String> getStreamKeys(); + +} diff --git a/netx/com/jogamp/plugin/ui/NativeWindowDownstream.java b/netx/com/jogamp/plugin/ui/NativeWindowDownstream.java new file mode 100644 index 0000000..0846505 --- /dev/null +++ b/netx/com/jogamp/plugin/ui/NativeWindowDownstream.java @@ -0,0 +1,81 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.plugin.ui; + +/** + * Implemented by user. + * <p> + * Representing the user applet child window, + * which is controlled by the plugin. + * </p> + */ +public interface NativeWindowDownstream { + /** + * Destroys this window incl. releasing all related resources. + */ + public void destroy(); + + /** + * @return The parent NativeWindow, or null if this NativeWindow is top level. + */ + public NativeWindowUpstream getParent(); + + /** + * Returns the window handle for this applet. <P> + * + * The window handle shall reflect the platform one + * for all window related operations, e.g. open, close, resize. <P> + * + * On X11 this returns an entity of type Window. <BR> + * On Microsoft Windows this returns an entity of type HWND. + */ + public long getWindowHandle(); + + /** + * Set size. + */ + public void setSize(int width, int height); + + /** + * Request focus. + */ + public void requestFocus(); + + /** + * Set this NativeWindow visible state. + */ + public void setVisible(boolean v); + + /** + * Trigger asynchronous rendering of this display's content. + * <p> + * Method shall return immediately and not wait for result. + * </p> + */ + public void display(); +} diff --git a/netx/com/jogamp/plugin/ui/NativeWindowUpstream.java b/netx/com/jogamp/plugin/ui/NativeWindowUpstream.java new file mode 100644 index 0000000..ccd42e0 --- /dev/null +++ b/netx/com/jogamp/plugin/ui/NativeWindowUpstream.java @@ -0,0 +1,75 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.plugin.ui; + +/** + * Representing the plugin window, i.e. the user applet's parent window. + */ +public interface NativeWindowUpstream { + /** + * Returns the display connection for this NativeWindow, + * maybe <code>null</code> for default. + * <p> + * On X11 this returns the X11 display connection string, e.g. <code>:0.0</code>.<BR> + * </p> + */ + public String getDisplayConnection(); + + /** + * Returns the screen index for this NativeWindow. + */ + public int getScreenIndex(); + + /** + * Returns the window handle for this NativeWindow. <P> + * + * The window handle shall reflect the platform one + * for all window related operations, e.g. open, close, resize. <P> + * + * On X11 this returns an entity of type Window. <BR> + * On Microsoft Windows this returns an entity of type HWND. + */ + public long getWindowHandle(); + + /** + * Returns the width of the client area excluding insets (window decorations). + * @return width of the client area + */ + public int getWidth(); + + /** + * Returns the height of the client area excluding insets (window decorations). + * @return height of the client area + */ + public int getHeight(); + + /** + * Notify plugin that the applet's window has been <i>updated</i>, e.g. rendered and swapped. + */ + public void notifySurfaceUpdated(NativeWindowDownstream swappedWin); +} diff --git a/netx/jogamp/applet/App3Context.java b/netx/jogamp/applet/App3Context.java new file mode 100644 index 0000000..3205a16 --- /dev/null +++ b/netx/jogamp/applet/App3Context.java @@ -0,0 +1,803 @@ +/* + * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * <p> + * Copyright (c) 2014, JogAmp Community. All rights reserved. + * + * JogAmp changes are compatible w/ GPLv2, + * and also available under the the New BSD 2-clause license. + * + * Please visit the JogAmp Community via http://jogamp.org/ + * and find appropriate channels (forum, email, irc) to contact us + * if you need additional information or have any + * questions. + * </p> + */ +package jogamp.applet; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import sun.util.logging.PlatformLogger; + +import com.jogamp.plugin.ui.NativeWindowDownstream; + +/** + * The AppContext is a table referenced by ThreadGroup which stores + * application service instances. (If you are not writing an application + * service, or don't know what one is, please do not use this class.) + * The AppContext allows applet access to what would otherwise be + * potentially dangerous services, such as the ability to peek at + * EventQueues or change the look-and-feel of a Swing application.<p> + * + * Most application services use a singleton object to provide their + * services, either as a default (such as getSystemEventQueue or + * getDefaultToolkit) or as static methods with class data (System). + * The AppContext works with the former method by extending the concept + * of "default" to be ThreadGroup-specific. Application services + * lookup their singleton in the AppContext.<p> + * + * For example, here we have a Foo service, with its pre-AppContext + * code:<p> + * <code><pre> + * public class Foo { + * private static Foo defaultFoo = new Foo(); + * + * public static Foo getDefaultFoo() { + * return defaultFoo; + * } + * + * ... Foo service methods + * }</pre></code><p> + * + * The problem with the above is that the Foo service is global in scope, + * so that applets and other untrusted code can execute methods on the + * single, shared Foo instance. The Foo service therefore either needs + * to block its use by untrusted code using a SecurityManager test, or + * restrict its capabilities so that it doesn't matter if untrusted code + * executes it.<p> + * + * Here's the Foo class written to use the AppContext:<p> + * <code><pre> + * public class Foo { + * public static Foo getDefaultFoo() { + * Foo foo = (Foo)AppContext.getAppContext().get(Foo.class); + * if (foo == null) { + * foo = new Foo(); + * getAppContext().put(Foo.class, foo); + * } + * return foo; + * } + * + * ... Foo service methods + * }</pre></code><p> + * + * Since a separate AppContext can exist for each ThreadGroup, trusted + * and untrusted code have access to different Foo instances. This allows + * untrusted code access to "system-wide" services -- the service remains + * within the AppContext "sandbox". For example, say a malicious applet + * wants to peek all of the key events on the EventQueue to listen for + * passwords; if separate EventQueues are used for each ThreadGroup + * using AppContexts, the only key events that applet will be able to + * listen to are its own. A more reasonable applet request would be to + * change the Swing default look-and-feel; with that default stored in + * an AppContext, the applet's look-and-feel will change without + * disrupting other applets or potentially the browser itself.<p> + * + * Because the AppContext is a facility for safely extending application + * service support to applets, none of its methods may be blocked by a + * a SecurityManager check in a valid Java implementation. Applets may + * therefore safely invoke any of its methods without worry of being + * blocked. + * + * Note: If a SecurityManager is installed which derives from + * sun.awt.AWTSecurityManager, it may override the + * AWTSecurityManager.getAppContext() method to return the proper + * AppContext based on the execution context, in the case where + * the default ThreadGroup-based AppContext indexing would return + * the main "system" AppContext. For example, in an applet situation, + * if a system thread calls into an applet, rather than returning the + * main "system" AppContext (the one corresponding to the system thread), + * an installed AWTSecurityManager may return the applet's AppContext + * based on the execution context. + * + * @author Thomas Ball + * @author Fred Ecks + */ +public final class App3Context { + private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.AppContext"); + + /* Since the contents of an AppContext are unique to each Java + * session, this class should never be serialized. */ + + /* + * The key to put()/get() the Java EventQueue into/from the AppContext. + */ + public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue"); + + /* + * The keys to store EventQueue push/pop lock and condition. + */ + public final static Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock"); + public final static Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition"); + + /* A map of AppContexts, referenced by ThreadGroup. + */ + private static final Map<ThreadGroup, App3Context> threadGroup2appContext = + Collections.synchronizedMap(new IdentityHashMap<ThreadGroup, App3Context>()); + + private final Set<NativeWindowDownstream> registeredWindows = new HashSet<NativeWindowDownstream>(); + + private static final Map<NativeWindowDownstream, App3Context> appletWindow2AppContext = + Collections.synchronizedMap(new WeakHashMap<NativeWindowDownstream, App3Context>()); + + /** + * Returns a set containing all <code>AppContext</code>s. + */ + public static Set<App3Context> getAppContexts() { + synchronized (threadGroup2appContext) { + return new HashSet<App3Context>(threadGroup2appContext.values()); + } + } + + /* The main "system" AppContext, used by everything not otherwise + contained in another AppContext. It is implicitly created for + standalone apps only (i.e. not applets) + */ + private static App3Context mainAppContext; + + /* + * The hash map associated with this AppContext. A private delegate + * is used instead of subclassing HashMap so as to avoid all of + * HashMap's potentially risky methods, such as clear(), elements(), + * putAll(), etc. + */ + private final Map<Object, Object> table = new HashMap<Object, Object>(); + + private final ThreadGroup threadGroup; + + /** + * If any <code>PropertyChangeListeners</code> have been registered, + * the <code>changeSupport</code> field describes them. + * + * @see #addPropertyChangeListener + * @see #removePropertyChangeListener + * @see #firePropertyChange + */ + private PropertyChangeSupport changeSupport = null; + + public static final String DISPOSED_PROPERTY_NAME = "disposed"; + public static final String GUI_DISPOSED = "guidisposed"; + + private enum State { + VALID, + BEING_DISPOSED, + DISPOSED + }; + + private volatile State state = State.VALID; + + public boolean isDisposed() { + return state == State.DISPOSED; + } + + /* + * The total number of AppContexts, system-wide. This number is + * incremented at the beginning of the constructor, and decremented + * at the end of dispose(). getAppContext() checks to see if this + * number is 1. If so, it returns the sole AppContext without + * checking Thread.currentThread(). + */ + private static final AtomicInteger numAppContexts = new AtomicInteger(0); + + + /* + * The context ClassLoader that was used to create this AppContext. + */ + private final ClassLoader contextClassLoader; + + /** + * Constructor for AppContext. This method is <i>not</i> public, + * nor should it ever be used as such. The proper way to construct + * an AppContext is through the use of SunToolkit.createNewAppContext. + * A ThreadGroup is created for the new AppContext, a Thread is + * created within that ThreadGroup, and that Thread calls + * SunToolkit.createNewAppContext before calling anything else. + * That creates both the new AppContext and its EventQueue. + * + * @param threadGroup The ThreadGroup for the new AppContext + * @see sun.awt.SunToolkit + * @since 1.2 + */ + App3Context(ThreadGroup threadGroup) { + numAppContexts.incrementAndGet(); + + this.threadGroup = threadGroup; + threadGroup2appContext.put(threadGroup, this); + + this.contextClassLoader = + AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + @Override + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + + // Initialize push/pop lock and its condition to be used by all the + // EventQueues within this AppContext + Lock eventQueuePushPopLock = new ReentrantLock(); + put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock); + Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition(); + put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond); + } + + private static final ThreadLocal<App3Context> threadAppContext = + new ThreadLocal<App3Context>(); + + static { + // On the main Thread, we get the ThreadGroup, make a corresponding + // AppContext, and instantiate the Java EventQueue. This way, legacy + // code is unaffected by the move to multiple AppContext ability. + AccessController.doPrivileged(new PrivilegedAction<Void>() { + @Override + public Void run() { + ThreadGroup currentThreadGroup = + Thread.currentThread().getThreadGroup(); + ThreadGroup parentThreadGroup = currentThreadGroup.getParent(); + while (parentThreadGroup != null) { + // Find the root ThreadGroup to construct our main AppContext + currentThreadGroup = parentThreadGroup; + parentThreadGroup = currentThreadGroup.getParent(); + } + + mainAppContext = new App3Context(currentThreadGroup); + return null; + } + }); + } + + public static final App3Context createAppContext() { + ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); + return new App3Context(threadGroup); + } + + public final static App3Context getAppContext(NativeWindowDownstream nw) { + return appletWindow2AppContext.get(nw); + } + + /** + * Returns the appropriate AppContext for the caller, + * as determined by its ThreadGroup. If the main "system" AppContext + * would be returned and there's an AWTSecurityManager installed, it + * is called to get the proper AppContext based on the execution + * context. + * + * @return the AppContext for the caller. + * @see java.lang.ThreadGroup + * @since 1.2 + */ + public final static App3Context getAppContext() { + // we are standalone app, return the main app context + if (numAppContexts.get() == 1 && mainAppContext != null) { + return mainAppContext; + } + + App3Context appContext = threadAppContext.get(); + + if (null == appContext) { + appContext = AccessController.doPrivileged(new PrivilegedAction<App3Context>() + { + @Override + public App3Context run() { + // Get the current ThreadGroup, and look for it and its + // parents in the hash from ThreadGroup to AppContext -- + // it should be found, because we use createNewContext() + // when new AppContext objects are created. + ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup(); + ThreadGroup threadGroup = currentThreadGroup; + + App3Context context = threadGroup2appContext.get(threadGroup); + while (context == null) { + threadGroup = threadGroup.getParent(); + if (threadGroup == null) { + return null; + } + context = threadGroup2appContext.get(threadGroup); + } + + // In case we did anything in the above while loop, we add + // all the intermediate ThreadGroups to threadGroup2appContext + // so we won't spin again. + for (ThreadGroup tg = currentThreadGroup; tg != threadGroup; tg = tg.getParent()) { + threadGroup2appContext.put(tg, context); + } + + // Now we're done, so we cache the latest key/value pair. + threadAppContext.set(context); + + return context; + } + }); + } + + return appContext; + } + + /** + * Returns true if the specified AppContext is the main AppContext. + * + * @param ctx the context to compare with the main context + * @return true if the specified AppContext is the main AppContext. + * @since 1.8 + */ + public final static boolean isMainContext(App3Context ctx) { + return (ctx != null && ctx == mainAppContext); + } + + private final long DISPOSAL_TIMEOUT = 5000; // Default to 5-second timeout + // for disposal of all Frames + // (we wait for this time twice, + // once for dispose(), and once + // to clear the EventQueue). + + private final long THREAD_INTERRUPT_TIMEOUT = 1000; + // Default to 1-second timeout for all + // interrupted Threads to exit, and another + // 1 second for all stopped Threads to die. + + public void registerAppletWindow(NativeWindowDownstream nw) { + if(null==nw) return; + synchronized(registeredWindows) { + registeredWindows.add(nw); + appletWindow2AppContext.put(nw, this); + } + } + + public void unregisterAppletWindow(NativeWindowDownstream nw) { + if(null==nw) return; + synchronized(registeredWindows) { + appletWindow2AppContext.remove(nw); + registeredWindows.remove(nw); + } + } + + /** + * Disposes of this AppContext, all of its top-level Frames, and + * all Threads and ThreadGroups contained within it. + * + * This method must be called from a Thread which is not contained + * within this AppContext. + * + * @exception IllegalThreadStateException if the current thread is + * contained within this AppContext + * @since 1.2 + */ + @SuppressWarnings("deprecation") + public void dispose() throws IllegalThreadStateException { + // Check to be sure that the current Thread isn't in this AppContext + if (this.threadGroup.parentOf(Thread.currentThread().getThreadGroup())) { + throw new IllegalThreadStateException( + "Current Thread is contained within AppContext to be disposed." + ); + } + + synchronized(this) { + if (this.state != State.VALID) { + return; // If already disposed or being disposed, bail. + } + + this.state = State.BEING_DISPOSED; + } + + final PropertyChangeSupport changeSupport = this.changeSupport; + if (changeSupport != null) { + changeSupport.firePropertyChange(DISPOSED_PROPERTY_NAME, false, true); + } + + // First, we post an InvocationEvent to be run on the + // EventDispatchThread which disposes of all top-level Frames and TrayIcons + + log.info("AppContect.dispose() : Closing "+registeredWindows.size()+" remaining windows"); + final Object notificationLock = new Object(); + Runnable runnable = new Runnable() { + @Override + public void run() { + synchronized(registeredWindows) { + for (Iterator<NativeWindowDownstream> iter=registeredWindows.iterator(); iter.hasNext(); ) { + final NativeWindowDownstream w = iter.next(); + appletWindow2AppContext.remove(w); + try { + log.info("AppContect2.dispose() : "+w); + w.destroy(); + } catch (Throwable t) { + log.warning("exception occured while disposing app context", t); + } + iter.remove(); + } + registeredWindows.clear(); + } + // Alert PropertyChangeListeners that the GUI has been disposed. + if (changeSupport != null) { + changeSupport.firePropertyChange(GUI_DISPOSED, false, true); + } + synchronized(notificationLock) { + notificationLock.notifyAll(); // Notify caller that we're done + } + } + }; + synchronized(notificationLock) { + Thread t = new Thread(runnable); + t.start(); + try { + notificationLock.wait(DISPOSAL_TIMEOUT); + } catch (InterruptedException e) { } + } + + // Next, we post another InvocationEvent to the end of the + // EventQueue. When it's executed, we know we've executed all + // events in the queue. + runnable = new Runnable() { @Override + public void run() { + synchronized(notificationLock) { + notificationLock.notifyAll(); // Notify caller that we're done + } + } }; + synchronized(notificationLock) { + Thread t = new Thread(runnable); + t.start(); + try { + notificationLock.wait(DISPOSAL_TIMEOUT); + } catch (InterruptedException e) { } + } + + // We are done with posting events, so change the state to disposed + synchronized(this) { + this.state = State.DISPOSED; + } + + // Next, we interrupt all Threads in the ThreadGroup + this.threadGroup.interrupt(); + // Note, the EventDispatchThread we've interrupted may dump an + // InterruptedException to the console here. This needs to be + // fixed in the EventDispatchThread, not here. + + // Next, we sleep 10ms at a time, waiting for all of the active + // Threads in the ThreadGroup to exit. + + long startTime = System.currentTimeMillis(); + long endTime = startTime + THREAD_INTERRUPT_TIMEOUT; + while ((this.threadGroup.activeCount() > 0) && + (System.currentTimeMillis() < endTime)) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { } + } + + // Then, we stop any remaining Threads + this.threadGroup.stop(); + + // Next, we sleep 10ms at a time, waiting for all of the active + // Threads in the ThreadGroup to die. + + startTime = System.currentTimeMillis(); + endTime = startTime + THREAD_INTERRUPT_TIMEOUT; + while ((this.threadGroup.activeCount() > 0) && + (System.currentTimeMillis() < endTime)) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { } + } + + // Next, we remove this and all subThreadGroups from threadGroup2appContext + int numSubGroups = this.threadGroup.activeGroupCount(); + if (numSubGroups > 0) { + ThreadGroup [] subGroups = new ThreadGroup[numSubGroups]; + numSubGroups = this.threadGroup.enumerate(subGroups); + for (int subGroup = 0; subGroup < numSubGroups; subGroup++) { + threadGroup2appContext.remove(subGroups[subGroup]); + } + } + threadGroup2appContext.remove(this.threadGroup); + + threadAppContext.set(null); + + // Finally, we destroy the ThreadGroup entirely. + try { + this.threadGroup.destroy(); + } catch (IllegalThreadStateException e) { + // Fired if not all the Threads died, ignore it and proceed + } + + synchronized (table) { + this.table.clear(); // Clear out the Hashtable to ease garbage collection + } + + numAppContexts.decrementAndGet(); + + mostRecentKeyValue = null; + } + + static final class CreateThreadAction implements PrivilegedAction<Thread> { + private final App3Context appContext; + private final Runnable runnable; + + public CreateThreadAction(App3Context ac, Runnable r) { + appContext = ac; + runnable = r; + } + + @Override + public Thread run() { + Thread t = new Thread(appContext.getThreadGroup(), runnable); + t.setContextClassLoader(appContext.getContextClassLoader()); + t.setPriority(Thread.NORM_PRIORITY + 1); + t.setDaemon(true); + return t; + } + } + + private MostRecentKeyValue mostRecentKeyValue = null; + private MostRecentKeyValue shadowMostRecentKeyValue = null; + + /** + * Returns the value to which the specified key is mapped in this context. + * + * @param key a key in the AppContext. + * @return the value to which the key is mapped in this AppContext; + * <code>null</code> if the key is not mapped to any value. + * @see #put(Object, Object) + * @since 1.2 + */ + public Object get(Object key) { + /* + * The most recent reference should be updated inside a synchronized + * block to avoid a race when put() and get() are executed in + * parallel on different threads. + */ + synchronized (table) { + // Note: this most recent key/value caching is thread-hot. + // A simple test using SwingSet found that 72% of lookups + // were matched using the most recent key/value. By instantiating + // a simple MostRecentKeyValue object on cache misses, the + // cache hits can be processed without synchronization. + + MostRecentKeyValue recent = mostRecentKeyValue; + if ((recent != null) && (recent.key == key)) { + return recent.value; + } + + Object value = table.get(key); + if(mostRecentKeyValue == null) { + mostRecentKeyValue = new MostRecentKeyValue(key, value); + shadowMostRecentKeyValue = new MostRecentKeyValue(key, value); + } else { + MostRecentKeyValue auxKeyValue = mostRecentKeyValue; + shadowMostRecentKeyValue.setPair(key, value); + mostRecentKeyValue = shadowMostRecentKeyValue; + shadowMostRecentKeyValue = auxKeyValue; + } + return value; + } + } + + /** + * Maps the specified <code>key</code> to the specified + * <code>value</code> in this AppContext. Neither the key nor the + * value can be <code>null</code>. + * <p> + * The value can be retrieved by calling the <code>get</code> method + * with a key that is equal to the original key. + * + * @param key the AppContext key. + * @param value the value. + * @return the previous value of the specified key in this + * AppContext, or <code>null</code> if it did not have one. + * @exception NullPointerException if the key or value is + * <code>null</code>. + * @see #get(Object) + * @since 1.2 + */ + public Object put(Object key, Object value) { + synchronized (table) { + MostRecentKeyValue recent = mostRecentKeyValue; + if ((recent != null) && (recent.key == key)) + recent.value = value; + return table.put(key, value); + } + } + + /** + * Removes the key (and its corresponding value) from this + * AppContext. This method does nothing if the key is not in the + * AppContext. + * + * @param key the key that needs to be removed. + * @return the value to which the key had been mapped in this AppContext, + * or <code>null</code> if the key did not have a mapping. + * @since 1.2 + */ + public Object remove(Object key) { + synchronized (table) { + MostRecentKeyValue recent = mostRecentKeyValue; + if ((recent != null) && (recent.key == key)) + recent.value = null; + return table.remove(key); + } + } + + /** + * Returns the root ThreadGroup for all Threads contained within + * this AppContext. + * @since 1.2 + */ + public ThreadGroup getThreadGroup() { + return threadGroup; + } + + /** + * Returns the context ClassLoader that was used to create this + * AppContext. + * + * @see java.lang.Thread#getContextClassLoader + */ + public ClassLoader getContextClassLoader() { + return contextClassLoader; + } + + /** + * Returns a string representation of this AppContext. + * @since 1.2 + */ + @Override + public String toString() { + return getClass().getName() + "[threadGroup=" + threadGroup.getName() + "]"; + } + + /** + * Returns an array of all the property change listeners + * registered on this component. + * + * @return all of this component's <code>PropertyChangeListener</code>s + * or an empty array if no property change + * listeners are currently registered + * + * @see #addPropertyChangeListener + * @see #removePropertyChangeListener + * @see #getPropertyChangeListeners(java.lang.String) + * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners + * @since 1.4 + */ + public synchronized PropertyChangeListener[] getPropertyChangeListeners() { + if (changeSupport == null) { + return new PropertyChangeListener[0]; + } + return changeSupport.getPropertyChangeListeners(); + } + + /** + * Adds a PropertyChangeListener to the listener list for a specific + * property. The specified property may be one of the following: + * <ul> + * <li>if this AppContext is disposed ("disposed")</li> + * </ul> + * <ul> + * <li>if this AppContext's unowned Windows have been disposed + * ("guidisposed"). Code to cleanup after the GUI is disposed + * (such as LookAndFeel.uninitialize()) should execute in response to + * this property being fired. Notifications for the "guidisposed" + * property are sent on the event dispatch thread.</li> + * </ul> + * <p> + * If listener is null, no exception is thrown and no action is performed. + * + * @param propertyName one of the property names listed above + * @param listener the PropertyChangeListener to be added + * + * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) + * @see #getPropertyChangeListeners(java.lang.String) + * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) + */ + public synchronized void addPropertyChangeListener( + String propertyName, + PropertyChangeListener listener) { + if (listener == null) { + return; + } + if (changeSupport == null) { + changeSupport = new PropertyChangeSupport(this); + } + changeSupport.addPropertyChangeListener(propertyName, listener); + } + + /** + * Removes a PropertyChangeListener from the listener list for a specific + * property. This method should be used to remove PropertyChangeListeners + * that were registered for a specific bound property. + * <p> + * If listener is null, no exception is thrown and no action is performed. + * + * @param propertyName a valid property name + * @param listener the PropertyChangeListener to be removed + * + * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) + * @see #getPropertyChangeListeners(java.lang.String) + * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) + */ + public synchronized void removePropertyChangeListener( + String propertyName, + PropertyChangeListener listener) { + if (listener == null || changeSupport == null) { + return; + } + changeSupport.removePropertyChangeListener(propertyName, listener); + } + + /** + * Returns an array of all the listeners which have been associated + * with the named property. + * + * @return all of the <code>PropertyChangeListeners</code> associated with + * the named property or an empty array if no listeners have + * been added + * + * @see #addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) + * @see #removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener) + * @see #getPropertyChangeListeners + * @since 1.4 + */ + public synchronized PropertyChangeListener[] getPropertyChangeListeners( + String propertyName) { + if (changeSupport == null) { + return new PropertyChangeListener[0]; + } + return changeSupport.getPropertyChangeListeners(propertyName); + } +} + +final class MostRecentKeyValue { + Object key; + Object value; + MostRecentKeyValue(Object k, Object v) { + key = k; + value = v; + } + void setPair(Object k, Object v) { + key = k; + value = v; + } +} diff --git a/netx/jogamp/applet/Applet3ClassLoader.java b/netx/jogamp/applet/Applet3ClassLoader.java new file mode 100644 index 0000000..9f3cda2 --- /dev/null +++ b/netx/jogamp/applet/Applet3ClassLoader.java @@ -0,0 +1,883 @@ +/* + * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * <p> + * Copyright (c) 2014, JogAmp Community. All rights reserved. + * + * JogAmp changes are compatible w/ GPLv2, + * and also available under the the New BSD 2-clause license. + * + * Please visit the JogAmp Community via http://jogamp.org/ + * and find appropriate channels (forum, email, irc) to contact us + * if you need additional information or have any + * questions. + * </p> + */ + +package jogamp.applet; + +import java.lang.NullPointerException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.MalformedURLException; +import java.io.File; +import java.io.FilePermission; +import java.io.IOException; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.NoSuchElementException; +import java.security.AccessController; +import java.security.AccessControlContext; +import java.security.PrivilegedAction; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; + +import sun.applet.AppletThreadGroup; +import sun.misc.IOUtils; +import sun.net.www.ParseUtil; +import sun.security.util.SecurityConstants; + +/** + * This class defines the class loader for loading applet classes and + * resources. It extends URLClassLoader to search the applet code base + * for the class or resource after checking any loaded JAR files. + */ +public class Applet3ClassLoader extends URLClassLoader { + private URL base; /* applet code base URL */ + private CodeSource codesource; /* codesource for the base URL */ + private AccessControlContext acc; + private boolean exceptionStatus = false; + + private final Object threadGroupSynchronizer = new Object(); + private final Object grabReleaseSynchronizer = new Object(); + + private boolean codebaseLookup = true; + private volatile boolean allowRecursiveDirectoryRead = true; + + /* + * Creates a new AppletClassLoader for the specified base URL. + */ + protected Applet3ClassLoader(URL base) { + super(new URL[0]); + this.base = base; + this.codesource = + new CodeSource(base, (java.security.cert.Certificate[]) null); + acc = AccessController.getContext(); + } + + public void disableRecursiveDirectoryRead() { + allowRecursiveDirectoryRead = false; + } + + + /** + * Set the codebase lookup flag. + */ + void setCodebaseLookup(boolean codebaseLookup) { + this.codebaseLookup = codebaseLookup; + } + + /* + * Returns the applet code base URL. + */ + URL getBaseURL() { + return base; + } + + /* + * Returns the URLs used for loading classes and resources. + */ + public URL[] getURLs() { + URL[] jars = super.getURLs(); + URL[] urls = new URL[jars.length + 1]; + System.arraycopy(jars, 0, urls, 0, jars.length); + urls[urls.length - 1] = base; + return urls; + } + + /* + * Adds the specified JAR file to the search path of loaded JAR files. + * Changed modifier to protected in order to be able to overwrite addJar() + * in PluginClassLoader.java + */ + protected void addJar(String name) throws IOException { + URL url; + try { + url = new URL(base, name); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("name"); + } + addURL(url); + // DEBUG + //URL[] urls = getURLs(); + //for (int i = 0; i < urls.length; i++) { + // System.out.println("url[" + i + "] = " + urls[i]); + //} + } + + /* + * Override loadClass so that class loading errors can be caught in + * order to print better error messages. + */ + public synchronized Class<?> loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + // First check if we have permission to access the package. This + // should go away once we've added support for exported packages. + int i = name.lastIndexOf('.'); + if (i != -1) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPackageAccess(name.substring(0, i)); + } + try { + return super.loadClass(name, resolve); + } catch (ClassNotFoundException e) { + //printError(name, e.getException()); + throw e; + } catch (RuntimeException e) { + //printError(name, e); + throw e; + } catch (Error e) { + //printError(name, e); + throw e; + } + } + + /* + * Finds the applet class with the specified name. First searches + * loaded JAR files then the applet code base for the class. + */ + protected Class<?> findClass(String name) throws ClassNotFoundException { + + int index = name.indexOf(";"); + String cookie = ""; + if(index != -1) { + cookie = name.substring(index, name.length()); + name = name.substring(0, index); + } + + // check loaded JAR files + try { + return super.findClass(name); + } catch (ClassNotFoundException e) { + } + + // Otherwise, try loading the class from the code base URL + + // 4668479: Option to turn off codebase lookup in AppletClassLoader + // during resource requests. [stanley.ho] + if (codebaseLookup == false) + throw new ClassNotFoundException(name); + +// final String path = name.replace('.', '/').concat(".class").concat(cookie); + String encodedName = ParseUtil.encodePath(name.replace('.', '/'), false); + final String path = (new StringBuffer(encodedName)).append(".class").append(cookie).toString(); + try { + byte[] b = (byte[]) AccessController.doPrivileged( + new PrivilegedExceptionAction<byte[]>() { + public byte[] run() throws IOException { + try { + URL finalURL = new URL(base, path); + + // Make sure the codebase won't be modified + if (base.getProtocol().equals(finalURL.getProtocol()) && + base.getHost().equals(finalURL.getHost()) && + base.getPort() == finalURL.getPort()) { + return getBytes(finalURL); + } + else { + return null; + } + } catch (Exception e) { + return null; + } + } + }, acc); + + if (b != null) { + return defineClass(name, b, 0, b.length, codesource); + } else { + throw new ClassNotFoundException(name); + } + } catch (PrivilegedActionException e) { + throw new ClassNotFoundException(name, e.getException()); + } + } + + /** + * Returns the permissions for the given codesource object. + * The implementation of this method first calls super.getPermissions, + * to get the permissions + * granted by the super class, and then adds additional permissions + * based on the URL of the codesource. + * <p> + * If the protocol is "file" + * and the path specifies a file, permission is granted to read all files + * and (recursively) all files and subdirectories contained in + * that directory. This is so applets with a codebase of + * file:/blah/some.jar can read in file:/blah/, which is needed to + * be backward compatible. We also add permission to connect back to + * the "localhost". + * + * @param codesource the codesource + * @throws NullPointerException if {@code codesource} is {@code null}. + * @return the permissions granted to the codesource + */ + protected PermissionCollection getPermissions(CodeSource codesource) + { + final PermissionCollection perms = super.getPermissions(codesource); + + URL url = codesource.getLocation(); + + String path = null; + Permission p; + + try { + p = url.openConnection().getPermission(); + } catch (java.io.IOException ioe) { + p = null; + } + + if (p instanceof FilePermission) { + path = p.getName(); + } else if ((p == null) && (url.getProtocol().equals("file"))) { + path = url.getFile().replace('/', File.separatorChar); + path = ParseUtil.decode(path); + } + + if (path != null) { + final String rawPath = path; + if (!path.endsWith(File.separator)) { + int endIndex = path.lastIndexOf(File.separatorChar); + if (endIndex != -1) { + path = path.substring(0, endIndex + 1) + "-"; + perms.add(new FilePermission(path, + SecurityConstants.FILE_READ_ACTION)); + } + } + final File f = new File(rawPath); + final boolean isDirectory = f.isDirectory(); + // grant codebase recursive read permission + // this should only be granted to non-UNC file URL codebase and + // the codesource path must either be a directory, or a file + // that ends with .jar or .zip + if (allowRecursiveDirectoryRead && (isDirectory || + rawPath.toLowerCase().endsWith(".jar") || + rawPath.toLowerCase().endsWith(".zip"))) { + + Permission bperm; + try { + bperm = base.openConnection().getPermission(); + } catch (java.io.IOException ioe) { + bperm = null; + } + if (bperm instanceof FilePermission) { + String bpath = bperm.getName(); + if (bpath.endsWith(File.separator)) { + bpath += "-"; + } + perms.add(new FilePermission(bpath, + SecurityConstants.FILE_READ_ACTION)); + } else if ((bperm == null) && (base.getProtocol().equals("file"))) { + String bpath = base.getFile().replace('/', File.separatorChar); + bpath = ParseUtil.decode(bpath); + if (bpath.endsWith(File.separator)) { + bpath += "-"; + } + perms.add(new FilePermission(bpath, SecurityConstants.FILE_READ_ACTION)); + } + + } + } + return perms; + } + + /* + * Returns the contents of the specified URL as an array of bytes. + */ + private static byte[] getBytes(URL url) throws IOException { + URLConnection uc = url.openConnection(); + if (uc instanceof java.net.HttpURLConnection) { + java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc; + int code = huc.getResponseCode(); + if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { + throw new IOException("open HTTP connection failed."); + } + } + int len = uc.getContentLength(); + + // Fixed #4507227: Slow performance to load + // class and resources. [stanleyh] + // + // Use buffered input stream [stanleyh] + InputStream in = new BufferedInputStream(uc.getInputStream()); + + byte[] b; + try { + b = IOUtils.readFully(in, len, true); + } finally { + in.close(); + } + return b; + } + + // Object for synchronization around getResourceAsStream() + private Object syncResourceAsStream = new Object(); + private Object syncResourceAsStreamFromJar = new Object(); + + // Flag to indicate getResourceAsStream() is in call + private boolean resourceAsStreamInCall = false; + private boolean resourceAsStreamFromJarInCall = false; + + /** + * Returns an input stream for reading the specified resource. + * + * The search order is described in the documentation for {@link + * #getResource(String)}.<p> + * + * @param name the resource name + * @return an input stream for reading the resource, or <code>null</code> + * if the resource could not be found + * @since JDK1.1 + */ + public InputStream getResourceAsStream(String name) + { + + if (name == null) { + throw new NullPointerException("name"); + } + + try + { + InputStream is = null; + + // Fixed #4507227: Slow performance to load + // class and resources. [stanleyh] + // + // The following is used to avoid calling + // AppletClassLoader.findResource() in + // super.getResourceAsStream(). Otherwise, + // unnecessary connection will be made. + // + synchronized(syncResourceAsStream) + { + resourceAsStreamInCall = true; + + // Call super class + is = super.getResourceAsStream(name); + + resourceAsStreamInCall = false; + } + + // 4668479: Option to turn off codebase lookup in AppletClassLoader + // during resource requests. [stanley.ho] + if (codebaseLookup == true && is == null) + { + // If resource cannot be obtained, + // try to download it from codebase + URL url = new URL(base, ParseUtil.encodePath(name, false)); + is = url.openStream(); + } + + return is; + } + catch (Exception e) + { + return null; + } + } + + + /** + * Returns an input stream for reading the specified resource from the + * the loaded jar files. + * + * The search order is described in the documentation for {@link + * #getResource(String)}.<p> + * + * @param name the resource name + * @return an input stream for reading the resource, or <code>null</code> + * if the resource could not be found + * @since JDK1.1 + */ + public InputStream getResourceAsStreamFromJar(String name) { + + if (name == null) { + throw new NullPointerException("name"); + } + + try { + InputStream is = null; + synchronized(syncResourceAsStreamFromJar) { + resourceAsStreamFromJarInCall = true; + // Call super class + is = super.getResourceAsStream(name); + resourceAsStreamFromJarInCall = false; + } + + return is; + } catch (Exception e) { + return null; + } + } + + + /* + * Finds the applet resource with the specified name. First checks + * loaded JAR files then the applet code base for the resource. + */ + public URL findResource(String name) { + // check loaded JAR files + URL url = super.findResource(name); + + // 6215746: Disable META-INF/* lookup from codebase in + // applet/plugin classloader. [stanley.ho] + if (name.startsWith("META-INF/")) + return url; + + // 4668479: Option to turn off codebase lookup in AppletClassLoader + // during resource requests. [stanley.ho] + if (codebaseLookup == false) + return url; + + if (url == null) + { + //#4805170, if it is a call from Applet.getImage() + //we should check for the image only in the archives + boolean insideGetResourceAsStreamFromJar = false; + synchronized(syncResourceAsStreamFromJar) { + insideGetResourceAsStreamFromJar = resourceAsStreamFromJarInCall; + } + + if (insideGetResourceAsStreamFromJar) { + return null; + } + + // Fixed #4507227: Slow performance to load + // class and resources. [stanleyh] + // + // Check if getResourceAsStream is called. + // + boolean insideGetResourceAsStream = false; + + synchronized(syncResourceAsStream) + { + insideGetResourceAsStream = resourceAsStreamInCall; + } + + // If getResourceAsStream is called, don't + // trigger the following code. Otherwise, + // unnecessary connection will be made. + // + if (insideGetResourceAsStream == false) + { + // otherwise, try the code base + try { + url = new URL(base, ParseUtil.encodePath(name, false)); + // check if resource exists + if(!resourceExists(url)) + url = null; + } catch (Exception e) { + // all exceptions, including security exceptions, are caught + url = null; + } + } + } + return url; + } + + + private boolean resourceExists(URL url) { + // Check if the resource exists. + // It almost works to just try to do an openConnection() but + // HttpURLConnection will return true on HTTP_BAD_REQUEST + // when the requested name ends in ".html", ".htm", and ".txt" + // and we want to be able to handle these + // + // Also, cannot just open a connection for things like FileURLConnection, + // because they succeed when connecting to a nonexistent file. + // So, in those cases we open and close an input stream. + boolean ok = true; + try { + URLConnection conn = url.openConnection(); + if (conn instanceof java.net.HttpURLConnection) { + java.net.HttpURLConnection hconn = + (java.net.HttpURLConnection) conn; + + // To reduce overhead, using http HEAD method instead of GET method + hconn.setRequestMethod("HEAD"); + + int code = hconn.getResponseCode(); + if (code == java.net.HttpURLConnection.HTTP_OK) { + return true; + } + if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { + return false; + } + } else { + /** + * Fix for #4182052 - stanleyh + * + * The same connection should be reused to avoid multiple + * HTTP connections + */ + + // our best guess for the other cases + InputStream is = conn.getInputStream(); + is.close(); + } + } catch (Exception ex) { + ok = false; + } + return ok; + } + + /* + * Returns an enumeration of all the applet resources with the specified + * name. First checks loaded JAR files then the applet code base for all + * available resources. + */ + public Enumeration<URL> findResources(String name) throws IOException { + + final Enumeration<URL> e = super.findResources(name); + + // 6215746: Disable META-INF/* lookup from codebase in + // applet/plugin classloader. [stanley.ho] + if (name.startsWith("META-INF/")) + return e; + + // 4668479: Option to turn off codebase lookup in AppletClassLoader + // during resource requests. [stanley.ho] + if (codebaseLookup == false) + return e; + + URL u = new URL(base, ParseUtil.encodePath(name, false)); + if (!resourceExists(u)) { + u = null; + } + + final URL url = u; + return new Enumeration<URL>() { + private boolean done; + public URL nextElement() { + if (!done) { + if (e.hasMoreElements()) { + return e.nextElement(); + } + done = true; + if (url != null) { + return url; + } + } + throw new NoSuchElementException(); + } + public boolean hasMoreElements() { + return !done && (e.hasMoreElements() || url != null); + } + }; + } + + /* + * Load and resolve the file specified by the applet tag CODE + * attribute. The argument can either be the relative path + * of the class file itself or just the name of the class. + */ + Class<?> loadCode(String name) throws ClassNotFoundException { + // first convert any '/' or native file separator to . + name = name.replace('/', '.'); + name = name.replace(File.separatorChar, '.'); + + // deal with URL rewriting + String cookie = null; + int index = name.indexOf(";"); + if(index != -1) { + cookie = name.substring(index, name.length()); + name = name.substring(0, index); + } + + // save that name for later + String fullName = name; + // then strip off any suffixes + if (name.endsWith(".class") || name.endsWith(".java")) { + name = name.substring(0, name.lastIndexOf('.')); + } + try { + if(cookie != null) + name = (new StringBuffer(name)).append(cookie).toString(); + return loadClass(name); + } catch (ClassNotFoundException e) { + } + // then if it didn't end with .java or .class, or in the + // really pathological case of a class named class or java + if(cookie != null) + fullName = (new StringBuffer(fullName)).append(cookie).toString(); + + return loadClass(fullName); + } + + /* + * The threadgroup that the applets loaded by this classloader live + * in. In the sun.* implementation of applets, the security manager's + * (AppletSecurity) getThreadGroup returns the thread group of the + * first applet on the stack, which is the applet's thread group. + */ + private AppletThreadGroup threadGroup; + private App3Context appContext; + + public ThreadGroup getThreadGroup() { + synchronized (threadGroupSynchronizer) { + if (threadGroup == null || threadGroup.isDestroyed()) { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + threadGroup = new AppletThreadGroup(base + "-threadGroup"); + // threadGroup.setDaemon(true); + // threadGroup is now destroyed by AppContext.dispose() + + // Create the new AppContext from within a Thread belonging + // to the newly created ThreadGroup, and wait for the + // creation to complete before returning from this method. + AppContextCreator creatorThread = new AppContextCreator(threadGroup); + + // Since this thread will later be used to launch the + // applet's AWT-event dispatch thread and we want the applet + // code executing the AWT callbacks to use their own class + // loader rather than the system class loader, explicitly + // set the context class loader to the AppletClassLoader. + creatorThread.setContextClassLoader(Applet3ClassLoader.this); + + creatorThread.start(); + try { + synchronized(creatorThread.syncObject) { + while (!creatorThread.created) { + creatorThread.syncObject.wait(); + } + } + } catch (InterruptedException e) { } + appContext = creatorThread.appContext; + return null; + } + }); + } + return threadGroup; + } + } + + /* + * Get the App3Context, if any, corresponding to this AppletClassLoader. + */ + public App3Context getAppContext() { + return appContext; + } + + int usageCount = 0; + + /** + * Grab this AppletClassLoader and its ThreadGroup/AppContext, so they + * won't be destroyed. + */ + public void grab() { + synchronized(grabReleaseSynchronizer) { + usageCount++; + } + getThreadGroup(); // Make sure ThreadGroup/AppContext exist + } + + protected void setExceptionStatus() + { + exceptionStatus = true; + } + + public boolean getExceptionStatus() + { + return exceptionStatus; + } + + /** + * Release this AppletClassLoader and its ThreadGroup/AppContext. + * If nothing else has grabbed this AppletClassLoader, its ThreadGroup + * and AppContext will be destroyed. + * + * Because this method may destroy the AppletClassLoader's ThreadGroup, + * this method should NOT be called from within the AppletClassLoader's + * ThreadGroup. + * + * Changed modifier to protected in order to be able to overwrite this + * function in PluginClassLoader.java + */ + protected void release() { + + App3Context tempAppContext = null; + + synchronized(grabReleaseSynchronizer) { + if (usageCount > 1) { + --usageCount; + } else { + synchronized(threadGroupSynchronizer) { + tempAppContext = resetAppContext(); + } + } + } + + // Dispose appContext outside any sync block to + // prevent potential deadlock. + if (tempAppContext != null) { + try { + tempAppContext.dispose(); // nuke the world! + } catch (IllegalThreadStateException e) { } + } + } + + /* + * reset classloader's AppContext and ThreadGroup + * This method is for subclass PluginClassLoader to + * reset superclass's AppContext and ThreadGroup but do + * not dispose the AppContext. PluginClassLoader does not + * use UsageCount to decide whether to dispose AppContext + * + * @return previous AppContext + */ + protected App3Context resetAppContext() { + App3Context tempAppContext = null; + + synchronized(threadGroupSynchronizer) { + // Store app context in temp variable + tempAppContext = appContext; + usageCount = 0; + appContext = null; + threadGroup = null; + } + return tempAppContext; + } + + + // Hash map to store applet compatibility info + private HashMap<String, Boolean> jdk11AppletInfo = new HashMap<String, Boolean>(); + private HashMap<String, Boolean> jdk12AppletInfo = new HashMap<String, Boolean>(); + + /** + * Set applet target level as JDK 1.1. + * + * @param clazz Applet class. + * @param bool true if JDK is targeted for JDK 1.1; + * false otherwise. + */ + void setJDK11Target(Class<?> clazz, boolean bool) + { + jdk11AppletInfo.put(clazz.toString(), Boolean.valueOf(bool)); + } + + /** + * Set applet target level as JDK 1.2. + * + * @param clazz Applet class. + * @param bool true if JDK is targeted for JDK 1.2; + * false otherwise. + */ + void setJDK12Target(Class<?> clazz, boolean bool) + { + jdk12AppletInfo.put(clazz.toString(), Boolean.valueOf(bool)); + } + + /** + * Determine if applet is targeted for JDK 1.1. + * + * @param applet Applet class. + * @return TRUE if applet is targeted for JDK 1.1; + * FALSE if applet is not; + * null if applet is unknown. + */ + Boolean isJDK11Target(Class<?> clazz) + { + return (Boolean) jdk11AppletInfo.get(clazz.toString()); + } + + /** + * Determine if applet is targeted for JDK 1.2. + * + * @param applet Applet class. + * @return TRUE if applet is targeted for JDK 1.2; + * FALSE if applet is not; + * null if applet is unknown. + */ + Boolean isJDK12Target(Class<?> clazz) + { + return (Boolean) jdk12AppletInfo.get(clazz.toString()); + } + + /* + private static AppletMessageHandler mh = + new AppletMessageHandler("appletclassloader"); + + * Prints a class loading error message. + private static void printError(String name, Throwable e) { + String s = null; + if (e == null) { + s = mh.getMessage("filenotfound", name); + } else if (e instanceof IOException) { + s = mh.getMessage("fileioexception", name); + } else if (e instanceof ClassFormatError) { + s = mh.getMessage("fileformat", name); + } else if (e instanceof ThreadDeath) { + s = mh.getMessage("filedeath", name); + } else if (e instanceof Error) { + s = mh.getMessage("fileerror", e.toString(), name); + } + if (s != null) { + System.err.println(s); + } + } + */ +} + +/* + * The AppContextCreator class is used to create an AppContext from within + * a Thread belonging to the new AppContext's ThreadGroup. To wait for + * this operation to complete before continuing, wait for the notifyAll() + * operation on the syncObject to occur. + */ +class AppContextCreator extends Thread { + Object syncObject = new Object(); + App3Context appContext = null; + volatile boolean created = false; + + AppContextCreator(ThreadGroup group) { + super(group, "AppContextCreator"); + } + + public void run() { + appContext = App3Context.createAppContext(); + created = true; + synchronized(syncObject) { + syncObject.notifyAll(); + } + } // run() + +} // class AppContextCreator diff --git a/netx/jogamp/applet/Applet3ObjectInputStream.java b/netx/jogamp/applet/Applet3ObjectInputStream.java new file mode 100644 index 0000000..3cf4b4e --- /dev/null +++ b/netx/jogamp/applet/Applet3ObjectInputStream.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 1996, 1997, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * <p> + * Copyright (c) 2014, JogAmp Community. All rights reserved. + * + * JogAmp changes are compatible w/ GPLv2, + * and also available under the the New BSD 2-clause license. + * + * Please visit the JogAmp Community via http://jogamp.org/ + * and find appropriate channels (forum, email, irc) to contact us + * if you need additional information or have any + * questions. + * </p> + */ +/* + * COPYRIGHT goes here + */ + +package jogamp.applet; + +import java.io.*; +import java.lang.reflect.Array; + +import sun.applet.AppletIllegalArgumentException; + +/** + * This subclass of ObjectInputStream delegates loading of classes to + * an existing ClassLoader. + */ + +class Applet3ObjectInputStream extends ObjectInputStream +{ + private Applet3ClassLoader loader; + + /** + * Loader must be non-null; + */ + + public Applet3ObjectInputStream(InputStream in, Applet3ClassLoader loader) + throws IOException, StreamCorruptedException { + + super(in); + if (loader == null) { + throw new AppletIllegalArgumentException("appletillegalargumentexception.objectinputstream"); + + } + this.loader = loader; + } + + /** + * Make a primitive array class + */ + + private Class<?> primitiveType(char type) { + switch (type) { + case 'B': return byte.class; + case 'C': return char.class; + case 'D': return double.class; + case 'F': return float.class; + case 'I': return int.class; + case 'J': return long.class; + case 'S': return short.class; + case 'Z': return boolean.class; + default: return null; + } + } + + /** + * Use the given ClassLoader rather than using the system class + */ + protected Class<?> resolveClass(ObjectStreamClass classDesc) + throws IOException, ClassNotFoundException { + + String cname = classDesc.getName(); + if (cname.startsWith("[")) { + // An array + Class<?> component; // component class + int dcount; // dimension + for (dcount=1; cname.charAt(dcount)=='['; dcount++) ; + if (cname.charAt(dcount) == 'L') { + component = loader.loadClass(cname.substring(dcount+1, + cname.length()-1)); + } else { + if (cname.length() != dcount+1) { + throw new ClassNotFoundException(cname);// malformed + } + component = primitiveType(cname.charAt(dcount)); + } + int dim[] = new int[dcount]; + for (int i=0; i<dcount; i++) { + dim[i]=0; + } + return Array.newInstance(component, dim).getClass(); + } else { + return loader.loadClass(cname); + } + } +} diff --git a/netx/jogamp/applet/Applet3Panel.java b/netx/jogamp/applet/Applet3Panel.java new file mode 100644 index 0000000..34d8d53 --- /dev/null +++ b/netx/jogamp/applet/Applet3Panel.java @@ -0,0 +1,1536 @@ +/* + * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + * <p> + * Copyright (c) 2014, JogAmp Community. All rights reserved. + * + * JogAmp changes are compatible w/ GPLv2, + * and also available under the the New BSD 2-clause license. + * + * Please visit the JogAmp Community via http://jogamp.org/ + * and find appropriate channels (forum, email, irc) to contact us + * if you need additional information or have any + * questions. + * </p> + */ + +package jogamp.applet; + +import java.io.File; +import java.io.FilePermission; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.lang.ref.WeakReference; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.SocketPermission; +import java.net.URL; +import java.security.AccessControlContext; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.Vector; + +import sun.applet.Applet3MessageHandler; +import sun.applet.AppletEvent; +import sun.applet.AppletEventMulticaster; +import sun.applet.AppletListener; +import sun.misc.MessageUtils; +import sun.misc.PerformanceLogger; +import sun.misc.Queue; +import sun.security.util.SecurityConstants; + +import com.jogamp.plugin.applet.Applet3; +import com.jogamp.plugin.applet.Applet3Context; +import com.jogamp.plugin.ui.NativeWindowDownstream; +import com.jogamp.plugin.ui.NativeWindowUpstream; + +/** + * Applet panel class. The panel manages and manipulates the + * applet as it is being loaded. It forks a separate thread in a new + * thread group to call the applet's init(), start(), stop(), and + * destroy() methods. + * + * @author Arthur van Hoff + */ +public abstract class Applet3Panel implements Applet3Context, Runnable { + + /** + * The applet (if loaded). + */ + protected Applet3 applet; + /** + * The applet window controller (if initialized). + */ + private NativeWindowDownstream appletWindow; + + private final MyNativeWindow parentWindow; + + /** + * Applet will allow initialization. Should be + * set to false if loading a serialized applet + * that was pickled in the init=true state. + */ + protected boolean doInit = true; + + + /** + * The {@link Applet3ClassLoader} for the applet. + * <p> + * In case {@link #getClassLoader(URL, String)} produced + * an instance of {@link Applet3ClassLoader}. + * </p> + */ + protected Applet3ClassLoader applet3Loader; + + /** + * The ClassLoader for the applet. + */ + protected ClassLoader loader; + + /* applet event ids */ + public final static int APPLET_DISPOSE = 0; + public final static int APPLET_LOAD = 1; + public final static int APPLET_INIT = 2; + public final static int APPLET_START = 3; + public final static int APPLET_STOP = 4; + public final static int APPLET_DESTROY = 5; + public final static int APPLET_QUIT = 6; + public final static int APPLET_ERROR = 7; + + /* send to the parent to force relayout */ + public final static int APPLET_RESIZE = 51234; + + /* sent to a (distant) parent to indicate that the applet is being + * loaded or as completed loading + */ + public final static int APPLET_LOADING = 51235; + public final static int APPLET_LOADING_COMPLETED = 51236; + + /** + * The current status. One of: + * APPLET_DISPOSE, + * APPLET_LOAD, + * APPLET_INIT, + * APPLET_START, + * APPLET_STOP, + * APPLET_DESTROY, + * APPLET_ERROR. + */ + protected int status; + + /** + * The thread for the applet. + */ + protected Thread handler; + + + /** + * The initial applet size. + */ + int[] defaultAppletSize = { 10, 10 }; + + /** + * The current applet size. + */ + int[] currentAppletSize = { 10, 10 }; + + MessageUtils mu = new MessageUtils(); + + /** + * The thread to use during applet loading + */ + + Thread loaderThread = null; + + /** + * Flag to indicate that a loading has been cancelled + */ + boolean loadAbortRequest = false; + + /* Are we debugging? */ + static boolean debug = false; + + /** + * The document url. + */ + final protected URL documentURL; + + /** + * The base url. + */ + final protected URL baseURL; + + /** + * The attributes of the applet. + */ + final Hashtable<String, String> parameters; + + public Applet3Panel(long nativeWindowHandle, int width, int height, URL documentURL, Hashtable<String, String> parameters) { + this.documentURL = documentURL; + this.parameters = parameters; + this.updateSizeInParameters(width, height); + this.currentAppletSize[0] = width; + this.currentAppletSize[1] = height; + { + URL _baseURL = null; + String att = getParameter("codebase"); + if (att != null) { + if (!att.endsWith("/")) { + att += "/"; + } + try { + _baseURL = new URL(documentURL, att); + } catch (MalformedURLException e) { + } + } + if (_baseURL == null) { + String file = documentURL.getFile(); + int i = file.lastIndexOf('/'); + if (i >= 0 && i < file.length() - 1) { + try { + _baseURL = new URL(documentURL, file.substring(0, i + 1)); + } catch (MalformedURLException e) { + } + } + } + + // when all is said & done, baseURL shouldn't be null + if (_baseURL == null) { + _baseURL = documentURL; + } + baseURL = _baseURL; + } + this.parentWindow = new MyNativeWindow(nativeWindowHandle); + } + class MyNativeWindow implements NativeWindowUpstream { + final long handle; + + MyNativeWindow(long nativeWindowHandle) { + handle = nativeWindowHandle; + } + + @Override + public final int getWidth() { + return Applet3Panel.this.getWidth(); + } + + @Override + public final int getHeight() { + return Applet3Panel.this.getHeight(); + } + + @Override + public final long getWindowHandle() { + return this.handle; + } + + @Override + public final String getDisplayConnection() { + return null; // FIXME: Using default for now + } + + @Override + public final int getScreenIndex() { + return 0; // FIXME: Using default for now + } + + @Override + public final void notifySurfaceUpdated(NativeWindowDownstream swappedWin) { + // TODO: May hook for composite extension + } + }; + + public void destroy(boolean notifyApplet, boolean notifyUser) throws java.security.AccessControlException { + try { + if( notifyApplet && null != applet ) { + applet.destroy(); + } + } catch (java.security.AccessControlException e) { + setExceptionStatus(e); + // rethrow exception to be handled as it normally would be. + throw e; + } finally { + if( null != appletWindow ) { + try { + appletWindow.destroy(); + } catch( Throwable t ) { + t.printStackTrace(); + } + if( null != applet3Loader ) { + applet3Loader.getAppContext().unregisterAppletWindow(appletWindow); + } + appletWindow = null; + } + } + if( notifyUser ) { + showAppletStatus("destroyed"); + } + } + + public NativeWindowDownstream getAppletWindow() { + return appletWindow; + } + public MyNativeWindow getBrowserWindow() { + return parentWindow; + } + + public int getStatus() { return status; } + + /** + * Must be override with upstream plugin implementation, i.e. browser connection. + * Known implementations are {@link jogamp.plugin.applet.PluginApplet3Viewer} ! + */ + protected abstract Applet3Context getAppletContext(); + + // + // Applet3Context + // + + @Override + public final Applet3 getApplet() { + return applet; + } + + @Override + public final String getAppletName() { + return getParameter("name"); + } + + @Override + public final boolean isActive() { + return status == APPLET_START; + } + + @Override + public final String getParameter(String name) { + return parameters.get(name.toLowerCase()); + } + + public String getDefaulted(String key, String defaultStr) { + final String value = getParameter(key); + return (value != null) ? value : defaultStr; + } + + @Override + public final URL getDocumentBase() { // TODO + return documentURL; + } + + @Override + public final URL getCodeBase() { + return baseURL; + } + + public void updateSizeInParameters(int width, int height) { + parameters.put("width", Integer.toString(width)); + parameters.put("height", Integer.toString(height)); + } + + @Override + public void resize(int width, int height) { + appletResize(width, height); + } + + /** + * Called when the applet wants to be resized. + * + * @param width the new requested width for the applet. + * @param height the new requested height for the applet. + */ + public void appletResize(int width, int height) { + updateSizeInParameters(width, height); + currentAppletSize[0] = width; + currentAppletSize[1] = height; + if( null != appletWindow ) { + appletWindow.setSize(width, height); + } + /** FIXME + if(loader != null) { + App3Context appCtxt = loader.getAppContext(); + if(appCtxt != null) + appEvtQ = (java.awt.EventQueue)appCtxt.get(App3Context.EVENT_QUEUE_KEY); + } + + final Applet3Panel ap = this; + if (appEvtQ != null){ + appEvtQ.postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(), + new Runnable(){ + public void run(){ + if(ap != null) + { + ap.dispatchAppletEvent(APPLET_RESIZE, currentSize); + } + } + })); + } */ + } + + @Override + public final Applet3Context getAppletContext(String name) { + return getAppletContext().getAppletContext(name); + } + + @Override + public final Enumeration<Applet3Context> getAllAppletContexts() { + return getAppletContext().getAllAppletContexts(); + } + + @Override + public final void showDocument(URL url) { + getAppletContext().showDocument(url); + } + + @Override + public final void showDocument(URL url, String target) { + getAppletContext().showDocument(url, target); + } + + @Override + public final void showStatus(String status) { + getAppletContext().showStatus(status); + } + + @Override + public final void setStream(String key, InputStream stream)throws IOException { + getAppletContext().setStream(key, stream); + } + + @Override + public final InputStream getStream(String key) { + return getAppletContext().getStream(key); + } + + @Override + public final Iterator<String> getStreamKeys() { + return getAppletContext().getStreamKeys(); + } + + // + // Internal Impl. + // + + /** + * Get the code parameter + */ + public String getCode() { + return getParameter("code"); + } + + public String getIsApplet3() { + return getDefaulted("is_applet3", "false"); + } + + /** + * Return the list of jar files if specified. + * Otherwise return null. + */ + public String getJarFiles() { + return getParameter("archive"); + } + + /** + * Return the value of the object param + */ + public String getSerializedObject() { + return getParameter("object");// another name? + } + + + public static void debug(String s) { // FIXME visibility ? + if(debug) + System.err.println("AppletViewerPanel:::" + s); + } + + static void debug(String s, Throwable t) { + if(debug) { + t.printStackTrace(); + debug(s); + } + } + + /** + * Get the width. + */ + public final int getWidth() { + return currentAppletSize[0]; + } + + + /** + * Get the height. + */ + public final int getHeight() { + return currentAppletSize[1]; + } + + /** + * Get initial_focus + */ + public boolean hasInitialFocus() { + + // 6234219: Do not set initial focus on an applet + // during startup if applet is targeted for + // JDK 1.1/1.2. [stanley.ho] + if (isJDK11Applet() || isJDK12Applet()) + return false; + + String initialFocus = getParameter("initial_focus"); + + if (initialFocus != null) + { + if (initialFocus.toLowerCase().equals("false")) + return false; + } + + return true; + } + + protected void setupAppletAppContext() { + // do nothing + } + + /* + * Creates a thread to run the applet. This method is called + * each time an applet is loaded and reloaded. + */ + public synchronized void createAppletThread() { + // Create a thread group for the applet, and start a new + // thread to load the applet. + String nm = "applet-" + getCode(); + loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); + applet3Loader.grab(); // Keep this puppy around! + + // 4668479: Option to turn off codebase lookup in AppletClassLoader + // during resource requests. [stanley.ho] + String param = getParameter("codebase_lookup"); + + if (param != null && param.equals("false")) + applet3Loader.setCodebaseLookup(false); + else + applet3Loader.setCodebaseLookup(true); + + + ThreadGroup appletGroup = applet3Loader.getThreadGroup(); + + handler = new Thread(appletGroup, this, "thread " + nm); + // set the context class loader for this thread + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + handler.setContextClassLoader(loader); + return null; + } + }); + handler.start(); + } + + public void joinAppletThread() throws InterruptedException { + if (handler != null) { + handler.join(); + handler = null; + } + } + + public void release() { + if( null != applet3Loader ) { + applet3Loader.release(); + applet3Loader = null; + } + loader = null; + } + + /** + * Construct an applet viewer and start the applet. + */ + public void init() { + try { + // Get the width (if any) + final int width = getWidth(); + defaultAppletSize[0] = width; + currentAppletSize[0] = width; + + // Get the height (if any) + final int height = getHeight(); + defaultAppletSize[1] = height; + currentAppletSize[1] = height; + + } catch (NumberFormatException e) { + // Turn on the error flag and let TagAppletPanel + // do the right thing. + status = APPLET_ERROR; + showAppletStatus("badattribute.exception"); + showAppletLog("badattribute.exception"); + showAppletException(e); + } + + createAppletThread(); + } + + private AppletListener listeners; + + /** + * AppletEvent Queue + */ + @SuppressWarnings("rawtypes") + private Queue queue = null; + + + synchronized public void addAppletListener(AppletListener l) { + listeners = AppletEventMulticaster.add(listeners, l); + } + + synchronized public void removeAppletListener(AppletListener l) { + listeners = AppletEventMulticaster.remove(listeners, l); + } + + /** + * Dispatch event to the listeners.. + */ + public void dispatchAppletEvent(int id, Object argument) { + //System.out.println("SEND= " + id); + if (listeners != null) { + AppletEvent evt = new AppletEvent(this, id, argument); + listeners.appletStateChanged(evt); + } + } + + /** + * Send an event. Queue it for execution by the handler thread. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void sendEvent(int id) { + synchronized(this) { + if (queue == null) { + //System.out.println("SEND0= " + id); + queue = new Queue(); + } + Integer eventId = Integer.valueOf(id); + queue.enqueue(eventId); + notifyAll(); + } + if (id == APPLET_QUIT) { + try { + joinAppletThread(); // Let the applet event handler exit + } catch (InterruptedException e) { + } + + // AppletClassLoader.release() must be called by a Thread + // not within the applet's ThreadGroup + if (loader == null) { + // FIXME loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); + setClassLoader(); + } + release(); + } + } + + /** + * Get an event from the queue. + */ + synchronized AppletEvent getNextEvent() throws InterruptedException { + while (queue == null || queue.isEmpty()) { + wait(); + } + Integer eventId = (Integer)queue.dequeue(); + return new AppletEvent(this, eventId.intValue(), null); + } + + public boolean emptyEventQueue() { + if ((queue == null) || (queue.isEmpty())) + return true; + else + return false; + } + + /** + * This kludge is specific to get over AccessControlException thrown during + * Applet.stop() or destroy() when static thread is suspended. Set a flag + * in AppletClassLoader to indicate that an + * AccessControlException for RuntimePermission "modifyThread" or + * "modifyThreadGroup" had occurred. + */ + private void setExceptionStatus(AccessControlException e) { + Permission p = e.getPermission(); + if (p instanceof RuntimePermission) { + if (p.getName().startsWith("modifyThread")) { + if (loader == null) { + // FIXME loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); + setClassLoader(); + } + if( null != applet3Loader ) { + applet3Loader.setExceptionStatus(); + } + } + } + } + + /** + * Execute applet events. + * Here is the state transition diagram + * + * Note: (XXX) is the action + * APPLET_XXX is the state + * (applet code loaded) --> APPLET_LOAD -- (applet init called)--> APPLET_INIT -- ( + * applet start called) --> APPLET_START -- (applet stop called) -->APPLET_STOP --(applet + * destroyed called) --> APPLET_DESTROY -->(applet gets disposed) --> + * APPLET_DISPOSE -->.... + * + * In the legacy lifecycle model. The applet gets loaded, inited and started. So it stays + * in the APPLET_START state unless the applet goes away(refresh page or leave the page). + * So the applet stop method called and the applet enters APPLET_STOP state. Then if the applet + * is revisited, it will call applet start method and enter the APPLET_START state and stay there. + * + * In the modern lifecycle model. When the applet first time visited, it is same as legacy lifecycle + * model. However, when the applet page goes away. It calls applet stop method and enters APPLET_STOP + * state and then applet destroyed method gets called and enters APPLET_DESTROY state. + * + * This code is also called by AppletViewer. In AppletViewer "Restart" menu, the applet is jump from + * APPLET_STOP to APPLET_DESTROY and to APPLET_INIT . + * + * Also, the applet can jump from APPLET_INIT state to APPLET_DESTROY (in Netscape/Mozilla case). + * Same as APPLET_LOAD to + * APPLET_DISPOSE since all of this are triggered by browser. + * + * + */ + @Override + public void run() { + + Thread curThread = Thread.currentThread(); + if (curThread == loaderThread) { + // if we are in the loader thread, cause + // loading to occur. We may exit this with + // status being APPLET_DISPOSE, APPLET_ERROR, + // or APPLET_LOAD + runLoader(); + return; + } + + boolean disposed = false; + while (!disposed && !curThread.isInterrupted()) { + AppletEvent evt; + try { + evt = getNextEvent(); + } catch (InterruptedException e) { + showAppletStatus("bail"); + return; + } + + //showAppletStatus("EVENT = " + evt.getID()); + try { + switch (evt.getID()) { + case APPLET_LOAD: + if (!okToLoad()) { + break; + } + // This complexity allows loading of applets to be + // interruptable. The actual thread loading runs + // in a separate thread, so it can be interrupted + // without harming the applet thread. + // So that we don't have to worry about + // concurrency issues, the main applet thread waits + // until the loader thread terminates. + // (one way or another). + if (loaderThread == null) { + // REMIND: do we want a name? + //System.out.println("------------------- loading applet"); + setLoaderThread(new Thread(this)); + loaderThread.start(); + // we get to go to sleep while this runs + loaderThread.join(); + setLoaderThread(null); + } else { + // REMIND: issue an error -- this case should never + // occur. + } + break; + + case APPLET_INIT: + // AppletViewer "Restart" will jump from destroy method to + // init, that is why we need to check status w/ APPLET_DESTROY + if (status != APPLET_LOAD && status != APPLET_DESTROY) { + showAppletStatus("notloaded"); + break; + } + if( null == appletWindow ) { + appletWindow = applet.createNativeWindow(this, parentWindow); + if( parentWindow != appletWindow.getParent() ) { + throw new IllegalArgumentException("Applet's parent doesn't match!"); + } + // FIXME loader.getAppContext().registerAppletWindow(appletWindow); + } else { + appletWindow.setSize(defaultAppletSize[0], defaultAppletSize[1]); + } + if (doInit) { + if (PerformanceLogger.loggingEnabled()) { + PerformanceLogger.setTime("Applet Init"); + PerformanceLogger.outputLog(); + } + applet.init(this); + } + + doInit = true; // allow restarts + + status = APPLET_INIT; + showAppletStatus("inited"); + break; + + case APPLET_START: + { + if (status != APPLET_INIT && status != APPLET_STOP) { + showAppletStatus("notinited"); + break; + } + appletWindow.setSize(currentAppletSize[0], currentAppletSize[1]); + appletWindow.setVisible(true); + // Fix for BugTraq ID 4041703. + // Set the default focus for an applet. + if (hasInitialFocus()) { + appletWindow.requestFocus(); + } + applet.start(); + + status = APPLET_START; + showAppletStatus("started"); + break; + } + + case APPLET_STOP: + if (status != APPLET_START) { + showAppletStatus("notstarted"); + break; + } + status = APPLET_STOP; + + // During Applet.stop(), any AccessControlException on an involved Class remains in + // the "memory" of the AppletClassLoader. If the same instance of the ClassLoader is + // reused, the same exception will occur during class loading. Set the AppletClassLoader's + // exceptionStatusSet flag to allow recognition of what had happened + // when reusing AppletClassLoader object. + try { + applet.stop(); + } catch (java.security.AccessControlException e) { + setExceptionStatus(e); + // rethrow exception to be handled as it normally would be. + throw e; + } finally { + try { + appletWindow.setVisible(false); + } catch( Throwable t ) { + t.printStackTrace(); + } + } + showAppletStatus("stopped"); + break; + + case APPLET_DESTROY: + if (status != APPLET_STOP && status != APPLET_INIT) { + showAppletStatus("notstopped"); + break; + } + status = APPLET_DESTROY; + + // During Applet.destroy(), any AccessControlException on an involved Class remains in + // the "memory" of the AppletClassLoader. If the same instance of the ClassLoader is + // reused, the same exception will occur during class loading. Set the AppletClassLoader's + // exceptionStatusSet flag to allow recognition of what had happened + // when reusing AppletClassLoader object. + destroy(true, true); + break; + + case APPLET_DISPOSE: + if (status != APPLET_DESTROY && status != APPLET_LOAD) { + showAppletStatus("notdestroyed"); + break; + } + status = APPLET_DISPOSE; + + applet = null; + showAppletStatus("disposed"); + disposed = true; + break; + + case APPLET_QUIT: + return; + } + } catch (Exception e) { + status = APPLET_ERROR; + if (e.getMessage() != null) { + showAppletStatus("exception2", e.getClass().getName(), + e.getMessage()); + } else { + showAppletStatus("exception", e.getClass().getName()); + } + showAppletException(e); + } catch (ThreadDeath e) { + showAppletStatus("death"); + return; + } catch (Error e) { + status = APPLET_ERROR; + if (e.getMessage() != null) { + showAppletStatus("error2", e.getClass().getName(), + e.getMessage()); + } else { + showAppletStatus("error", e.getClass().getName()); + } + showAppletException(e); + } + clearLoadAbortRequest(); + } + } + + /** + * Load the applet into memory. + * Runs in a seperate (and interruptible) thread from the rest of the + * applet event processing so that it can be gracefully interrupted from + * things like HotJava. + */ + protected void runLoader() { + if (status != APPLET_DISPOSE) { + showAppletStatus("notdisposed"); + return; + } + + dispatchAppletEvent(APPLET_LOADING, null); + + // REMIND -- might be cool to visually indicate loading here -- + // maybe do animation? + status = APPLET_LOAD; + + // Create a class loader + // FIXME loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); + setClassLoader(); + + // Load the archives if present. + // REMIND - this probably should be done in a separate thread, + // or at least the additional archives (epll). + + String code = getCode(); + + // setup applet AppContext + // this must be called before loadJarFiles + setupAppletAppContext(); + + try { + loadJarFiles(applet3Loader); + applet = createApplet(applet3Loader); + } catch (ClassNotFoundException e) { + status = APPLET_ERROR; + showAppletStatus("notfound", code); + showAppletLog("notfound", code); + showAppletException(e); + return; + } catch (InstantiationException e) { + status = APPLET_ERROR; + showAppletStatus("nocreate", code); + showAppletLog("nocreate", code); + showAppletException(e); + return; + } catch (IllegalAccessException e) { + status = APPLET_ERROR; + showAppletStatus("noconstruct", code); + showAppletLog("noconstruct", code); + showAppletException(e); + // sbb -- I added a return here + return; + } catch (Exception e) { + status = APPLET_ERROR; + showAppletStatus("exception", e.getMessage()); + showAppletException(e); + return; + } catch (ThreadDeath e) { + status = APPLET_ERROR; + showAppletStatus("death"); + return; + } catch (Error e) { + status = APPLET_ERROR; + showAppletStatus("error", e.getMessage()); + showAppletException(e); + return; + } finally { + // notify that loading is no longer going on + dispatchAppletEvent(APPLET_LOADING_COMPLETED, null); + } + + // Fixed #4508194: NullPointerException thrown during + // quick page switch + // + if (applet != null) + { + // Stick it in the frame + showAppletStatus("loaded"); + } + } + + private Applet3 createApplet(final Applet3ClassLoader loader) throws ClassNotFoundException, + IllegalAccessException, IOException, InstantiationException, InterruptedException { + final String serName = getSerializedObject(); + String code = getCode(); + + if (code != null && serName != null) { + System.err.println(amh.getMessage("runloader.err")); +// return null; + throw new InstantiationException("Either \"code\" or \"object\" should be specified, but not both."); + } + if (code == null && serName == null) { + String msg = "nocode"; + status = APPLET_ERROR; + showAppletStatus(msg); + showAppletLog(msg); + } + if (code != null) { + applet = (Applet3)loader.loadCode(code).newInstance(); + doInit = true; + } else { + // serName is not null; + InputStream is = null; + ObjectInputStream ois = null; + try { + is = AccessController.doPrivileged(new PrivilegedAction<InputStream>() { + @Override + public InputStream run() { + return loader.getResourceAsStream(serName); + } } ); + handler.setContextClassLoader(loader); + ois = new Applet3ObjectInputStream(is, loader); + applet = (Applet3) ois.readObject(); + } finally { + if(null != ois) { + ois.close(); + } + if(null != is) { + is.close(); + } + } + doInit = false; // skip over the first init + /** + try (InputStream is = AccessController.doPrivileged( + (PrivilegedAction<InputStream>)() -> loader.getResourceAsStream(serName)); + ObjectInputStream ois = new AppletObjectInputStream(is, loader)) { + + applet = (Applet3) ois.readObject(); + doInit = false; // skip over the first init + } */ + } + + // Determine the JDK level that the applet targets. + // This is critical for enabling certain backward + // compatibility switch if an applet is a JDK 1.1 + // applet. [stanley.ho] + findAppletJDKLevel(applet); + + if (Thread.interrupted()) { + try { + status = APPLET_DISPOSE; // APPLET_ERROR? + applet = null; + // REMIND: This may not be exactly the right thing: the + // status is set by the stop button and not necessarily + // here. + showAppletStatus("death"); + } finally { + Thread.currentThread().interrupt(); // resignal interrupt + } + return null; + } + return applet; + } + + protected void loadJarFiles(Applet3ClassLoader loader) throws IOException, + InterruptedException { + // Load the archives if present. + // REMIND - this probably should be done in a separate thread, + // or at least the additional archives (epll). + String jarFiles = getJarFiles(); + + if (jarFiles != null) { + StringTokenizer st = new StringTokenizer(jarFiles, ",", false); + while(st.hasMoreTokens()) { + String tok = st.nextToken().trim(); + try { + loader.addJar(tok); + } catch (IllegalArgumentException e) { + // bad archive name + continue; + } + } + } + } + + /** + * Request that the loading of the applet be stopped. + */ + protected synchronized void stopLoading() { + // REMIND: fill in the body + if (loaderThread != null) { + //System.out.println("Interrupting applet loader thread: " + loaderThread); + loaderThread.interrupt(); + } else { + setLoadAbortRequest(); + } + } + + + protected synchronized boolean okToLoad() { + return !loadAbortRequest; + } + + protected synchronized void clearLoadAbortRequest() { + loadAbortRequest = false; + } + + protected synchronized void setLoadAbortRequest() { + loadAbortRequest = true; + } + + + private synchronized void setLoaderThread(Thread loaderThread) { + this.loaderThread = loaderThread; + } + + + // FIXME private EventQueue appEvtQ = null; + + /** + * Status line. Called by the AppletPanel to provide + * feedback on the Applet's state. + */ + protected final void showAppletStatus(String status) { + showStatus(amh.getMessage(status)); + } + + protected final void showAppletStatus(String status, Object arg) { + showStatus(amh.getMessage(status, arg)); + } + protected final void showAppletStatus(String status, Object arg1, Object arg2) { + showStatus(amh.getMessage(status, arg1, arg2)); + } + + /** + * Called by the AppletPanel to print to the log. + */ + protected void showAppletLog(String msg) { + System.out.println(amh.getMessage(msg)); + } + + protected void showAppletLog(String msg, Object arg) { + System.out.println(amh.getMessage(msg, arg)); + } + + /** + * Called by the AppletPanel to provide + * feedback when an exception has happened. + */ + protected void showAppletException(Throwable t) { + t.printStackTrace(); + } + + /** + * Get caching key for classloader cache + */ + public String getClassLoaderCacheKey() + { + /** + * Fixed #4501142: Classlaoder sharing policy doesn't + * take "archive" into account. This will be overridden + * by Java Plug-in. [stanleyh] + */ + return getCodeBase().toString(); + } + + /** + * The class loaders + */ + private static final HashMap<String, Applet3ClassLoader> classloaders = new HashMap<String, Applet3ClassLoader>(); + + /** + * Flush a class loader. + */ + public static synchronized void flushClassLoader(String key) { + classloaders.remove(key); + } + + /** + * Flush all class loaders. + */ + public static synchronized void flushClassLoaders() { + classloaders.clear(); + } + + /** + * FIXME: Hack - Until knowing which CL to use .. via {@link #getClassLoader(URL, String)}. + */ + private void setClassLoader() { + final ClassLoader _loader0 = getAppletClassLoader(); + if( null != _loader0 ) { + loader = _loader0; + applet3Loader = null; + } + final ClassLoader _loader = getClassLoader(getCodeBase(), getClassLoaderCacheKey()); + if( _loader instanceof Applet3ClassLoader ) { + applet3Loader = (Applet3ClassLoader)_loader; + } else { + applet3Loader = null; + } + loader = _loader; + } + + /** + * Implementation may provide it's own specific ClassLoader, otherwise null. + */ + public abstract ClassLoader getAppletClassLoader(); + + /** + * This method actually creates an AppletClassLoader. + * + * It can be override by subclasses (such as the Plug-in) + * to provide different classloaders. + */ + protected Applet3ClassLoader createClassLoader(final URL codebase) { + return new Applet3ClassLoader(codebase); + } + + /** + * Get a class loader. Create in a restricted context + */ + synchronized ClassLoader getClassLoader(final URL codebase, final String key) { + Applet3ClassLoader c = classloaders.get(key); + if (c == null) { + final AccessControlContext acc = getAccessControlContext(codebase); + c = AccessController.doPrivileged(new PrivilegedAction<Applet3ClassLoader>() { + @Override + public Applet3ClassLoader run() { + Applet3ClassLoader ac = createClassLoader(codebase); + /* Should the creation of the classloader be + * within the class synchronized block? Since + * this class is used by the plugin, take care + * to avoid deadlocks, or specialize + * AppletPanel within the plugin. It may take + * an arbitrary amount of time to create a + * class loader (involving getting Jar files + * etc.) and may block unrelated applets from + * finishing createAppletThread (due to the + * class synchronization). If + * createAppletThread does not finish quickly, + * the applet cannot process other messages, + * particularly messages such as destroy + * (which timeout when called from the browser). + */ + synchronized (getClass()) { + Applet3ClassLoader res = classloaders.get(key); + if (res == null) { + classloaders.put(key, ac); + return ac; + } else { + return res; + } + } + } + },acc); + } + return c; + } + + /** + * get the context for the AppletClassLoader we are creating. + * the context is granted permission to create the class loader, + * connnect to the codebase, and whatever else the policy grants + * to all codebases. + */ + private AccessControlContext getAccessControlContext(final URL codebase) { + + PermissionCollection perms = + AccessController.doPrivileged(new PrivilegedAction<PermissionCollection>() { + @Override + public PermissionCollection run() { + Policy p = java.security.Policy.getPolicy(); + if (p != null) { + return p.getPermissions(new CodeSource(null, + (java.security.cert.Certificate[]) null)); + } else { + return null; + } + } + }); + + if (perms == null) + perms = new Permissions(); + + //XXX: this is needed to be able to create the classloader itself! + + perms.add(SecurityConstants.CREATE_CLASSLOADER_PERMISSION); + + Permission p; + java.net.URLConnection urlConnection = null; + try { + urlConnection = codebase.openConnection(); + p = urlConnection.getPermission(); + } catch (java.io.IOException ioe) { + p = null; + } + + if (p != null) + perms.add(p); + + if (p instanceof FilePermission) { + + String path = p.getName(); + + int endIndex = path.lastIndexOf(File.separatorChar); + + if (endIndex != -1) { + path = path.substring(0, endIndex+1); + + if (path.endsWith(File.separator)) { + path += "-"; + } + perms.add(new FilePermission(path, + SecurityConstants.FILE_READ_ACTION)); + } + } else { + URL locUrl = codebase; + if (urlConnection instanceof JarURLConnection) { + locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); + } + String host = locUrl.getHost(); + if (host != null && (host.length() > 0)) + perms.add(new SocketPermission(host, + SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION)); + } + + ProtectionDomain domain = + new ProtectionDomain(new CodeSource(codebase, + (java.security.cert.Certificate[]) null), perms); + AccessControlContext acc = + new AccessControlContext(new ProtectionDomain[] { domain }); + + return acc; + } + + public Thread getAppletHandlerThread() { + return handler; + } + + public int getAppletWidth() { + return currentAppletSize[0]; + } + + public int getAppletHeight() { + return currentAppletSize[1]; + } + + public static void changeFrameAppContext(NativeWindowDownstream nw, App3Context newAppContext) // FIXME + { + // Fixed #4754451: Applet can have methods running on main + // thread event queue. + // + // The cause of this bug is that the frame of the applet + // is created in main thread group. Thus, when certain + // AWT/Swing events are generated, the events will be + // dispatched through the wrong event dispatch thread. + // + // To fix this, we rearrange the AppContext with the frame, + // so the proper event queue will be looked up. + // + // Swing also maintains a Frame list for the AppContext, + // so we will have to rearrange it as well. + + // Check if frame's AppContext has already been set properly + App3Context oldAppContext = App3Context.getAppContext(nw); + + if (oldAppContext == newAppContext) { + return; + } + + // Synchronization on Window.class is needed for locking the + // critical section of the window list in AppContext. + synchronized (NativeWindowDownstream.class) + { + WeakReference weakRef = null; + // Remove frame from the Window list in wrong AppContext + if( null != oldAppContext ) { + // Lookup current frame's AppContext + Vector<WeakReference<NativeWindowDownstream>> windowList = (Vector<WeakReference<NativeWindowDownstream>>)oldAppContext.get(NativeWindowDownstream.class); + if (windowList != null) { + for (WeakReference ref : windowList) { + if (ref.get() == nw) { + weakRef = ref; + break; + } + } + // Remove frame from wrong AppContext + if (weakRef != null) + windowList.remove(weakRef); + } + } else { + weakRef = new WeakReference<NativeWindowDownstream>(nw); + } + + // Put the frame into the applet's AppContext map + // FIXME SunToolkit.insertTargetMapping(frame, newAppContext); + + // Insert frame into the Window list in the applet's AppContext map + { + Vector<WeakReference<NativeWindowDownstream>> windowList = (Vector)newAppContext.get(NativeWindowDownstream.class); + if (windowList == null) { + windowList = new Vector<WeakReference<NativeWindowDownstream>>(); + newAppContext.put(NativeWindowDownstream.class, windowList); + } + // use the same weakRef here as it is used elsewhere + windowList.add(weakRef); + } + } + } + + // Flag to indicate if applet is targeted for JDK 1.1. + private boolean jdk11Applet = false; + + // Flag to indicate if applet is targeted for JDK 1.2. + private boolean jdk12Applet = false; + + /** + * Determine JDK level of an applet. + */ + private void findAppletJDKLevel(Applet3 applet) + { + // To determine the JDK level of an applet, the + // most reliable way is to check the major version + // of the applet class file. + + // synchronized on applet class object, so calling from + // different instances of the same applet will be + // serialized. + Class<?> appletClass = applet.getClass(); + + synchronized(appletClass) { + // Determine if the JDK level of an applet has been + // checked before. + Boolean jdk11Target = applet3Loader.isJDK11Target(appletClass); + Boolean jdk12Target = applet3Loader.isJDK12Target(appletClass); + + // if applet JDK level has been checked before, retrieve + // value and return. + if (jdk11Target != null || jdk12Target != null) { + jdk11Applet = (jdk11Target == null) ? false : jdk11Target.booleanValue(); + jdk12Applet = (jdk12Target == null) ? false : jdk12Target.booleanValue(); + return; + } + + String name = appletClass.getName(); + + // first convert any '.' to '/' + name = name.replace('.', '/'); + + // append .class + final String resourceName = name + ".class"; + + byte[] classHeader = new byte[8]; + + InputStream is = null; + try { + is = AccessController.doPrivileged(new PrivilegedAction<InputStream>() { + @Override + public InputStream run() { + return loader.getResourceAsStream(resourceName); + } } ); + // Read the first 8 bytes of the class file + int byteRead = is.read(classHeader, 0, 8); + + // return if the header is not read in entirely + // for some reasons. + if (byteRead != 8) + return; + } catch (IOException e) { + return; + } finally { + if(null != is) { + try { + is.close(); + } catch (IOException e) { } + } + } + + // Check major version in class file header + int major_version = readShort(classHeader, 6); + + // Major version in class file is as follows: + // 45 - JDK 1.1 + // 46 - JDK 1.2 + // 47 - JDK 1.3 + // 48 - JDK 1.4 + // 49 - JDK 1.5 + if (major_version < 46) + jdk11Applet = true; + else if (major_version == 46) + jdk12Applet = true; + + // Store applet JDK level in AppContext for later lookup, + // e.g. page switch. + applet3Loader.setJDK11Target(appletClass, jdk11Applet); + applet3Loader.setJDK12Target(appletClass, jdk12Applet); + } + } + + /** + * Return true if applet is targeted to JDK 1.1. + */ + protected boolean isJDK11Applet() { + return jdk11Applet; + } + + /** + * Return true if applet is targeted to JDK1.2. + */ + protected boolean isJDK12Applet() { + return jdk12Applet; + } + + /** + * Read short from byte array. + */ + private int readShort(byte[] b, int off) { + int hi = readByte(b[off]); + int lo = readByte(b[off + 1]); + return (hi << 8) | lo; + } + + private int readByte(byte b) { + return (b) & 0xFF; + } + + + private static Applet3MessageHandler amh = new Applet3MessageHandler("appletpanel"); +} diff --git a/netx/jogamp/plugin/jnlp/App3Launcher.java b/netx/jogamp/plugin/jnlp/App3Launcher.java new file mode 100644 index 0000000..1d0b9c6 --- /dev/null +++ b/netx/jogamp/plugin/jnlp/App3Launcher.java @@ -0,0 +1,882 @@ +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package jogamp.plugin.jnlp; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jogamp.applet.App3Context; +import jogamp.plugin.jnlp.runtime.Applet3Instance; + +import com.jogamp.plugin.applet.Applet3; + +import net.sourceforge.jnlp.AppletDesc; +import net.sourceforge.jnlp.ApplicationDesc; +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.LaunchHandler; +import net.sourceforge.jnlp.ParserSettings; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.PropertyDesc; +import net.sourceforge.jnlp.ResourcesDesc; +import net.sourceforge.jnlp.StreamEater; +import net.sourceforge.jnlp.util.JarFile; +import net.sourceforge.jnlp.cache.CacheUtil; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.runtime.JNLPClassLoader; +import net.sourceforge.jnlp.services.InstanceExistsException; +import net.sourceforge.jnlp.services.ServiceUtil; +import net.sourceforge.jnlp.splashscreen.SplashUtils; +import net.sourceforge.jnlp.util.logging.OutputController; + +/** + * Launches JNLPFiles either in the foreground or background.<p> + * + * An optional LaunchHandler can be specified that is notified of + * warning and error condition while launching and that indicates + * whether a launch may proceed after a warning has occurred. If + * specified, the LaunchHandler is notified regardless of whether + * the file is launched in the foreground or background.<p> + * + * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.22 $ + */ +public class App3Launcher { + + // defines class Launcher.BgRunner, Launcher.TgThread + + /** shared thread group */ + /*package*/static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup")); + + public static ThreadGroup getMainGroup() { return mainGroup; } + + /** the handler */ + private LaunchHandler handler = null; + + /** the update policy */ + private UpdatePolicy updatePolicy = JNLPRuntime.getDefaultUpdatePolicy(); + + /** whether to create an AppContext (if possible) */ + private boolean context = true; + + /** If the application should call JNLPRuntime.exit on fatal errors */ + private boolean exitOnFailure = true; + + private ParserSettings parserSettings = new ParserSettings(); + + private Map<String, String[]> extra = null; + + /** + * Create a launcher with the runtime's default update policy + * and launch handler. + */ + public App3Launcher() { + this(null, null); + + if (handler == null) { + handler = JNLPRuntime.getDefaultLaunchHandler(); + } + } + + /** + * Create a launcher with the runtime's default update policy + * and launch handler. + * + * @param exitOnFailure Exit if there is an error (usually default, but false when being used from the plugin) + */ + public App3Launcher(boolean exitOnFailure) { + this(null, null); + + if (handler == null) { + handler = JNLPRuntime.getDefaultLaunchHandler(); + } + + this.exitOnFailure = exitOnFailure; + } + + /** + * Create a launcher with the specified handler and the + * runtime's default update policy. + * + * @param handler the handler to use or null for no handler. + */ + public App3Launcher(LaunchHandler handler) { + this(handler, null); + } + + /** + * Create a launcher with an optional handler using the + * specified update policy and launch handler. + * + * @param handler the handler to use or null for no handler. + * @param policy the update policy to use or null for default policy. + */ + public App3Launcher(LaunchHandler handler, UpdatePolicy policy) { + if (policy == null) + policy = JNLPRuntime.getDefaultUpdatePolicy(); + + this.handler = handler; + this.updatePolicy = policy; + + } + + /** + * Sets the update policy used by launched applications. + */ + public void setUpdatePolicy(UpdatePolicy policy) { + if (policy == null) { + throw new IllegalArgumentException(R("LNullUpdatePolicy")); + } + + this.updatePolicy = policy; + } + + /** + * Returns the update policy used when launching applications. + */ + public UpdatePolicy getUpdatePolicy() { + return updatePolicy; + } + + /** + * Sets whether to launch the application in a new AppContext + * (a separate event queue, look and feel, etc). If the + * sun.awt.SunToolkit class is not present then this method + * has no effect. The default value is true. + */ + public void setCreateAppContext(boolean context) { + this.context = context; + } + + /** + * Returns whether applications are launched in their own + * AppContext. + */ + public boolean isCreateAppContext() { + return this.context; + } + + /** + * Set the parser settings to use when the Launcher initiates parsing of + * a JNLP file. + * @param settings + */ + public void setParserSettings(ParserSettings settings) { + parserSettings = settings; + } + + /** + * Set a map to use when trying to extract extra information, including + * arguments, properties and parameters, to be merged into the main JNLP + * @param input a map containing extra information to add to the main JNLP. + * the values for keys "arguments", "parameters", and "properties" are + * used. + */ + public void setInformationToMerge(Map<String, String[]> input) { + this.extra = input; + } + + /** + * Launches a JNLP file by calling the launch method for the + * appropriate file type. The application will be started in + * a new window. + * + * @param file the JNLP file to launch + * @return the application instance + * @throws LaunchException if an error occurred while launching (also sent to handler) + */ + public ApplicationInstance launch(JNLPFile file) throws LaunchException { + TgThread tg; + + mergeExtraInformation(file, extra); + + JNLPRuntime.markNetxRunning(); + + //First checks whether offline-allowed tag is specified inside the jnlp + //file. + if (!file.getInformation().isOfflineAllowed()) { + try { + //Checks the offline/online status of the system. + //If system is offline do not launch. + InetAddress.getByName(file.getSourceLocation().getHost()); + + } catch (UnknownHostException ue) { + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "File cannot be launched because offline-allowed tag not specified and system currently offline."); + return null; + } catch (Exception e) { + OutputController.getLogger().log(e); + } + } + + if (file instanceof PluginBridge) { + tg = new TgThread(file, true); + } + else { + tg = new TgThread(file); + } + + tg.start(); + + try { + tg.join(); + } catch (InterruptedException ex) { + //By default, null is thrown here, and the message dialog is shown. + throw launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCSystem"), R("LThreadInterrupted"), R("LThreadInterruptedInfo"))); + } + + if (tg.getException() != null) { + throw tg.getException(); + } // passed to handler when first created + + if (handler != null) { + // FIXME handler.launchCompleted(tg.getApplication()); + } + + return tg.getApplication(); + } + + + /** + * Launches a JNLP file by calling the launch method for the + * appropriate file type. + * + * @param location the URL of the JNLP file to launch + * location to get the pristine version + * @throws LaunchException if there was an exception + * @return the application instance + */ + public ApplicationInstance launch(URL location) throws LaunchException { + return launch(fromUrl(location)); + } + + /** + * Merges extra information into the jnlp file + * + * @param file the JNLPFile + * @param extra extra information to merge into the JNLP file + * @throws LaunchException if an exception occurs while extracting + * extra information + */ + private void mergeExtraInformation(JNLPFile file, Map<String, String[]> extra) throws LaunchException { + if (extra == null) { + return; + } + + String[] properties = extra.get("properties"); + if (properties != null) { + addProperties(file, properties); + } + + String[] arguments = extra.get("arguments"); + if (arguments != null && file.isApplication()) { + addArguments(file, arguments); + } + + String[] parameters = extra.get("parameters"); + if (parameters != null && file.isApplet()) { + addParameters(file, parameters); + } + } + + /** + * Add the properties to the JNLP file. + * @throws LaunchException if an exception occurs while extracting + * extra information + */ + private void addProperties(JNLPFile file, String[] props) throws LaunchException { + ResourcesDesc resources = file.getResources(); + + for (int i = 0; i < props.length; i++) { + // allows empty property, not sure about validity of that. + int equals = props[i].indexOf("="); + if (equals == -1) { + throw launchError(new LaunchException(R("BBadProp", props[i]))); + } + + String key = props[i].substring(0, equals); + String value = props[i].substring(equals + 1, props[i].length()); + + resources.addResource(new PropertyDesc(key, value)); + } + } + + /** + * Add the params to the JNLP file; only call if file is + * actually an applet file. + * @throws LaunchException if an exception occurs while extracting + * extra information + */ + private void addParameters(JNLPFile file, String[] params) throws LaunchException { + AppletDesc applet = file.getApplet(); + + for (int i = 0; i < params.length; i++) { + // allows empty param, not sure about validity of that. + int equals = params[i].indexOf("="); + if (equals == -1) { + throw launchError(new LaunchException(R("BBadParam", params[i]))); + } + + String name = params[i].substring(0, equals); + String value = params[i].substring(equals + 1, params[i].length()); + + applet.addParameter(name, value); + } + } + + /** + * Add the arguments to the JNLP file; only call if file is + * actually an application (not installer). + */ + private void addArguments(JNLPFile file, String[] args) { + ApplicationDesc app = file.getApplication(); + + for (int i = 0; i < args.length; i++) { + app.addArgument(args[i]); + } + } + + + /** + * Launches the JNLP file in a new JVM instance. The launched + * application's output is sent to the system out and it's + * standard input channel is closed. + * + * @param vmArgs the arguments to pass to the new JVM. Can be empty but + * must not be null. + * @param file the JNLP file to launch + * @param javawsArgs the arguments to pass to the javaws command. Can be + * an empty list but must not be null. + * @throws LaunchException if there was an exception + */ + public void launchExternal(List<String> vmArgs, JNLPFile file, List<String> javawsArgs) throws LaunchException { + List<String> updatedArgs = new LinkedList<String>(javawsArgs); + + if (file.getFileLocation() != null) { + updatedArgs.add(file.getFileLocation().toString()); + } + else if (file.getSourceLocation() != null) { + updatedArgs.add(file.getFileLocation().toString()); + } + else { + launchError(new LaunchException(file, null, R("LSFatal"), R("LCExternalLaunch"), R("LNullLocation"), R("LNullLocationInfo"))); + } + + launchExternal(vmArgs, updatedArgs); + + } + + /** + * Launches the JNLP file in a new JVM instance. The launched + * application's output is sent to the system out and it's + * standard input channel is closed. + * + * @param url the URL of the JNLP file to launch + * @throws LaunchException if there was an exception + */ + public void launchExternal(URL url) throws LaunchException { + List<String> javawsArgs = new LinkedList<String>(); + javawsArgs.add(url.toString()); + launchExternal(new LinkedList<String>(), javawsArgs); + } + + /** + * Launches the JNLP file at the specified location in a new JVM + * instance. The launched application's output is sent to the + * system out and it's standard input channel is closed. + * @param vmArgs the arguments to pass to the jvm + * @param javawsArgs the arguments to pass to javaws (aka Netx) + * @throws LaunchException if there was an exception + */ + public void launchExternal(List<String> vmArgs, List<String> javawsArgs) throws LaunchException { + try { + + List<String> commands = new LinkedList<String>(); + + // this property is set by the javaws launcher to point to the javaws binary + String pathToWebstartBinary = System.getProperty("icedtea-web.bin.location"); + commands.add(pathToWebstartBinary); + // use -Jargument format to pass arguments to the JVM through the launcher + for (String arg : vmArgs) { + commands.add("-J" + arg); + } + commands.addAll(javawsArgs); + + String[] command = commands.toArray(new String[] {}); + + Process p = Runtime.getRuntime().exec(command); + new StreamEater(p.getErrorStream()).start(); + new StreamEater(p.getInputStream()).start(); + p.getOutputStream().close(); + + } catch (NullPointerException ex) { + throw launchError(new LaunchException(null, null, R("LSFatal"), R("LCExternalLaunch"), R("LNetxJarMissing"), R("LNetxJarMissingInfo"))); + } catch (Exception ex) { + throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCExternalLaunch"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); + } + } + + /** + * Returns the JNLPFile for the URL, with error handling. + */ + private JNLPFile fromUrl(URL location) throws LaunchException { + try { + JNLPFile file = new JNLPFile(location, parserSettings); + + boolean isLocal = false; + boolean haveHref = false; + if ("file".equalsIgnoreCase(location.getProtocol()) && new File(location.getFile()).exists()) { + isLocal = true; + } + if (file.getSourceLocation() != null) { + haveHref = true; + } + + if (isLocal && haveHref) { + file = new JNLPFile(file.getSourceLocation(), parserSettings); + } + return file; + } catch (Exception ex) { + if (ex instanceof LaunchException) { + throw (LaunchException) ex; // already sent to handler when first thrown + } else { + // IO and Parse + throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCReadError"), R("LCantRead"), R("LCantReadInfo"))); + } + } + } + + /** + * Launches a JNLP application. This method should be called + * from a thread in the application's thread group. + */ + protected ApplicationInstance launchApplication(JNLPFile file) throws LaunchException { + if (!file.isApplication()) { + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplication"), R("LNotApplicationInfo"))); + } + + try { + + try { + ServiceUtil.checkExistingSingleInstance(file); + } catch (InstanceExistsException e) { + OutputController.getLogger().log("Single instance application is already running."); + return null; + } + + if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) { + List<String> netxArguments = new LinkedList<String>(); + netxArguments.add("-Xnofork"); + netxArguments.addAll(JNLPRuntime.getInitialArguments()); + launchExternal(file.getNewVMArgs(), netxArguments); + return null; + } + + handler.launchInitialized(file); + + ApplicationInstance app = createApplication(file); + app.initialize(); + + String mainName = file.getApplication().getMainClass(); + + // When the application-desc field is empty, we should take a + // look at the main jar for the main class. + if (mainName == null) { + JARDesc mainJarDesc = file.getResources().getMainJAR(); + File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null); + if (f != null) { + final JarFile mainJar = new JarFile(f); + try { + mainName = mainJar.getManifest().getMainAttributes().getValue("Main-Class"); + } finally { + mainJar.close(); + } + } + } + + if (mainName == null) { + throw launchError(new LaunchException(file, null, + R("LSFatal"), R("LCClient"), R("LCantDetermineMainClass"), + R("LCantDetermineMainClassInfo"))); + } + + Class<?> mainClass = app.getClassLoader().loadClass(mainName); + + Method main = mainClass.getMethod("main", new Class<?>[] { String[].class }); + String args[] = file.getApplication().getArguments(); + + setContextClassLoaderForAllThreads(app.getThreadGroup(), app.getClassLoader()); + + handler.launchStarting(app); + + main.setAccessible(true); + + OutputController.getLogger().log("Invoking main() with args: " + Arrays.toString(args)); + main.invoke(null, new Object[] { args }); + + return app; + } catch (LaunchException lex) { + throw launchError(lex); + } catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); + } + } + + /** + * Set the classloader as the context classloader for all threads in + * the given threadgroup. This is required to make some applications + * work. For example, an application that provides a custom Swing LnF + * may ask the swing thread to load resources from their JNLP, which + * would only work if the Swing thread knows about the JNLPClassLoader. + * + * @param tg The threadgroup for which the context classloader should be set + * @param classLoader the classloader to set as the context classloader + */ + private void setContextClassLoaderForAllThreads(ThreadGroup tg, ClassLoader classLoader) { + + /* be prepared for change in thread size */ + int threadCountGuess = tg.activeCount(); + Thread[] threads; + do { + threadCountGuess = threadCountGuess * 2; + threads = new Thread[threadCountGuess]; + tg.enumerate(threads, true); + } while (threads[threadCountGuess - 1] != null); + + for (Thread thread : threads) { + if (thread != null) { + OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Setting " + classLoader + " as the classloader for thread " + thread.getName()); + thread.setContextClassLoader(classLoader); + } + } + + } + + /** + * Launches a JNLP applet. This method should be called from a + * thread in the application's thread group.<p> + * + * The enableCodeBase parameter adds the applet's codebase to + * the locations searched for resources and classes. This can + * slow down the applet loading but allows browser-style applets + * that don't use JAR files exclusively to be run from a applet + * JNLP file. If the applet JNLP file does not specify any + * resources then the code base will be enabled regardless of + * the specified value.<p> + * + * @param file the JNLP file + * @param enableCodeBase whether to add the codebase URL to the classloader + */ + protected ApplicationInstance launchApplet(JNLPFile file, boolean enableCodeBase) throws LaunchException { + if (!file.isApplet()) { + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); + } + + if (handler != null) { + handler.launchInitialized(file); + } + + Applet3Instance applet = null; + try { + ServiceUtil.checkExistingSingleInstance(file); + applet = createApplet(file, enableCodeBase); + applet.initialize(); + applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance + return applet; + } catch (InstanceExistsException ieex) { + OutputController.getLogger().log("Single instance applet is already running."); + throw launchError(new LaunchException(file, ieex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LSingleInstanceExists")), applet); + } catch (LaunchException lex) { + throw launchError(lex, applet); + } catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")), applet); + }finally{ + if (handler != null) { + handler.launchStarting(applet); + } + } + } + + /** + * Gets an ApplicationInstance, but does not launch the applet. + */ + protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase) throws LaunchException { + if (!file.isApplet()) { + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); + } + Applet3Instance applet = null; + try { + ServiceUtil.checkExistingSingleInstance(file); + applet = createApplet(file, enableCodeBase); + applet.initialize(); + return applet; + + } catch (InstanceExistsException ieex) { + OutputController.getLogger().log("Single instance applet is already running."); + throw launchError(new LaunchException(file, ieex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LSingleInstanceExists")), applet); + } catch (LaunchException lex) { + throw launchError(lex, applet); + } catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")), applet); + } + } + + /** + * Launches a JNLP installer. This method should be called from + * a thread in the application's thread group. + */ + protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException { + // TODO Check for an existing single instance once implemented. + // ServiceUtil.checkExistingSingleInstance(file); + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo"))); + } + + /** + * Create an AppletInstance. + * + * @param enableCodeBase whether to add the code base URL to the classloader + */ + //FIXME - when multiple applets are on one page, this method is visited simultaneously + //and then appelts creates in little bit strange manner. This issue is visible with + //randomly showing/notshowing spalshscreens. + //See also PluginAppletViewer.framePanel + protected Applet3Instance createApplet(JNLPFile file, boolean enableCodeBase) throws LaunchException { + Applet3Instance appletInstance = null; + try { + JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); + + if (enableCodeBase) { + loader.enableCodeBase(); + } else if (file.getResources().getJARs().length == 0) { + throw new ClassNotFoundException("Can't do a codebase look up and there are no jars. Failing sooner rather than later"); + } + + ThreadGroup group = Thread.currentThread().getThreadGroup(); + + // appletInstance is needed by ServiceManager when looking up + // services. This could potentially be done in applet constructor + // so initialize appletInstance before creating applet. + appletInstance = new Applet3Instance(file, group, loader, null); + + loader.setApplication(appletInstance); + + // Initialize applet now that ServiceManager has access to its + // appletInstance. + String appletName = file.getApplet().getMainClass(); + Class<?> appletClass = loader.loadClass(appletName); + Applet3 applet = (Applet3) appletClass.newInstance(); + // Finish setting up appletInstance. + appletInstance.setApplet(applet); + appletInstance.getAppletEnvironment().setApplet(applet); + + setContextClassLoaderForAllThreads(appletInstance.getThreadGroup(), appletInstance.getClassLoader()); + + return appletInstance; + } catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo")), appletInstance); + } + } + + /** + * Creates an Applet object from a JNLPFile. This is mainly to be used with + * gcjwebplugin. + * @param file the PluginBridge to be used. + * @param enableCodeBase whether to add the code base URL to the classloader. + */ + protected Applet3 createAppletObject(JNLPFile file, boolean enableCodeBase) throws LaunchException { + try { + JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); + + if (enableCodeBase) { + loader.enableCodeBase(); + } else if (file.getResources().getJARs().length == 0) { + throw new ClassNotFoundException("Can't do a codebase look up and there are no jars. Failing sooner rather than later"); + } + + String appletName = file.getApplet().getMainClass(); + Class<?> appletClass = loader.loadClass(appletName); + Applet3 applet = (Applet3) appletClass.newInstance(); + + return applet; + } catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo"))); + } + } + + /** + * Creates an Application. + */ + protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException { + try { + JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); + ThreadGroup group = Thread.currentThread().getThreadGroup(); + + ApplicationInstance app = new ApplicationInstance(file, group, loader, App3Context.getAppContext()); + loader.setApplication(app); + + return app; + } catch (Exception ex) { + throw new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplication"), R("LInitApplicationInfo")); + } + } + + /** + * Create a thread group for the JNLP file. + * + * Note: if the JNLPFile is an applet (ie it is a subclass of PluginBridge) + * then this method simply returns the existing ThreadGroup. The applet + * ThreadGroup has to be created at an earlier point in the applet code. + */ + protected ThreadGroup createThreadGroup(JNLPFile file) { + final ThreadGroup tg; + + if (file instanceof PluginBridge) { + tg = Thread.currentThread().getThreadGroup(); + } else { + tg = new ThreadGroup(mainGroup, file.getTitle()); + } + + return tg; + } + + /** + * Send n launch error to the handler, if set, and also to the + * caller. + */ + private LaunchException launchError(LaunchException ex) { + return launchError(ex, null); + } + + private LaunchException launchError(LaunchException ex, Applet3Instance applet) { + if (applet != null) { + SplashUtils.showErrorCaught(ex, applet); + } + if (handler != null) { + handler.launchError(ex); + } + + return ex; + } + + /** + * Send a launch error to the handler, if set, and to the + * caller only if the handler indicated that the launch should + * continue despite the warning. + * + * @return an exception to throw if the launch should be aborted, or null otherwise + */ + private LaunchException launchWarning(LaunchException ex) { + if (handler != null) { + if (!handler.launchWarning(ex)) + // no need to destroy the app b/c it hasn't started + return ex; + } // chose to abort + + return null; // chose to continue, or no handler + } + + /** + * This runnable is used to call the appropriate launch method + * for the application, applet, or installer in its thread group. + */ + private class TgThread extends Thread { // ThreadGroupThread + private JNLPFile file; + private ApplicationInstance application; + private LaunchException exception; + private boolean isPlugin = false; + + TgThread(JNLPFile file) { + super(createThreadGroup(file), file.getTitle()); + + this.file = file; + } + + TgThread(JNLPFile file, boolean isPlugin) { + super(createThreadGroup(file), file.getTitle()); + this.file = file; + this.isPlugin = isPlugin; + } + + @Override + public void run() { + try { + // Do not create new AppContext if we're using NetX and icedteaplugin. + // The plugin needs an AppContext too, but it has to be created earlier. + /** FIXME: Non plugin context + if (context && !isPlugin) { + SunToolkit.createNewAppContext(); + } + doPerApplicationAppContextHacks(); + */ + + if (isPlugin) { + // Do not display download indicators if we're using gcjwebplugin. + JNLPRuntime.setDefaultDownloadIndicator(null); + application = getApplet(file, ((PluginBridge)file).codeBaseLookup()); + } else { + if (file.isApplication()) { + application = launchApplication(file); + } + else if (file.isApplet()) { + application = launchApplet(file, true); + } // enable applet code base + else if (file.isInstaller()) { + application = launchInstaller(file); + } + else { + throw launchError(new LaunchException(file, null, + R("LSFatal"), R("LCClient"), R("LNotLaunchable"), + R("LNotLaunchableInfo"))); + } + } + } catch (LaunchException ex) { + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, ex); + exception = ex; + // Exit if we can't launch the application. + if (exitOnFailure) { + JNLPRuntime.exit(1); + } + } catch (Throwable ex) { + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, ex); + throw new RuntimeException(ex); + } + } + + public LaunchException getException() { + return exception; + } + + public ApplicationInstance getApplication() { + return application; + } + + }; + + + +} diff --git a/netx/jogamp/plugin/jnlp/NetxApplet3Panel.java b/netx/jogamp/plugin/jnlp/NetxApplet3Panel.java new file mode 100644 index 0000000..875dc5a --- /dev/null +++ b/netx/jogamp/plugin/jnlp/NetxApplet3Panel.java @@ -0,0 +1,230 @@ +/* + * Copyright 2012 Red Hat, Inc. + * This file is part of IcedTea, http://icedtea.classpath.org + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +package jogamp.plugin.jnlp; + +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import jogamp.applet.App3Context; +import jogamp.applet.Applet3Panel; +import jogamp.plugin.jnlp.runtime.Applet3Instance; +import net.sourceforge.jnlp.Launcher; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.PluginParameters; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.splashscreen.SplashController; +import net.sourceforge.jnlp.splashscreen.SplashPanel; +import net.sourceforge.jnlp.splashscreen.SplashUtils; +import net.sourceforge.jnlp.util.logging.OutputController; + +import com.jogamp.plugin.applet.Applet3Context; + +/** + * This panel calls into netx to run an applet, and pipes the display + * into a panel from the icedtea-web browser plugin. + * + * @author Francis Kung <[email protected]> + */ +public class NetxApplet3Panel extends Applet3Panel implements SplashController { + private final PluginParameters pluginParameters; + private PluginBridge bridge = null; + private Applet3Instance appInst = null; + private SplashController splashController; + /** The plugin .. */ + private Applet3Context upstreamContext = null; + private volatile boolean initialized; + + // We use this so that we can create exactly one thread group + // for all panels with the same uKey. + private static final Map<String, ThreadGroup> uKeyToTG = + new HashMap<String, ThreadGroup>(); + private static final Object TGMapMutex = new Object(); + + // This map is actually a set (unfortunately there is no ConcurrentSet + // in java.util.concurrent). If KEY is in this map, then we know that + // an app context has been created for the panel that has uKey.equals(KEY), + // so we avoid creating it a second time for panels with the same uKey. + // Because it's a set, only the keys matter. However, we can't insert + // null values in because if we did, we couldn't use null checks to see + // if a key was absent before a putIfAbsent. + private static final ConcurrentMap<String, Boolean> appContextCreated = + new ConcurrentHashMap<String, Boolean>(); + + public NetxApplet3Panel(long nativeWindowHandle, int width, int height, URL documentURL, PluginParameters params) { + super(nativeWindowHandle, width, height, documentURL, params.getUnderlyingHashtable()); + + this.pluginParameters = params; + this.initialized = false; + + String uniqueKey = params.getUniqueKey(getCodeBase()); + synchronized(TGMapMutex) { + if (!uKeyToTG.containsKey(uniqueKey)) { + ThreadGroup tg = new ThreadGroup(Launcher.getMainGroup(), this.documentURL.toString()); + uKeyToTG.put(uniqueKey, tg); + } + } + } + + @Override + protected void showAppletException(Throwable t) { + /* + * Log any exceptions thrown while loading, initializing, starting, + * and stopping the applet. + */ + OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, t); //new logger + super.showAppletException(t); + } + + //Overriding to use Netx classloader. You might need to relax visibility + //in sun.applet.AppletPanel for runLoader(). + @Override + protected void runLoader() { + + try { + bridge = new PluginBridge(baseURL, + getDocumentBase(), + getJarFiles(), + getCode(), + getWidth(), + getHeight(), + pluginParameters); + + doInit = true; + dispatchAppletEvent(APPLET_LOADING, null); + status = APPLET_LOAD; + + App3Launcher l = new App3Launcher(false); + + // May throw LaunchException: + appInst = (Applet3Instance) l.launch(bridge); + applet = appInst.getApplet(); + + if (applet != null) { + // Stick it in the frame + showAppletStatus("loaded"); + } + } catch (Exception e) { + status = APPLET_ERROR; + OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); + replaceSplash(SplashUtils.getErrorSplashScreen(getWidth(), getHeight(), e)); + } finally { + // PR1157: This needs to occur even in the case of an exception + // so that the applet's event listeners are signaled. + // Once PluginAppletViewer.AppletEventListener is signaled PluginAppletViewer can properly stop waiting + // in PluginAppletViewer.waitForAppletInit + this.initialized = true; + dispatchAppletEvent(APPLET_LOADING_COMPLETED, null); + } + } + + /** + * Creates a new Thread (in a new applet-specific ThreadGroup) for running + * the applet + */ + // Reminder: Relax visibility in sun.applet.AppletPanel + @Override + public synchronized void createAppletThread() { + // initialize JNLPRuntime in the main threadgroup + synchronized (JNLPRuntime.initMutex) { + //The custom NetX Policy and SecurityManager are set here. + if (!JNLPRuntime.isInitialized()) { + OutputController.getLogger().log("initializing JNLPRuntime..."); + + JNLPRuntime.initialize(false); + } else { + OutputController.getLogger().log("JNLPRuntime already initialized"); + } + } + + handler = new Thread(getThreadGroup(), this, "NetxPanelThread@" + this.documentURL); + handler.start(); + } + + public final void setAppletContext(Applet3Context ctx) { + upstreamContext = ctx; + } + + @Override + protected final Applet3Context getAppletContext() { + if( null != upstreamContext ) { + return upstreamContext; + } else { + return appInst.getAppletEnvironment(); + } + } + + @Override + public ClassLoader getAppletClassLoader() { + return appInst.getClassLoader(); + } + + public boolean isInitialized() { + return initialized; + } + + public ThreadGroup getThreadGroup() { + synchronized(TGMapMutex) { + return uKeyToTG.get(pluginParameters.getUniqueKey(getCodeBase())); + } + } + + public void createNewAppContext() { + if (Thread.currentThread().getThreadGroup() != getThreadGroup()) { + throw new RuntimeException("createNewAppContext called from the wrong thread."); + } + // only create a new context if one hasn't already been created for the + // applets with this unique key. + if (null == appContextCreated.putIfAbsent(pluginParameters.getUniqueKey(getCodeBase()), Boolean.TRUE)) { + App3Context.createAppContext(); // FIXME: Really ? + // SunToolkit.createNewAppContext(); + } + } + + public void setAppletViewerFrame(SplashController framePanel) { + splashController=framePanel; + } + + @Override + public void removeSplash() { + splashController.removeSplash(); + } + + @Override + public void replaceSplash(SplashPanel r) { + splashController.replaceSplash(r); + } + + @Override + public int getSplashWidth() { + return splashController.getSplashWidth(); + } + + @Override + public int getSplashHeigth() { + return splashController.getSplashHeigth(); + } + +} diff --git a/netx/jogamp/plugin/jnlp/runtime/Applet3Environment.java b/netx/jogamp/plugin/jnlp/runtime/Applet3Environment.java new file mode 100644 index 0000000..66df33a --- /dev/null +++ b/netx/jogamp/plugin/jnlp/runtime/Applet3Environment.java @@ -0,0 +1,273 @@ +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package jogamp.plugin.jnlp.runtime; + +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.splashscreen.SplashController; +import net.sourceforge.jnlp.util.logging.OutputController; + +import com.jogamp.plugin.applet.Applet3; +import com.jogamp.plugin.applet.Applet3Context; + +/** + * The applet environment including stub, context, and frame. The + * default environment puts the applet in a non-resiable frame; + * this can be changed by obtaining the frame and setting it + * resizable. + * + * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.12 $ + */ +public class Applet3Environment implements Applet3Context { + + /** the JNLP file */ + private JNLPFile file; + + /** the applet */ + private Applet3 applet; + + /** the parameters */ + private Map<String, String> parameters; + + /** whether the applet has been started / displayed */ + private boolean appletStarted = false; + + /** whether the applet has been destroyed */ + private boolean destroyed = false; + + /** + * Create a new applet environment for the applet specified by + * the JNLP file. + */ + public Applet3Environment(JNLPFile file, final Applet3Instance appletInstance) { + this.file = file; + this.applet = appletInstance.getApplet(); + + parameters = file.getApplet().getParameters(); + } + + /** + * Checks whether the applet has been destroyed, and throws an + * IllegalStateException if the applet has been destroyed of. + * + * @throws IllegalStateException + */ + private void checkDestroyed() { + if (destroyed) { + throw new IllegalStateException("Illegal applet stub/context access: applet destroyed."); + } + } + + /** + * Disposes the applet's resources and disables the applet + * environment from further use; after calling this method the + * applet stub and context methods throw IllegalStateExceptions. + */ + public void destroy() { + destroyed = true; + } + + /** + * FIXME: Currently null + */ + public SplashController getSplashControler() { + return null; + } + + /** + * Initialize, start, and show the applet. + */ + public void startApplet() { + checkDestroyed(); + + if (appletStarted) { + return; + } + + appletStarted = true; + + try { + // This is only needed if the applet is in its own frame. + applet.init(Applet3Environment.this); + applet.start(); + } catch (Exception ex) { + OutputController.getLogger().log(ex); + + // should also kill the applet? + } + } + + // applet context methods + + /** + * Returns the applet if the applet's name is specified, + * otherwise return null. + */ + @Override + public Applet3Context getAppletContext(String name) { + checkDestroyed(); + + if (name != null && name.equals(file.getApplet().getName())) { + return this; + } else { + return null; + } + } + + /** + * Set the applet of this environment; can only be called once. + */ + public void setApplet(Applet3 applet) { + if (this.applet != null) { + OutputController.getLogger().log(new IllegalStateException("Applet can only be set once.")); + return; + } + this.applet = applet; + } + + @Override + public Enumeration<Applet3Context> getAllAppletContexts() { + checkDestroyed(); + + return Collections.enumeration(Arrays.asList(new Applet3Context[] { this})); + } + + /** + * Not implemented yet. + */ + @Override + public void showDocument(java.net.URL uRL) { + checkDestroyed(); + + } + + /** + * Not implemented yet. + */ + @Override + public void showDocument(java.net.URL uRL, java.lang.String str) { + checkDestroyed(); + + } + + /** + * Not implemented yet. + */ + @Override + public void showStatus(java.lang.String str) { + checkDestroyed(); + + } + + /** + * Required for JRE1.4, but not implemented yet. + */ + @Override + public void setStream(String key, InputStream stream) { + checkDestroyed(); + + } + + /** + * Required for JRE1.4, but not implemented yet. + */ + @Override + public InputStream getStream(String key) { + checkDestroyed(); + + return null; + } + + /** + * Required for JRE1.4, but not implemented yet. + */ + @Override + public Iterator<String> getStreamKeys() { + checkDestroyed(); + + return null; + } + + // stub methods + + @Override + public void resize(int width, int height) { + appletResize(width, height); + } + public void appletResize(int width, int height) { + checkDestroyed(); + // FIXME: ?? + } + + @Override + public Applet3 getApplet() { + checkDestroyed(); + + return applet; + } + + @Override + public URL getCodeBase() { + checkDestroyed(); + + return file.getCodeBase(); + } + + @Override + public URL getDocumentBase() { + checkDestroyed(); + + return file.getApplet().getDocumentBase(); + } + + // FIXME: Sun's applet code forces all parameters to lower case. + // Does Netx's JNLP code do the same, so we can remove the first lookup? + @Override + public String getParameter(String name) { + checkDestroyed(); + + String s = parameters.get(name); + if (s != null) { + return s; + } + + return parameters.get(name.toLowerCase()); + } + + @Override + public boolean isActive() { + checkDestroyed(); + + // it won't be started or stopped, so if it can call it's running + return true; + } + + @Override + public String getAppletName() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/netx/jogamp/plugin/jnlp/runtime/Applet3Instance.java b/netx/jogamp/plugin/jnlp/runtime/Applet3Instance.java new file mode 100644 index 0000000..7293936 --- /dev/null +++ b/netx/jogamp/plugin/jnlp/runtime/Applet3Instance.java @@ -0,0 +1,134 @@ +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package jogamp.plugin.jnlp.runtime; + +import jogamp.applet.App3Context; + +import com.jogamp.plugin.applet.Applet3; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.util.logging.OutputController; + +/** + * Represents a launched application instance created from a JNLP + * file. This class does not control the operation of the applet, + * use the AppletEnvironment class to start and stop the applet. + * + * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.9 $ + */ +public class Applet3Instance extends ApplicationInstance { + + /** whether the applet's stop and destroy methods have been called */ + private boolean appletStopped = false; + + /** the applet */ + private Applet3 applet; + + /** the applet environment */ + private Applet3Environment environment; + + /** + * Create a New Task based on the Specified URL + */ + public Applet3Instance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet3 applet) { + super(file, group, loader, App3Context.getAppContext()); + + this.applet = applet; + + this.environment = new Applet3Environment(file, this); + } + + /** + * Set the applet of this launched application; can only be called once. + */ + public void setApplet(Applet3 applet) { + if (this.applet != null) { + OutputController.getLogger().log(new IllegalStateException("Applet can only be set once.")); + return; + } + this.applet = applet; + } + + /** + * Sets whether the applet is resizable or not. Applets default + * to being not resizable. + */ + public void setResizable(boolean resizable) { + /** FIXME + Container c = environment.getAppletFrame(); + if (c instanceof Frame) + ((Frame) c).setResizable(resizable); */ + } + + /** + * Returns whether the applet is resizable. + */ + public boolean isResizable() { + /** FIXME + Container c = environment.getAppletFrame(); + if (c instanceof Frame) + return ((Frame) c).isResizable(); + + return false; */ + return false; + } + + /** + * Returns the application title. + */ + public String getTitle() { + return getJNLPFile().getApplet().getName(); + } + + /** + * Returns the applet environment. + */ + public Applet3Environment getAppletEnvironment() { + return environment; + } + + /** + * Returns the applet. + */ + public Applet3 getApplet() { + return applet; + } + + /** + * Stop the application and destroy its resources. + */ + public void destroy() { + if (appletStopped) + return; + + appletStopped = true; + + try { + applet.stop(); + applet.destroy(); + } catch (Exception ex) { + OutputController.getLogger().log(ex); + } + + environment.destroy(); + + super.destroy(); + } + +} diff --git a/netx/jogamp/plugin/jnlp/runtime/JNLP3SecurityManager.java b/netx/jogamp/plugin/jnlp/runtime/JNLP3SecurityManager.java new file mode 100644 index 0000000..0804bdc --- /dev/null +++ b/netx/jogamp/plugin/jnlp/runtime/JNLP3SecurityManager.java @@ -0,0 +1,388 @@ +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package jogamp.plugin.jnlp.runtime; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.security.AccessControlException; +import java.security.Permission; + +import jogamp.applet.App3Context; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.JNLPClassLoader; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.logging.OutputController; +import net.sourceforge.jnlp.util.WeakList; + +/** + * Security manager for JNLP environment. This security manager + * cannot be replaced as it always denies attempts to replace the + * security manager or policy.<p> + * + * The JNLP security manager tracks windows created by an + * application, allowing those windows to be disposed when the + * application exits but the JVM does not. If security is not + * enabled then the first application to call System.exit will + * halt the JVM.<p> + * + * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.17 $ + */ +public class JNLP3SecurityManager extends SecurityManager { + + // todo: some apps like JDiskReport can close the VM even when + // an exit class is set - fix! + + // todo: create an event dispatch thread for each application, + // so that the context classloader doesn't have to be switched + // to the foreground application (the currently the approach + // since some apps need their classloader as event dispatch + // thread's context classloader). + + // todo: use a custom Permission object to identify the current + // application in an AccessControlContext by setting a side + // effect in its implies method. Use a custom + // AllPermissions-like permission to do this for apps granted + // all permissions (but investigate whether this will nuke + // the all-permission optimizations in the JRE). + + // todo: does not exit app if close button pressed on JFrame + // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an + // WindowListener to catch WindowClosing event, then if exit is + // called immediately afterwards from AWT thread. + + // todo: deny all permissions to applications that should have + // already been 'shut down' by closing their resources and + // interrupt the threads if operating in a shared-VM (exit class + // set). Deny will probably will slow checks down a lot though. + + // todo: weak remember last getProperty application and + // re-install properties if another application calls, or find + // another way for different apps to have different properties + // in java.lang.Sytem with the same names. + + /** only class that can exit the JVM, if set */ + private Object exitClass = null; + + /** this exception prevents exiting the JVM */ + private SecurityException closeAppEx = // making here prevents huge stack traces + new SecurityException(R("RShutdown")); + + /** weak list of windows created */ + private WeakList<Object> weakWindows = new WeakList<Object>(); + + /** weak list of applications corresponding to window list */ + private WeakList<ApplicationInstance> weakApplications = + new WeakList<ApplicationInstance>(); + + /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */ + private boolean exitAllowed = true; + + /** + * Creates a JNLP SecurityManager. + */ + public JNLP3SecurityManager() { + } + + /** + * Returns whether the exit class is present on the stack, or + * true if no exit class is set. + */ + public boolean isExitClass() { + return isExitClass(getClassContext()); + } + + /** + * Returns whether the exit class is present on the stack, or + * true if no exit class is set. + */ + private boolean isExitClass(Class<?> stack[]) { + if (exitClass == null) { + return true; + } + + for (int i = 0; i < stack.length; i++) { + if (stack[i] == exitClass) { + return true; + } + } + + return false; + } + + /** + * Set the exit class, which is the only class that can exit the + * JVM; if not set then any class can exit the JVM. + * + * @param exitClass the exit class + * @throws IllegalStateException if the exit class is already set + */ + public void setExitClass(Class<?> exitClass) throws IllegalStateException { + if (this.exitClass != null) { + throw new IllegalStateException(R("RExitTaken")); + } + + this.exitClass = exitClass; + } + + /** + * Return the current Application, or null if none can be + * determined. + */ + public ApplicationInstance getApplication() { + return getApplication(Thread.currentThread(), getClassContext(), 0); + } + + /** + * Return the application the opened the specified window (only + * call from event dispatch thread). + */ + protected ApplicationInstance getApplication(Object window) { + for (int i = weakWindows.size(); i-- > 0;) { + Object w = weakWindows.get(i); + if (w == null) { + weakWindows.remove(i); + weakApplications.remove(i); + } + + if (w == window) { + return weakApplications.get(i); + } + } + + return null; + } + + /** + * Return the current Application, or null. + */ + protected ApplicationInstance getApplication(Thread thread, Class<?> stack[], int maxDepth) { + ClassLoader cl; + JNLPClassLoader jnlpCl; + + cl = thread.getContextClassLoader(); + while (cl != null) { + jnlpCl = getJnlpClassLoader(cl); + if (jnlpCl != null && jnlpCl.getApplication() != null) { + return jnlpCl.getApplication(); + } + cl = cl.getParent(); + } + + if (maxDepth <= 0) { + maxDepth = stack.length; + } + + // this needs to be tightened up + for (int i = 0; i < stack.length && i < maxDepth; i++) { + cl = stack[i].getClassLoader(); + while (cl != null) { + jnlpCl = getJnlpClassLoader(cl); + if (jnlpCl != null && jnlpCl.getApplication() != null) { + return jnlpCl.getApplication(); + } + cl = cl.getParent(); + } + } + return null; + } + + /** + * Returns the JNLPClassLoader associated with the given ClassLoader, or + * null. + * @param cl a ClassLoader + * @return JNLPClassLoader or null + */ + private JNLPClassLoader getJnlpClassLoader(ClassLoader cl) { + // Since we want to deal with JNLPClassLoader, extract it if this + // is a codebase loader + if (cl instanceof JNLPClassLoader.CodeBaseClassLoader) { + cl = ((JNLPClassLoader.CodeBaseClassLoader) cl).getParentJNLPClassLoader(); + } + + if (cl instanceof JNLPClassLoader) { + JNLPClassLoader loader = (JNLPClassLoader) cl; + return loader; + } + + return null; + } + + /** + * Returns the application's thread group if the application can + * be determined; otherwise returns super.getThreadGroup() + */ + @Override + public ThreadGroup getThreadGroup() { + ApplicationInstance app = getApplication(); + if (app == null) { + return super.getThreadGroup(); + } + + return app.getThreadGroup(); + } + + /** + * Throws a SecurityException if the permission is denied, + * otherwise return normally. This method always denies + * permission to change the security manager or policy. + */ + @Override + public void checkPermission(Permission perm) { + String name = perm.getName(); + + // Enable this manually -- it'll produce too much output for -verbose + // otherwise. + // if (true) + // OutputController.getLogger().log("Checking permission: " + perm.toString()); + + if (!JNLPRuntime.isWebstartApplication() && + ("setPolicy".equals(name) || "setSecurityManager".equals(name))) { + throw new SecurityException(R("RCantReplaceSM")); + } + + try { + // deny all permissions to stopped applications + // The call to getApplication() below might not work if an + // application hasn't been fully initialized yet. + // if (JNLPRuntime.isDebug()) { + // if (!"getClassLoader".equals(name)) { + // ApplicationInstance app = getApplication(); + // if (app != null && !app.isRunning()) + // throw new SecurityException(R("RDenyStopped")); + // } + // } + + super.checkPermission(perm); + } catch (SecurityException ex) { + OutputController.getLogger().log("Denying permission: " + perm); + throw ex; + } + } + + /** + * Checks whether the window can be displayed without an applet + * warning banner, and adds the window to the list of windows to + * be disposed when the calling application exits. + */ + @SuppressWarnings("deprecation") + @Override + public boolean checkTopLevelWindow(Object window) { + ApplicationInstance app = getApplication(); + + // remember window -> application mapping for focus, close on exit + if (app != null ) { + OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "SM: app: " + app.getTitle() + " is adding a window: " + window + " with appContext " + App3Context.getAppContext()); + + weakWindows.add(window); // for mapping window -> app + weakApplications.add(app); + + app.addWindow(window); + } + + // todo: set awt.appletWarning to custom message + // todo: logo on with glass pane on JFrame/JWindow? + + return super.checkTopLevelWindow(window); + } + + /** + * Checks whether the caller can exit the system. This method + * identifies whether the caller is a real call to Runtime.exec + * and has special behavior when returning from this method + * would exit the JVM and an exit class is set: if the caller is + * not the exit class then the calling application will be + * stopped and its resources destroyed (when possible), and an + * exception will be thrown to prevent the JVM from shutting + * down.<p> + * + * Calls not from Runtime.exit or with no exit class set will + * behave normally, and the exit class can always exit the JVM. + */ + @Override + public void checkExit(int status) { + + // applets are not allowed to exit, but the plugin main class (primordial loader) is + Class<?> stack[] = getClassContext(); + if (!exitAllowed) { + for (int i = 0; i < stack.length; i++) { + if (stack[i].getClassLoader() != null) { + throw new AccessControlException("Applets may not call System.exit()"); + } + } + } + + super.checkExit(status); + + boolean realCall = (stack[1] == Runtime.class); + + if (isExitClass(stack)) { + return; + } // to Runtime.exit or fake call to see if app has permission + + // not called from Runtime.exit() + if (!realCall) { + // apps that can't exit should think they can exit normally + super.checkExit(status); + return; + } + + // but when they really call, stop only the app instead of the JVM + ApplicationInstance app = getApplication(Thread.currentThread(), stack, 0); + if (app == null) { + throw new SecurityException(R("RExitNoApp")); + } + + app.destroy(); + + throw closeAppEx; + } + + public void disableExit() { + exitAllowed = false; + } + + /** + * Tests if a client can get access to the AWT event queue. This version allows + * complete access to the EventQueue for its own AppContext-specific EventQueue. + * + * FIXME there are probably huge security implications for this. Eg: + * http://hg.openjdk.java.net/jdk7/awt/jdk/rev/8022709a306d + * + * @exception SecurityException if the caller does not have + * permission to accesss the AWT event queue. + */ + @SuppressWarnings("deprecation") + @Override + public void checkAwtEventQueueAccess() { + /* + * this is the templace of the code that should allow applets access to + * eventqueues + */ + + // AppContext appContext = AppContext.getAppContext(); + // ApplicationInstance instance = getApplication(); + + // if ((appContext == mainAppContext) && (instance != null)) { + // If we're about to allow access to the main EventQueue, + // and anything untrusted is on the class context stack, + // disallow access. + super.checkAwtEventQueueAccess(); + // } + } + +} diff --git a/netx/net/sourceforge/jnlp/Launcher.java b/netx/net/sourceforge/jnlp/Launcher.java index 6e07b22..6076267 100644 --- a/netx/net/sourceforge/jnlp/Launcher.java +++ b/netx/net/sourceforge/jnlp/Launcher.java @@ -31,8 +31,8 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; -import net.sourceforge.jnlp.util.JarFile; +import net.sourceforge.jnlp.util.JarFile; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.cache.UpdatePolicy; import net.sourceforge.jnlp.runtime.AppletInstance; @@ -44,9 +44,10 @@ import net.sourceforge.jnlp.services.ServiceUtil; import javax.swing.SwingUtilities; import javax.swing.text.html.parser.ParserDelegator; + import net.sourceforge.jnlp.splashscreen.SplashUtils; import net.sourceforge.jnlp.util.logging.OutputController; - +import sun.awt.AppContext; import sun.awt.SunToolkit; /** @@ -67,6 +68,8 @@ public class Launcher { /** shared thread group */ /*package*/static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup")); + + public static ThreadGroup getMainGroup() { return mainGroup; } /** the handler */ private LaunchHandler handler = null; @@ -521,12 +524,16 @@ public class Launcher { // When the application-desc field is empty, we should take a // look at the main jar for the main class. if (mainName == null) { - JARDesc mainJarDesc = file.getResources().getMainJAR(); - File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null); + final JARDesc mainJarDesc = file.getResources().getMainJAR(); + final File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null); if (f != null) { - JarFile mainJar = new JarFile(f); - mainName = mainJar.getManifest(). - getMainAttributes().getValue("Main-Class"); + final JarFile mainJar = new JarFile(f); + try { + mainName = mainJar.getManifest(). + getMainAttributes().getValue("Main-Class"); + } finally { + mainJar.close(); + } } } @@ -767,7 +774,7 @@ public class Launcher { JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); ThreadGroup group = Thread.currentThread().getThreadGroup(); - ApplicationInstance app = new ApplicationInstance(file, group, loader); + ApplicationInstance app = new ApplicationInstance(file, group, loader, AppContext.getAppContext()); loader.setApplication(app); return app; diff --git a/netx/net/sourceforge/jnlp/PluginParameters.java b/netx/net/sourceforge/jnlp/PluginParameters.java index fa4e8fa..c3a958c 100644 --- a/netx/net/sourceforge/jnlp/PluginParameters.java +++ b/netx/net/sourceforge/jnlp/PluginParameters.java @@ -80,7 +80,7 @@ public class PluginParameters { * * @return the underlying hashtable. */ - Hashtable<String, String> getUnderlyingHashtable() { + public Hashtable<String, String> getUnderlyingHashtable() { return parameters; } @@ -134,6 +134,10 @@ public class PluginParameters { return getDefaulted("code", ""); } + public String getIsApplet3() { + return getDefaulted("is_applet3", "false"); + } + public String getJNLPHref() { return get("jnlp_href"); } diff --git a/netx/net/sourceforge/jnlp/runtime/AppletInstance.java b/netx/net/sourceforge/jnlp/runtime/AppletInstance.java index 757a9f7..49947ff 100644 --- a/netx/net/sourceforge/jnlp/runtime/AppletInstance.java +++ b/netx/net/sourceforge/jnlp/runtime/AppletInstance.java @@ -19,6 +19,7 @@ package net.sourceforge.jnlp.runtime; import java.applet.*; import java.awt.*; +import sun.awt.AppContext; import net.sourceforge.jnlp.*; import net.sourceforge.jnlp.util.logging.OutputController; @@ -45,7 +46,7 @@ public class AppletInstance extends ApplicationInstance { * Create a New Task based on the Specified URL */ public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet) { - super(file, group, loader); + super(file, group, loader, AppContext.getAppContext()); this.applet = applet; @@ -67,7 +68,7 @@ public class AppletInstance extends ApplicationInstance { * */ public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet, Container cont) { - super(file, group, loader); + super(file, group, loader, AppContext.getAppContext()); this.applet = applet; this.environment = new AppletEnvironment(file, this, cont); } diff --git a/netx/net/sourceforge/jnlp/runtime/ApplicationInstance.java b/netx/net/sourceforge/jnlp/runtime/ApplicationInstance.java index 0258840..a16b0b5 100644 --- a/netx/net/sourceforge/jnlp/runtime/ApplicationInstance.java +++ b/netx/net/sourceforge/jnlp/runtime/ApplicationInstance.java @@ -16,7 +16,7 @@ package net.sourceforge.jnlp.runtime; -import java.awt.Window; +// import java.awt.Window; import java.io.File; import java.net.URL; import java.security.AccessControlContext; @@ -25,17 +25,17 @@ import java.security.CodeSource; import java.security.PrivilegedAction; import java.security.ProtectionDomain; -import javax.swing.event.EventListenerList; - -import sun.awt.AppContext; +// FIXME replace - currently unused +// import javax.swing.event.EventListenerList; import net.sourceforge.jnlp.JNLPFile; import net.sourceforge.jnlp.PropertyDesc; import net.sourceforge.jnlp.SecurityDesc; import net.sourceforge.jnlp.ShortcutDesc; import net.sourceforge.jnlp.config.DeploymentConfiguration; -import net.sourceforge.jnlp.event.ApplicationEvent; -import net.sourceforge.jnlp.event.ApplicationListener; +// FIXME replace - currently unused +// import net.sourceforge.jnlp.event.ApplicationEvent; +// import net.sourceforge.jnlp.event.ApplicationListener; import net.sourceforge.jnlp.security.SecurityDialogs; import net.sourceforge.jnlp.security.SecurityDialogs.AccessType; import net.sourceforge.jnlp.util.logging.OutputController; @@ -56,13 +56,16 @@ public class ApplicationInstance { // installed by the application. /** the file */ - private JNLPFile file; + private final JNLPFile file; /** the thread group */ - private ThreadGroup group; + private final ThreadGroup group; /** the classloader */ - private ClassLoader loader; + private final ClassLoader loader; + + /** whether or not this application is signed */ + private final boolean isSigned; /** * Every application/applet gets its own AppContext. This allows us to do @@ -71,51 +74,55 @@ public class ApplicationInstance { * event queue (safely) and (possibly) more.<p> * * It is set to the AppContext which created this ApplicationInstance + * + * Either {@link sun.awt.AppContext} or .. + * + * FIXME: Add proper interface */ - private AppContext appContext; + private final Object appContext; /** whether the application has stopped running */ private boolean stopped = false; - /** weak list of windows opened by the application */ - private WeakList<Window> weakWindows = new WeakList<Window>(); + /** weak list of UI window objects opened by the application */ + private WeakList<Object> weakWindows = new WeakList<Object>(); /** list of application listeners */ - private EventListenerList listeners = new EventListenerList(); - - /** whether or not this application is signed */ - private boolean isSigned = false; + // FIXME replace - currently unused + // private EventListenerList listeners = new EventListenerList(); /** * Create an application instance for the file. This should be done in the * appropriate {@link ThreadGroup} only. */ - public ApplicationInstance(JNLPFile file, ThreadGroup group, ClassLoader loader) { + public ApplicationInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Object appContext) { this.file = file; this.group = group; this.loader = loader; this.isSigned = ((JNLPClassLoader) loader).getSigning(); - this.appContext = AppContext.getAppContext(); + this.appContext = appContext; } /** + // FIXME replace - currently unused * Add an Application listener - */ public void addApplicationListener(ApplicationListener listener) { listeners.add(ApplicationListener.class, listener); } - /** * Remove an Application Listener - */ public void removeApplicationListener(ApplicationListener listener) { listeners.remove(ApplicationListener.class, listener); } + */ /** * Notify listeners that the application has been terminated. */ protected void fireDestroyed() { + /** + // FIXME replace - currently unused + * Object list[] = listeners.getListenerList(); ApplicationEvent event = null; @@ -125,6 +132,7 @@ public class ApplicationInstance { ((ApplicationListener) list[i]).applicationDestroyed(event); } + */ } /** @@ -280,9 +288,13 @@ public class ApplicationInstance { try { // destroy resources - for (Window w : weakWindows) { - if (w != null) - w.dispose(); + for (Object w : weakWindows) { + if (w != null) { + // FIXME! + if( w instanceof java.awt.Window ) { + ((java.awt.Window)w).dispose(); + } + } } weakWindows.clear(); @@ -339,7 +351,7 @@ public class ApplicationInstance { * Adds a window that this application opened. When the * application is disposed, these windows will also be disposed. */ - protected void addWindow(Window window) { + public void addWindow(Object window) { weakWindows.add(window); weakWindows.trimToSize(); } @@ -351,7 +363,11 @@ public class ApplicationInstance { return isSigned; } - public AppContext getAppContext() { + /** + * Returns either {@link sun.awt.AppContext} or .. + * FIXME: Add proper interface + */ + public Object getAppContext() { return appContext; } diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java b/netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java index c9d7397..b69d36c 100644 --- a/netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java +++ b/netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java @@ -16,7 +16,6 @@ package net.sourceforge.jnlp.runtime; -import java.awt.EventQueue; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -42,14 +41,16 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; -import javax.swing.JOptionPane; -import javax.swing.UIManager; -import javax.swing.text.html.parser.ParserDelegator; +// import javax.swing.JOptionPane; +// import javax.swing.UIManager; +// import javax.swing.text.html.parser.ParserDelegator; + + +import jogamp.plugin.jnlp.runtime.JNLP3SecurityManager; import net.sourceforge.jnlp.DefaultLaunchHandler; import net.sourceforge.jnlp.GuiLaunchHandler; import net.sourceforge.jnlp.LaunchHandler; -import net.sourceforge.jnlp.Launcher; import net.sourceforge.jnlp.browser.BrowserAwareProxySelector; import net.sourceforge.jnlp.cache.CacheUtil; import net.sourceforge.jnlp.cache.DefaultDownloadIndicator; @@ -92,7 +93,7 @@ public class JNLPRuntime { private static ResourceBundle resources; /** the security manager */ - private static JNLPSecurityManager security; + private static JNLP3SecurityManager security; /** the security policy */ private static JNLPPolicy policy; @@ -223,15 +224,17 @@ public class JNLPRuntime { ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start policy = new JNLPPolicy(); - security = new JNLPSecurityManager(); // side effect: create JWindow + // security = new JNLPSecurityManager(); // side effect: create JWindow + security = new JNLP3SecurityManager(); // side effect: create JWindow + /** FIXME AWT try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); - } - + } doMainAppContextHacks(); + */ if (securityEnabled) { Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy @@ -322,7 +325,7 @@ public class JNLPRuntime { /** * This must NOT be called form the application ThreadGroup. An application - * can inject events into its {@link EventQueue} and bypass the security + * can inject events into its {@link java.awt.EventQueue} and bypass the security * dialogs. * * @return a {@link SecurityDialogMessageHandler} that can be used to post @@ -341,18 +344,20 @@ public class JNLPRuntime { * Performs a few hacks that are needed for the main AppContext * * @see Launcher#doPerApplicationAppContextHacks - */ + * + * FIXME AWT private static void doMainAppContextHacks() { - /* + * * With OpenJDK6 (but not with 7) a per-AppContext dtd is maintained. * This dtd is created by the ParserDelgate. However, the code in * HTMLEditorKit (used to render HTML in labels and textpanes) creates * the ParserDelegate only if there are no existing ParserDelegates. The * result is that all other AppContexts see a null dtd. - */ + * new ParserDelegator(); } + */ /** @@ -379,9 +384,10 @@ public class JNLPRuntime { //all exceptions are causing InstantiatizationError so this do it much more readble OutputController.getLogger().log(OutputController.Level.ERROR_ALL, t); OutputController.getLogger().log(OutputController.Level.WARNING_ALL, Translator.R("RFailingToDefault")); + /* FIXME AWT if (!JNLPRuntime.isHeadless()){ JOptionPane.showMessageDialog(null, getMessage("RFailingToDefault")+"\n"+t.toString()); - } + } */ //try to survive this unlikely exception config.resetToDefaults(); } finally { @@ -707,6 +713,7 @@ public class JNLPRuntime { */ public synchronized static void markNetxRunning() { if (fileLock != null) return; + FileInputStream is = null; try { String message = "This file is used to check if netx is running"; @@ -723,7 +730,7 @@ public class JNLPRuntime { } } - FileInputStream is = new FileInputStream(netxRunningFile); + is = new FileInputStream(netxRunningFile); FileChannel channel = is.getChannel(); fileLock = channel.lock(0, 1, true); if (!fileLock.isShared()){ // We know shared locks aren't offered on this system. @@ -741,6 +748,12 @@ public class JNLPRuntime { } } catch (IOException e) { OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e); + } finally { + if( null != is ) { + try { + is.close(); + } catch (IOException e) { } + } } Runtime.getRuntime().addShutdownHook(new Thread("JNLPRuntimeShutdownHookThread") { diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java b/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java index 762bb91..5b6f2b0 100644 --- a/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java +++ b/netx/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java @@ -442,7 +442,8 @@ class JNLPSecurityManager extends AWTSecurityManager { */ return mainAppContext; } else { - return app.getAppContext(); + // FIXME: Remove AWT dependencies! + return (AppContext) app.getAppContext(); } } diff --git a/netx/net/sourceforge/jnlp/splashscreen/SplashUtils.java b/netx/net/sourceforge/jnlp/splashscreen/SplashUtils.java index b43476e..8825183 100644 --- a/netx/net/sourceforge/jnlp/splashscreen/SplashUtils.java +++ b/netx/net/sourceforge/jnlp/splashscreen/SplashUtils.java @@ -36,6 +36,8 @@ obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ package net.sourceforge.jnlp.splashscreen; +import jogamp.plugin.jnlp.runtime.Applet3Environment; +import jogamp.plugin.jnlp.runtime.Applet3Instance; import net.sourceforge.jnlp.runtime.AppletEnvironment; import net.sourceforge.jnlp.runtime.AppletInstance; import net.sourceforge.jnlp.runtime.Boot; @@ -83,17 +85,44 @@ public class SplashUtils { OutputController.getLogger().log(t); } } + public static void showErrorCaught(Throwable ex, Applet3Instance appletInstance) { + try { + showError(ex, appletInstance); + } catch (Throwable t) { + // prinitng this exception is discutable. I have let it in for case that + //some retyping will fail + OutputController.getLogger().log(t); + } + } public static void showError(Throwable ex, AppletInstance appletInstance) { if (appletInstance == null) { + OutputController.getLogger().log(ex); return; } AppletEnvironment ae = appletInstance.getAppletEnvironment(); showError(ex, ae); } + public static void showError(Throwable ex, Applet3Instance appletInstance) { + if (appletInstance == null) { + OutputController.getLogger().log(ex); + return; + } + Applet3Environment ae = appletInstance.getAppletEnvironment(); + showError(ex, ae); + } public static void showError(Throwable ex, AppletEnvironment ae) { if (ae == null) { + OutputController.getLogger().log(ex); + return; + } + SplashController p = ae.getSplashControler(); + showError(ex, p); + } + public static void showError(Throwable ex, Applet3Environment ae) { + if (ae == null) { + OutputController.getLogger().log(ex); return; } SplashController p = ae.getSplashControler(); @@ -102,6 +131,7 @@ public class SplashUtils { public static void showError(Throwable ex, SplashController f) { if (f == null) { + OutputController.getLogger().log(ex); return; } diff --git a/netx/sun/applet/Applet3MessageHandler.java b/netx/sun/applet/Applet3MessageHandler.java new file mode 100644 index 0000000..140e5eb --- /dev/null +++ b/netx/sun/applet/Applet3MessageHandler.java @@ -0,0 +1,21 @@ +package sun.applet; + +import sun.applet.AppletMessageHandler; + +public class Applet3MessageHandler extends AppletMessageHandler { + + public Applet3MessageHandler(String baseKey) { + super(baseKey); + } + + public String getMessage(String key) { + return super.getMessage(key); + } + public String getMessage(String key, Object arg){ + return super.getMessage(key, arg); + } + public String getMessage(String key, Object arg1, Object arg2){ + return super.getMessage(key, arg1, arg2); + } + +} |