Mega Code Archive

 
Categories / Java / Network Protocol
 

JNLP-based applet launcher class for deploying applets that use extension libraries containing native code

/*  * $RCSfile: JNLPAppletLauncher.java,v $  *  * Copyright 2007 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, licensed or  * intended for use in the design, construction, operation or  * maintenance of any nuclear facility.  *  * $Revision: 1.29 $  * $Date: 2008/10/21 21:33:50 $  * $State: Exp $  */ import java.applet.Applet; import java.applet.AppletContext; import java.applet.AppletStub; import java.awt.BorderLayout; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingUtilities; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /**  * The JNLPAppletLauncher is a general purpose JNLP-based applet  * launcher class for deploying applets that use extension libraries  * containing native code. It allows applets to use extensions like  * Java 3D, JOGL, and JOAL very easily, with just a few additional  * parameters to the <code>&lt;applet&gt;</code> tag, on Java SE  * versions as far back as 1.4.2.  *  * <p>  *  * Like Java Web Start, the JNLPAppletLauncher uses an extension's  * .jnlp file to locate the native resources for a given extension.  * The applet developer only needs to specify the platform-independent  * .jar files containing the .class files for the extension. The  * platform-specific "nativelib" .jar files are downloaded  * automatically from the same server that hosts the extension's Java  * Web Start binaries.  *  * <p>  *  * Extensions that support JNLPAppletLauncher include Java 3D, JOGL,  * and JOAL. More can be added without needing to modify the  * JNLPAppletLauncher. See the section below on <a  * href="#MODIFYING">modifying extensions to work with the  * JNLPAppletLauncher</a>.  *  * <h2> How to Deploy Applets Using the JNLPAppletLauncher </h2>  * <p>  *  * The <code>applet-launcher.jar</code> file containing the  * JNLPAppletLauncher class must be signed with the same certificate  * as the extension's native resources, for example "sun microsystems,  * inc.". The user will receive a security dialog and will be prompted  * to accept the certificate for the JLNPAppletLauncher.  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  * (in addition to the applet-launcher jar, if it is signed by a  * different entity).  *  * <p>  *  * The steps for deploying such applets are straightforward. First,  * the <code>archive</code> parameter to the applet tag must contain  * <code>applet-laucher.jar</code>, the extension .jar files, and any  * jar files associated with your applet. See the section on <a  * href="#ORGANIZING">organizing jar files</a> for more details.  *  * <p>  *  * Second, the name of your applet's main class and a textual  * description must be specified via the applet tag parameters  * <code>subapplet.classname</code> and  * <code>subapplet.displayname</code>.  *  * <p>  *  * Finally, the URLs for the extension .jnlp files being used must be  * specified as parameters. The <code>jnlpNumExtensions</code>  * parameter indicates the number of JNLP files that are referenced,  * and for <code>n</code> such files, their URLs are passed in as  * parameters <code>jnlpExtension1</code> ...  * <code>jnlpExtension[n]</code>.  *  * <h2><a name="ORGANIZING">Organizing Jar Files</a></h2>  *  * <p>  *  * Traditionally, applets are specified with a codebase and an archive  * parameter, the latter which is a list of jar files relative to that  * codebase. The codebase is optional and defaults to the directory on  * the web server containing the HTML document which contains the  * applet tag. See the documentation for the <a  * href="http://java.sun.com/j2se/1.4.2/docs/guide/misc/applet.html">applet  * tag</a>.  *  * <p>  *  * It is not well documented, but at least in the Sun JRE at least as  * far back as Java SE 1.4.2, it is possible to use absolute URLs in  * the applet tag's archive parameter. This functionality works on all  * major operating systems: Windows, Mac OS X, Linux, and Solaris.  * This means that you can pull code resources from multiple web  * servers, not just one, in similar fashion to Java Web Start and its  * extension mechanism. (The security implications are that each  * unsigned piece of code downloaded from a separate server receives  * sandboxed permissions to connect back to that server; if there are  * multiple pieces of unsigned code on the stack during execution of  * the program then the permissions will be the intersection of all of  * those on the stack, implying that no programmatic network  * connections back to the web server(s) will be allowed. See the <a  * href="http://java.sun.com/sfaq/">Applet Security FAQ</a> for more  * details.)  *  * <p>  *  * This capability means that your applets can refer directly to  * extensions like Java 3D and JOGL hosted on Sun's web servers  * without having to duplicate their jar files on your web server.  *  * <p>  *  * To use this capability effectively with the JNLPAppletLauncher, you  * need to pull in at least three primary pieces of code: the applet  * launcher itself, your applet's code, and the Java code for the  * extension, or extensions, your applet depends on. (Remember that  * the JNLPAppletLauncher's primary function is to automatically  * download the native code associated with these extensions, and not  * the Java code for these extensions.)  *  * <p>  *  * You might choose to specify the codebase of your applet to point to  * your web server's directory containing the jar files of your  * applet, and specify absolute URLs to the  * <code>applet-launcher.jar</code> and the extension jar files. Or  * you might decide to point the codebase to the server which hosts  * the applet launcher and specify all of the other resources,  * including your applet, with absolute URLs. Or you might decide to  * use all absolute URLs in your archive tag with no codebase. The  * techniques are basically equivalent. We recommend either pointing  * the codebase to the directory containing your applet's jars, using  * relative URLs for your applet, and using absolute URLs for all  * other resources; or using all absolute URLs.  *  * <p>  *  * Alternatively, you can re-host the jar files and/or JNLP files and  * nativelib jars for the extensions you use on your own web  * server. This has the advantage that your applet will connect to  * fewer web servers upon startup, but has the disadvantages of  * requiring additional maintenance on your part and not automatically  * receiving updates to the extensions when they are published.  *  * <p>  *  * <b>Note</b> that if you are planning to call into your applet from  * JavaScript that there are some <a  * href="#SCRIPTING">scripting-related caveats</a> that you need to be  * aware of.  *  * <p>  *  * The <code>jnlpExtension</code> parameters passed to the  * JNLPAppletLauncher must be specified with absolute URLs.  *  * <p>  *  * The <a href="#EXAMPLES">examples</a> show how to use the  * JNLPAppletLauncher in a few different scenarios.  *  * <h2>The codebase_lookup parameter</h2>  *  * <p>  *  * This applet parameter was not well documented until <a  * href="http://java.sun.com/javase/6/docs/technotes/guides/plugin/developer_guide/special_attributes.html#codebase">recently</a>,  * but it disables certain legacy behavior of the Java Plug-In. Before  * the introduction of jar files, applets used to host their class  * files and resources as flat files on the web server. Once jar files  * were introduced, it was possible to improve the efficiency of  * resource loading for applets, but (apparently) not without breaking  * compatibility. An applet can specify the parameter  *  * <pre>  * &lt;param name="codebase_lookup" value="false"&gt;  * </pre>  *  * <p>  *  * to improve efficiency of its loading if it does not rely on  * fetching flat files from the web server off the codebase.  * We recommend setting this parameter.  *  * <h2>Applets using the OpenGL(r) 3D API</h2>  *  * <p>  *  * Applets using the OpenGL 3D graphics API, for example through JOGL  * or Java 3D, may encounter robustness issues on the Windows platform  * because Sun's Java 2D implementation on Windows uses Microsoft's  * DirectDraw API. DirectDraw and OpenGL are incompatible at the  * driver level.  *  * <p>  *  * As a workaround for this problem, the JNLPAppletLauncher supports  * disabling the use of DirectDraw. Currently this can only be done on  * a global basis, for all applets, but doing so is unlikely to slow  * down other non-3D applets significantly.  *  * <p>  *  * Specifying the applet parameter  *  * <pre>  * &lt;param name="noddraw.check" value="true"&gt;  * </pre>  *  * <p>  *  * will cause the applet launcher, when run on Windows, to check to  * see whether DirectDraw is enabled and, if so, will prompt the user  * with a dialog box asking to disable it. A browser restart is  * required if the setting is changed.  *  * <p>  *  * If the dialog box is undesirable in a given situation, you can  * force the noddraw check to always disable DirectDraw with the two  * applet parameters:  *  * <pre>  * &lt;param name="noddraw.check" value="true"&gt;  * &lt;param name="noddraw.check.silent" value="true"&gt;  * </pre>  *  * <p>  *  * In this case it will not be obvious to the end user that a browser  * restart might be required for best robustness, but you could  * potentially document the need to try restarting the browser in case  * of instability.  *  * <h2><a name="SCRIPTING">Scripting Support</a></h2>  *  * <p>  *  * The JNLPAppletLauncher supports interaction with the sub-applet via  * the <code>getSubApplet()</code> method. Calling this method from  * JavaScript will return the subordinate applet with which you can  * then interact via JavaScript.  *  * <p>  *  * There are currently some scripting-related caveats associated with  * <a href="#ORGANIZING">pulling jar files from multiple locations</a>  * for a particular applet. In particular, it appears that the  * LiveConnect security model on Mac OS X in the Safari browser  * prohibits JavaScript from one domain from communicating with Java  * code (such as an applet) downloaded from another domain. This is  * correct according to older versions of the LiveConnect  * specification, although some more recent implementations of  * LiveConnect allow this, restricting the privileges of such calls in  * other ways.  *  * <p>  *  * The workaround for this problem seems to be to host the  * <code>applet-launcher.jar</code> on your web site if you need to  * talk to your applet from JavaScript. Your applet's jars will likely  * also need to be hosted from the same web server. If you talk to  * extension APIs in your <code>archive</code> tag directly from  * JavaScript, you may find it necessary to host those jars on your  * web server as well.  *  * <p>  *  * From a practical standpoint, most applet developers using  * JavaScript with the JNLPAppletLauncher will only need to re-host at  * most <code>applet-launcher.jar</code> on their web site.  *  * <h2><a name="EXAMPLES">Examples</a></h2>  *  * <p>  *  * An applet using JOGL as an extension. Note that this example does  * not specify a codebase, instead specifying all of its archive tag  * elements with absolute URLs (split here for readability; in a real  * applet tag they must be all on one line). Note also the use of the  * <code>noddraw.check</code> parameter to disable the use of  * DirectDraw since using JOGL implies the use of OpenGL.  *  * <pre>  * &lt;applet code="org.jdesktop.applet.util.JNLPAppletLauncher"  *      width=600  *      height=400  *      archive="http://download.java.net/media/applet-launcher/applet-launcher.jar,  *               http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jar,  *               http://download.java.net/media/gluegen/webstart/gluegen-rt.jar,  *               http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl-demos.jar"&gt;  *   &lt;param name="codebase_lookup" value="false"&gt;  *   &lt;param name="subapplet.classname" value="demos.applets.GearsApplet"&gt;  *   &lt;param name="subapplet.displayname" value="JOGL Gears Applet"&gt;  *   &lt;param name="noddraw.check" value="true"&gt;  *   &lt;param name="progressbar" value="true"&gt;  *   &lt;param name="jnlpNumExtensions" value="1"&gt;  *   &lt;param name="jnlpExtension1"  *          value="http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp"&gt;  * &lt;/applet&gt;  * </pre>  *  * <p>  *  * An applet using both JOGL and JOAL as extensions. Note again that  * all code resources are specified with absolute URLs. In this  * example the unsigned applet pulls in code from both  * <code>jogl-demos.jar</code> and <code>joal-demos.jar</code>. Note  * again the use of the <code>noddraw.check</code> parameter.  *  * <pre>  * &lt;applet code="org.jdesktop.applet.util.JNLPAppletLauncher"  *      width=600  *      height=400  *      archive="http://download.java.net/media/applet-launcher/applet-launcher.jar,  *               http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jar,  *               http://download.java.net/media/gluegen/webstart/gluegen-rt.jar,  *               http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl-demos.jar,  *               http://download.java.net/media/joal/webstart/joal.jar,  *               http://download.java.net/media/joal/webstart/joal-demos.jar"&gt;  *   &lt;param name="codebase_lookup" value="false"&gt;  *   &lt;param name="subapplet.classname" VALUE="demos.applets.GearsJOALApplet"&gt;  *   &lt;param name="subapplet.displayname" VALUE="JOGL / JOAL Gears Applet"&gt;  *   &lt;param name="noddraw.check" value="true"&gt;  *   &lt;param name="progressbar" value="true"&gt;  *   &lt;param name="jnlpNumExtensions" value="2"&gt;  *   &lt;param name="jnlpExtension1"  *          value="http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp"&gt;  *   &lt;param name="jnlpExtension2"  *          value="http://download.java.net/media/joal/webstart/joal.jnlp"&gt;  * &lt;/applet&gt;  * </pre>  *  * <p>  * An applet using Java 3D as an extension:  *  * <pre>  * &lt;applet code="org.jdesktop.applet.util.JNLPAppletLauncher"  *      width=400  *      height=300  *      archive="myapplet.jar,  *               http://download.java.net/media/applet-launcher/applet-launcher.jar,  *               http://download.java.net/media/java3d/webstart/release/j3d/latest/j3dcore.jar,  *               http://download.java.net/media/java3d/webstart/release/j3d/latest/j3dutils.jar,  *               http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jar,  *               http://download.java.net/media/gluegen/webstart/gluegen-rt.jar,  *               http://download.java.net/media/java3d/webstart/release/vecmath/latest/vecmath.jar"&gt;  *   &lt;param name="codebase_lookup" value="false"&gt;  *   &lt;param name="subapplet.classname" value="mypkg.MyApplet"&gt;  *   &lt;param name="subapplet.displayname" value="My Java 3D Applet"&gt;  *   &lt;param name="jnlpNumExtensions" value="1"&gt;  *   &lt;param name="jnlpExtension1" value="http://download.java.net/media/java3d/webstart/release/java3d-latest.jnlp"&gt;  *   &lt;param name="progressbar" value="true"&gt;  *   &lt;param name="noddraw.check" value="true"&gt;  * &lt;/applet&gt;  * </pre>  *  * <p>  * Note that the JOGL jar files are also included in this example. This is  * necessary in order to run on Mac OS X, for which Java 3D always uses  * JOGL to render.  *  * <h2> Locations of Standard Extensions </h2>  *  * <p>  *  * This section describes how to set up the <code>archive</code> and  * <code>jnlpExtension</code> parameters for a few standard  * extensions.  *  * <h4>JNLPAppletLauncher</h4>  *  * <p>  *  * The master jar file for the JNLPAppletLauncher is located at the following URL:  * <pre>  * http://download.java.net/media/applet-launcher/applet-launcher.jar  * </pre>  *  * <p>  *  * This jar needs to be added to your archive parameter.  *  * <h4>Java 3D</h4>  *  * <p>  *  * Java 3D 1.5.1 and later supports the  * JNLPAppletLauncher. You will need to add the following URLs to your  * archive parameter:  *  * <pre>  * http://download.java.net/media/java3d/webstart/release/j3d/latest/j3dcore.jar  * http://download.java.net/media/java3d/webstart/release/j3d/latest/j3dutils.jar  * http://download.java.net/media/java3d/webstart/release/vecmath/latest/vecmath.jar  * </pre>  *  * Then add the following to one of your <code>jnlpExtension</code> parameters:  *  * <pre>  * http://download.java.net/media/java3d/webstart/release/java3d-latest.jnlp  * </pre>  *  * If you want to deploy your applet on Mac OS X, you will also need to  * include JOGL by adding the following URLs to your archive parameter:  *  * <pre>  * http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jar  * http://download.java.net/media/gluegen/webstart/gluegen-rt.jar  * </pre>  *  * Note that this will only work if Java 3D is not installed into the JRE as an  * extension. Since Apple ships their JRE with Java 3D pre-installed, the  * end-user must uninstall Java 3D in order for Java 3D applets to work on Mac.  * <p>  *  * Note that the Java 3D .jnlp extension will automatically pull in the  * native code associated with JOGL and the GlueGen runtime, so you don't have  * to separately refer to those .jnlp files.  *  * <h4>JOGL</h4>  *  * <p>  *  * JOGL 1.1.1-rc3 and later support the JNLPAppletLauncher. You will  * need to add the following URL to your archive parameter:  *  * <pre>  * http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jar  * </pre>  *  * <p>  *  * Because JOGL depends on the GlueGen runtime, you will also need to  * add the following URL to your archive parameter:  *  * <pre>  * http://download.java.net/media/gluegen/webstart/gluegen-rt.jar  * </pre>  *  * <p>  *  * Finally, add the following to one of your  * <code>jnlpExtension</code> parameters:  *  * <pre>  * http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp  * </pre>  *  * <p>  *  * Note that the jogl.jnlp extension will automatically pull in the  * native code associated with the GlueGen runtime, so you don't have  * to separately refer to the gluegen-rt.jnlp file.  *  * <h4>JOAL</h4>  *  * <p>  *  * JOAL 1.1.1 and later support the JNLPAppletLauncher. You will need  * to add the following URL to your archive parameter:  *  * <pre>  * http://download.java.net/media/joal/webstart/joal.jar  * </pre>  *  * <p>  *  * Because JOAL, like JOGL, depends on the GlueGen runtime, you will  * also need to add the following URL to your archive parameter:  *  * <pre>  * http://download.java.net/media/gluegen/webstart/gluegen-rt.jar  * </pre>  *  * <p>  *  * (If you are using both JOGL and JOAL, you only need to refer to  * gluegen-rt.jar once in your archive parameter.)  *  * <p>  *  * Finally, add the following to one of your  * <code>jnlpExtension</code> parameters:  *  * <pre>  * http://download.java.net/media/joal/webstart/joal.jnlp  * </pre>  *  * <p>  *  * Note that the joal.jnlp extension will automatically pull in the  * native code associated with the GlueGen runtime, so you don't have  * to separately refer to the gluegen-rt.jnlp file.  *  * <h2><a name="MODIFYING">Modifying Your Extension To Work With The JNLPAppletLauncher</a></h2>  *  * <p>  *  * If you are the author of an extension like JOGL which requires some  * native code, with only a simple code change you can make your  * extension work with the JNLPAppletLauncher. Simply add the  * following method somewhere in your code:  *  * <pre>  *  private static void loadLibraryInternal(String libraryName) {  *      String sunAppletLauncher = System.getProperty("sun.jnlp.applet.launcher");  *      boolean usingJNLPAppletLauncher =  *          Boolean.valueOf(sunAppletLauncher).booleanValue();  *  *      boolean loaded = false;  *      if (usingJNLPAppletLauncher) {  *          try {  *              Class jnlpAppletLauncherClass =  *                  Class.forName("org.jdesktop.applet.util.JNLPAppletLauncher");  *              Method jnlpLoadLibraryMethod =  *                  jnlpAppletLauncherClass.getDeclaredMethod("loadLibrary",  *                                                            new Class[] { String.class });  *              jnlpLoadLibraryMethod.invoke(null, new Object[] { libraryName });  *              loaded = true;  *          } catch (ClassNotFoundException ex) {  *              System.err.println("loadLibrary(" + libName + ")");  *              System.err.println(ex);  *              System.err.println("Attempting to use System.loadLibrary instead");  *          } catch (Exception e) {  *              Throwable t = e;  *              if (t instanceof InvocationTargetException) {  *                  t = ((InvocationTargetException) t).getTargetException();  *              }  *              if (t instanceof Error)  *                  throw (Error) t;  *              if (t instanceof RuntimeException) {  *                  throw (RuntimeException) t;  *              }  *              // Throw UnsatisfiedLinkError for best compatibility with System.loadLibrary()  *              throw (UnsatisfiedLinkError) new UnsatisfiedLinkError().initCause(e);  *          }  *      }  *  *      if (!loaded) {  *          System.loadLibrary(libraryName);  *      }  *  }  * </pre>  *  * <p>  *  * and wherever you would call <code>System.loadLibrary()</code> (from  * within an <code>AccessController.doPrivileged()</code> block) to  * load your extension's native code, call the above  * <code>loadLibraryInternal</code> method instead.  *  * <p>  *  * Note again that because the <code>applet-launcher.jar</code> and  * the nativelib jars for all extensions must currently be signed with  * the same certificate, this implies that you must resign both the  * applet launcher as well as any other extensions your applet relies  * on (unless yours is a Sun-standard extension and can be signed with  * Sun's code signing certificate).  *  * <h2>Acknowledgments</h2>  *  * <p>  *  * The JNLPAppletLauncher was developed by Kevin Rushforth, Kenneth  * Russell, and Chien Yang. It is based on the earlier  * JOGLAppletLauncher developed by Lilian Chamontin.  */ public class JNLPAppletLauncher extends Applet {     private static final boolean VERBOSE = false;     private static final boolean DEBUG = false;     // Indicated that the applet was successfully initialized     private boolean isInitOk = false;     // True the first time start is called, false afterwards     private boolean firstStart = true;     // Indicates that the applet was started successfully     private boolean appletStarted = false;     // The applet we have to start     private Applet subApplet;     // Class name of applet to load (required)     private String subAppletClassName; // from applet PARAM subapplet.classname     // String representing the name of the applet (optional)     private String subAppletDisplayName; // from applet PARAM subapplet.displayname     // URL to an image that we will display while installing (optional)     private URL subAppletImageURL; // from applet PARAM subapplet.image     // Panel that will hold the splash-screen image and progress bar while loading     private JPanel loaderPanel;     // Helpers for updating deployment.properties with -Dsun.java2d.noddraw=true     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";     // Optional progress bar     private JProgressBar progressBar = null;     /*      * The following variables are defined per-applet, but we can assert that      * they will not differ for each applet that is loaded by the same      * ClassLoader. This means we can just cache the values from the first      * applet. We will check the values for subsequent applets and throw an      * exception if there are any differences.      */     // Flag indicating that this is the first applet     private static boolean firstApplet = true;     // List of extension JNLP files. This is saved for the first applet     // and verified for each subsequent applet.     private static List/*<URL>*/ jnlpExtensions = null;     // Code base and archive tag for all applets that use the same ClassLoader     private static URL codeBase;     private static String archive = null;     // Persistent cache directory for storing native libraries and time stamps.     // The directory is of the form:     //     //     ${user.home}/.jnlp-applet/cache/<HOSTNAME>/<DIGEST-OF-CODEBASE-ARCHIVE>     //     private static File cacheDir;     // Set of jar files specified in the JNLP files.     // Currently unused.     private static Set/*<URL>*/ jarFiles;     // Set of native jar files to be loaded. We need to download these     // native jars, verify the signatures, verify the security certificates,     // and extract the native libraries from each jar.     private static Set/*<URL>*/ nativeJars;     // Native library prefix (e.g., "lib") and suffix (e.g. ".dll" or ".so")     private static String nativePrefix;     private static String nativeSuffix;     // A HashMap of native libraries that can be loaded with System.load()     // The key is the string name of the library as passed into the loadLibrary     // call; it is the file name without the directory or the platform-dependent     // library prefix and suffix. The value is the absolute path name to the     // unpacked library file in nativeTmpDir.     private static Map/*<String, String>*/ nativeLibMap;     /*      * The following variables are per-ClassLoader static globals.      */     // Flag indicating that we got a fatal error in the static initializer.     // If this happens we will not attempt to start any applets.     private static boolean staticInitError = false;     // Base temp directory used by JNLPAppletLauncher. This is set to:     //     // ${java.io.tmpdir}/jnlp-applet     //     private static File tmpBaseDir;     // String representing the name of the temp root directory relative to the     // tmpBaseDir. Its value is "jlnNNNNN", which is the unique filename created     // by File.createTempFile() without the ".tmp" extension.     //     private static String tmpRootPropValue;     // Root temp directory for this JVM instance. Used to store the individual,     // per-ClassLoader directories that will be used to load native code. The     // directory name is:     //     // <tmpBaseDir>/<tmpRootPropValue>     //     // Old temp directories are cleaned up the next time a JVM is launched that     // uses JNLPAppletLauncher.     //     private static File tmpRootDir;     // Temporary directory for loading native libraries for this instance of     // the class loader. The directory name is:     //     // <tmpRootDir>/jlnMMMMM     //     // where jlnMMMMM is the unique filename created by File.createTempFile()     // without the ".tmp" extension.     //     private static File nativeTmpDir;     /*      * IMPLEMENTATION NOTES      *      * Assumptions:      *      * A. Multiple applets can be launched from the same class loader, and thus      *    share the same set of statics and same set of native library symbols.      *    This can only happen if the codebase and set of jar files as specified      *    in the archive tag are identical. Applets launched from different code      *    bases or whose set of jar files are different will always get a      *    different ClassLoader. If this assumption breaks, too many other      *    things wouldn't work properly, so we can be assured that it will hold.      *    However, we cannot assume that the converse is true; it is possible      *    that two applets with the same codebase and archive tag will be loaded      *    from a different ClassLoader.      *      * B. Given the above, this means that we must store the native libraries,      *    and keep track of which ones have already been loaded statically, that      *    is, per-ClassLoader rather than per-Applet. This is a good thing,      *    because it turns out to be difficult (at best) to find the instance of      *    the Applet at loadLibrary time.      *      * Our solution is as follows:      *      *    Use the same criteria for determining the cache dir that JPI      *    uses to determine the class loader to use. More precisely, we will      *    create a directory based on the codebase and complete set of jar files      *    specified by the archive tag. To support the case where each applet is      *    in a unique class loader, we will copy the native libraries into a      *    unique-per-ClassLoader temp directory and do the System.load() from      *    there. For a robust solution, we need to lock the cache directory      *    during validation, since multiple threads, or even multiple processes,      *    can access it concurrently.      *      * TODO: We need a way to clear the cache.      *      * We also considered, but rejected, the following solutions:      *      * 1. Use a temporary directory for native jars, download, verify, unpack,      *    and loadLibrary in this temp dir. No persistent cache.      *      * 2. Cache the native libraries in a directory based on the codebase and      *    the extension jars (i.e., the subset of the jars in the archive tag      *    that also appear in one of the extension JNLP files). Copy the native      *    libraries into a unique-per-ClassLoader temp directory and load from      *    there. Note that this has the potential problem of violating the      *    assertion that two different applets that share the same ClassLoader      *    must map to the same cache directory.      *      * 3. Use the exact criteria for determining the cache dir that JPI      *    uses to determine which class loader to use (as in our proposed      *    solution above), unpack the native jars into the cache directory and      *    load from there. This obviates the need for locking, but it will break      *    if the JPI ever isolates each applet into its own ClassLoader.      */     /**      * Constructs an instance of the JNLPAppletLauncher class. This is called by      * Java Plug-in, and should not be called directly by an application or      * applet.      */     public JNLPAppletLauncher() {     }     /* @Override */     public void init() {         if (VERBOSE) {             System.err.println();         }         if (DEBUG) {             System.err.println("Applet.init");         }         if (staticInitError) {             return;         }         subAppletClassName = getParameter("subapplet.classname");         if (subAppletClassName == null) {             displayError("Init failed : Missing subapplet.classname parameter");             return;         }         subAppletDisplayName = getParameter("subapplet.displayname");         if (subAppletDisplayName == null) {             subAppletDisplayName = "Applet";         }         subAppletImageURL = null;         try {             String subAppletImageStr = getParameter("subapplet.image");             if (subAppletImageStr != null && subAppletImageStr.length() > 0) {                 subAppletImageURL = new URL(subAppletImageStr);             }         } catch (IOException ex) {             ex.printStackTrace();             // Continue with a null subAppletImageURL         }         if (DEBUG) {             System.err.println("subapplet.classname = " + subAppletClassName);             System.err.println("subapplet.displayname = " + subAppletDisplayName);             if (subAppletImageURL != null) {                 System.err.println("subapplet.image = " + subAppletImageURL.toExternalForm());             }         }         initLoaderLayout();         isInitOk = true;     }     /* @Override */     public void start() {         if (DEBUG) {             System.err.println("Applet.start");         }         if (isInitOk) {             if (firstStart) { // first time                 firstStart = false;                 Thread startupThread = new Thread() {                     public void run() {                         initAndStartApplet();                     }                 };                 startupThread.setName("AppletLauncher-Startup");                 startupThread.setPriority(Thread.NORM_PRIORITY - 1);                 startupThread.start();             } else if (appletStarted) {                 checkNoDDrawAndUpdateDeploymentProperties();                 // We have to start again the applet (start can be called multiple times,                 // e.g once per tabbed browsing                 subApplet.start();             }         }     }     /* @Override */     public void stop(){         if (subApplet != null) {             subApplet.stop();         }     }     /* @Override */     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;     }     //----------------------------------------------------------------------     // Support for forwarding notifications about dragged-out applets     //     public void appletDragStarted() {         try {             Method m = getSubApplet().getClass().getMethod("appletDragStarted", null);             m.invoke(getSubApplet(), null);         } catch (Throwable t) {         }     }     public void appletDragFinished() {         try {             Method m = getSubApplet().getClass().getMethod("appletDragFinished", null);             m.invoke(getSubApplet(), null);         } catch (Throwable t) {         }     }     public void appletRestored() {         try {             Method m = getSubApplet().getClass().getMethod("appletRestored", null);             m.invoke(getSubApplet(), null);         } catch (Throwable t) {         }     }     public boolean isAppletDragStart(MouseEvent e) {         try {             Method m = getSubApplet().getClass().getMethod("isAppletDragStart",                                                            new Class[] { MouseEvent.class });             return ((Boolean) m.invoke(getSubApplet(), new Object[] { e })).booleanValue();         } catch (Throwable t) {             // Throw an exception back to the Java Plug-In to cause it             // to use the default functionality             throw new RuntimeException(t);         }     }     public void setAppletCloseListener(ActionListener l) {         try {             Method m = getSubApplet().getClass().getMethod("setAppletCloseListener",                                                            new Class[] { ActionListener.class });             m.invoke(getSubApplet(), new Object[] { l });         } catch (Throwable t) {             // Throw an exception back to the Java Plug-In to cause it             // to use the default functionality             throw new RuntimeException(t);         }     }     /**      * This method is called by the static initializer to create / initialize      * the temp root directory that will hold the temp directories for this      * instance of the JVM. This is done as follows:      *      *     1. Synchronize on a global lock. Note that for this purpose we will      *        use System.out in the absence of a true global lock facility.      *        We are careful not to hold this lock too long.      *      *     2. Check for the existence of the "jnlp.applet.launcher.tmproot"      *        system property.      *      *         a. If set, then some other thread in a different ClassLoader has      *            already created the tmprootdir, so we just need to      *            use it. The remaining steps are skipped.      *      *         b. If not set, then we are the first thread in this JVM to run,      *            and we need to create the the tmprootdir.      *      *     3. Create the tmprootdir, along with the appropriate locks.      *        Note that we perform the operations in the following order,      *        prior to creating tmprootdir itself, to work around the fact that      *        the file creation and file lock steps are not atomic, and we need      *        to ensure that a newly-created tmprootdir isn't reaped by a      *        concurrently running JVM.      *      *            create jlnNNNN.tmp using File.createTempFile()      *            lock jlnNNNN.tmp      *            create jlnNNNN.lck while holding the lock on the .tmp file      *            lock jlnNNNN.lck      *      *        Since the Reaper thread will enumerate the list of *.lck files      *        before starting, we can guarantee that if there exists a *.lck file      *        for an active process, then the corresponding *.tmp file is locked      *        by that active process. This guarantee lets us avoid reaping an      *        active process' files.      *      *     4. Set the "jnlp.applet.launcher.tmproot" system property.      *      *     5. Add a shutdown hook to cleanup jlnNNNN.lck and jlnNNNN.tmp. We      *        don't actually expect that this shutdown hook will ever be called,      *        but the act of doing this, ensures that the locks never get      *        garbage-collected, which is necessary for correct behavior when      *        the first ClassLoader is later unloaded, while subsequent Applets      *        are still running.      *      *     6. Start the Reaper thread to cleanup old installations.      */     private static void initTmpRoot() throws IOException {         if (VERBOSE) {             System.err.println("---------------------------------------------------");         }         synchronized (System.out) {             // Get the name of the tmpbase directory.             String tmpBaseName = System.getProperty("java.io.tmpdir") +                     File.separator + "jnlp-applet";             tmpBaseDir = new File(tmpBaseName);             // Get the value of the tmproot system property             final String tmpRootPropName = "jnlp.applet.launcher.tmproot";             tmpRootPropValue = System.getProperty(tmpRootPropName);             if (tmpRootPropValue == null) {                 // Create the tmpbase directory if it doesn't already exist                 tmpBaseDir.mkdir();                 if (!tmpBaseDir.isDirectory()) {                     throw new IOException("Cannot create directory " + tmpBaseDir);                 }                 // Create ${tmpbase}/jlnNNNN.tmp then lock the file                 File tmpFile = File.createTempFile("jln", ".tmp", tmpBaseDir);                 if (VERBOSE) {                     System.err.println("tmpFile = " + tmpFile.getAbsolutePath());                 }                 final FileOutputStream tmpOut = new FileOutputStream(tmpFile);                 final FileChannel tmpChannel = tmpOut.getChannel();                 final FileLock tmpLock = tmpChannel.lock();                 // Strip off the ".tmp" to get the name of the tmprootdir                 String tmpFileName = tmpFile.getAbsolutePath();                 String tmpRootName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp"));                 // create ${tmpbase}/jlnNNNN.lck then lock the file                 String lckFileName = tmpRootName + ".lck";                 File lckFile = new File(lckFileName);                 if (VERBOSE) {                     System.err.println("lckFile = " + lckFile.getAbsolutePath());                 }                 lckFile.createNewFile();                 final FileOutputStream lckOut = new FileOutputStream(lckFile);                 final FileChannel lckChannel = lckOut.getChannel();                 final FileLock lckLock = lckChannel.lock();                 // Create tmprootdir                 tmpRootDir = new File(tmpRootName);                 if (DEBUG) {                     System.err.println("tmpRootDir = " + tmpRootDir.getAbsolutePath());                 }                 if (!tmpRootDir.mkdir()) {                     throw new IOException("Cannot create " + tmpRootDir);                 }                 // Add shutdown hook to cleanup the OutputStream, FileChannel,                 // and FileLock for the jlnNNNN.lck and jlnNNNN.lck files.                 // We do this so that the locks never get garbage-collected.                 Runtime.getRuntime().addShutdownHook(new Thread() {                     /* @Override */                     public void run() {                         // NOTE: we don't really expect that this code will ever                         // be called. If it does, we will close the output                         // stream, which will in turn close the channel.                         // We will then release the lock.                         try {                             tmpOut.close();                             tmpLock.release();                             lckOut.close();                             lckLock.release();                         } catch (IOException ex) {                             // Do nothing                         }                     }                 });                 // Set the system property...                 tmpRootPropValue = tmpRootName.substring(tmpRootName.lastIndexOf(File.separator) + 1);                 System.setProperty(tmpRootPropName, tmpRootPropValue);                 if (VERBOSE) {                     System.err.println("Setting " + tmpRootPropName + "=" + tmpRootPropValue);                 }                 // Start a new Reaper thread to do stuff...                 Thread reaperThread = new Thread() {                     /* @Override */                     public void run() {                         deleteOldTempDirs();                     }                 };                 reaperThread.setName("AppletLauncher-Reaper");                 reaperThread.start();             } else {                 // Make sure that the property is not set to an illegal value                 if (tmpRootPropValue.indexOf('/') >= 0 ||                         tmpRootPropValue.indexOf(File.separatorChar) >= 0) {                     throw new IOException("Illegal value of: " + tmpRootPropName);                 }                 // Set tmpRootDir = ${tmpbase}/${jnlp.applet.launcher.tmproot}                 if (VERBOSE) {                     System.err.println("Using existing value of: " +                             tmpRootPropName + "=" + tmpRootPropValue);                 }                 tmpRootDir = new File(tmpBaseDir, tmpRootPropValue);                 if (DEBUG) {                     System.err.println("tmpRootDir = " + tmpRootDir.getAbsolutePath());                 }                 if (!tmpRootDir.isDirectory()) {                     throw new IOException("Cannot access " + tmpRootDir);                 }             }         }     }     /**      * Called by the Reaper thread to delete old temp directories      * Only one of these threads will run per JVM invocation.      */     private static void deleteOldTempDirs() {         if (VERBOSE) {             System.err.println("*** Reaper: deleteOldTempDirs in " +                     tmpBaseDir.getAbsolutePath());         }         // enumerate list of jnl*.lck files, ignore our own jlnNNNN file         final String ourLockFile = tmpRootPropValue + ".lck";         FilenameFilter lckFilter = new FilenameFilter() {             /* @Override */             public boolean accept(File dir, String name) {                 return name.endsWith(".lck") && !name.equals(ourLockFile);             }         };         // For each file <file>.lck in the list we will first try to lock         // <file>.tmp if that succeeds then we will try to lock <file>.lck         // (which should always succeed unless there is a problem). If we can         // get the lock on both files, then it must be an old installation, and         // we will delete it.         String[] fileNames = tmpBaseDir.list(lckFilter);         if (fileNames != null) {             for (int i = 0; i < fileNames.length; i++) {                 String lckFileName = fileNames[i];                 String tmpDirName = lckFileName.substring(0, lckFileName.lastIndexOf(".lck"));                 String tmpFileName = tmpDirName + ".tmp";                 File lckFile = new File(tmpBaseDir, lckFileName);                 File tmpFile = new File(tmpBaseDir, tmpFileName);                 File tmpDir = new File(tmpBaseDir, tmpDirName);                 if (lckFile.exists() && tmpFile.exists() && tmpDir.isDirectory()) {                     FileOutputStream tmpOut = null;                     FileChannel tmpChannel = null;                     FileLock tmpLock = null;                     try {                         tmpOut = new FileOutputStream(tmpFile);                         tmpChannel = tmpOut.getChannel();                         tmpLock = tmpChannel.tryLock();                     } catch (Exception ex) {                         // Ignore exceptions                         if (DEBUG) {                             ex.printStackTrace();                         }                     }                     if (tmpLock != null) {                         FileOutputStream lckOut = null;                         FileChannel lckChannel = null;                         FileLock lckLock = null;                         try {                             lckOut = new FileOutputStream(lckFile);                             lckChannel = lckOut.getChannel();                             lckLock = lckChannel.tryLock();                         } catch (Exception ex) {                             if (DEBUG) {                                 ex.printStackTrace();                             }                         }                         if (lckLock != null) {                             // Recursively remove the old tmpDir and all of                             // its contents                             removeAll(tmpDir);                             // Close the streams and delete the .lck and .tmp                             // files. Note that there is a slight race condition                             // in that another process could open a stream at                             // the same time we are trying to delete it, which will                             // prevent deletion, but we won't worry about it, since                             // the worst that will happen is we might have an                             // occasional 0-byte .lck or .tmp file left around                             try {                                 lckOut.close();                             } catch (IOException ex) {                             }                             lckFile.delete();                             try {                                 tmpOut.close();                             } catch (IOException ex) {                             }                             tmpFile.delete();                         } else {                             try {                                 // Close the file and channel for the *.lck file                                 if (lckOut != null) {                                     lckOut.close();                                 }                                 // Close the file/channel and release the lock                                 // on the *.tmp file                                 tmpOut.close();                                 tmpLock.release();                             } catch (IOException ex) {                                 if (DEBUG) {                                     ex.printStackTrace();                                 }                             }                         }                     }                 } else {                     if (VERBOSE) {                         System.err.println("    Skipping: " + tmpDir.getAbsolutePath());                     }                 }             }         }     }     /**      * Remove the specified file or directory. If "path" is a directory, then      * recursively remove all entries, then remove the directory itself.      */     private static void removeAll(File path) {         if (VERBOSE) {             System.err.println("removeAll(" + path + ")");         }         if (path.isDirectory()) {             // Recursively remove all files/directories in this directory             File[] list = path.listFiles();             if (list != null) {                 for (int i = 0; i < list.length; i++) {                     removeAll(list[i]);                 }             }         }         path.delete();     }     /**      * This method is executed from outside the Event Dispatch Thread. It      * initializes, downloads, and unpacks the required native libraries into      * the cache, and then starts the applet on the EDT.      */     private void initAndStartApplet() {         // Parse the extension JNLP files and download the native resources         try {             initResources();         } catch (Exception ex) {             ex.printStackTrace();             displayError(toErrorString(ex));             return;         }         // Indicate that we are starting the applet         displayMessage("Starting applet " + subAppletDisplayName);         setProgress(0);         // Now schedule the starting of the subApplet on the EDT         SwingUtilities.invokeLater(new Runnable() {             public void run() {                 // start the subapplet                 startSubApplet();             }         });     }     /**      * Initializes, downloads, and extracts the native resources needed by this      * applet.      */     private void initResources() throws IOException {         synchronized (JNLPAppletLauncher.class) {             if (firstApplet) {                 // Save codeBase and archive parameter for assertion checking                 codeBase = getCodeBase();                 assert codeBase != null;                 archive = getParameter("archive");                 if (archive == null || archive.length() == 0) {                     throw new IllegalArgumentException("Missing archive parameter");                 }                 // Initialize the collections of resources                 jarFiles = new HashSet/*<URL>*/();                 nativeJars = new HashSet/*<URL>*/();                 nativeLibMap = new HashMap/*<String, String>*/();             } else {                 // The following must hold for applets in the same ClassLoader                 assert getCodeBase().equals(codeBase);                 assert getParameter("archive").equals(archive);             }             int jnlpNumExt = -1;             String numParamString = getParameter("jnlpNumExtensions");             if (numParamString != null) {                 try {                     jnlpNumExt = Integer.parseInt(numParamString);                 } catch (NumberFormatException ex) {                 }                 if (jnlpNumExt <= 0) {                     throw new IllegalArgumentException("Missing or invalid jnlpNumExtensions parameter");                 }             }             List/*<URL>*/ urls = new ArrayList/*<URL>*/();             for (int i = 1; i <= jnlpNumExt; i++) {                 String paramName = "jnlpExtension" + i;                 String urlString = getParameter(paramName);                 if (urlString == null || urlString.length() == 0) {                     throw new IllegalArgumentException("Missing " + paramName + " parameter");                 }                 URL url = new URL(urlString);                 urls.add(url);             }             // If this is the first time, process the list of extensions and             // save the results. Otherwise, verify that the list of extensions             // is the same as the first applet.             if (firstApplet) {                 jnlpExtensions = urls;                 parseJNLPExtensions(urls);                 if (VERBOSE) {                     System.err.println();                     System.err.println("All files successfully parsed");                     printResources();                 }                 if (nativeJars.size() > 0) {                     // Create the cache directory if not already created.                     // Create the temporary directory that will hold a copy of                     // the extracted native libraries, then copy each native                     // library into the temp dir so we can call System.load().                     createCacheDir();                     createTmpDir();                     // Download and validate the set of native jars, if the                     // cache is out of date. Then extract the native DLLs,                     // creating a list of native libraries to be loaded.                     for (Iterator iter = nativeJars.iterator(); iter.hasNext(); ) {                         URL url = (URL) iter.next();                         processNativeJar(url);                     }                 }                 // Set a system property that libraries can use to know when to call                 // JNLPAppletLauncher.loadLibrary instead of System.loadLibrary                 System.setProperty("sun.jnlp.applet.launcher", "true");             } else {                 // Verify that the list of jnlpExtensions is the same as the                 // first applet                 if (!jnlpExtensions.equals(urls)) {                     throw new IllegalArgumentException(                             "jnlpExtension parameters do not match previously loaded applet");                 }             }             firstApplet = false;         }     }     /**      * Detemine the cache directory location based on the codebase and archive      * tag. Create the cache directory if not already created.      */     private void createCacheDir() throws IOException {         StringBuffer cacheBaseName = new StringBuffer();         cacheBaseName.append(System.getProperty("user.home")).append(File.separator).                 append(".jnlp-applet").append(File.separator).                 append("cache");         File cacheBaseDir = new File(cacheBaseName.toString());         if (VERBOSE) {             System.err.println("cacheBaseDir = " + cacheBaseDir.getAbsolutePath());         }         cacheDir = new File(cacheBaseDir, getCacheDirName());         if (VERBOSE) {             System.err.println("cacheDir = " + cacheDir.getAbsolutePath());         }         // Create cache directory and load native library         if (!cacheDir.isDirectory()) {             if (!cacheDir.mkdirs()) {                 throw new IOException("Cannot create directory " + cacheDir);             }         }         assert cacheBaseDir.isDirectory();     }     /**      * Returns a directory name of the form: hostname/hash(codebase,archive)      */     private String getCacheDirName() {         final String codeBasePath = getCodeBase().toExternalForm();         // Extract the host name; replace characters in the set ".:\[]" with "_"         int hostIdx1 = -1;         int hostIdx2 = -1;         String hostNameDir = "UNKNOWN";         hostIdx1 = codeBasePath.indexOf("://");         if (hostIdx1 >= 0) {             hostIdx1 += 3; // skip the "://"             // Verify that the character immediately following the "://"             // exists and is not a "/"             if (hostIdx1 < codeBasePath.length() &&                     codeBasePath.charAt(hostIdx1) != '/') {                 hostIdx2 = codeBasePath.indexOf('/', hostIdx1);                 if (hostIdx2 > hostIdx1) {                     hostNameDir = codeBasePath.substring(hostIdx1, hostIdx2).                             replace('.', '_').                             replace(':', '_').                             replace('\\', '_').                             replace('[', '_').                             replace(']', '_');                 }             }         }         // Now concatenate the codebase and the list of jar files in the archive         // Separate them by an "out-of-band" character which cannot appear in         // either the codeBasePath or archive list.         StringBuffer key = new StringBuffer();         key.append(codeBasePath).                 append("\n").                 append(getParameter("archive"));         if (VERBOSE) {             System.err.println("key = " + key);         }         StringBuffer result = new StringBuffer();         result.append(hostNameDir).                 append(File.separator).                 append(sha1Hash(key.toString()));         if (VERBOSE) {             System.err.println("result = " + result);         }         return result.toString();     }     /**      * Produces a 40-byte SHA-1 hash of the input string.      */     private static String sha1Hash(String str) {         MessageDigest sha1 = null;         try {             sha1 = MessageDigest.getInstance("SHA-1");         } catch (NoSuchAlgorithmException ex) {             throw new RuntimeException(ex);         }         byte[] digest = sha1.digest(str.getBytes());         if (digest == null || digest.length == 0) {             throw new RuntimeException("Error reading message digest");         }         StringBuffer res = new StringBuffer();         for (int i = 0; i < digest.length; i++) {             int val = (int)digest[i] & 0xFF;             if (val < 0x10) {                 res.append("0");             }             res.append(Integer.toHexString(val));         }         return res.toString();     }     /**      * Create the temp directory in tmpRootDir. To do this, we create a temp      * file with a ".tmp" extension, and then create a directory of the      * same name but without the ".tmp". The temp file, directory, and all      * files in the directory will be reaped the next time this is started.      * We avoid deleteOnExit, because it doesn't work reliably.      */     private void createTmpDir() throws IOException {         if (VERBOSE) {             System.err.println("---------------------------------------------------");         }         File tmpFile = File.createTempFile("jln", ".tmp", tmpRootDir);         String tmpFileName = tmpFile.getAbsolutePath();         String tmpDirName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".tmp"));         nativeTmpDir = new File(tmpDirName);         if (VERBOSE) {             System.err.println("tmpFile = " + tmpFile.getAbsolutePath() +                     "  tmpDir = " + nativeTmpDir.getAbsolutePath());         }         if (!nativeTmpDir.mkdir()) {             throw new IOException("Cannot create " + nativeTmpDir);         }     }     /**      * Download, cache, verify, and unpack the specified native jar file.      * Before downloading, check the cached time stamp for the jar file      * against the server. If the time stamp is valid and matches that of the      * server, then we will use the locally cached files. This method assumes      * that cacheDir and nativeTmpDir both exist.      *      * An IOException is thrown if the files cannot loaded for some reason.      */     private void processNativeJar(URL url) throws IOException {         assert cacheDir.isDirectory();         assert nativeTmpDir.isDirectory();         // 6618105: Map '\' to '/' prior to stripping off the path         String urlString = url.toExternalForm().replace('\\', '/');         String nativeFileName = urlString.substring(urlString.lastIndexOf("/") + 1);         File nativeFile = new File(cacheDir, nativeFileName);         // Make sure the file is not "." or ".."         if (nativeFile.isDirectory()) {             throw new IOException(nativeFile + " is a directory");         }         String tmpStr = nativeFileName;         int idx = nativeFileName.lastIndexOf(".");         if (idx > 0) {             tmpStr = nativeFileName.substring(0, idx);         }         String indexFileName = tmpStr + ".idx";         File indexFile = new File(cacheDir, indexFileName);         if (VERBOSE) {             System.err.println("nativeFile = " + nativeFile);             System.err.println("indexFile = " + indexFile);         }         displayMessage("Loading: " + nativeFileName);         setProgress(0);         URLConnection conn = url.openConnection();         conn.connect();         Map/*<String,List<String>>*/ headerFields = conn.getHeaderFields();         if (VERBOSE) {             for (Iterator iter = headerFields.entrySet().iterator(); iter.hasNext(); ) {                 Entry/*<String,List<String>>*/ e = (Entry) iter.next();                 for (Iterator iter2 = ((List/*<String>*/) e.getValue()).iterator(); iter2.hasNext(); ) {                     String s = (String) iter2.next();                     if (e.getKey() != null) {                         System.err.print(e.getKey() + ": ");                     }                     System.err.print(s + " ");                 }                 System.err.println();             }             System.err.println();         }         // Validate the cache, download the jar if needed         // TODO: rather than synchronizing on System.out during cache validation,         // we should use a System property as a lock token (protected by         // System.out) so we don't hold a synchronized lock on System.out during         // a potentially long download operation.         synchronized (System.out) {             validateCache(conn, nativeFile, indexFile);         }         // Unpack the jar file         displayMessage("Unpacking: " + nativeFileName);         setProgress(0);         // Enumerate the jar file looking for native libraries         JarFile jarFile = new JarFile(nativeFile);         Set/*<String>*/ rootEntries = getRootEntries(jarFile);         Set/*<String>*/ nativeLibNames = getNativeLibNames(rootEntries);         // Validate certificates; throws exception upon validation error         validateCertificates(jarFile, rootEntries);         // Extract native libraries from the jar file         extractNativeLibs(jarFile, rootEntries, nativeLibNames);         if (VERBOSE) {             System.err.println();         }     }     // Validate the cached file. If the cached file is out of date or otherwise     // invalid, download the file and store the new time stamp.     // This method must be called with a global lock being held such that     // no other thread -- even in another class loader -- can executed this     // method concurrently.     private void validateCache(URLConnection conn,             File nativeFile,             File indexFile) throws IOException {         // Lock the cache directory         final String lckFileName = "cache.lck";         File lckFile = new File(cacheDir, lckFileName);         lckFile.createNewFile();         final FileOutputStream lckOut = new FileOutputStream(lckFile);         final FileChannel lckChannel = lckOut.getChannel();         final FileLock lckLock = lckChannel.lock();         try {             // Check to see whether the cached jar file exists and is valid             boolean valid = false;             long cachedTimeStamp = readTimeStamp(indexFile);             long urlTimeStamp = conn.getLastModified();             if (nativeFile.exists() &&                     urlTimeStamp > 0 &&                     urlTimeStamp == readTimeStamp(indexFile)) {                 valid = true;             }             // Validate the cache, download the jar if needed             if (!valid) {                 if (VERBOSE) {                     System.err.println("processNativeJar: downloading " + nativeFile.getAbsolutePath());                 }                 indexFile.delete();                 nativeFile.delete();                 // Copy from URL to File                 int len = conn.getContentLength();                 if (VERBOSE) {                     System.err.println("Content length = " + len + " bytes");                 }                 int totalNumBytes = copyURLToFile(conn, nativeFile);                 if (DEBUG) {                     System.err.println("processNativeJar: " + conn.getURL().toString() +                             " --> " + nativeFile.getAbsolutePath() + " : " +                             totalNumBytes + " bytes written");                 }                 // Write timestamp to index file.                 writeTimeStamp(indexFile, urlTimeStamp);             } else {                 if (DEBUG) {                     System.err.println("processNativeJar: using previously cached: " +                             nativeFile.getAbsolutePath());                 }             }         } finally {             // Unlock the cache directory             lckLock.release();         }     }     private long readTimeStamp(File indexFile) {         try {             BufferedReader reader = new BufferedReader(new FileReader(indexFile));             try {                 String str = reader.readLine();                 return Long.parseLong(str);             } finally {                 reader.close();             }         } catch (Exception ex) {         }         return -1;     }     private void writeTimeStamp(File indexFile, long timestamp) {         try {             BufferedWriter writer = new BufferedWriter(new FileWriter(indexFile));             try {                 writer.write("" + timestamp + "\n");                 writer.flush();             } finally {                 writer.close();             }         } catch (Exception ex) {             displayError("Error writing time stamp for native libraries");         }     }     // Copy the specified URL to the specified File     private int copyURLToFile(URLConnection inConnection,             File outFile) throws IOException {         int totalNumBytes = 0;         InputStream in = new BufferedInputStream(inConnection.getInputStream());         try {             OutputStream out = new BufferedOutputStream(new FileOutputStream(outFile));             try {                 totalNumBytes = copyStream(in, out, inConnection.getContentLength());             } finally {                 out.close();             }         } finally {             in.close();         }         return totalNumBytes;     }     /**      * Copy the specified input stream to the specified output stream. The total      * number of bytes written is returned. If the close flag is set, both      * streams are closed upon completeion.      */     private int copyStream(InputStream in, OutputStream out,             int totalNumBytes) throws IOException {         int numBytes = 0;         final int BUFFER_SIZE = 1000;         final float pctScale = 100.0f / (float)totalNumBytes;         byte[] buf = new byte[BUFFER_SIZE];         setProgress(0);         while (true) {             int count;             if ((count = in.read(buf)) == -1) {                 break;             }             out.write(buf, 0, count);             numBytes += count;             if (totalNumBytes > 0) {                 setProgress((int)Math.round((float)numBytes * pctScale));             }         }         setProgress(100);         return numBytes;     }     /**      * Enumerate the list of entries in the jar file and return those that are      * the root entries.      */     private Set/*<String>*/ getRootEntries(JarFile jarFile) {         if (VERBOSE) {             System.err.println("getRootEntries:");         }         Set/*<String>*/ names = new HashSet/*<String>*/();         Enumeration/*<JarEntry>*/ entries = jarFile.entries();         while (entries.hasMoreElements()) {             JarEntry entry = (JarEntry) entries.nextElement();             String entryName = entry.getName();             if (VERBOSE) {                 System.err.println("JarEntry : " + entryName);             }             // only look at entries with no "/"             if (entryName.indexOf('/') == -1 &&                 entryName.indexOf(File.separatorChar) == -1) {                 names.add(entryName);             }         }         return names;     }     /**      * Filter the root entries in the jar file and return those that      * are native library names.      */     private Set/*<String>*/ getNativeLibNames(Set/*<String>*/ entryNames) {         if (VERBOSE) {             System.err.println("getNativeLibNames:");         }         Set/*<String>*/ names = new HashSet/*<String>*/();         for (Iterator iter = entryNames.iterator(); iter.hasNext(); ) {             String name = (String) iter.next();             String lowerCaseName = name.toLowerCase();             // Match entries with correct prefix and suffix (ignoring case)             if (lowerCaseName.startsWith(nativePrefix) &&                 lowerCaseName.endsWith(nativeSuffix)) {                 names.add(name);             }         }         return names;     }     /**      * Validate the certificates for each native Lib in the jar file.      * Throws an IOException if any certificate is not valid.      */     private void validateCertificates(JarFile jarFile,             Set/*<String>*/ nativeLibNames) throws IOException {         if (DEBUG) {             System.err.println("validateCertificates:");         }         byte[] buf = new byte[1000];         Enumeration/*<JarEntry>*/ entries = jarFile.entries();         while (entries.hasMoreElements()) {             JarEntry entry = (JarEntry) entries.nextElement();             String entryName = entry.getName();             if (VERBOSE) {                 System.err.println("JarEntry : " + entryName);             }             if (nativeLibNames.contains(entryName)) {                 if (DEBUG) {                     System.err.println("VALIDATE: " + entryName);                 }                 if (!checkNativeCertificates(jarFile, entry, buf)) {                     throw new IOException("Cannot validate certificate for " + entryName);                 }             }         }     }     /**      * Check the native certificates with the ones in the jar file containing the      * certificates for the JNLPAppletLauncher class (all must match).      */     private boolean checkNativeCertificates(JarFile jar, JarEntry entry,             byte[] buf) throws IOException {         // API states that we must read all of the data from the entry's         // InputStream in order to be able to get its certificates         InputStream is = jar.getInputStream(entry);         int totalLength = (int) entry.getSize();         int len;         while ((len = is.read(buf)) > 0) {         }         is.close();         // locate JNLPAppletLauncher certificates         Certificate[] appletLauncherCerts = JNLPAppletLauncher.class.getProtectionDomain().                 getCodeSource().getCertificates();         if (appletLauncherCerts == null || appletLauncherCerts.length == 0) {             throw new IOException("Cannot find certificates for JNLPAppletLauncher class");         }         // Get the certificates for the JAR entry         Certificate[] nativeCerts = entry.getCertificates();         if (nativeCerts == null || nativeCerts.length == 0) {             return false;         }         int checked = 0;         for (int i = 0; i < appletLauncherCerts.length; i++) {             for (int j = 0; j < nativeCerts.length; j++) {                 if (nativeCerts[j].equals(appletLauncherCerts[i])){                     checked++;                     break;                 }             }         }         return  (checked == appletLauncherCerts.length);     }     /**      * Extract the specified set of native libraries in the given jar file.      */     private void extractNativeLibs(JarFile jarFile,                                    Set/*<String>*/ rootEntries,                                    Set/*<String>*/ nativeLibNames) throws IOException {         if (DEBUG) {             System.err.println("extractNativeLibs:");         }         Enumeration/*<JarEntry>*/ entries = jarFile.entries();         while (entries.hasMoreElements()) {             JarEntry entry = (JarEntry) entries.nextElement();             String entryName = entry.getName();             if (VERBOSE) {                 System.err.println("JarEntry : " + entryName);             }             // In order to be compatible with Java Web Start, we need             // to extract all root entries from the jar file. However,             // we only allow direct loading of the previously             // discovered native library names.             if (rootEntries.contains(entryName)) {                 // strip prefix & suffix                 String libName = entryName.substring(nativePrefix.length(),                         entryName.length() - nativeSuffix.length());                 if (DEBUG) {                     System.err.println("EXTRACT: " + entryName + "(" + libName + ")");                 }                 File nativeLib = new File(nativeTmpDir, entryName);                 InputStream in = new BufferedInputStream(jarFile.getInputStream(entry));                 OutputStream out = new BufferedOutputStream(new FileOutputStream(nativeLib));                 int numBytesWritten = copyStream(in, out, -1);                 in.close();                 out.close();                 if (nativeLibNames.contains(entryName)) {                     nativeLibMap.put(libName, nativeLib.getAbsolutePath());                 }             }         }     }     /**      * The true start of the sub applet (invoked in the EDT)      */     private void startSubApplet() {         try {             subApplet = (Applet)                 Class.forName(subAppletClassName,                               true,                               Thread.currentThread().getContextClassLoader()).newInstance();              subApplet.setStub(new AppletStubProxy());         } catch (ClassNotFoundException ex) {             ex.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();             appletStarted = true;         } catch (Exception ex) {             ex.printStackTrace();         }     }     /**      * Method called by an extension such as JOGL or Java 3D to load the      * specified library. Applications and applets should not call this method.      *      * @param libraryName name of the library to be loaded      *      * @throws SecurityException if the caller does not have permission to      * call System.load      */     public static void loadLibrary(String libraryName) {         if (VERBOSE) {             System.err.println("-----------");             Thread.dumpStack();         }         if (DEBUG) {             System.err.println("JNLPAppletLauncher.loadLibrary(\"" + libraryName + "\")");         }         String fullLibraryName = (String) nativeLibMap.get(libraryName);         if (fullLibraryName == null) {             // Throw UnsatisfiedLinkError to try to match behavior of System.loadLibrary()             throw new UnsatisfiedLinkError(libraryName);         }         if (DEBUG) {             System.err.println("    loading: " + fullLibraryName + "");         }         System.load(fullLibraryName);     }          public static void removeLibrary(String libraryName) {         String fullLibraryName = (String) nativeLibMap.get(libraryName);         if (fullLibraryName == null) {             return;         }         if (DEBUG) {             System.err.println("    removing: " + fullLibraryName + "");         }         new File(fullLibraryName).delete();         nativeLibMap.remove(libraryName);     }     private static String toErrorString(Throwable throwable) {         StringBuffer errStr = new StringBuffer(throwable.toString());         Throwable cause = throwable.getCause();         while (cause != null) {             errStr.append(": ").append(cause);             cause = cause.getCause();         }         return errStr.toString();     }     private void displayMessage(final String message) {         if (progressBar != null) {             SwingUtilities.invokeLater(new Runnable() {                 public void run() {                     progressBar.setString(message);                 }             });         }     }     private void displayError(final String errorMessage) {         // Log message on Java console and display in applet progress bar         Logger.getLogger("global").severe(errorMessage);         if (progressBar != null) {             SwingUtilities.invokeLater(new Runnable() {                 public void run() {                     progressBar.setString("Error : " + errorMessage);                 }             });         }     }     private void setProgress(final int value) {         if (progressBar != null) {             SwingUtilities.invokeLater(new Runnable() {                 public void run() {                     progressBar.setValue(value);                 }             });         }     }     private void initLoaderLayout() {         setLayout(new BorderLayout());         loaderPanel = new JPanel(new BorderLayout());         if (getBooleanParameter("progressbar")) {             progressBar = new JProgressBar(0, 100);             progressBar.setBorderPainted(true);             progressBar.setStringPainted(true);             progressBar.setString("Loading...");         }         boolean includeImage = false;         ImageIcon image = null;         if (subAppletImageURL != null) {             image = new ImageIcon(subAppletImageURL);             includeImage = true;         }         add(loaderPanel, BorderLayout.SOUTH);         if (includeImage) {             loaderPanel.add(new JLabel(image), BorderLayout.CENTER);             if (progressBar != null) {                 loaderPanel.add(progressBar, BorderLayout.SOUTH);             }         } else {             if (progressBar != null) {                 loaderPanel.add(progressBar, BorderLayout.CENTER);             }         }     }     private void parseJNLPExtensions(List/*<URL>*/ urls) throws IOException {         for (Iterator iter = urls.iterator(); iter.hasNext(); ) {             URL url = (URL) iter.next();             JNLPParser parser = new JNLPParser(this, url);             parser.parse();         }     }     private void addJarFile(URL jarFile) {         jarFiles.add(jarFile);     }     private void addNativeJar(URL nativeJar) {         nativeJars.add(nativeJar);     }     /*      * Debug method to print out resources from the JNLP file      */     private static void printResources() {         System.err.println("  Resources:");         System.err.println("    Class Jars:");         doPrint(jarFiles);         System.err.println();         System.err.println("    Native Jars:");         doPrint(nativeJars);     }     /*      * Debug method to print out resources from the JNLP file      */     private static void doPrint(Collection/*<URL>*/ urls) {         for (Iterator iter = urls.iterator(); iter.hasNext(); ) {             URL url = (URL) iter.next();             String urlString = url.toExternalForm();             System.err.println("      " + urlString);         }     }     // Static initializer for JNLPAppletLauncher     static {         System.err.println("JNLPAppletLauncher: static initializer");         String systemOsName = System.getProperty("os.name").toLowerCase();         if (systemOsName.startsWith("mac")) {             // Mac OS X             nativePrefix = "lib";             nativeSuffix = ".jnilib";         } else if (systemOsName.startsWith("windows")) {             // Microsoft Windows             nativePrefix = "";             nativeSuffix = ".dll";         } else {             // Unix of some variety             nativePrefix = "lib";             nativeSuffix = ".so";         }         if (DEBUG) {             System.err.println("os.name = " + systemOsName);             System.err.println("nativePrefix = " + nativePrefix + "  nativeSuffix = " + nativeSuffix);         }         // Create / initialize the temp root directory, starting the Reaper         // thread to reclaim old installations if necessary. If we get an         // exception, set an error code so we don't try to start the applets.         try {             initTmpRoot();         } catch (Exception ex) {             ex.printStackTrace();             staticInitError = true;         }     }     private static final String _javaVersionProperty =             System.getProperty("java.version");     private static final boolean _atLeast13 =             (!_javaVersionProperty.startsWith("1.2"));     private static final boolean _atLeast14 = (_atLeast13 &&             !_javaVersionProperty.startsWith("1.3"));     private static final boolean _atLeast15 = (_atLeast14 &&             !_javaVersionProperty.startsWith("1.4"));     private static final String vendorURL =             System.getProperty("java.vendor.url");     private static final String sunVendorURL = "http://java.sun.com/";     private static boolean isJavaVersionAtLeast15() {         return _atLeast15;     }     private static boolean isJavaVersion142Family() {         return _javaVersionProperty.startsWith("1.4.2");     }     // -----------------------------------------------------------------------     /**      * Proxy implementation class of AppletStub. Delegates to the      * JNLPAppletLauncher class.      */     private class AppletStubProxy implements AppletStub {         public boolean isActive() {             return JNLPAppletLauncher.this.isActive();         }         public URL getDocumentBase() {             return JNLPAppletLauncher.this.getDocumentBase();         }         public URL getCodeBase() {             return JNLPAppletLauncher.this.getCodeBase();         }         public String getParameter(String name) {             return JNLPAppletLauncher.this.getParameter(name);         }         public AppletContext getAppletContext() {             return JNLPAppletLauncher.this.getAppletContext();         }         public void appletResize(int width, int height) {             JNLPAppletLauncher.this.resize(width, height);         }     }     /**      * Parser class for JNLP files for the applet launcher. For simplicitly, we      * assume that everything of interest is within a single "jnlp" tag and      * that the "resources" tags are not nested.      */     private static class JNLPParser {         // The following represents the various states we can be in         private static final int INITIAL = 1;         private static final int STARTED = 2;         private static final int IN_JNLP = 3;         private static final int IN_RESOURCES = 4;         private static final int SKIP_ELEMENT = 5;         private static SAXParserFactory factory;         private static String systemOsName;         private static String systemOsArch;         private JNLPAppletLauncher launcher;         private URL url;         private InputStream in;         private JNLPHandler handler;         private String codebase = "";         private int state = INITIAL;         private int prevState = INITIAL;         private int depth = 0;         private int skipDepth = -1;         private JNLPParser(JNLPAppletLauncher launcher, URL url) throws IOException {             this.launcher = launcher;             this.url = url;             this.handler = new JNLPHandler();         }         private void parse() throws IOException {             if (VERBOSE) {                 System.err.println("JNLPParser: " + url.toString());             }             try {                 URLConnection conn = url.openConnection();                 conn.connect();                 InputStream in = new BufferedInputStream(conn.getInputStream());                 SAXParser parser = factory.newSAXParser();                 parser.parse(in, handler);                 in.close();             } catch (ParserConfigurationException ex) {                 throw (IOException) new IOException().initCause(ex);             } catch (SAXException ex) {                 throw (IOException) new IOException().initCause(ex);             }         }         // Static initializer for JNLPParser         static {             if (sunVendorURL.equals(vendorURL)) {                 // set the "javax.xml.parsers.SAXParserFactory" to the default                 // implementation, so we won't pull down all the JARs listed                 // in the archive tag to look for a SAXParserFactory implementation                 if (isJavaVersionAtLeast15()) {                     System.setProperty("javax.xml.parsers.SAXParserFactory",                             "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");                 } else if (isJavaVersion142Family()) {                     System.setProperty("javax.xml.parsers.SAXParserFactory",                             "org.apache.crimson.jaxp.SAXParserFactoryImpl");                 }             }             factory = SAXParserFactory.newInstance();             systemOsName = System.getProperty("os.name").toLowerCase();             systemOsArch = System.getProperty("os.arch").toLowerCase();             if (DEBUG) {                 System.err.println("os.name = " + systemOsName);                 System.err.println("os.arch = " + systemOsArch);             }         }         /**          * Handler class containing callback methods for the parser.          */         private class JNLPHandler extends DefaultHandler {             JNLPHandler() {             }             /* @Override */             public void startDocument() {                 if (VERBOSE) {                     System.err.println("START DOCUMENT: " + url);                 }                 state = STARTED;                 if (VERBOSE) {                     System.err.println("state = " + state);                 }             }             /* @Override */             public void endDocument() {                 if (VERBOSE) {                     System.err.println("END DOCUMENT");                 }                 state = INITIAL;                 if (VERBOSE) {                     System.err.println("state = " + state);                 }             }             /* @Override */             public void startElement(String uri,                     String localName,                     String qName,                     Attributes attributes) throws SAXException {                 ++depth;                 if (VERBOSE) {                     System.err.println("<" + qName + ">" + " : depth=" + depth);                     for (int i = 0; i < attributes.getLength(); i++) {                         System.err.println("    [" + i + "]  " + attributes.getQName(i) +                                 " = \"" + attributes.getValue(i) + "\"");                     }                 }                 // Parse qName based on current state                 switch (state) {                 case STARTED:                     if (qName.equals("jnlp")) {                         state = IN_JNLP;                         codebase = attributes.getValue("codebase");                         if (codebase == null) {                             throw new SAXException("<jnlp> unable to determine codebase");                         }                         if (codebase.lastIndexOf('/') != codebase.length()-1) {                             codebase = codebase + "/";                         }                         if (VERBOSE) {                             System.err.println("JNLP : codebase=" + codebase);                         }                         if (VERBOSE) {                             System.err.println("state = " + state);                         }                     } else if (qName.equals("resources")) {                         throw new SAXException("<resources> tag not within <jnlp> tag");                     }                     // Ignore all other tags                     break;                 case IN_JNLP:                     if (qName.equals("jnlp")) {                         throw new SAXException("Nested <jnlp> tags");                     } else if (qName.equals("resources")) {                         String osName = attributes.getValue("os");                         String osArch = attributes.getValue("arch");                         if ((osName == null || systemOsName.startsWith(osName.toLowerCase())) &&                                 (osArch == null || systemOsArch.startsWith(osArch.toLowerCase()))) {                             if (VERBOSE) {                                 System.err.println("Loading resources : os=" + osName + "  arch=" + osArch);                             }                             state = IN_RESOURCES;                         } else {                             prevState = state;                             skipDepth = depth - 1;                             state = SKIP_ELEMENT;                         }                         if (VERBOSE) {                             System.err.println("Resources : os=" + osName + "  arch=" + osArch + "  state = " + state);                         }                     }                     break;                 case IN_RESOURCES:                     try {                         if (qName.equals("jnlp")) {                             throw new SAXException("Nested <jnlp> tags");                         } else if (qName.equals("resources")) {                             throw new SAXException("Nested <resources> tags");                         } else if (qName.equals("jar")) {                             String str = attributes.getValue("href");                             if (str == null || str.length() == 0) {                                 throw new SAXException("<jar> tag missing href attribute");                             }                             String jarFileStr = codebase + str;                             if (VERBOSE) {                                 System.err.println("Jar: " + jarFileStr);                             }                             URL jarFile = new URL(jarFileStr);                             launcher.addJarFile(jarFile);                         } else if (qName.equals("nativelib")) {                             String str = attributes.getValue("href");                             if (str == null || str.length() == 0) {                                 throw new SAXException("<nativelib> tag missing href attribute");                             }                             String nativeLibStr = codebase + str;                             if (VERBOSE) {                                 System.err.println("Native Lib: " + nativeLibStr);                             }                             URL nativeLib = new URL(nativeLibStr);                             launcher.addNativeJar(nativeLib);                         } else if (qName.equals("extension")) {                             String extensionURLString = attributes.getValue("href");                             if (extensionURLString == null || extensionURLString.length() == 0) {                                 throw new SAXException("<extension> tag missing href attribute");                             }                             if (VERBOSE) {                                 System.err.println("Extension: " + extensionURLString);                             }                             URL extensionURL = new URL(extensionURLString);                             JNLPParser parser = new JNLPParser(launcher, extensionURL);                             parser.parse();                         } else {                             prevState = state;                             skipDepth = depth - 1;                             state = SKIP_ELEMENT;                             if (VERBOSE) {                                 System.err.println("state = " + state);                             }                         }                     } catch (IOException ex) {                         throw (SAXException) new SAXException(ex).initCause(ex);                     }                     break;                 case INITIAL:                 case SKIP_ELEMENT:                 default:                     break;                 }             }             /* @Override */             public void endElement(String uri,                     String localName,                     String qName) throws SAXException {                 --depth;                 if (VERBOSE) {                     System.err.println("</" + qName + ">");                 }                 // Parse qName based on current state                 switch (state) {                 case IN_JNLP:                     if (qName.equals("jnlp")) {                         state = STARTED;                         if (VERBOSE) {                             System.err.println("state = " + state);                         }                     }                     break;                 case IN_RESOURCES:                     if (qName.equals("resources")) {                         state = IN_JNLP;                         if (VERBOSE) {                             System.err.println("state = " + state);                         }                     }                     break;                 case SKIP_ELEMENT:                     if (depth == skipDepth) {                         state = prevState;                         skipDepth = -1;                         if (VERBOSE) {                             System.err.println("state = " + state);                         }                     }                     break;                 case INITIAL:                 case STARTED:                 default:                     break;                 }             }         }     }     //----------------------------------------------------------------------     // Helper routines for adding -Dsun.java2d.noddraw=true to deployment.properties     // Get a "boolean" parameter     private boolean getBooleanParameter(String parameterName) {         return Boolean.valueOf(getParameter(parameterName)).booleanValue();     }     private String getDeploymentPropsDir() {         final String osName = System.getProperty("os.name").toLowerCase();         StringBuffer result = new StringBuffer();         result.append(System.getProperty("user.home"));         if (osName.startsWith("windows")) {             if (osName.indexOf("vista") != -1) {                 result.append(File.separator).append("AppData").                         append(File.separator).append("LocalLow");             } else {                 result.append(File.separator).append("Application Data");             }             result.append(File.separator).append("Sun").                     append(File.separator).append("Java").                     append(File.separator).append("Deployment");         } else if (osName.startsWith("mac")) {             result.append(File.separator).append("Library").                     append(File.separator).append("Caches").                     append(File.separator).append("Java");         } else {             result.append(File.separator).append(".java").                     append(File.separator).append("deployment");         }         return result.toString();     }     private void checkNoDDrawAndUpdateDeploymentProperties() {         if (!getBooleanParameter("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 + ".jnlp-applet" +                                     File.separator + DONT_ASK);         if (dontAskFile.exists())             return; // User asked us not to prompt again         int option = 0;         if (!getBooleanParameter("noddraw.check.silent")) {             option = JOptionPane.showOptionDialog(null,                                                   "For best robustness of OpenGL 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(getDeploymentPropsDir());             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("noddraw.check.silent")) {                 // 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();         }     } }