aboutsummaryrefslogtreecommitdiffstats
path: root/src/classes/javax/media/opengl/util/swing/JOGLAppletLauncher.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/classes/javax/media/opengl/util/swing/JOGLAppletLauncher.java')
-rwxr-xr-xsrc/classes/javax/media/opengl/util/swing/JOGLAppletLauncher.java1080
1 files changed, 1080 insertions, 0 deletions
diff --git a/src/classes/javax/media/opengl/util/swing/JOGLAppletLauncher.java b/src/classes/javax/media/opengl/util/swing/JOGLAppletLauncher.java
new file mode 100755
index 000000000..7a5e29174
--- /dev/null
+++ b/src/classes/javax/media/opengl/util/swing/JOGLAppletLauncher.java
@@ -0,0 +1,1080 @@
+/* This java class is distributed under the BSD license.
+ *
+ * Copyright 2005 Lilian Chamontin.
+ * contact lilian.chamontin at f r e e . f r
+ */
+
+/*
+ * Portions Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution 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.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ */
+
+package javax.media.opengl.util.swing;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Label;
+import java.awt.Panel;
+import java.applet.Applet;
+import java.applet.AppletStub;
+import java.applet.AppletContext;
+import java.io.*;
+import java.net.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.*;
+import java.text.*;
+import java.util.*;
+import java.util.jar.*;
+import javax.swing.*;
+
+import javax.media.opengl.*;
+
+
+/** This class enables deployment of high-end applets which use OpenGL
+ * for 3D graphics via JOGL and (optionally) OpenAL for spatialized
+ * audio via JOAL. The applet being deployed may be either signed or
+ * unsigned; if it is unsigned, it runs inside the security sandbox,
+ * and if it is signed, the user receives a security dialog to accept
+ * the certificate for the applet as well as for JOGL and JOAL. <P>
+ *
+ * The steps for deploying such applets are straightforward. First,
+ * the "archive" parameter to the applet tag must contain jogl.jar
+ * and gluegen-rt.jar, as well as any jar files associated with your
+ * applet (in this case, "your_applet.jar"). <P>
+ *
+ * Second, the codebase directory on the server, which contains the
+ * applet's jar files, must also contain jogl.jar, gluegen-rt.jar,
+ * and all of the jogl-natives-*.jar and gluegen-rt-natives-*.jar
+ * files from the standard JOGL and GlueGen runtime distributions
+ * (provided in jogl-[version]-webstart.zip from the <a
+ * href="http://jogl.dev.java.net/servlets/ProjectDocumentList">JOGL
+ * release builds</a> and gluegen-rt-[version]-webstart.zip from the
+ * <a
+ * href="http://gluegen.dev.java.net/servlets/ProjectDocumentList">GlueGen
+ * runtime release builds</a>). Note that the codebase of the applet
+ * is currently the location from which the JOGL native library used
+ * by the applet is downloaded. All of the JOGL and GlueGen-related
+ * jars must be signed by the same entity, which is typically Sun
+ * Microsystems, Inc. <P>
+ *
+ * To deploy an applet using both JOGL and JOAL, simply add joal.jar
+ * to the list of jars in the archive tag of the applet, and put
+ * joal.jar and the joal-natives-*.jar signed jars into the same
+ * codebase directory on the web server. These signed jars are
+ * supplied in the joal-[version]-webstart.zip archive from the <a
+ * href="http://joal.dev.java.net/servlets/ProjectDocumentList">JOAL
+ * release builds</a>. <P>
+ *
+ * Sample applet code:
+ * <pre>
+ * &lt;applet code="com.sun.opengl.util.JOGLAppletLauncher"
+ * width=600
+ * height=400
+ * codebase="/lib"
+ * archive="jogl.jar,gluegen-rt.jar,your_applet.jar"&gt;
+ * &lt;param name="subapplet.classname" VALUE="untrusted.JOGLApplet"&gt;
+ * &lt;param name="subapplet.displayname" VALUE="My JOGL Applet"&gt;
+ * &lt;param name="progressbar" value="true"&gt;
+ * &lt;param name="cache_archive" VALUE="jogl.jar,gluegen-rt.jar,your_applet.jar"&gt;
+ * &lt;param name="cache_archive_ex" VALUE="jogl.jar;preload,gluegen-rt.jar;preload,your_applet.jar;preload"&gt;
+ * &lt;/applet&gt;
+ * </pre>
+ * <p>
+ *
+ * There are some limitations with this approach. It is not possible
+ * to specify e.g. -Dsun.java2d.noddraw=true or
+ * -Dsun.java2d.opengl=true for better control over the Java2D
+ * pipeline as it is with Java Web Start. However, the
+ * JOGLAppletLauncher tries to force the use of
+ * -Dsun.java2d.noddraw=true on Windows platforms for best robustness
+ * by detecting if it has not been set and asking the user whether it
+ * can update the Java Plug-In configuration automatically. If the
+ * user agrees to this, a browser restart is required in order for the
+ * change to take effect, though it is permanent for subsequent
+ * browser restarts. <P>
+ *
+ * The behavior of the noddraw-related dialog box can be changed via
+ * two applet parameters. The <CODE>jogl.silent.noddraw.check</CODE>
+ * parameter, if set to <CODE>"true"</CODE>, silences the two dialog
+ * boxes associated with this check, forcing it to always be performed
+ * and deployment.properties to be silently updated if necessary
+ * (unless the user previously saw such a dialog box and dismissed it
+ * by saying "No, Don't Ask Again"). The noddraw check can be disabled
+ * completely by setting the <CODE>jogl.disable.noddraw.check</CODE>
+ * applet parameter to <CODE>"true"</CODE>. <P>
+ *
+ * The JOGL (and optionally JOAL) natives are cached in the user's
+ * home directory (the value of the "user.home" system property in
+ * Java) under the directory .jogl_ext. The Java Plug-In is
+ * responsible for performing all other jar caching. If the JOGL
+ * installation is updated on the server, the .jogl_ext cache will
+ * automatically be updated. <p>
+ *
+ * This technique requires that JOGL has not been installed in to the
+ * JRE under e.g. jre/lib/ext. If problems are seen when deploying
+ * this applet launcher, the first question to ask the end user is
+ * whether jogl.jar and any associated DLLs, .so's, etc. are installed
+ * directly in to the JRE. The applet launcher has been tested
+ * primarily under Mozilla, Firefox and Internet Explorer; there may
+ * be problems when running under, for example, Opera. <p>
+ *
+ * It has been discovered that the Talkback agent in Mozilla / Firefox
+ * has bad interactions with OpenGL applets. For highest performance,
+ * we recommend disabling the Talkback agent; find talkback.exe, run
+ * it, and follow the directions for turning it off. Please see
+ * <a href="http://www.javagaming.org/forums/index.php?topic=12200.30">this
+ * thread</a> on the javagaming.org forums and
+ * <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=326381">this
+ * thread</a> on the Mozilla bug reporting database. <p>
+ *
+ * @author Lilian Chamontin
+ * @author Kenneth Russell
+ */
+public class JOGLAppletLauncher extends Applet {
+ static {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception ignore) {
+ }
+ }
+
+ // metadata for native libraries
+ private static class NativeLibInfo {
+ private String osName;
+ private String osArch;
+ private String osNameAndArchPair;
+ private String nativePrefix;
+ private String nativeSuffix;
+
+ public NativeLibInfo(String osName, String osArch, String osNameAndArchPair, String nativePrefix, String nativeSuffix) {
+ this.osName = osName;
+ this.osArch = osArch;
+ this.osNameAndArchPair = osNameAndArchPair;
+ this.nativePrefix = nativePrefix;
+ this.nativeSuffix = nativeSuffix;
+ }
+
+ public boolean matchesOSAndArch(String osName, String osArch) {
+ if (osName.toLowerCase().startsWith(this.osName)) {
+ if ((this.osArch == null) ||
+ (osArch.toLowerCase().equals(this.osArch))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean matchesNativeLib(String fileName) {
+ if (fileName.toLowerCase().endsWith(nativeSuffix)) {
+ return true;
+ }
+ return false;
+ }
+
+ public String formatNativeJarName(String nativeJarPattern) {
+ return MessageFormat.format(nativeJarPattern, new Object[] { osNameAndArchPair });
+ }
+
+ public String getNativeLibName(String baseName) {
+ return nativePrefix + baseName + nativeSuffix;
+ }
+
+ public boolean isMacOS() {
+ return (osName.equals("mac"));
+ }
+
+ public boolean mayNeedDRIHack() {
+ return (!isMacOS() && !osName.equals("win"));
+ }
+ }
+
+ private static final NativeLibInfo[] allNativeLibInfo = {
+ new NativeLibInfo("win", "x86", "windows-i586", "", ".dll"),
+ new NativeLibInfo("win", "amd64", "windows-amd64", "", ".dll"),
+ new NativeLibInfo("win", "x86_64","windows-amd64", "", ".dll"),
+ new NativeLibInfo("mac", "ppc", "macosx-ppc", "lib", ".jnilib"),
+ new NativeLibInfo("mac", "i386", "macosx-universal", "lib", ".jnilib"),
+ new NativeLibInfo("linux", "i386", "linux-i586", "lib", ".so"),
+ new NativeLibInfo("linux", "x86", "linux-i586", "lib", ".so"),
+ new NativeLibInfo("linux", "amd64", "linux-amd64", "lib", ".so"),
+ new NativeLibInfo("linux", "x86_64","linux-amd64", "lib", ".so"),
+ new NativeLibInfo("sunos", "sparc", "solaris-sparc", "lib", ".so"),
+ new NativeLibInfo("sunos", "sparcv9","solaris-sparcv9", "lib", ".so"),
+ new NativeLibInfo("sunos", "x86", "solaris-i586", "lib", ".so"),
+ new NativeLibInfo("sunos", "amd64", "solaris-amd64", "lib", ".so"),
+ new NativeLibInfo("sunos", "x86_64","solaris-amd64", "lib", ".so")
+ };
+
+ private NativeLibInfo nativeLibInfo;
+ // Library names computed once the jar comes down.
+ // The signatures of these native libraries are checked before
+ // installing them.
+ private String[] nativeLibNames;
+
+ /** The applet we have to start */
+ private Applet subApplet;
+
+ private String subAppletClassName; // from applet PARAM
+ private String subAppletDisplayName; // from applet PARAM
+ /** URL string to an image used while installing */
+ private String subAppletImageName; // from applet PARAM
+
+ private String installDirectory; // (defines a private directory for native libs)
+
+ private JPanel loaderPanel = new JPanel(new BorderLayout());
+
+ private JProgressBar progressBar = new JProgressBar(0,100);
+
+ private boolean isInitOk = false;
+
+ /** false once start() has been invoked */
+ private boolean firstStart = true;
+
+ /** true if start() has passed successfully */
+ private boolean joglStarted = false;
+
+ /** Indicates whether JOAL is present */
+ private boolean haveJOAL = false;
+
+ // Helpers for question about whether to update deployment.properties
+ private static final String JRE_PREFIX = "deployment.javapi.jre.";
+ private static final String NODDRAW_PROP = "-Dsun.java2d.noddraw=true";
+ private static final String DONT_ASK = ".dont_ask";
+
+ public JOGLAppletLauncher() {
+ }
+
+ private static String md2Hash(String str) {
+ // Helps hash the jars in the "archive" tag into a hex value to
+ // avoid having too-long path names in the install directory's
+ // path name but also to have unique directories for each
+ // different archive set used (also meaning for each class loader
+ // loading something via the JOGLAppletLauncher) -- note that this
+ // is somewhat dependent on the Sun implementation of applets and
+ // their class loaders
+ MessageDigest md2 = null;
+ try {
+ md2 = MessageDigest.getInstance("MD2");
+ } catch (NoSuchAlgorithmException e) {
+ return "";
+ }
+ byte[] digest = md2.digest(str.getBytes());
+ if (digest == null || (digest.length == 0))
+ return "";
+ StringBuffer res = new StringBuffer();
+ for (int i = 0; i < digest.length; i++) {
+ res.append(Integer.toHexString(digest[i] & 0xFF));
+ }
+ return res.toString();
+ }
+
+ /** Applet initialization */
+ public void init() {
+
+ this.subAppletClassName = getParameter("subapplet.classname");
+ if (subAppletClassName == null){
+ displayError("Init failed : Missing subapplet.classname argument");
+ return;
+ }
+ this.subAppletDisplayName = getParameter("subapplet.displayname");
+ if (subAppletDisplayName == null){
+ subAppletDisplayName = "Applet";
+ }
+
+ this.subAppletImageName = getParameter("subapplet.image");
+
+ initLoaderLayout();
+ validate();
+
+ String extForm = getCodeBase().toExternalForm();
+ String codeBase = extForm.substring(extForm.indexOf(":") + 3); // minus http:// or https://
+
+ this.installDirectory = codeBase.replace(':', '_')
+ .replace('.', '_').replace('/', '_').replace('~','_') // clean up the name
+ + md2Hash(getParameter("archive")); // make it unique across different applet class loaders
+
+ String osName = System.getProperty("os.name");
+ String osArch = System.getProperty("os.arch");
+ if (checkOSAndArch(osName, osArch)) {
+ this.isInitOk = true;
+ } else {
+ displayError("Init failed : Unsupported os / arch ( " + osName + " / " + osArch + " )");
+ }
+ }
+
+ private void displayMessage(final String message){
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ progressBar.setString(message);
+ }
+ });
+ }
+
+ private void displayError(final String errorMessage){
+ // Print message to Java console too in case it's truncated in the applet's display
+ System.err.println(errorMessage);
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ progressBar.setString("Error : " + errorMessage);
+ }
+ });
+ }
+
+ private void setProgress(final int value) {
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ progressBar.setValue(value);
+ }
+ });
+ }
+
+ private void initLoaderLayout(){
+ setLayout(new BorderLayout());
+ progressBar.setBorderPainted(true);
+ progressBar.setStringPainted(true);
+ progressBar.setString("Loading...");
+ boolean includeImage = false;
+ ImageIcon image = null;
+ if (subAppletImageName != null){
+ try {
+ image = new ImageIcon(new URL(subAppletImageName));
+ includeImage = true;
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ // not blocking
+ }
+ }
+ if (includeImage){
+ add(loaderPanel, BorderLayout.SOUTH);
+ loaderPanel.add(new JLabel(image), BorderLayout.CENTER);
+ loaderPanel.add(progressBar, BorderLayout.SOUTH);
+ } else {
+ add(loaderPanel, BorderLayout.SOUTH);
+ loaderPanel.add(progressBar, BorderLayout.CENTER);
+ }
+ }
+
+
+ /** start asynchroneous loading of libraries if needed */
+ public void start(){
+ if (isInitOk){
+ if (firstStart) {
+ firstStart = false;
+ String userHome = System.getProperty("user.home");
+
+ try {
+ // We need to load in the jogl package so that we can query the version information
+ ClassLoader classloader = getClass().getClassLoader();
+ classloader.loadClass("javax.media.opengl.GL");
+ Package p = Package.getPackage("javax.media.opengl");
+
+ String installDirName = userHome + File.separator + ".jogl_ext"
+ + File.separator + installDirectory + File.separator + p.getImplementationVersion().replace(':', '_');
+
+ final File installDir = new File(installDirName);
+
+ Thread refresher = new Thread() {
+ public void run() {
+ refreshJOGL(installDir);
+ }
+ };
+ refresher.setPriority(Thread.NORM_PRIORITY - 1);
+ refresher.start();
+ }
+ catch (ClassNotFoundException e) {
+ System.err.println("Unable to load javax.media.opengl package");
+ System.exit(0);
+ }
+
+ } else if (joglStarted) {
+ checkNoDDrawAndUpdateDeploymentProperties();
+ // we have to start again the applet (start can be called multiple times,
+ // e.g once per tabbed browsing
+ subApplet.start();
+ }
+ }
+ }
+
+ public void stop(){
+ if (subApplet != null){
+ subApplet.stop();
+ }
+ }
+
+ public void destroy(){
+ if (subApplet != null){
+ subApplet.destroy();
+ }
+ }
+
+
+ /** Helper method to make it easier to call methods on the
+ sub-applet from JavaScript. */
+ public Applet getSubApplet() {
+ return subApplet;
+ }
+
+ private boolean checkOSAndArch(String osName, String osArch) {
+ for (int i = 0; i < allNativeLibInfo.length; i++) {
+ NativeLibInfo info = allNativeLibInfo[i];
+ if (info.matchesOSAndArch(osName, osArch)) {
+ nativeLibInfo = info;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Get a "boolean" parameter, assuming that anything non-null aside
+ // from "false" is true
+ private boolean getBooleanParameter(String parameterName) {
+ String val = getParameter(parameterName);
+ if (val == null)
+ return false;
+ return !val.toLowerCase().equals("false");
+ }
+
+ private void checkNoDDrawAndUpdateDeploymentProperties() {
+ if (getBooleanParameter("jogl.disable.noddraw.check"))
+ return;
+ if (System.getProperty("os.name").toLowerCase().startsWith("windows") &&
+ !"true".equalsIgnoreCase(System.getProperty("sun.java2d.noddraw"))) {
+ if (!SwingUtilities.isEventDispatchThread()) {
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ updateDeploymentPropertiesImpl();
+ }
+ });
+ } catch (Exception e) {
+ }
+ } else {
+ updateDeploymentPropertiesImpl();
+ }
+ }
+ }
+
+ private void updateDeploymentPropertiesImpl() {
+ String userHome = System.getProperty("user.home");
+ File dontAskFile = new File(userHome + File.separator + ".jogl_ext" +
+ File.separator + DONT_ASK);
+ if (dontAskFile.exists())
+ return; // User asked us not to prompt again
+
+ int option = 0;
+
+ if (!getBooleanParameter("jogl.silent.noddraw.check")) {
+ option = JOptionPane.showOptionDialog(null,
+ "For best robustness of JOGL applets on Windows,\n" +
+ "we recommend disabling Java2D's use of DirectDraw.\n" +
+ "This setting will affect all applets, but is unlikely\n" +
+ "to slow other applets down significantly. May we update\n" +
+ "your deployment.properties to turn off DirectDraw for\n" +
+ "applets? You can change this back later if necessary\n" +
+ "using the Java Control Panel, Java tab, under Java\n" +
+ "Applet Runtime Settings.",
+ "Update deployment.properties?",
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ new Object[] {
+ "Yes",
+ "No",
+ "No, Don't Ask Again"
+ },
+ "Yes");
+ }
+
+ if (option < 0 ||
+ option == 1)
+ return; // No
+
+ if (option == 2) {
+ try {
+ dontAskFile.createNewFile();
+ } catch (IOException e) {
+ }
+ return; // No, Don't Ask Again
+ }
+
+ try {
+ // Must update deployment.properties
+ File propsDir = new File(System.getProperty("user.home") + File.separator +
+ "Application Data/Sun/Java/Deployment");
+ if (!propsDir.exists())
+ // Don't know what's going on or how to set this permanently
+ return;
+
+ File propsFile = new File(propsDir, "deployment.properties");
+ if (!propsFile.exists())
+ // Don't know what's going on or how to set this permanently
+ return;
+
+ Properties props = new Properties();
+ InputStream input = new BufferedInputStream(new FileInputStream(propsFile));
+ props.load(input);
+ input.close();
+ // Search through the keys looking for JRE versions
+ Set/*<String>*/ jreVersions = new HashSet/*<String>*/();
+ for (Iterator/*<String>*/ iter = props.keySet().iterator(); iter.hasNext(); ) {
+ String key = (String) iter.next();
+ if (key.startsWith(JRE_PREFIX)) {
+ int idx = key.lastIndexOf(".");
+ if (idx >= 0 && idx > JRE_PREFIX.length()) {
+ String jreVersion = key.substring(JRE_PREFIX.length(), idx);
+ jreVersions.add(jreVersion);
+ }
+ }
+ }
+
+ // Make sure the currently-running JRE shows up in this set to
+ // avoid repeated displays of the dialog. It might not in some
+ // upgrade scenarios where there was a pre-existing
+ // deployment.properties and the new Java Control Panel hasn't
+ // been run yet.
+ jreVersions.add(System.getProperty("java.version"));
+
+ // OK, now that we know all JRE versions covered by the
+ // deployment.properties, check out the args for each and update
+ // them
+ for (Iterator/*<String>*/ iter = jreVersions.iterator(); iter.hasNext(); ) {
+ String version = (String) iter.next();
+ String argKey = JRE_PREFIX + version + ".args";
+ String argVal = props.getProperty(argKey);
+ if (argVal == null) {
+ argVal = NODDRAW_PROP;
+ } else if (argVal.indexOf(NODDRAW_PROP) < 0) {
+ argVal = argVal + " " + NODDRAW_PROP;
+ }
+ props.setProperty(argKey, argVal);
+ }
+
+ OutputStream output = new BufferedOutputStream(new FileOutputStream(propsFile));
+ props.store(output, null);
+ output.close();
+
+ if (!getBooleanParameter("jogl.silent.noddraw.check")) {
+ // Tell user we're done
+ JOptionPane.showMessageDialog(null,
+ "For best robustness, we recommend you now exit and\n" +
+ "restart your web browser. (Note: clicking \"OK\" will\n" +
+ "not exit your browser.)",
+ "Browser Restart Recommended",
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** This method is executed from outside the Event Dispatch Thread, and installs
+ * the required native libraries in the local folder.
+ */
+ private void refreshJOGL(final File installDir) {
+ try {
+ Class subAppletClass = Class.forName(subAppletClassName);
+ // this will block until the applet jar is downloaded
+ } catch (ClassNotFoundException cnfe){
+ displayError("Start failed : class not found : " + subAppletClassName);
+ return;
+ }
+
+ if (!installDir.exists()){
+ if (!installDir.mkdirs()) {
+ displayError("Unable to create directories for target: " + installDir);
+ return;
+ }
+ }
+
+ // See whether JOAL is present
+ try {
+ Class alClass = Class.forName("net.java.games.joal.AL", false, this.getClass().getClassLoader());
+ haveJOAL = true;
+ // Note: it seems that some JRE implementations can throw
+ // SecurityException as well as ClassNotFoundException, at least
+ // if the OpenAL classes are not present and the web server
+ // redirects elsewhere
+ } catch (Exception e) {
+ }
+
+ String[] nativeJarNames = new String[] {
+ nativeLibInfo.formatNativeJarName("jogl-natives-{0}.jar"),
+ nativeLibInfo.formatNativeJarName("gluegen-rt-natives-{0}.jar"),
+ (haveJOAL ? nativeLibInfo.formatNativeJarName("joal-natives-{0}.jar") : null)
+ };
+
+ for (int n = 0; n < nativeJarNames.length; n++) {
+ String nativeJarName = nativeJarNames[n];
+
+ if (nativeJarName == null)
+ continue;
+
+ URL nativeLibURL;
+ URLConnection urlConnection;
+ String path = getCodeBase().toExternalForm() + nativeJarName;
+ try {
+ nativeLibURL = new URL(path);
+ urlConnection = nativeLibURL.openConnection();
+ } catch (Exception e){
+ e.printStackTrace();
+ displayError("Couldn't access the native lib URL : " + path);
+ return;
+ }
+
+ // the timestamp used to determine if we have to download the native jar again
+ // don't rely on the OS's timestamp to cache this
+ long lastModified = getTimestamp(installDir, nativeJarName, urlConnection.getLastModified());
+ if (lastModified != urlConnection.getLastModified()) {
+ displayMessage("Updating local version of the native libraries");
+ // first download the full jar locally
+ File localJarFile = new File(installDir, nativeJarName);
+ try {
+ saveNativesJarLocally(localJarFile, urlConnection);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ displayError("Unable to install the native file locally");
+ return;
+ }
+
+ try {
+ JarFile jf = new JarFile(localJarFile);
+
+ // Iterate the entries finding all candidate libraries that need
+ // to have their signatures verified
+ if (!findNativeEntries(jf)) {
+ displayError("native libraries not found in jar file");
+ return;
+ }
+
+ byte[] buf = new byte[8192];
+
+ // Go back and verify the signatures
+ for (int i = 0; i < nativeLibNames.length; i++) {
+ JarEntry entry = jf.getJarEntry(nativeLibNames[i]);
+ if (entry == null) {
+ displayError("error looking up jar entry " + nativeLibNames[i]);
+ return;
+ }
+ if (!checkNativeCertificates(jf, entry, buf)) {
+ displayError("Native library " + nativeLibNames[i] + " isn't properly signed or has other errors");
+ return;
+ }
+ }
+
+ // Now install the native library files
+ setProgress(0);
+ for (int i = 0; i < nativeLibNames.length; i++) {
+ displayMessage("Installing native files from " + nativeJarName);
+ if (!installFile(installDir, jf, nativeLibNames[i], buf)) {
+ return;
+ }
+ int percent = (100 * (i + 1) / nativeLibNames.length);
+ setProgress(percent);
+ }
+
+ // At this point we can delete the jar file we just downloaded
+ jf.close();
+ localJarFile.delete();
+
+ // If installation succeeded, write a timestamp for all of the
+ // files to be checked next time
+ try {
+ File timestampFile = new File(installDir, getTimestampFileName(nativeJarName));
+ timestampFile.delete();
+ BufferedWriter writer = new BufferedWriter(new FileWriter(timestampFile));
+ writer.write("" + urlConnection.getLastModified());
+ writer.flush();
+ writer.close();
+ } catch (Exception e) {
+ displayError("Error writing time stamp for native libraries");
+ return;
+ }
+
+ } catch (Exception e) {
+ displayError("Error opening jar file " + localJarFile.getName() + " for reading");
+ return;
+ }
+ }
+ }
+
+ loadNativesAndStart(installDir);
+ }
+
+ private String getTimestampFileName(String nativeJarName) {
+ return "timestamp-" + nativeJarName.replace('.', '-');
+ }
+
+ private long getTimestamp(File installDir, String nativeJarName, long timestamp) {
+ // Avoid returning valid value if timestamp file doesn't exist
+ try {
+ String timestampName = getTimestampFileName(nativeJarName);
+ BufferedReader reader = new BufferedReader(new FileReader(new File(installDir, timestampName)));
+ try {
+ StreamTokenizer tokenizer = new StreamTokenizer(reader);
+ // Avoid screwing up by not being able to read full longs
+ tokenizer.resetSyntax();
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('-', '-');
+ tokenizer.nextToken();
+ String tok = tokenizer.sval;
+ if (tok != null) {
+ return Long.parseLong(tok);
+ }
+ } catch (Exception e) {
+ } finally {
+ reader.close();
+ }
+ } catch (Exception e) {
+ }
+ return ((timestamp == 0) ? 1 : 0);
+ }
+
+ private void saveNativesJarLocally(File localJarFile,
+ URLConnection urlConnection) throws IOException {
+ BufferedOutputStream out = null;;
+ InputStream in = null;
+ displayMessage("Downloading native library");
+ setProgress(0);
+ try {
+ out = new BufferedOutputStream(new
+ FileOutputStream(localJarFile));
+ int totalLength = urlConnection.getContentLength();
+ in = urlConnection.getInputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ int sum = 0;
+ while ( (len = in.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
+ sum += len;
+ int percent = (100 * sum / totalLength);
+ setProgress(percent);
+ }
+ out.close();
+ in.close();
+ } finally {
+ // close the files
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ignore) {
+ }
+ }
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ private boolean findNativeEntries(JarFile jf) {
+ List list = new ArrayList();
+ Enumeration e = jf.entries();
+ while (e.hasMoreElements()) {
+ JarEntry entry = (JarEntry) e.nextElement();
+ if (nativeLibInfo.matchesNativeLib(entry.getName())) {
+ list.add(entry.getName());
+ }
+ }
+ if (list.isEmpty()) {
+ return false;
+ }
+ nativeLibNames = (String[]) list.toArray(new String[0]);
+ return true;
+ }
+
+ /** checking the native certificates with the jogl ones (all must match)*/
+ private boolean checkNativeCertificates(JarFile jar, JarEntry entry, byte[] buf){
+ // API states that we must read all of the data from the entry's
+ // InputStream in order to be able to get its certificates
+ try {
+ InputStream is = jar.getInputStream(entry);
+ int totalLength = (int) entry.getSize();
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ }
+ is.close();
+ Certificate[] nativeCerts = entry.getCertificates();
+ // locate the JOGL certificates
+ Certificate[] joglCerts = GLDrawableFactory.class.getProtectionDomain().
+ getCodeSource().getCertificates();
+
+ if (nativeCerts == null || nativeCerts.length == 0) {
+ return false;
+ }
+ int checked = 0;
+ for (int i = 0; i < joglCerts.length; i++) {
+ for (int j = 0; j < nativeCerts.length; j++) {
+ if (nativeCerts[j].equals(joglCerts[i])){
+ checked++;
+ break;
+ }
+ }
+ }
+ return (checked == joglCerts.length);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean installFile(File installDir,
+ JarFile jar,
+ String fileName,
+ byte[] buf) {
+ try {
+ JarEntry entry = jar.getJarEntry(fileName);
+ if (entry == null) {
+ displayError("Error finding native library " + fileName);
+ return false;
+ }
+ InputStream is = jar.getInputStream(entry);
+ int totalLength = (int) entry.getSize();
+ BufferedOutputStream out = null;
+ File outputFile = new File(installDir, fileName);
+ boolean exists = false;
+ try {
+ exists = outputFile.exists();
+ out = new BufferedOutputStream(new FileOutputStream(outputFile));
+ } catch (Exception e) {
+ if (exists) {
+ // It's possible the files were updated on the web server
+ // but we still have them loaded in this process; skip this
+ // update
+ return true;
+ } else {
+ displayError("Error opening file " + fileName + " for writing");
+ return false;
+ }
+ }
+ int len;
+ try {
+ while ( (len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ } catch (IOException ioe) {
+ displayError("Error writing file " + fileName + " to disk");
+ ioe.printStackTrace();
+ outputFile.delete();
+ return false;
+ }
+ out.flush();
+ out.close();
+ is.close();
+ return true;
+ } catch (Exception e2) {
+ e2.printStackTrace();
+ displayError("Error writing file " + fileName + " to disk");
+ return false;
+ }
+ }
+
+ /** last step before launch : System.load() the natives and init()/start() the child applet */
+ private void loadNativesAndStart(final File nativeLibDir) {
+ // back to the EDT
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ displayMessage("Loading native libraries");
+
+ // disable JOGL and GlueGen runtime library loading from elsewhere
+ com.sun.opengl.impl.NativeLibLoader.disableLoading();
+ com.sun.gluegen.runtime.NativeLibLoader.disableLoading();
+
+ // Open GlueGen runtime library optimistically. Note that
+ // currently we do not need this on any platform except X11
+ // ones, because JOGL doesn't use the GlueGen NativeLibrary
+ // class anywhere except the DRIHack class, but if for
+ // example we add JOAL support then we will need this on
+ // every platform.
+ loadLibrary(nativeLibDir, "gluegen-rt");
+
+ Class driHackClass = null;
+ if (nativeLibInfo.mayNeedDRIHack()) {
+ // Run the DRI hack
+ try {
+ driHackClass = Class.forName("com.sun.opengl.impl.x11.DRIHack");
+ driHackClass.getMethod("begin", new Class[] {}).invoke(null, new Object[] {});
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ // Load core JOGL native library
+ loadLibrary(nativeLibDir, "jogl");
+
+ if (nativeLibInfo.mayNeedDRIHack()) {
+ // End DRI hack
+ try {
+ driHackClass.getMethod("end", new Class[] {}).invoke(null, new Object[] {});
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (!nativeLibInfo.isMacOS()) { // borrowed from NativeLibLoader
+ // Must pre-load JAWT on all non-Mac platforms to
+ // ensure references from jogl_awt shared object
+ // will succeed since JAWT shared object isn't in
+ // default library path
+ try {
+ System.loadLibrary("jawt");
+ } catch (UnsatisfiedLinkError ex) {
+ // Accessibility technologies load JAWT themselves; safe to continue
+ // as long as JAWT is loaded by any loader
+ if (ex.getMessage().indexOf("already loaded") == -1) {
+ displayError("Unable to load JAWT");
+ throw ex;
+ }
+ }
+ }
+
+ // Load AWT-specific native code
+ loadLibrary(nativeLibDir, "jogl_awt");
+
+ if (haveJOAL) {
+ // Turn off the System.loadLibrary call of the joal_native
+ // library. It will still need to load the OpenAL library
+ // internally via another mechanism.
+ try {
+ Class c = Class.forName("net.java.games.joal.impl.NativeLibLoader");
+ c.getMethod("disableLoading", new Class[] {}).invoke(null, new Object[] {});
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ // Append the installed native library directory to
+ // java.library.path. This is the most convenient way to
+ // make this directory available to the NativeLibrary code,
+ // which needs it for loading OpenAL if present.
+ String javaLibPath = System.getProperty("java.library.path");
+ String absPath = nativeLibDir.getAbsolutePath();
+ boolean shouldSet = false;
+ if (javaLibPath == null) {
+ javaLibPath = absPath;
+ shouldSet = true;
+ } else if (javaLibPath.indexOf(absPath) < 0) {
+ javaLibPath = javaLibPath + File.pathSeparator + absPath;
+ shouldSet = true;
+ }
+ if (shouldSet) {
+ System.setProperty("java.library.path", javaLibPath);
+ }
+
+ // Load core JOAL native library
+ loadLibrary(nativeLibDir, "joal_native");
+ }
+
+ displayMessage("Starting applet " + subAppletDisplayName);
+
+ // start the subapplet
+ startSubApplet();
+ }
+ });
+ }
+
+ private void loadLibrary(File installDir, String libName) {
+ String nativeLibName = nativeLibInfo.getNativeLibName(libName);
+ try {
+ System.load(new File(installDir, nativeLibName).getPath());
+ } catch (UnsatisfiedLinkError ex) {
+ // Note: if we have loaded this particular copy of the
+ // JOGL-related native library in another class loader, the
+ // steps taken above to ensure the installation directory name
+ // was unique have failed. We can't continue properly in this
+ // case, so just print and re-throw the exception.
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ /** The true start of the sub applet (invoked in the EDT) */
+ private void startSubApplet(){
+ try {
+ subApplet = (Applet)Class.forName(subAppletClassName).newInstance();
+ subApplet.setStub(new AppletStubProxy());
+ } catch (ClassNotFoundException cnfe) {
+ cnfe.printStackTrace();
+ displayError("Class not found (" + subAppletClassName + ")");
+ return;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ displayError("Unable to start " + subAppletDisplayName);
+ return;
+ }
+
+ add(subApplet, BorderLayout.CENTER);
+
+ try {
+ subApplet.init();
+ remove(loaderPanel);
+ validate();
+ checkNoDDrawAndUpdateDeploymentProperties();
+ subApplet.start();
+ joglStarted = true;
+ } catch (Exception ex){
+ ex.printStackTrace();
+ }
+
+ }
+
+ /** a proxy to allow the subApplet to work like a real applet */
+ class AppletStubProxy implements AppletStub {
+ public boolean isActive() {
+ return JOGLAppletLauncher.this.isActive();
+ }
+
+ public URL getDocumentBase() {
+ return JOGLAppletLauncher.this.getDocumentBase();
+ }
+
+ public URL getCodeBase() {
+ return JOGLAppletLauncher.this.getCodeBase();
+ }
+
+ public String getParameter(String name) {
+ return JOGLAppletLauncher.this.getParameter(name);
+ }
+
+ public AppletContext getAppletContext() {
+ return JOGLAppletLauncher.this.getAppletContext();
+ }
+
+ public void appletResize(int width, int height) {
+ JOGLAppletLauncher.this.resize(width, height);
+ }
+ }
+}
+