changeset 1650:e315bc774531

Convert DOS line endings to UNIX. 2009-08-04 Andrew John Hughes <ahughes@redhat.com> * rt/net/sourceforge/jnlp/DefaultLaunchHandler.java, * rt/net/sourceforge/jnlp/ExtensionDesc.java, * rt/net/sourceforge/jnlp/JNLPFile.java, * rt/net/sourceforge/jnlp/LaunchException.java, * rt/net/sourceforge/jnlp/LaunchHandler.java, * rt/net/sourceforge/jnlp/Launcher.java, * rt/net/sourceforge/jnlp/ParseException.java, * rt/net/sourceforge/jnlp/Parser.java, * rt/net/sourceforge/jnlp/cache/CacheEntry.java, * rt/net/sourceforge/jnlp/cache/CacheUtil.java, * rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java, * rt/net/sourceforge/jnlp/cache/ResourceTracker.java, * rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java, * rt/net/sourceforge/jnlp/runtime/AppletInstance.java, * rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java, * rt/net/sourceforge/jnlp/runtime/Boot.java, * rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java, * rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java, * rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java, * rt/net/sourceforge/jnlp/services/XBasicService.java, * rt/net/sourceforge/jnlp/util/PropertiesFile.java, * rt/net/sourceforge/jnlp/util/Reflect.java, * rt/net/sourceforge/jnlp/util/WeakList.java, * test/jtreg/com/sun/javatest/exec/FileTable.java, * test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java, * test/jtreg/com/sun/javatest/report/XMLReportMaker.java: Convert DOS line endings to UNIX.
author Andrew John Hughes <ahughes@redhat.com>
date Tue, 04 Aug 2009 17:39:11 +0100
parents f5ed1489ddcf
children bf12ce0165c0
files ChangeLog rt/net/sourceforge/jnlp/DefaultLaunchHandler.java rt/net/sourceforge/jnlp/ExtensionDesc.java rt/net/sourceforge/jnlp/JNLPFile.java rt/net/sourceforge/jnlp/LaunchException.java rt/net/sourceforge/jnlp/LaunchHandler.java rt/net/sourceforge/jnlp/Launcher.java rt/net/sourceforge/jnlp/ParseException.java rt/net/sourceforge/jnlp/Parser.java rt/net/sourceforge/jnlp/cache/CacheEntry.java rt/net/sourceforge/jnlp/cache/CacheUtil.java rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java rt/net/sourceforge/jnlp/cache/ResourceTracker.java rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java rt/net/sourceforge/jnlp/runtime/AppletInstance.java rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java rt/net/sourceforge/jnlp/runtime/Boot.java rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java rt/net/sourceforge/jnlp/services/XBasicService.java rt/net/sourceforge/jnlp/util/PropertiesFile.java rt/net/sourceforge/jnlp/util/Reflect.java rt/net/sourceforge/jnlp/util/WeakList.java test/jtreg/com/sun/javatest/exec/FileTable.java test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java test/jtreg/com/sun/javatest/report/XMLReportMaker.java
diffstat 27 files changed, 9963 insertions(+), 9933 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Aug 04 11:34:49 2009 -0400
+++ b/ChangeLog	Tue Aug 04 17:39:11 2009 +0100
@@ -1,3 +1,33 @@
+2009-08-04  Andrew John Hughes  <ahughes@redhat.com>
+
+	* rt/net/sourceforge/jnlp/DefaultLaunchHandler.java,
+	* rt/net/sourceforge/jnlp/ExtensionDesc.java,
+	* rt/net/sourceforge/jnlp/JNLPFile.java,
+	* rt/net/sourceforge/jnlp/LaunchException.java,
+	* rt/net/sourceforge/jnlp/LaunchHandler.java,
+	* rt/net/sourceforge/jnlp/Launcher.java,
+	* rt/net/sourceforge/jnlp/ParseException.java,
+	* rt/net/sourceforge/jnlp/Parser.java,
+	* rt/net/sourceforge/jnlp/cache/CacheEntry.java,
+	* rt/net/sourceforge/jnlp/cache/CacheUtil.java,
+	* rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java,
+	* rt/net/sourceforge/jnlp/cache/ResourceTracker.java,
+	* rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java,
+	* rt/net/sourceforge/jnlp/runtime/AppletInstance.java,
+	* rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java,
+	* rt/net/sourceforge/jnlp/runtime/Boot.java,
+	* rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java,
+	* rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java,
+	* rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java,
+	* rt/net/sourceforge/jnlp/services/XBasicService.java,
+	* rt/net/sourceforge/jnlp/util/PropertiesFile.java,
+	* rt/net/sourceforge/jnlp/util/Reflect.java,
+	* rt/net/sourceforge/jnlp/util/WeakList.java,
+	* test/jtreg/com/sun/javatest/exec/FileTable.java,
+	* test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java,
+	* test/jtreg/com/sun/javatest/report/XMLReportMaker.java:
+	Convert DOS line endings to UNIX.
+
 2009-08-04  Omair Majid  <omajid@redhat.com>
 
 	* rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java
--- a/rt/net/sourceforge/jnlp/DefaultLaunchHandler.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/DefaultLaunchHandler.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,114 +1,114 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-package net.sourceforge.jnlp;
-
-import net.sourceforge.jnlp.runtime.*;
-import net.sourceforge.jnlp.util.*;
-
-import java.awt.*;
-import java.util.*;
-import javax.swing.*;
-
-
-/**
- * This default implementation shows prints the exception to
- * stdout and if not in headless mode displays the exception in a
- * dialog.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.1 $
- */
-public class DefaultLaunchHandler implements LaunchHandler {
-
-    /**
-     * Called when the application could not be launched due to a
-     * fatal error, such as the inability to find the main class
-     * or non-parseable XML.
-     */
-    public void launchError(LaunchException exception) {
-        printMessage(exception);
-    }
-
-    /**
-     * Called when launching the application can not be launched
-     * due to an error that is not fatal.  For example a JNLP file
-     * that is not strictly correct yet does not necessarily
-     * prohibit the system from attempting to launch the
-     * application.
-     *
-     * @return true if the launch should continue, false to abort
-     */
-    public boolean launchWarning(LaunchException warning) {
-        printMessage(warning);
-        return true;
-    }
-
-    /**
-     * Called when a security validation error occurs while
-     * launching the application.
-     *
-     * @return true to allow the application to continue, false to stop it.
-     */
-    public boolean validationError(LaunchException security) {
-        printMessage(security);
-        return true;
-    }
-
-    /**
-     * Called when an application, applet, or installer has been
-     * launched successfully (the main method or applet start method
-     * returned normally).
-     *
-     * @param application the launched application instance
-     */
-    public void launchCompleted(ApplicationInstance application) {
-        //
-    }
-
-    /**
-     * Print a message to stdout.
-     */
-    protected void printMessage(LaunchException ex) {
-        StringBuffer result = new StringBuffer();
-        result.append("netx: ");
-        result.append(ex.getCategory());
-        if (ex.getSummary() != null) {
-            result.append(": ");
-            result.append(ex.getSummary());
-        }
-
-        if (JNLPRuntime.isDebug()) {
-            if (ex.getCause() != null)
-                ex.getCause().printStackTrace();
-            else
-                ex.printStackTrace();
-        }
-
-        Throwable causes[] = ex.getCauses();
-
-        for (int i=0; i < causes.length; i++) {
-            result.append(" (");
-            result.append(causes[i].getClass().getName());
-            result.append(" ");
-            result.append(causes[i].getMessage());
-            result.append(")");
-        }
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp;
+
+import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.util.*;
+
+import java.awt.*;
+import java.util.*;
+import javax.swing.*;
+
+
+/**
+ * This default implementation shows prints the exception to
+ * stdout and if not in headless mode displays the exception in a
+ * dialog.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.1 $
+ */
+public class DefaultLaunchHandler implements LaunchHandler {
+
+    /**
+     * Called when the application could not be launched due to a
+     * fatal error, such as the inability to find the main class
+     * or non-parseable XML.
+     */
+    public void launchError(LaunchException exception) {
+        printMessage(exception);
+    }
+
+    /**
+     * Called when launching the application can not be launched
+     * due to an error that is not fatal.  For example a JNLP file
+     * that is not strictly correct yet does not necessarily
+     * prohibit the system from attempting to launch the
+     * application.
+     *
+     * @return true if the launch should continue, false to abort
+     */
+    public boolean launchWarning(LaunchException warning) {
+        printMessage(warning);
+        return true;
+    }
+
+    /**
+     * Called when a security validation error occurs while
+     * launching the application.
+     *
+     * @return true to allow the application to continue, false to stop it.
+     */
+    public boolean validationError(LaunchException security) {
+        printMessage(security);
+        return true;
+    }
+
+    /**
+     * Called when an application, applet, or installer has been
+     * launched successfully (the main method or applet start method
+     * returned normally).
+     *
+     * @param application the launched application instance
+     */
+    public void launchCompleted(ApplicationInstance application) {
+        //
+    }
+
+    /**
+     * Print a message to stdout.
+     */
+    protected void printMessage(LaunchException ex) {
+        StringBuffer result = new StringBuffer();
+        result.append("netx: ");
+        result.append(ex.getCategory());
+        if (ex.getSummary() != null) {
+            result.append(": ");
+            result.append(ex.getSummary());
+        }
+
+        if (JNLPRuntime.isDebug()) {
+            if (ex.getCause() != null)
+                ex.getCause().printStackTrace();
+            else
+                ex.printStackTrace();
+        }
+
+        Throwable causes[] = ex.getCauses();
+
+        for (int i=0; i < causes.length; i++) {
+            result.append(" (");
+            result.append(causes[i].getClass().getName());
+            result.append(" ");
+            result.append(causes[i].getMessage());
+            result.append(")");
+        }
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/ExtensionDesc.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/ExtensionDesc.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,145 +1,145 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
-
-
-/**
- * The extension element.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.8 $
- */
-public class ExtensionDesc {
-
-    /** the extension name */
-    private String name;
-
-    /** the required extension version */
-    private Version version;
-
-    /** the location of the extension JNLP file */
-    private URL location;
-
-    /** the JNLPFile the extension refers to */
-    private JNLPFile file;
-
-    /** map from ext-part to local part */
-    private Map extToPart = new HashMap();
-
-    /** eager ext parts */ 
-    private List eagerExtParts = new ArrayList();
-
-
-    /**
-     * Create an extention descriptor.
-     *
-     * @param name the extension name
-     * @param version the required version of the extention JNLPFile
-     * @param location the location of the extention JNLP file
-     */
-    public ExtensionDesc(String name, Version version, URL location) {
-        this.name = name;
-        this.version = version;
-        this.location = location;
-    }
-
-    /**
-     * Adds an extension part to be downloaded when the specified
-     * part of the main JNLP file is loaded.  The extension part
-     * will be downloaded before the application is launched if the
-     * lazy value is false or the part is empty or null.
-     *
-     * @param extPart the part name in the extension file
-     * @param part the part name in the main file
-     * @param lazy whether to load the part before launching
-     */
-    protected void addPart(String extPart, String part, boolean lazy) {
-        extToPart.put(extPart, part);
-
-        if (!lazy || part == null || part.length() == 0)
-            eagerExtParts.add(extPart);
-    }
-
-    /**
-     * Returns the parts in the extension JNLP file mapped to the
-     * part of the main file.
-     */
-    public String[] getExtensionParts(String thisPart) {
-
-        return null;
-    }
-
-    /**
-     * Returns the name of the extension.
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns the required version of the extension JNLP file.
-     */
-    public Version getVersion() {
-        return version;
-    }
-
-    /**
-     * Returns the location of the extension JNLP file.
-     */
-    public URL getLocation() {
-        return location;
-    }
-
-    /**
-     * Resolves the extension by creating a JNLPFile from the file
-     * specified by the extension's location property.
-     *
-     * @throws IOException if the extension JNLPFile could not be resolved.
-     * @throws ParseException if the extension JNLPFile could not be
-     * parsed or was not a component or installer descriptor.
-     */
-    public void resolve() throws ParseException, IOException {
-        if (file == null) {
-            file = new JNLPFile(location);
-
-            if (JNLPRuntime.isDebug())
-                System.out.println("Resolve: "+file.getInformation().getTitle());
-
-            // check for it being an extension descriptor
-            if (!file.isComponent() && !file.isInstaller())
-                throw new ParseException(JNLPRuntime.getMessage("JInvalidExtensionDescriptor", new Object[] {name, location} ));
-        }
-
-    }
-
-    /**
-     * Returns a JNLPFile for the extension, or null if the JNLP
-     * file has not been resolved.
-     */
-    public JNLPFile getJNLPFile() {
-        return file;
-    }
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+
+
+/**
+ * The extension element.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.8 $
+ */
+public class ExtensionDesc {
+
+    /** the extension name */
+    private String name;
+
+    /** the required extension version */
+    private Version version;
+
+    /** the location of the extension JNLP file */
+    private URL location;
+
+    /** the JNLPFile the extension refers to */
+    private JNLPFile file;
+
+    /** map from ext-part to local part */
+    private Map extToPart = new HashMap();
+
+    /** eager ext parts */ 
+    private List eagerExtParts = new ArrayList();
+
+
+    /**
+     * Create an extention descriptor.
+     *
+     * @param name the extension name
+     * @param version the required version of the extention JNLPFile
+     * @param location the location of the extention JNLP file
+     */
+    public ExtensionDesc(String name, Version version, URL location) {
+        this.name = name;
+        this.version = version;
+        this.location = location;
+    }
+
+    /**
+     * Adds an extension part to be downloaded when the specified
+     * part of the main JNLP file is loaded.  The extension part
+     * will be downloaded before the application is launched if the
+     * lazy value is false or the part is empty or null.
+     *
+     * @param extPart the part name in the extension file
+     * @param part the part name in the main file
+     * @param lazy whether to load the part before launching
+     */
+    protected void addPart(String extPart, String part, boolean lazy) {
+        extToPart.put(extPart, part);
+
+        if (!lazy || part == null || part.length() == 0)
+            eagerExtParts.add(extPart);
+    }
+
+    /**
+     * Returns the parts in the extension JNLP file mapped to the
+     * part of the main file.
+     */
+    public String[] getExtensionParts(String thisPart) {
+
+        return null;
+    }
+
+    /**
+     * Returns the name of the extension.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the required version of the extension JNLP file.
+     */
+    public Version getVersion() {
+        return version;
+    }
+
+    /**
+     * Returns the location of the extension JNLP file.
+     */
+    public URL getLocation() {
+        return location;
+    }
+
+    /**
+     * Resolves the extension by creating a JNLPFile from the file
+     * specified by the extension's location property.
+     *
+     * @throws IOException if the extension JNLPFile could not be resolved.
+     * @throws ParseException if the extension JNLPFile could not be
+     * parsed or was not a component or installer descriptor.
+     */
+    public void resolve() throws ParseException, IOException {
+        if (file == null) {
+            file = new JNLPFile(location);
+
+            if (JNLPRuntime.isDebug())
+                System.out.println("Resolve: "+file.getInformation().getTitle());
+
+            // check for it being an extension descriptor
+            if (!file.isComponent() && !file.isInstaller())
+                throw new ParseException(JNLPRuntime.getMessage("JInvalidExtensionDescriptor", new Object[] {name, location} ));
+        }
+
+    }
+
+    /**
+     * Returns a JNLPFile for the extension, or null if the JNLP
+     * file has not been resolved.
+     */
+    public JNLPFile getJNLPFile() {
+        return file;
+    }
+}
+
+
--- a/rt/net/sourceforge/jnlp/JNLPFile.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/JNLPFile.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,578 +1,578 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-
-import net.sourceforge.jnlp.cache.ResourceTracker;
-import net.sourceforge.jnlp.cache.UpdatePolicy;
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
-
-/**
- * Provides methods to access the information in a Java Network
- * Launching Protocol (JNLP) file.  The Java Network Launching
- * Protocol specifies in an XML file the information needed to
- * load, cache, and run Java code over the network and in a secure
- * environment.<p>
- *
- * This class represents the overall information about a JNLP file
- * from the jnlp element.  Other information is accessed through
- * objects that represent the elements of a JNLP file
- * (information, resources, application-desc, etc).  References to
- * these objects are obtained by calling the getInformation,
- * getResources, getSecurity, etc methods.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.21 $ 
- */
-public class JNLPFile {
-
-    // todo: save the update policy, then if file was not updated
-    // then do not check resources for being updated.
-    //
-    // todo: make getLaunchInfo return a superclass that all the
-    // launch types implement (can get codebase from it).
-    //
-    // todo: currently does not filter resources by jvm version.
-    //
-    
-    private static String R(String key) { return JNLPRuntime.getMessage(key); }
-
-    /** the location this JNLP file was created from */
-    protected URL sourceLocation = null;
-
-    /** the network location of this JNLP file */
-    protected URL fileLocation;
-
-    /** the URL used to resolve relative URLs in the file */
-    protected URL codeBase;
-
-    /** file version */
-    protected Version fileVersion;
-
-    /** spec version */
-    protected Version specVersion;
-
-    /** information */
-    protected List info;
-
-    /** resources */
-    protected List resources;
-
-    /** additional resources not in JNLP file (from command line) */
-    protected ResourcesDesc sharedResources = new ResourcesDesc(this, null, null, null);
-
-    /** the application description */
-    protected Object launchType;
-
-    /** the security descriptor */
-    protected SecurityDesc security;
-
-    /** the default OS */
-    protected Locale defaultLocale = null;
-
-    /** the default arch */
-    protected String defaultOS = null;
-
-    /** the default jvm */
-    protected String defaultArch = null;
-
-    { // initialize defaults if security allows
-        try {
-            defaultLocale = Locale.getDefault();
-            defaultOS = System.getProperty("os.name");
-            defaultArch = System.getProperty("os.arch");
-        }
-        catch (SecurityException ex) {
-            // null values will still work, and app can set defaults later
-        }
-    }
-    
-    /**
-     * Empty stub, allowing child classes to override the constructor
-     */
-    protected JNLPFile() {
-    }
-
-    /**
-     * Create a JNLPFile from a URL.
-     *
-     * @param location the location of the JNLP file
-     * @throws IOException if an IO exception occurred
-     * @throws ParseException if the JNLP file was invalid
-     */
-    public JNLPFile(URL location) throws IOException, ParseException {
-        this(location, false); // not strict
-    }
-
-    /**
-     * Create a JNLPFile from a URL checking for updates using the
-     * default policy.
-     *
-     * @param location the location of the JNLP file
-     * @param strict whether to enforce the spec when 
-     * @throws IOException if an IO exception occurred
-     * @throws ParseException if the JNLP file was invalid
-     */
-    public JNLPFile(URL location, boolean strict) throws IOException, ParseException {
-        this(location, (Version) null, strict);
-    }
-    
-    /**
-     * Create a JNLPFile from a URL and a Version checking for updates using 
-     * the default policy.
-     *
-     * @param location the location of the JNLP file
-     * @param version the version of the JNLP file
-     * @param strict whether to enforce the spec when 
-     * @throws IOException if an IO exception occurred
-     * @throws ParseException if the JNLP file was invalid
-     */
-    public JNLPFile(URL location, Version version, boolean strict) throws IOException, ParseException {
-        this(location, version, strict, JNLPRuntime.getDefaultUpdatePolicy());
-    }
-
-    /**
-     * Create a JNLPFile from a URL and a version, checking for updates 
-     * using the specified policy.
-     *
-     * @param location the location of the JNLP file
-     * @param version the version of the JNLP file
-     * @param strict whether to enforce the spec when 
-     * @param policy the update policy
-     * @throws IOException if an IO exception occurred
-     * @throws ParseException if the JNLP file was invalid
-     */
-    public JNLPFile(URL location, Version version, boolean strict, UpdatePolicy policy) throws IOException, ParseException {
-        Node root = Parser.getRootNode(openURL(location, version, policy));
-        parse(root, strict, location);
-
-        this.fileLocation = location;
-    }
-
-    /**
-     * Create a JNLPFile from an input stream.
-     *
-     * @throws IOException if an IO exception occurred
-     * @throws ParseException if the JNLP file was invalid
-     */
-    public JNLPFile(InputStream input, boolean strict) throws ParseException {
-        parse(Parser.getRootNode(input), strict, null);
-    }
-
-    /**
-     * Create a JNLPFile from a character stream.
-     *
-     * @param input the stream
-     * @param strict whether to enforce the spec when 
-     * @throws IOException if an IO exception occurred
-     * @throws ParseException if the JNLP file was invalid
-     */
-    private JNLPFile(Reader input, boolean strict) throws ParseException {
-        // todo: now that we are using NanoXML we can use a Reader
-        //parse(Parser.getRootNode(input), strict, null);
-    }
-
-
-    /**
-     * Open the jnlp file URL from the cache if there, otherwise
-     * download to the cache.  Called from constructor.
-     */
-    private static InputStream openURL(URL location, Version version, UpdatePolicy policy) throws IOException {
-        if (location == null || policy == null)
-            throw new IllegalArgumentException(R("NullParameter"));
-
-        try {
-            ResourceTracker tracker = new ResourceTracker(false); // no prefetch
-            tracker.addResource(location, version , policy);
-
-            return tracker.getInputStream(location);
-        }
-        catch (Exception ex) {
-            throw new IOException(ex.getMessage());
-        }
-    }
-
-    /**
-     * Returns the JNLP specification versions supported.
-     */
-    public static Version getSupportedVersions() {
-        return Parser.getSupportedVersions();
-    }
-
-    /**
-     * Returns the JNLP file's title.  This method returns the same
-     * value as InformationDesc.getTitle().
-     */
-    public String getTitle() {
-        return getInformation().getTitle();
-    }
-
-    /**
-     * Returns the JNLP file's network location as specified in the
-     * JNLP file.
-     */
-    public URL getSourceLocation() {
-        return sourceLocation;
-    }
-
-    /**
-     * Returns the location of the file parsed to create the JNLP
-     * file, or null if it was not created from a URL.
-     */
-    public URL getFileLocation() {
-        return fileLocation;
-    }
-
-    /**
-     * Returns the JNLP file's version.
-     */
-    public Version getFileVersion() {
-        return fileVersion;
-    }
-
-    /**
-     * Returns the specification version required by the file.
-     */
-    public Version getSpecVersion() {
-        return specVersion;
-    }
-
-    /**
-     * Returns the codebase URL for the JNLP file.
-     */
-    public URL getCodeBase() {
-        return codeBase;
-    }
-    
-    /**
-     * Returns the information section of the JNLP file as viewed
-     * through the default locale.
-     */
-    public InformationDesc getInformation() {
-        return getInformation(defaultLocale);
-    }
-
-    /**
-     * Returns the information section of the JNLP file as viewed
-     * through the specified locale.
-     */
-    public InformationDesc getInformation(final Locale locale) {
-        return new InformationDesc(this, new Locale[] {locale}) {
-            protected List getItems(Object key) {
-                List result = new ArrayList();
-
-                for (int i=0; i < info.size(); i++) {
-                    InformationDesc infoDesc = (InformationDesc) info.get(i);
-
-                    if (localMatches(locale, infoDesc.getLocales()))
-                        if (localMatches(locale, infoDesc.getLocales()))
-                            result.addAll(infoDesc.getItems(key) );
-                }
-
-                return result;
-            }
-        };
-    }
-
-    /**
-     * Returns the security section of the JNLP file.
-     */
-    public SecurityDesc getSecurity() {
-        return security;
-    }
-
-    /**
-     * Returns the resources section of the JNLP file as viewed
-     * through the default locale and the os.name and os.arch
-     * properties.
-     */
-    public ResourcesDesc getResources() {
-        return getResources(defaultLocale, defaultOS, defaultArch);
-    }
-
-    /**
-     * Returns the information section of the JNLP file for the
-     * specified locale, os, and arch.
-     */
-    public ResourcesDesc getResources(final Locale locale, final String os, final String arch) {
-        return new ResourcesDesc(this, new Locale[] {locale}, new String[] {os}, new String[] {arch}) {
-            public List getResources(Class launchType) {
-                List result = new ArrayList();
-
-                for (int i=0; i < resources.size(); i++) {
-                    ResourcesDesc rescDesc = (ResourcesDesc) resources.get(i);
-
-                    if (localMatches(locale, rescDesc.getLocales())
-                        && stringMatches(os, rescDesc.getOS())
-                        && stringMatches(arch, rescDesc.getArch()))
-                        result.addAll(rescDesc.getResources(launchType) );
-                }
-
-                result.addAll(sharedResources.getResources(launchType));
-
-                return result;
-            }
-
-            public void addResource(Object resource) {
-                // todo: honor the current locale, os, arch values
-                sharedResources.addResource(resource);
-            }
-        };
-    }
-
-    /**
-     * Returns an object of one of the following types: AppletDesc,
-     * ApplicationDesc, InstallerDesc, and ComponentDesc.
-     */
-    public Object getLaunchInfo() {
-        return launchType;
-    }
-
-    /**
-     * Returns the launch information for an applet.
-     *
-     * @throws UnsupportedOperationException if there is no applet information
-     */
-    public AppletDesc getApplet() {
-        if (!isApplet())
-            throw new UnsupportedOperationException(R("JNotApplet"));
-
-        return (AppletDesc) launchType;
-    }
-
-    /**
-     * Returns the launch information for an application.
-     *
-     * @throws UnsupportedOperationException if there is no application information
-     */
-    public ApplicationDesc getApplication() {
-        if (!isApplication())
-            throw new UnsupportedOperationException(R("JNotApplication"));
-
-        return (ApplicationDesc) launchType;
-    }
-
-    /**
-     * Returns the launch information for a component.
-     *
-     * @throws UnsupportedOperationException if there is no component information
-     */
-    public ComponentDesc getComponent() {
-        if (!isComponent())
-            throw new UnsupportedOperationException(R("JNotComponent"));
-
-        return (ComponentDesc) launchType;
-    }
-
-    /**
-     * Returns the launch information for an installer.
-     *
-     * @throws UnsupportedOperationException if there is no installer information
-     */
-    public InstallerDesc getInstaller() {
-        if (!isInstaller())
-            throw new UnsupportedOperationException(R("NotInstaller"));
-
-        return (InstallerDesc) launchType;
-    }
-
-    /** 
-     * Returns whether the lauch descriptor describes an Applet.
-     */
-    public boolean isApplet() {
-        return launchType instanceof AppletDesc;
-    }
-
-    /** 
-     * Returns whether the lauch descriptor describes an Application.
-     */
-    public boolean isApplication() {
-        return launchType instanceof ApplicationDesc;
-    }
-
-    /** 
-     * Returns whether the lauch descriptor describes a Component.
-     */
-    public boolean isComponent() {
-        return launchType instanceof ComponentDesc;
-    }
-
-    /** 
-     * Returns whether the lauch descriptor describes an Installer.
-     */
-    public boolean isInstaller() {
-        return launchType instanceof InstallerDesc;
-    }
-
-    /**
-     * Sets the default view of the JNLP file returned by
-     * getInformation, getResources, etc.  If unset, the defaults
-     * are the properties os.name, os.arch, and the locale returned
-     * by Locale.getDefault().
-     */
-    public void setDefaults(String os, String arch, Locale locale) {
-        defaultOS = os;
-        defaultArch = arch;
-        defaultLocale = locale;
-    }
-
-
-    /**
-     * Returns whether a locale is matched by one of more other
-     * locales.  Only the non-empty language, country, and variant
-     * codes are compared; for example, a requested locale of
-     * Locale("","","") would always return true.
-     *
-     * @param requested the local
-     * @param available the available locales
-     * @return true if requested matches any of available, or if
-     * available is empty or null.
-     */
-    private boolean localMatches(Locale requested, Locale available[]) {
-        if (available == null || available.length == 0)
-            return true;
-
-        for (int i=0; i < available.length; i++) {
-            String language = requested.getLanguage(); // "" but never null
-            String country = requested.getCountry();
-            String variant = requested.getVariant();
-
-            if (!"".equals(language) && !language.equalsIgnoreCase(available[i].getLanguage()))
-                continue;
-            if (!"".equals(country) && !country.equalsIgnoreCase(available[i].getCountry()))
-                continue;
-            if (!"".equals(variant) && !variant.equalsIgnoreCase(available[i].getVariant()))
-                continue;
-
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns whether the string is a prefix for any of the strings
-     * in the specified array.
-     *
-     * @param prefixStr the prefix string
-     * @param available the strings to test
-     * @return true if prefixStr is a prefix of any strings in
-     * available, or if available is empty or null.
-     */
-    private boolean stringMatches(String prefixStr, String available[]) {
-        if (available == null || available.length == 0)
-            return true;
-
-        for (int i=0; i < available.length; i++)
-            if (available[i] != null && available[i].startsWith(prefixStr))
-                return true;
-
-        return false;
-    }
-
-    /**
-     * Initialize the JNLPFile fields. Private because it's called
-     * from the constructor.
-     *
-     * @param root the root node
-     * @param strict whether to enforce the spec when 
-     * @param location the file location or null
-     */
-    private void parse(Node root, boolean strict, URL location) throws ParseException {
-        try {
-            //if (location != null)
-            //  location = new URL(location, "."); // remove filename
-
-            Parser parser = new Parser(this, location, root, strict, true); // true == allow extensions
-
-            // JNLP tag information
-            specVersion = parser.getSpecVersion();
-            fileVersion = parser.getFileVersion();
-            codeBase = parser.getCodeBase();
-            sourceLocation = parser.getFileLocation();
-
-            info = parser.getInfo(root);
-            resources = parser.getResources(root, false); // false == not a j2se/java resources section
-            launchType = parser.getLauncher(root);
-            security = parser.getSecurity(root);
-        }
-        catch (ParseException ex) {
-            throw ex;
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            throw new RuntimeException(ex.toString());
-        }
-    }
-
-    /**
-     * 
-     * @return true if the JNLP file specifies things that can only be 
-     * applied on a new vm (eg: different max heap memory)
-     */
-    public boolean needsNewVM() {
-        
-        if (getNewVMArgs().size() == 0) {
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     *  @return a list of args to pass to the new 
-     *  JVM based on this JNLP file
-     */
-    public List<String> getNewVMArgs() {
-        
-        List<String> newVMArgs = new LinkedList<String>();
-        
-        JREDesc[] jres = getResources().getJREs();
-        for (int jreIndex = 0; jreIndex < jres.length; jreIndex++) {
-            String initialHeapSize = jres[jreIndex].getInitialHeapSize();
-            if (initialHeapSize != null) {
-                newVMArgs.add("-Xms" + initialHeapSize);
-            }
-            
-            String maxHeapSize = jres[jreIndex].getMaximumHeapSize();
-            if (maxHeapSize != null) {
-                newVMArgs.add("-Xmx" + maxHeapSize);
-            }
-            
-            String vmArgsFromJre = jres[jreIndex].getVMArgs();
-            if (vmArgsFromJre != null) {
-                String[] args = vmArgsFromJre.split(" ");
-                newVMArgs.addAll(Arrays.asList(args));
-            }
-        }
-        
-        return newVMArgs;
-    }
-
-}
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import net.sourceforge.jnlp.cache.ResourceTracker;
+import net.sourceforge.jnlp.cache.UpdatePolicy;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+
+/**
+ * Provides methods to access the information in a Java Network
+ * Launching Protocol (JNLP) file.  The Java Network Launching
+ * Protocol specifies in an XML file the information needed to
+ * load, cache, and run Java code over the network and in a secure
+ * environment.<p>
+ *
+ * This class represents the overall information about a JNLP file
+ * from the jnlp element.  Other information is accessed through
+ * objects that represent the elements of a JNLP file
+ * (information, resources, application-desc, etc).  References to
+ * these objects are obtained by calling the getInformation,
+ * getResources, getSecurity, etc methods.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.21 $ 
+ */
+public class JNLPFile {
+
+    // todo: save the update policy, then if file was not updated
+    // then do not check resources for being updated.
+    //
+    // todo: make getLaunchInfo return a superclass that all the
+    // launch types implement (can get codebase from it).
+    //
+    // todo: currently does not filter resources by jvm version.
+    //
+    
+    private static String R(String key) { return JNLPRuntime.getMessage(key); }
+
+    /** the location this JNLP file was created from */
+    protected URL sourceLocation = null;
+
+    /** the network location of this JNLP file */
+    protected URL fileLocation;
+
+    /** the URL used to resolve relative URLs in the file */
+    protected URL codeBase;
+
+    /** file version */
+    protected Version fileVersion;
+
+    /** spec version */
+    protected Version specVersion;
+
+    /** information */
+    protected List info;
+
+    /** resources */
+    protected List resources;
+
+    /** additional resources not in JNLP file (from command line) */
+    protected ResourcesDesc sharedResources = new ResourcesDesc(this, null, null, null);
+
+    /** the application description */
+    protected Object launchType;
+
+    /** the security descriptor */
+    protected SecurityDesc security;
+
+    /** the default OS */
+    protected Locale defaultLocale = null;
+
+    /** the default arch */
+    protected String defaultOS = null;
+
+    /** the default jvm */
+    protected String defaultArch = null;
+
+    { // initialize defaults if security allows
+        try {
+            defaultLocale = Locale.getDefault();
+            defaultOS = System.getProperty("os.name");
+            defaultArch = System.getProperty("os.arch");
+        }
+        catch (SecurityException ex) {
+            // null values will still work, and app can set defaults later
+        }
+    }
+    
+    /**
+     * Empty stub, allowing child classes to override the constructor
+     */
+    protected JNLPFile() {
+    }
+
+    /**
+     * Create a JNLPFile from a URL.
+     *
+     * @param location the location of the JNLP file
+     * @throws IOException if an IO exception occurred
+     * @throws ParseException if the JNLP file was invalid
+     */
+    public JNLPFile(URL location) throws IOException, ParseException {
+        this(location, false); // not strict
+    }
+
+    /**
+     * Create a JNLPFile from a URL checking for updates using the
+     * default policy.
+     *
+     * @param location the location of the JNLP file
+     * @param strict whether to enforce the spec when 
+     * @throws IOException if an IO exception occurred
+     * @throws ParseException if the JNLP file was invalid
+     */
+    public JNLPFile(URL location, boolean strict) throws IOException, ParseException {
+        this(location, (Version) null, strict);
+    }
+    
+    /**
+     * Create a JNLPFile from a URL and a Version checking for updates using 
+     * the default policy.
+     *
+     * @param location the location of the JNLP file
+     * @param version the version of the JNLP file
+     * @param strict whether to enforce the spec when 
+     * @throws IOException if an IO exception occurred
+     * @throws ParseException if the JNLP file was invalid
+     */
+    public JNLPFile(URL location, Version version, boolean strict) throws IOException, ParseException {
+        this(location, version, strict, JNLPRuntime.getDefaultUpdatePolicy());
+    }
+
+    /**
+     * Create a JNLPFile from a URL and a version, checking for updates 
+     * using the specified policy.
+     *
+     * @param location the location of the JNLP file
+     * @param version the version of the JNLP file
+     * @param strict whether to enforce the spec when 
+     * @param policy the update policy
+     * @throws IOException if an IO exception occurred
+     * @throws ParseException if the JNLP file was invalid
+     */
+    public JNLPFile(URL location, Version version, boolean strict, UpdatePolicy policy) throws IOException, ParseException {
+        Node root = Parser.getRootNode(openURL(location, version, policy));
+        parse(root, strict, location);
+
+        this.fileLocation = location;
+    }
+
+    /**
+     * Create a JNLPFile from an input stream.
+     *
+     * @throws IOException if an IO exception occurred
+     * @throws ParseException if the JNLP file was invalid
+     */
+    public JNLPFile(InputStream input, boolean strict) throws ParseException {
+        parse(Parser.getRootNode(input), strict, null);
+    }
+
+    /**
+     * Create a JNLPFile from a character stream.
+     *
+     * @param input the stream
+     * @param strict whether to enforce the spec when 
+     * @throws IOException if an IO exception occurred
+     * @throws ParseException if the JNLP file was invalid
+     */
+    private JNLPFile(Reader input, boolean strict) throws ParseException {
+        // todo: now that we are using NanoXML we can use a Reader
+        //parse(Parser.getRootNode(input), strict, null);
+    }
+
+
+    /**
+     * Open the jnlp file URL from the cache if there, otherwise
+     * download to the cache.  Called from constructor.
+     */
+    private static InputStream openURL(URL location, Version version, UpdatePolicy policy) throws IOException {
+        if (location == null || policy == null)
+            throw new IllegalArgumentException(R("NullParameter"));
+
+        try {
+            ResourceTracker tracker = new ResourceTracker(false); // no prefetch
+            tracker.addResource(location, version , policy);
+
+            return tracker.getInputStream(location);
+        }
+        catch (Exception ex) {
+            throw new IOException(ex.getMessage());
+        }
+    }
+
+    /**
+     * Returns the JNLP specification versions supported.
+     */
+    public static Version getSupportedVersions() {
+        return Parser.getSupportedVersions();
+    }
+
+    /**
+     * Returns the JNLP file's title.  This method returns the same
+     * value as InformationDesc.getTitle().
+     */
+    public String getTitle() {
+        return getInformation().getTitle();
+    }
+
+    /**
+     * Returns the JNLP file's network location as specified in the
+     * JNLP file.
+     */
+    public URL getSourceLocation() {
+        return sourceLocation;
+    }
+
+    /**
+     * Returns the location of the file parsed to create the JNLP
+     * file, or null if it was not created from a URL.
+     */
+    public URL getFileLocation() {
+        return fileLocation;
+    }
+
+    /**
+     * Returns the JNLP file's version.
+     */
+    public Version getFileVersion() {
+        return fileVersion;
+    }
+
+    /**
+     * Returns the specification version required by the file.
+     */
+    public Version getSpecVersion() {
+        return specVersion;
+    }
+
+    /**
+     * Returns the codebase URL for the JNLP file.
+     */
+    public URL getCodeBase() {
+        return codeBase;
+    }
+    
+    /**
+     * Returns the information section of the JNLP file as viewed
+     * through the default locale.
+     */
+    public InformationDesc getInformation() {
+        return getInformation(defaultLocale);
+    }
+
+    /**
+     * Returns the information section of the JNLP file as viewed
+     * through the specified locale.
+     */
+    public InformationDesc getInformation(final Locale locale) {
+        return new InformationDesc(this, new Locale[] {locale}) {
+            protected List getItems(Object key) {
+                List result = new ArrayList();
+
+                for (int i=0; i < info.size(); i++) {
+                    InformationDesc infoDesc = (InformationDesc) info.get(i);
+
+                    if (localMatches(locale, infoDesc.getLocales()))
+                        if (localMatches(locale, infoDesc.getLocales()))
+                            result.addAll(infoDesc.getItems(key) );
+                }
+
+                return result;
+            }
+        };
+    }
+
+    /**
+     * Returns the security section of the JNLP file.
+     */
+    public SecurityDesc getSecurity() {
+        return security;
+    }
+
+    /**
+     * Returns the resources section of the JNLP file as viewed
+     * through the default locale and the os.name and os.arch
+     * properties.
+     */
+    public ResourcesDesc getResources() {
+        return getResources(defaultLocale, defaultOS, defaultArch);
+    }
+
+    /**
+     * Returns the information section of the JNLP file for the
+     * specified locale, os, and arch.
+     */
+    public ResourcesDesc getResources(final Locale locale, final String os, final String arch) {
+        return new ResourcesDesc(this, new Locale[] {locale}, new String[] {os}, new String[] {arch}) {
+            public List getResources(Class launchType) {
+                List result = new ArrayList();
+
+                for (int i=0; i < resources.size(); i++) {
+                    ResourcesDesc rescDesc = (ResourcesDesc) resources.get(i);
+
+                    if (localMatches(locale, rescDesc.getLocales())
+                        && stringMatches(os, rescDesc.getOS())
+                        && stringMatches(arch, rescDesc.getArch()))
+                        result.addAll(rescDesc.getResources(launchType) );
+                }
+
+                result.addAll(sharedResources.getResources(launchType));
+
+                return result;
+            }
+
+            public void addResource(Object resource) {
+                // todo: honor the current locale, os, arch values
+                sharedResources.addResource(resource);
+            }
+        };
+    }
+
+    /**
+     * Returns an object of one of the following types: AppletDesc,
+     * ApplicationDesc, InstallerDesc, and ComponentDesc.
+     */
+    public Object getLaunchInfo() {
+        return launchType;
+    }
+
+    /**
+     * Returns the launch information for an applet.
+     *
+     * @throws UnsupportedOperationException if there is no applet information
+     */
+    public AppletDesc getApplet() {
+        if (!isApplet())
+            throw new UnsupportedOperationException(R("JNotApplet"));
+
+        return (AppletDesc) launchType;
+    }
+
+    /**
+     * Returns the launch information for an application.
+     *
+     * @throws UnsupportedOperationException if there is no application information
+     */
+    public ApplicationDesc getApplication() {
+        if (!isApplication())
+            throw new UnsupportedOperationException(R("JNotApplication"));
+
+        return (ApplicationDesc) launchType;
+    }
+
+    /**
+     * Returns the launch information for a component.
+     *
+     * @throws UnsupportedOperationException if there is no component information
+     */
+    public ComponentDesc getComponent() {
+        if (!isComponent())
+            throw new UnsupportedOperationException(R("JNotComponent"));
+
+        return (ComponentDesc) launchType;
+    }
+
+    /**
+     * Returns the launch information for an installer.
+     *
+     * @throws UnsupportedOperationException if there is no installer information
+     */
+    public InstallerDesc getInstaller() {
+        if (!isInstaller())
+            throw new UnsupportedOperationException(R("NotInstaller"));
+
+        return (InstallerDesc) launchType;
+    }
+
+    /** 
+     * Returns whether the lauch descriptor describes an Applet.
+     */
+    public boolean isApplet() {
+        return launchType instanceof AppletDesc;
+    }
+
+    /** 
+     * Returns whether the lauch descriptor describes an Application.
+     */
+    public boolean isApplication() {
+        return launchType instanceof ApplicationDesc;
+    }
+
+    /** 
+     * Returns whether the lauch descriptor describes a Component.
+     */
+    public boolean isComponent() {
+        return launchType instanceof ComponentDesc;
+    }
+
+    /** 
+     * Returns whether the lauch descriptor describes an Installer.
+     */
+    public boolean isInstaller() {
+        return launchType instanceof InstallerDesc;
+    }
+
+    /**
+     * Sets the default view of the JNLP file returned by
+     * getInformation, getResources, etc.  If unset, the defaults
+     * are the properties os.name, os.arch, and the locale returned
+     * by Locale.getDefault().
+     */
+    public void setDefaults(String os, String arch, Locale locale) {
+        defaultOS = os;
+        defaultArch = arch;
+        defaultLocale = locale;
+    }
+
+
+    /**
+     * Returns whether a locale is matched by one of more other
+     * locales.  Only the non-empty language, country, and variant
+     * codes are compared; for example, a requested locale of
+     * Locale("","","") would always return true.
+     *
+     * @param requested the local
+     * @param available the available locales
+     * @return true if requested matches any of available, or if
+     * available is empty or null.
+     */
+    private boolean localMatches(Locale requested, Locale available[]) {
+        if (available == null || available.length == 0)
+            return true;
+
+        for (int i=0; i < available.length; i++) {
+            String language = requested.getLanguage(); // "" but never null
+            String country = requested.getCountry();
+            String variant = requested.getVariant();
+
+            if (!"".equals(language) && !language.equalsIgnoreCase(available[i].getLanguage()))
+                continue;
+            if (!"".equals(country) && !country.equalsIgnoreCase(available[i].getCountry()))
+                continue;
+            if (!"".equals(variant) && !variant.equalsIgnoreCase(available[i].getVariant()))
+                continue;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether the string is a prefix for any of the strings
+     * in the specified array.
+     *
+     * @param prefixStr the prefix string
+     * @param available the strings to test
+     * @return true if prefixStr is a prefix of any strings in
+     * available, or if available is empty or null.
+     */
+    private boolean stringMatches(String prefixStr, String available[]) {
+        if (available == null || available.length == 0)
+            return true;
+
+        for (int i=0; i < available.length; i++)
+            if (available[i] != null && available[i].startsWith(prefixStr))
+                return true;
+
+        return false;
+    }
+
+    /**
+     * Initialize the JNLPFile fields. Private because it's called
+     * from the constructor.
+     *
+     * @param root the root node
+     * @param strict whether to enforce the spec when 
+     * @param location the file location or null
+     */
+    private void parse(Node root, boolean strict, URL location) throws ParseException {
+        try {
+            //if (location != null)
+            //  location = new URL(location, "."); // remove filename
+
+            Parser parser = new Parser(this, location, root, strict, true); // true == allow extensions
+
+            // JNLP tag information
+            specVersion = parser.getSpecVersion();
+            fileVersion = parser.getFileVersion();
+            codeBase = parser.getCodeBase();
+            sourceLocation = parser.getFileLocation();
+
+            info = parser.getInfo(root);
+            resources = parser.getResources(root, false); // false == not a j2se/java resources section
+            launchType = parser.getLauncher(root);
+            security = parser.getSecurity(root);
+        }
+        catch (ParseException ex) {
+            throw ex;
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            throw new RuntimeException(ex.toString());
+        }
+    }
+
+    /**
+     * 
+     * @return true if the JNLP file specifies things that can only be 
+     * applied on a new vm (eg: different max heap memory)
+     */
+    public boolean needsNewVM() {
+        
+        if (getNewVMArgs().size() == 0) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     *  @return a list of args to pass to the new 
+     *  JVM based on this JNLP file
+     */
+    public List<String> getNewVMArgs() {
+        
+        List<String> newVMArgs = new LinkedList<String>();
+        
+        JREDesc[] jres = getResources().getJREs();
+        for (int jreIndex = 0; jreIndex < jres.length; jreIndex++) {
+            String initialHeapSize = jres[jreIndex].getInitialHeapSize();
+            if (initialHeapSize != null) {
+                newVMArgs.add("-Xms" + initialHeapSize);
+            }
+            
+            String maxHeapSize = jres[jreIndex].getMaximumHeapSize();
+            if (maxHeapSize != null) {
+                newVMArgs.add("-Xmx" + maxHeapSize);
+            }
+            
+            String vmArgsFromJre = jres[jreIndex].getVMArgs();
+            if (vmArgsFromJre != null) {
+                String[] args = vmArgsFromJre.split(" ");
+                newVMArgs.addAll(Arrays.asList(args));
+            }
+        }
+        
+        return newVMArgs;
+    }
+
+}
--- a/rt/net/sourceforge/jnlp/LaunchException.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/LaunchException.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,190 +1,190 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-package net.sourceforge.jnlp;
-
-import java.io.*;
-import java.util.*;
-
-import net.sourceforge.jnlp.util.*;
-
-/**
- * Thrown when a JNLP application, applet, or installer could not
- * be created.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.9 $
- */
-public class LaunchException extends Exception {
-
-    /** the original exception */
-    private Throwable cause = null;
-
-    /** the file being launched */
-    private JNLPFile file;
-
-    /** the category of the exception */
-    private String category;
-
-    /** summary */
-    private String summary;
-
-    /** description of the action that was taking place */
-    private String description;
-
-    /** severity of the warning/error */
-    private String severity;
-
-
-    /**
-     * Creates a LaunchException without detail message.
-     */
-    public LaunchException(JNLPFile file, Exception cause, String severity, String category, String summary, String description) {
-        super(severity + ": " + category  + ": "+ summary);
-
-        this.file = file;
-        this.category = category;
-        this.summary = summary;
-        this.description = description;
-        this.severity = severity;
-
-        // replace with setCause when no longer 1.3 compatible
-        this.cause = cause;
-    }
-
-    /**
-     * Creates a LaunchException with a cause. 
-     */
-    public LaunchException(Throwable cause) {
-        this(cause.getMessage());
-
-        // replace with setCause when no longer 1.3 compatible
-        this.cause = cause;
-    }
-
-    /**
-     * Creates a LaunchException with a cause and detail message
-     */
-    public LaunchException(String message, Throwable cause) {
-        this(message+": "+cause.getMessage());
-
-        // replace with setCause when no longer 1.3 compatible
-        this.cause = cause;
-    }
-
-    /**
-     * Constructs a LaunchException with the specified detail
-     * message.
-     * 
-     * @param message the detail message
-     */
-    public LaunchException(String message) {
-        super(message);
-    }
-
-    /**
-     * Returns the JNLPFile being launched.
-     */
-    public JNLPFile getFile() {
-        return file;
-    }
-
-    /**
-     * Returns the category string, a short description of the
-     * exception suitable for displaying in a window title.
-     */
-    public String getCategory() {
-        return category;
-    }
-
-    /**
-     * Returns a one-sentence summary of the problem.
-     */
-    public String getSummary() {
-        return summary;
-    }
-
-    /**
-     * Return a description of the exception and the action being
-     * performed when the exception occurred.
-     */
-    public String getDescription() {
-        return description;
-    }
-
-    /**
-     * Returns a short description of the severity of the problem.
-     */
-    public String getSeverity() {
-        return severity;
-    }
-
-    /**
-     * Return the cause of the launch exception or null if there
-     * is no cause exception.
-     */
-    public Throwable getCause() {
-        return cause;
-    }
-
-    /**
-     * Returns the causes for this exception.  This method is
-     * useful on JRE 1.3 since getCause is not a standard method,
-     * and will be removed once netx no longer supports 1.3.
-     */
-    public Throwable[] getCauses() {
-        ArrayList result = new ArrayList();
-
-        Reflect r = new Reflect();
-        Throwable cause = this.cause;
-
-        while (cause != null) {
-            result.add(cause);
-            cause = (Throwable) r.invoke(cause, "getCause");
-        }
-
-        return (Throwable[]) result.toArray(new Throwable[0]);
-    }
-
-    /**
-     * Print the stack trace and the cause exception (1.3
-     * compatible)
-     */
-    public void printStackTrace(PrintStream stream) {
-        super.printStackTrace(stream);
-
-        if (cause != null) {
-            stream.println("Caused by: ");
-            cause.printStackTrace(stream);
-        }
-    }
-
-    /**
-     * Print the stack trace and the cause exception (1.3
-     * compatible)
-     */
-    public void printStackTrace(PrintWriter stream) {
-        super.printStackTrace(stream);
-
-        if (cause != null) {
-            stream.println("Caused by: ");
-            cause.printStackTrace(stream);
-        }
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp;
+
+import java.io.*;
+import java.util.*;
+
+import net.sourceforge.jnlp.util.*;
+
+/**
+ * Thrown when a JNLP application, applet, or installer could not
+ * be created.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.9 $
+ */
+public class LaunchException extends Exception {
+
+    /** the original exception */
+    private Throwable cause = null;
+
+    /** the file being launched */
+    private JNLPFile file;
+
+    /** the category of the exception */
+    private String category;
+
+    /** summary */
+    private String summary;
+
+    /** description of the action that was taking place */
+    private String description;
+
+    /** severity of the warning/error */
+    private String severity;
+
+
+    /**
+     * Creates a LaunchException without detail message.
+     */
+    public LaunchException(JNLPFile file, Exception cause, String severity, String category, String summary, String description) {
+        super(severity + ": " + category  + ": "+ summary);
+
+        this.file = file;
+        this.category = category;
+        this.summary = summary;
+        this.description = description;
+        this.severity = severity;
+
+        // replace with setCause when no longer 1.3 compatible
+        this.cause = cause;
+    }
+
+    /**
+     * Creates a LaunchException with a cause. 
+     */
+    public LaunchException(Throwable cause) {
+        this(cause.getMessage());
+
+        // replace with setCause when no longer 1.3 compatible
+        this.cause = cause;
+    }
+
+    /**
+     * Creates a LaunchException with a cause and detail message
+     */
+    public LaunchException(String message, Throwable cause) {
+        this(message+": "+cause.getMessage());
+
+        // replace with setCause when no longer 1.3 compatible
+        this.cause = cause;
+    }
+
+    /**
+     * Constructs a LaunchException with the specified detail
+     * message.
+     * 
+     * @param message the detail message
+     */
+    public LaunchException(String message) {
+        super(message);
+    }
+
+    /**
+     * Returns the JNLPFile being launched.
+     */
+    public JNLPFile getFile() {
+        return file;
+    }
+
+    /**
+     * Returns the category string, a short description of the
+     * exception suitable for displaying in a window title.
+     */
+    public String getCategory() {
+        return category;
+    }
+
+    /**
+     * Returns a one-sentence summary of the problem.
+     */
+    public String getSummary() {
+        return summary;
+    }
+
+    /**
+     * Return a description of the exception and the action being
+     * performed when the exception occurred.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Returns a short description of the severity of the problem.
+     */
+    public String getSeverity() {
+        return severity;
+    }
+
+    /**
+     * Return the cause of the launch exception or null if there
+     * is no cause exception.
+     */
+    public Throwable getCause() {
+        return cause;
+    }
+
+    /**
+     * Returns the causes for this exception.  This method is
+     * useful on JRE 1.3 since getCause is not a standard method,
+     * and will be removed once netx no longer supports 1.3.
+     */
+    public Throwable[] getCauses() {
+        ArrayList result = new ArrayList();
+
+        Reflect r = new Reflect();
+        Throwable cause = this.cause;
+
+        while (cause != null) {
+            result.add(cause);
+            cause = (Throwable) r.invoke(cause, "getCause");
+        }
+
+        return (Throwable[]) result.toArray(new Throwable[0]);
+    }
+
+    /**
+     * Print the stack trace and the cause exception (1.3
+     * compatible)
+     */
+    public void printStackTrace(PrintStream stream) {
+        super.printStackTrace(stream);
+
+        if (cause != null) {
+            stream.println("Caused by: ");
+            cause.printStackTrace(stream);
+        }
+    }
+
+    /**
+     * Print the stack trace and the cause exception (1.3
+     * compatible)
+     */
+    public void printStackTrace(PrintWriter stream) {
+        super.printStackTrace(stream);
+
+        if (cause != null) {
+            stream.println("Caused by: ");
+            cause.printStackTrace(stream);
+        }
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/LaunchHandler.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/LaunchHandler.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,68 +1,68 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-package net.sourceforge.jnlp;
-
-import net.sourceforge.jnlp.runtime.*;
-
-/**
- * This optional interface is used to handle conditions that occur
- * while launching JNLP applications, applets, and installers.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.9 $
- */
-public interface LaunchHandler {
-
-    /**
-     * Called when the application could not be launched due to a
-     * fatal error, such as the inability to find the main class or
-     * non-parseable XML.
-     */
-    public void launchError(LaunchException exception);
-
-    /**
-     * Called when launching the application can not be launched due
-     * to an error that is not fatal.  For example a JNLP file that
-     * is not strictly correct yet does not necessarily prohibit the
-     * system from attempting to launch the application.
-     *
-     * @return true if the launch should continue, false to abort
-     */
-    public boolean launchWarning(LaunchException warning);
-
-    /**
-     * Called when a security validation error occurs while
-     * launching the application.
-     *
-     * @return true to allow the application to continue, false to stop it.
-     */
-    public boolean validationError(LaunchException security);
-    // this method will probably be replaced when real security
-    // controller is in place.
-
-    /**
-     * Called when an application, applet, or installer has been
-     * launched successfully (the main method or applet start method
-     * returned normally).
-     *
-     * @param application the launched application instance
-     */
-    public void launchCompleted(ApplicationInstance application); 
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp;
+
+import net.sourceforge.jnlp.runtime.*;
+
+/**
+ * This optional interface is used to handle conditions that occur
+ * while launching JNLP applications, applets, and installers.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.9 $
+ */
+public interface LaunchHandler {
+
+    /**
+     * Called when the application could not be launched due to a
+     * fatal error, such as the inability to find the main class or
+     * non-parseable XML.
+     */
+    public void launchError(LaunchException exception);
+
+    /**
+     * Called when launching the application can not be launched due
+     * to an error that is not fatal.  For example a JNLP file that
+     * is not strictly correct yet does not necessarily prohibit the
+     * system from attempting to launch the application.
+     *
+     * @return true if the launch should continue, false to abort
+     */
+    public boolean launchWarning(LaunchException warning);
+
+    /**
+     * Called when a security validation error occurs while
+     * launching the application.
+     *
+     * @return true to allow the application to continue, false to stop it.
+     */
+    public boolean validationError(LaunchException security);
+    // this method will probably be replaced when real security
+    // controller is in place.
+
+    /**
+     * Called when an application, applet, or installer has been
+     * launched successfully (the main method or applet start method
+     * returned normally).
+     *
+     * @param application the launched application instance
+     */
+    public void launchCompleted(ApplicationInstance application); 
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/Launcher.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/Launcher.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,775 +1,775 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp;
-
-import java.applet.Applet;
-import java.awt.Container;
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.management.ManagementFactory;
-import java.lang.management.ThreadMXBean;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.jar.JarFile;
-
-import net.sourceforge.jnlp.cache.CacheUtil;
-import net.sourceforge.jnlp.cache.ResourceTracker;
-import net.sourceforge.jnlp.cache.UpdatePolicy;
-import net.sourceforge.jnlp.runtime.AppThreadGroup;
-import net.sourceforge.jnlp.runtime.AppletInstance;
-import net.sourceforge.jnlp.runtime.ApplicationInstance;
-import net.sourceforge.jnlp.runtime.JNLPClassLoader;
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
-import net.sourceforge.jnlp.services.InstanceExistsException;
-import net.sourceforge.jnlp.services.ServiceUtil;
-import net.sourceforge.jnlp.util.Reflect;
-
-/**
- * Launches JNLPFiles either in the foreground or background.<p>
- *
- * An optional LaunchHandler can be specified that is notified of
- * warning and error condition while launching and that indicates
- * whether a launch may proceed after a warning has occurred.  If
- * specified, the LaunchHandler is notified regardless of whether
- * the file is launched in the foreground or background.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.22 $ 
- */
-public class Launcher {
-
-    // defines class Launcher.BgRunner, Launcher.TgThread
-
-    /** shortcut for resources */
-    private static String R(String key) { return JNLPRuntime.getMessage(key); }
-
-    /** shared thread group */
-    private static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup"));
-
-    /** the handler */
-    private LaunchHandler handler = null;
-
-    /** the update policy */
-    private UpdatePolicy updatePolicy = JNLPRuntime.getDefaultUpdatePolicy();
-
-    /** whether to create an AppContext (if possible) */
-    private boolean context = true;
-
-    /** If the application should call System.exit on fatal errors */
-    private boolean exitOnFailure = true;
-
-    /**
-     * Create a launcher with the runtime's default update policy
-     * and launch handler.
-     */
-    public Launcher() {
-        this(null, null);
-
-        if (handler == null)
-            handler = JNLPRuntime.getDefaultLaunchHandler();
-    }
-    
-    /**
-     * Create a launcher with the runtime's default update policy
-     * and launch handler.
-     * 
-     * @param exitOnError Exit if there is an error (usually default, but false when being used from the plugin)
-     */
-    public Launcher(boolean exitOnFailure) {
-        this(null, null);
-
-        if (handler == null)
-            handler = JNLPRuntime.getDefaultLaunchHandler();
-        
-        this.exitOnFailure = exitOnFailure;
-    }
-
-    /**
-     * Create a launcher with the specified handler and the
-     * runtime's default update policy.
-     *
-     * @param handler the handler to use or null for no handler.
-     */
-    public Launcher(LaunchHandler handler) {
-        this(handler, null);
-    }
-
-    /**
-     * Create a launcher with an optional handler using the
-     * specified update policy and launch handler.
-     *
-     * @param handler the handler to use or null for no handler.
-     * @param policy the update policy to use or null for default policy.
-     */
-    public Launcher(LaunchHandler handler, UpdatePolicy policy) {
-        if (policy == null)
-            policy = JNLPRuntime.getDefaultUpdatePolicy();
-
-        this.handler = handler;
-        this.updatePolicy = policy;
-    }
-
-    /**
-     * Sets the update policy used by launched applications.
-     */
-    public void setUpdatePolicy(UpdatePolicy policy) {
-        if (policy == null)
-            throw new IllegalArgumentException(R("LNullUpdatePolicy"));
-
-        this.updatePolicy = policy;
-    }
-
-    /**
-     * Returns the update policy used when launching applications.
-     */
-    public UpdatePolicy getUpdatePolicy() {
-        return updatePolicy;
-    }
-
-    /**
-     * Sets whether to launch the application in a new AppContext
-     * (a separate event queue, look and feel, etc).  If the
-     * sun.awt.SunToolkit class is not present then this method
-     * has no effect.  The default value is true.
-     */
-    public void setCreateAppContext(boolean context) {
-        this.context = context;
-    }
-
-    /**
-     * Returns whether applications are launched in their own
-     * AppContext.
-     */
-    public boolean isCreateAppContext() {
-        return this.context;
-    }
-
-    /**
-     * Launches a JNLP file by calling the launch method for the
-     * appropriate file type.  The application will be started in
-     * a new window.
-     *
-     * @param file the JNLP file to launch
-     * @return the application instance
-     * @throws LaunchException if an error occurred while launching (also sent to handler)
-     */
-    public ApplicationInstance launch(JNLPFile file) throws LaunchException {
-        return launch(file, null);
-    }
-
-    /**
-     * Launches a JNLP file inside the given container if it is an applet.  Specifying a
-     * container has no effect for Applcations and Installers.
-     *
-     * @param file the JNLP file to launch
-     * @param cont the container in which to place the application, if it is an applet
-     * @return the application instance
-     * @throws LaunchException if an error occurred while launching (also sent to handler)
-     */
-    public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchException {
-        TgThread tg;
-
-        if (file instanceof PluginBridge && cont != null)
-        	tg = new TgThread(file, cont, true);
-        else if (cont == null)
-        	tg = new TgThread(file);
-        else
-        	tg = new TgThread(file, cont);
-
-        tg.start();
-
-        try {
-            tg.join();
-        }
-        catch (InterruptedException ex) {
-			//By default, null is thrown here, and the message dialog is shown.
-            throw launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCSystem"), R("LThreadInterrupted"), R("LThreadInterruptedInfo")));
-        }
-
-        if (tg.getException() != null)
-            throw tg.getException(); // passed to handler when first created
-
-        if (handler != null)
-            handler.launchCompleted(tg.getApplication());
-
-        return tg.getApplication();
-    }    
-
-    /**
-     * Launches a JNLP file by calling the launch method for the
-     * appropriate file type.
-     *
-     * @param location the URL of the JNLP file to launch
-     * @throws LaunchException if there was an exception 
-     * @return the application instance
-     */
-    public ApplicationInstance launch(URL location) throws LaunchException {
-        return launch(toFile(location));
-    }
-
-    /** 
-     * Launches a JNLP file by calling the launch method for the
-     * appropriate file type in a different thread.
-     *
-     * @param file the JNLP file to launch
-     */
-    public void launchBackground(JNLPFile file) {
-        BgRunner runner = new BgRunner(file, null);
-        new Thread(runner).start();
-    }
-
-    /**
-     * Launches the JNLP file at the specified location in the
-     * background by calling the launch method for its file type.
-     *
-     * @param location the location of the JNLP file
-     */
-    public void launchBackground(URL location) {
-        BgRunner runner = new BgRunner(null, location);
-        new Thread(runner).start();
-    }
-
-    /**
-     * Launches the JNLP file in a new JVM instance.  The launched
-     * application's output is sent to the system out and it's
-     * standard input channel is closed.
-     *
-     * @param vmArgs the arguments to pass to the new JVM. Can be empty but
-     *        must not be null.
-     * @param file the JNLP file to launch
-     * @param javawsArgs the arguments to pass to the javaws command. Can be
-     *        an empty list but must not be null.
-     * @throws LaunchException if there was an exception 
-     */
-    public void launchExternal(List<String> vmArgs, JNLPFile file, List<String> javawsArgs) throws LaunchException {
-        List<String> updatedArgs = new LinkedList<String>(javawsArgs);
-        
-        if (file.getFileLocation() != null)
-            updatedArgs.add(file.getFileLocation().toString());
-        else if (file.getSourceLocation() != null)
-            updatedArgs.add(file.getFileLocation().toString());
-        else
-            launchError(new LaunchException(file, null, R("LSFatal"), R("LCExternalLaunch"), R("LNullLocation"), R("LNullLocationInfo")));
-        
-        launchExternal(vmArgs, updatedArgs);
-        
-    }
-    
-    /**
-     * Launches the JNLP file in a new JVM instance.  The launched
-     * application's output is sent to the system out and it's
-     * standard input channel is closed.
-     *
-     * @param url the URL of the JNLP file to launch
-     * @throws LaunchException if there was an exception 
-     */
-    public void launchExternal(URL url) throws LaunchException {
-        List<String> javawsArgs = new LinkedList<String>();
-        javawsArgs.add(url.toString());
-        launchExternal(new LinkedList<String>(), javawsArgs);       
-    }
-
-    /**
-     * Launches the JNLP file at the specified location in a new JVM
-     * instance.  The launched application's output is sent to the
-     * system out and it's standard input channel is closed.
-     * @param vmArgs the arguments to pass to the jvm
-     * @param javawsArgs the arguments to pass to javaws (aka Netx)
-     * @throws LaunchException if there was an exception 
-     */
-    public void launchExternal(List<String> vmArgs, List<String> javawsArgs) throws LaunchException {
-        try {
-
-            List<String> commands = new LinkedList<String>();
-            
-            String pathToWebstartBinary = System.getProperty("java.home") + 
-                                      File.separatorChar + 
-                                      "bin" + 
-                                      File.separatorChar + 
-                                      "javaws";
-            commands.add(pathToWebstartBinary);
-            // use -Jargument format to pass arguments to the JVM through the launcher
-            for (String arg: vmArgs) {
-                commands.add("-J" + arg);
-            }
-            commands.addAll(javawsArgs);
-            
-            String[] command = commands.toArray(new String[] {});
-
-            Process p = Runtime.getRuntime().exec(command);
-            new StreamEater(p.getErrorStream()).start();
-            new StreamEater(p.getInputStream()).start();
-            p.getOutputStream().close();
-
-        }
-        catch (NullPointerException ex) {
-            throw launchError(new LaunchException(null, null, R("LSFatal"), R("LCExternalLaunch"), R("LNetxJarMissing"), R("LNetxJarMissingInfo")));
-        }
-        catch (Exception ex) {
-            throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCExternalLaunch"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
-        }
-    }
-
-    /**
-     * Returns the JNLPFile for the URL, with error handling.
-     */
-    private JNLPFile toFile(URL location) throws LaunchException {
-        try { 
-            JNLPFile file = null;
-
-            try {
-                file = new JNLPFile(location, (Version) null, true, updatePolicy); // strict
-            }
-            catch (ParseException ex) {
-                file = new JNLPFile(location, (Version) null, false, updatePolicy);
-
-                // only here if strict failed but lax did not fail 
-                LaunchException lex = 
-                    launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCFileFormat"), R("LNotToSpec"), R("LNotToSpecInfo")));
-
-                if (lex != null)
-                    throw lex;
-            }
-
-            return file;
-        }
-        catch (Exception ex) {
-            if (ex instanceof LaunchException)
-                throw (LaunchException) ex; // already sent to handler when first thrown
-            else  // IO and Parse
-                throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCReadError"), R("LCantRead"), R("LCantReadInfo")));
-        }
-    }
-
-    /** 
-     * Launches a JNLP application.  This method should be called
-     * from a thread in the application's thread group.
-     */
-    protected ApplicationInstance launchApplication(JNLPFile file) throws LaunchException {
-        if (!file.isApplication())
-            throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplication"), R("LNotApplicationInfo")));
-
-        try {
-            
-            try {
-                ServiceUtil.checkExistingSingleInstance(file);
-            } catch (InstanceExistsException e) {
-                return null;
-            }
-            
-            if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) {
-                List<String> netxArguments = new LinkedList<String>();
-                netxArguments.add("-Xnofork");
-                netxArguments.addAll(JNLPRuntime.getInitialArguments());
-                launchExternal(file.getNewVMArgs(), netxArguments);
-                return null;
-            }
-            
-            final int preferredWidth = 500;
-            final int preferredHeight = 400;
-            JNLPSplashScreen splashScreen = null;
-            URL splashImageURL = file.getInformation().getIconLocation(
-                    IconDesc.SPLASH, preferredWidth, preferredHeight);
-            if (splashImageURL != null) {
-                ResourceTracker resourceTracker = new ResourceTracker(true);
-                resourceTracker.addResource(splashImageURL, file.getFileVersion(), updatePolicy);
-                splashScreen = new JNLPSplashScreen(resourceTracker, null, null);
-                splashScreen.setSplashImageURL(splashImageURL);
-                if (splashScreen.isSplashScreenValid()) {
-                    splashScreen.setVisible(true);
-                }
-            }
-
-
-            ApplicationInstance app = createApplication(file);
-            app.initialize();
-
-            String mainName = file.getApplication().getMainClass();
-            
-            // When the application-desc field is empty, we should take a 
-            // look at the main jar for the main class.
-            if (mainName == null) {
-            	JARDesc mainJarDesc = file.getResources().getMainJAR();
-            	File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null);
-            	if (f != null) {
-            		JarFile mainJar = new JarFile(f);
-            		mainName = mainJar.getManifest().
-            			getMainAttributes().getValue("Main-Class");
-            	}
-            }
-            
-            if (mainName == null)
-            	throw launchError(new LaunchException(file, null, 
-            		R("LSFatal"), R("LCClient"), R("LCantDetermineMainClass") , 
-            		R("LCantDetermineMainClassInfo")));
-            
-            Class mainClass = app.getClassLoader().loadClass(mainName);
-
-            Method main = mainClass.getDeclaredMethod("main", new Class[] {String[].class} );
-            String args[] = file.getApplication().getArguments();
-
-            setContextClassLoaderForAllThreads(app.getClassLoader());
-
-            if (splashScreen != null) {
-                if (splashScreen.isSplashScreenValid()) {
-                    splashScreen.setVisible(false);
-                }
-                splashScreen.dispose();
-            }
-
-            main.invoke(null, new Object[] { args } );
-
-            return app;
-        }
-        catch (LaunchException lex) {
-            throw launchError(lex);
-        }
-        catch (Exception ex) {
-            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
-        }
-    }
-
-    /**
-     * Set the classloader as the context classloader for all threads. This is
-     * required to make some applications work. For example, an application that
-     * provides a custom Swing LnF may ask the swing thread to load resources
-     * from their JNLP, which would only work if the Swing thread knows about
-     * the JNLPClassLoader.
-     * 
-     * @param classLoader the classloader to set as the context classloader
-     */
-    private void setContextClassLoaderForAllThreads(ClassLoader classLoader) {
-        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
-        ThreadGroup root;
-        
-        root = Thread.currentThread().getThreadGroup();
-        while (root.getParent() != null) {
-            root = root.getParent();
-        }
-
-        /* be prepared for change in thread size */
-        int threadCountGuess = threadBean.getThreadCount();
-        Thread[] threads;
-        do {
-            threadCountGuess = threadCountGuess * 2;
-            threads = new Thread[threadCountGuess];
-            root.enumerate(threads, true);
-        } while (threads[threadCountGuess-1] != null);
-        
-        
-        for (Thread thread: threads) {
-            if (thread != null) {
-                if (JNLPRuntime.isDebug()) {
-                    System.err.println("Setting " + classLoader + " as the classloader for thread " + thread.getName());
-                }
-                thread.setContextClassLoader(classLoader);
-            }
-        }
-        
-    }
-
-    /** 
-     * Launches a JNLP applet. This method should be called from a
-     * thread in the application's thread group.<p>
-     *
-     * The enableCodeBase parameter adds the applet's codebase to
-     * the locations searched for resources and classes.  This can
-     * slow down the applet loading but allows browser-style applets
-     * that don't use JAR files exclusively to be run from a applet
-     * JNLP file.  If the applet JNLP file does not specify any
-     * resources then the code base will be enabled regardless of
-     * the specified value.<p>
-     *
-     * @param file the JNLP file
-     * @param enableCodeBase whether to add the codebase URL to the classloader
-     */
-    protected ApplicationInstance launchApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
-        if (!file.isApplet())
-            throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo")));
-
-        try {
-            AppletInstance applet = createApplet(file, enableCodeBase, cont);
-            applet.initialize();
-            
-            applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance
-            return applet;
-        }
-        catch (LaunchException lex) {
-            throw launchError(lex);
-        }
-        catch (Exception ex) {
-            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
-        }
-    }
-    
-    /**
-     * Gets an ApplicationInstance, but does not launch the applet.
-     */
-    protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
-        if (!file.isApplet())
-            throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo")));
-
-        try {
-            AppletInstance applet = createApplet(file, enableCodeBase, cont);
-            applet.initialize();
-            return applet;
-        }
-        catch (LaunchException lex) {
-            throw launchError(lex);
-        }
-        catch (Exception ex) {
-            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
-        }
-    }
-
-    /** 
-     * Launches a JNLP installer.  This method should be called from
-     * a thread in the application's thread group.
-     */
-    protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException {
-        throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo")));
-    }
-
-    /**
-     * Create an AppletInstance.
-     *
-     * @param enableCodeBase whether to add the code base URL to the classloader
-     */
-    protected AppletInstance createApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
-        try {
-            JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
-
-            if (enableCodeBase || file.getResources().getJARs().length == 0)
-                loader.enableCodeBase();
-
-            AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup();
-
-            String appletName = file.getApplet().getMainClass();
-
-			//Classloader chokes if there's '/' in the path to the main class.
-			//Must replace with '.' instead.
-			appletName = appletName.replace('/', '.');
-            Class appletClass = loader.loadClass(appletName);
-            Applet applet = (Applet) appletClass.newInstance();
-
-            AppletInstance appletInstance;
-            if (cont == null)
-              appletInstance = new AppletInstance(file, group, loader, applet);
-            else
-              appletInstance = new AppletInstance(file, group, loader, applet, cont);
-
-            group.setApplication(appletInstance);
-            loader.setApplication(appletInstance);
-
-            return appletInstance;
-        }
-        catch (Exception ex) {
-            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo")));
-        }
-    }
-
-    /**
-     * Creates an Applet object from a JNLPFile. This is mainly to be used with
-     * gcjwebplugin.
-     * @param file the PluginBridge to be used.
-     * @param enableCodeBase whether to add the code base URL to the classloader.
-     */
-    protected Applet createAppletObject(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
-        try {
-            JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
-
-            if (enableCodeBase || file.getResources().getJARs().length == 0)
-                loader.enableCodeBase();
-
-            String appletName = file.getApplet().getMainClass();
-
-			//Classloader chokes if there's '/' in the path to the main class.
-			//Must replace with '.' instead.
-			appletName = appletName.replace('/', '.');
-            Class appletClass = loader.loadClass(appletName);
-            Applet applet = (Applet) appletClass.newInstance();
-
-            return applet;
-        }
-        catch (Exception ex) {
-            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo")));
-        }
-    }
-    
-    /**
-     * Creates an Application.
-     */
-    protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException {
-        try {
-            JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
-            AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup();
-
-            ApplicationInstance app = new ApplicationInstance(file, group, loader);
-            group.setApplication(app);
-            loader.setApplication(app);
-
-            return app;
-        }
-        catch (Exception ex) {
-            throw new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplication"), R("LInitApplicationInfo"));
-        }
-    }
-
-    /**
-     * Create a thread group for the JNLP file.
-     */
-    protected AppThreadGroup createThreadGroup(JNLPFile file) {
-        return new AppThreadGroup(mainGroup, file.getTitle());
-    }
-
-    /**
-     * Send n launch error to the handler, if set, and also to the
-     * caller.
-     */
-    private LaunchException launchError(LaunchException ex) {
-        if (handler != null)
-            handler.launchError(ex);
-
-        return ex;
-    }
-
-    /**
-     * Send a launch error to the handler, if set, and to the
-     * caller only if the handler indicated that the launch should
-     * continue despite the warning.
-     *
-     * @return an exception to throw if the launch should be aborted, or null otherwise
-     */
-    private LaunchException launchWarning(LaunchException ex) {
-        if (handler != null)
-            if (!handler.launchWarning(ex))
-                // no need to destroy the app b/c it hasn't started
-                return ex; // chose to abort
-
-        return null; // chose to continue, or no handler
-    }
-
-
-
-    /**
-     * This runnable is used to call the appropriate launch method
-     * for the application, applet, or installer in its thread group.
-     */
-    private class TgThread extends Thread { // ThreadGroupThread
-        private JNLPFile file;
-        private ApplicationInstance application;
-        private LaunchException exception;
-        private Container cont;
-        private boolean isPlugin = false;
-
-        TgThread(JNLPFile file) {
-            this(file, null);
-        }
-
-        TgThread(JNLPFile file, Container cont) {
-            super(createThreadGroup(file), file.getTitle());
-
-            this.file = file;
-            this.cont = cont;
-        }
-        
-        TgThread(JNLPFile file, Container cont, boolean isPlugin) {
-            super(createThreadGroup(file), file.getTitle());
-            this.file = file;
-            this.cont = cont;
-            this.isPlugin = isPlugin;
-        }
-
-        public void run() {
-            try {
-                // Do not create new AppContext if we're using NetX and gcjwebplugin.
-                if (context && !isPlugin)
-                	new Reflect().invokeStatic("sun.awt.SunToolkit", "createNewAppContext");
-
-                if (isPlugin) {
-                	// Do not display download indicators if we're using gcjwebplugin.
-                	JNLPRuntime.setDefaultDownloadIndicator(null);
-                	application = getApplet(file, true, cont);
-                } else {
-                	if (file.isApplication())
-                		application = launchApplication(file);
-                	else if (file.isApplet())
-                		application = launchApplet(file, true, cont); // enable applet code base
-                	else if (file.isInstaller())
-                		application = launchInstaller(file);
-                	else 
-                		throw launchError(new LaunchException(file, null, 
-                				R("LSFatal"), R("LCClient"), R("LNotLaunchable"), 
-                				R("LNotLaunchableInfo")));
-                }
-            }
-            catch (LaunchException ex) {
-                ex.printStackTrace();
-                exception = ex;
-                // Exit if we can't launch the application.
-                if (exitOnFailure)
-                	System.exit(0);
-            }
-        }
-
-        public LaunchException getException() {
-            return exception;
-        }
-
-        public ApplicationInstance getApplication() {
-            return application;
-        }
-        
-    };
-
-
-    /**
-     * This runnable is used by the <code>launchBackground</code>
-     * methods to launch a JNLP file from a separate thread.
-     */
-    private class BgRunner implements Runnable {
-        private JNLPFile file;
-        private URL location;
-
-        BgRunner(JNLPFile file, URL location) {
-            this.file = file;
-            this.location = location;
-        }
-
-        public void run() {
-            try {
-                if (file != null)
-                    launch(file);
-                if (location != null)
-                    launch(location);
-            }
-            catch (LaunchException ex) {
-                // launch method communicates error conditions to the
-                // handler if it exists, otherwise we don't care because
-                // there's nothing that can be done about the exception.
-            }
-        }
-    };
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp;
+
+import java.applet.Applet;
+import java.awt.Container;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.JarFile;
+
+import net.sourceforge.jnlp.cache.CacheUtil;
+import net.sourceforge.jnlp.cache.ResourceTracker;
+import net.sourceforge.jnlp.cache.UpdatePolicy;
+import net.sourceforge.jnlp.runtime.AppThreadGroup;
+import net.sourceforge.jnlp.runtime.AppletInstance;
+import net.sourceforge.jnlp.runtime.ApplicationInstance;
+import net.sourceforge.jnlp.runtime.JNLPClassLoader;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.services.InstanceExistsException;
+import net.sourceforge.jnlp.services.ServiceUtil;
+import net.sourceforge.jnlp.util.Reflect;
+
+/**
+ * Launches JNLPFiles either in the foreground or background.<p>
+ *
+ * An optional LaunchHandler can be specified that is notified of
+ * warning and error condition while launching and that indicates
+ * whether a launch may proceed after a warning has occurred.  If
+ * specified, the LaunchHandler is notified regardless of whether
+ * the file is launched in the foreground or background.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.22 $ 
+ */
+public class Launcher {
+
+    // defines class Launcher.BgRunner, Launcher.TgThread
+
+    /** shortcut for resources */
+    private static String R(String key) { return JNLPRuntime.getMessage(key); }
+
+    /** shared thread group */
+    private static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup"));
+
+    /** the handler */
+    private LaunchHandler handler = null;
+
+    /** the update policy */
+    private UpdatePolicy updatePolicy = JNLPRuntime.getDefaultUpdatePolicy();
+
+    /** whether to create an AppContext (if possible) */
+    private boolean context = true;
+
+    /** If the application should call System.exit on fatal errors */
+    private boolean exitOnFailure = true;
+
+    /**
+     * Create a launcher with the runtime's default update policy
+     * and launch handler.
+     */
+    public Launcher() {
+        this(null, null);
+
+        if (handler == null)
+            handler = JNLPRuntime.getDefaultLaunchHandler();
+    }
+    
+    /**
+     * Create a launcher with the runtime's default update policy
+     * and launch handler.
+     * 
+     * @param exitOnError Exit if there is an error (usually default, but false when being used from the plugin)
+     */
+    public Launcher(boolean exitOnFailure) {
+        this(null, null);
+
+        if (handler == null)
+            handler = JNLPRuntime.getDefaultLaunchHandler();
+        
+        this.exitOnFailure = exitOnFailure;
+    }
+
+    /**
+     * Create a launcher with the specified handler and the
+     * runtime's default update policy.
+     *
+     * @param handler the handler to use or null for no handler.
+     */
+    public Launcher(LaunchHandler handler) {
+        this(handler, null);
+    }
+
+    /**
+     * Create a launcher with an optional handler using the
+     * specified update policy and launch handler.
+     *
+     * @param handler the handler to use or null for no handler.
+     * @param policy the update policy to use or null for default policy.
+     */
+    public Launcher(LaunchHandler handler, UpdatePolicy policy) {
+        if (policy == null)
+            policy = JNLPRuntime.getDefaultUpdatePolicy();
+
+        this.handler = handler;
+        this.updatePolicy = policy;
+    }
+
+    /**
+     * Sets the update policy used by launched applications.
+     */
+    public void setUpdatePolicy(UpdatePolicy policy) {
+        if (policy == null)
+            throw new IllegalArgumentException(R("LNullUpdatePolicy"));
+
+        this.updatePolicy = policy;
+    }
+
+    /**
+     * Returns the update policy used when launching applications.
+     */
+    public UpdatePolicy getUpdatePolicy() {
+        return updatePolicy;
+    }
+
+    /**
+     * Sets whether to launch the application in a new AppContext
+     * (a separate event queue, look and feel, etc).  If the
+     * sun.awt.SunToolkit class is not present then this method
+     * has no effect.  The default value is true.
+     */
+    public void setCreateAppContext(boolean context) {
+        this.context = context;
+    }
+
+    /**
+     * Returns whether applications are launched in their own
+     * AppContext.
+     */
+    public boolean isCreateAppContext() {
+        return this.context;
+    }
+
+    /**
+     * Launches a JNLP file by calling the launch method for the
+     * appropriate file type.  The application will be started in
+     * a new window.
+     *
+     * @param file the JNLP file to launch
+     * @return the application instance
+     * @throws LaunchException if an error occurred while launching (also sent to handler)
+     */
+    public ApplicationInstance launch(JNLPFile file) throws LaunchException {
+        return launch(file, null);
+    }
+
+    /**
+     * Launches a JNLP file inside the given container if it is an applet.  Specifying a
+     * container has no effect for Applcations and Installers.
+     *
+     * @param file the JNLP file to launch
+     * @param cont the container in which to place the application, if it is an applet
+     * @return the application instance
+     * @throws LaunchException if an error occurred while launching (also sent to handler)
+     */
+    public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchException {
+        TgThread tg;
+
+        if (file instanceof PluginBridge && cont != null)
+        	tg = new TgThread(file, cont, true);
+        else if (cont == null)
+        	tg = new TgThread(file);
+        else
+        	tg = new TgThread(file, cont);
+
+        tg.start();
+
+        try {
+            tg.join();
+        }
+        catch (InterruptedException ex) {
+			//By default, null is thrown here, and the message dialog is shown.
+            throw launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCSystem"), R("LThreadInterrupted"), R("LThreadInterruptedInfo")));
+        }
+
+        if (tg.getException() != null)
+            throw tg.getException(); // passed to handler when first created
+
+        if (handler != null)
+            handler.launchCompleted(tg.getApplication());
+
+        return tg.getApplication();
+    }    
+
+    /**
+     * Launches a JNLP file by calling the launch method for the
+     * appropriate file type.
+     *
+     * @param location the URL of the JNLP file to launch
+     * @throws LaunchException if there was an exception 
+     * @return the application instance
+     */
+    public ApplicationInstance launch(URL location) throws LaunchException {
+        return launch(toFile(location));
+    }
+
+    /** 
+     * Launches a JNLP file by calling the launch method for the
+     * appropriate file type in a different thread.
+     *
+     * @param file the JNLP file to launch
+     */
+    public void launchBackground(JNLPFile file) {
+        BgRunner runner = new BgRunner(file, null);
+        new Thread(runner).start();
+    }
+
+    /**
+     * Launches the JNLP file at the specified location in the
+     * background by calling the launch method for its file type.
+     *
+     * @param location the location of the JNLP file
+     */
+    public void launchBackground(URL location) {
+        BgRunner runner = new BgRunner(null, location);
+        new Thread(runner).start();
+    }
+
+    /**
+     * Launches the JNLP file in a new JVM instance.  The launched
+     * application's output is sent to the system out and it's
+     * standard input channel is closed.
+     *
+     * @param vmArgs the arguments to pass to the new JVM. Can be empty but
+     *        must not be null.
+     * @param file the JNLP file to launch
+     * @param javawsArgs the arguments to pass to the javaws command. Can be
+     *        an empty list but must not be null.
+     * @throws LaunchException if there was an exception 
+     */
+    public void launchExternal(List<String> vmArgs, JNLPFile file, List<String> javawsArgs) throws LaunchException {
+        List<String> updatedArgs = new LinkedList<String>(javawsArgs);
+        
+        if (file.getFileLocation() != null)
+            updatedArgs.add(file.getFileLocation().toString());
+        else if (file.getSourceLocation() != null)
+            updatedArgs.add(file.getFileLocation().toString());
+        else
+            launchError(new LaunchException(file, null, R("LSFatal"), R("LCExternalLaunch"), R("LNullLocation"), R("LNullLocationInfo")));
+        
+        launchExternal(vmArgs, updatedArgs);
+        
+    }
+    
+    /**
+     * Launches the JNLP file in a new JVM instance.  The launched
+     * application's output is sent to the system out and it's
+     * standard input channel is closed.
+     *
+     * @param url the URL of the JNLP file to launch
+     * @throws LaunchException if there was an exception 
+     */
+    public void launchExternal(URL url) throws LaunchException {
+        List<String> javawsArgs = new LinkedList<String>();
+        javawsArgs.add(url.toString());
+        launchExternal(new LinkedList<String>(), javawsArgs);       
+    }
+
+    /**
+     * Launches the JNLP file at the specified location in a new JVM
+     * instance.  The launched application's output is sent to the
+     * system out and it's standard input channel is closed.
+     * @param vmArgs the arguments to pass to the jvm
+     * @param javawsArgs the arguments to pass to javaws (aka Netx)
+     * @throws LaunchException if there was an exception 
+     */
+    public void launchExternal(List<String> vmArgs, List<String> javawsArgs) throws LaunchException {
+        try {
+
+            List<String> commands = new LinkedList<String>();
+            
+            String pathToWebstartBinary = System.getProperty("java.home") + 
+                                      File.separatorChar + 
+                                      "bin" + 
+                                      File.separatorChar + 
+                                      "javaws";
+            commands.add(pathToWebstartBinary);
+            // use -Jargument format to pass arguments to the JVM through the launcher
+            for (String arg: vmArgs) {
+                commands.add("-J" + arg);
+            }
+            commands.addAll(javawsArgs);
+            
+            String[] command = commands.toArray(new String[] {});
+
+            Process p = Runtime.getRuntime().exec(command);
+            new StreamEater(p.getErrorStream()).start();
+            new StreamEater(p.getInputStream()).start();
+            p.getOutputStream().close();
+
+        }
+        catch (NullPointerException ex) {
+            throw launchError(new LaunchException(null, null, R("LSFatal"), R("LCExternalLaunch"), R("LNetxJarMissing"), R("LNetxJarMissingInfo")));
+        }
+        catch (Exception ex) {
+            throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCExternalLaunch"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
+        }
+    }
+
+    /**
+     * Returns the JNLPFile for the URL, with error handling.
+     */
+    private JNLPFile toFile(URL location) throws LaunchException {
+        try { 
+            JNLPFile file = null;
+
+            try {
+                file = new JNLPFile(location, (Version) null, true, updatePolicy); // strict
+            }
+            catch (ParseException ex) {
+                file = new JNLPFile(location, (Version) null, false, updatePolicy);
+
+                // only here if strict failed but lax did not fail 
+                LaunchException lex = 
+                    launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCFileFormat"), R("LNotToSpec"), R("LNotToSpecInfo")));
+
+                if (lex != null)
+                    throw lex;
+            }
+
+            return file;
+        }
+        catch (Exception ex) {
+            if (ex instanceof LaunchException)
+                throw (LaunchException) ex; // already sent to handler when first thrown
+            else  // IO and Parse
+                throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCReadError"), R("LCantRead"), R("LCantReadInfo")));
+        }
+    }
+
+    /** 
+     * Launches a JNLP application.  This method should be called
+     * from a thread in the application's thread group.
+     */
+    protected ApplicationInstance launchApplication(JNLPFile file) throws LaunchException {
+        if (!file.isApplication())
+            throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplication"), R("LNotApplicationInfo")));
+
+        try {
+            
+            try {
+                ServiceUtil.checkExistingSingleInstance(file);
+            } catch (InstanceExistsException e) {
+                return null;
+            }
+            
+            if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) {
+                List<String> netxArguments = new LinkedList<String>();
+                netxArguments.add("-Xnofork");
+                netxArguments.addAll(JNLPRuntime.getInitialArguments());
+                launchExternal(file.getNewVMArgs(), netxArguments);
+                return null;
+            }
+            
+            final int preferredWidth = 500;
+            final int preferredHeight = 400;
+            JNLPSplashScreen splashScreen = null;
+            URL splashImageURL = file.getInformation().getIconLocation(
+                    IconDesc.SPLASH, preferredWidth, preferredHeight);
+            if (splashImageURL != null) {
+                ResourceTracker resourceTracker = new ResourceTracker(true);
+                resourceTracker.addResource(splashImageURL, file.getFileVersion(), updatePolicy);
+                splashScreen = new JNLPSplashScreen(resourceTracker, null, null);
+                splashScreen.setSplashImageURL(splashImageURL);
+                if (splashScreen.isSplashScreenValid()) {
+                    splashScreen.setVisible(true);
+                }
+            }
+
+
+            ApplicationInstance app = createApplication(file);
+            app.initialize();
+
+            String mainName = file.getApplication().getMainClass();
+            
+            // When the application-desc field is empty, we should take a 
+            // look at the main jar for the main class.
+            if (mainName == null) {
+            	JARDesc mainJarDesc = file.getResources().getMainJAR();
+            	File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null);
+            	if (f != null) {
+            		JarFile mainJar = new JarFile(f);
+            		mainName = mainJar.getManifest().
+            			getMainAttributes().getValue("Main-Class");
+            	}
+            }
+            
+            if (mainName == null)
+            	throw launchError(new LaunchException(file, null, 
+            		R("LSFatal"), R("LCClient"), R("LCantDetermineMainClass") , 
+            		R("LCantDetermineMainClassInfo")));
+            
+            Class mainClass = app.getClassLoader().loadClass(mainName);
+
+            Method main = mainClass.getDeclaredMethod("main", new Class[] {String[].class} );
+            String args[] = file.getApplication().getArguments();
+
+            setContextClassLoaderForAllThreads(app.getClassLoader());
+
+            if (splashScreen != null) {
+                if (splashScreen.isSplashScreenValid()) {
+                    splashScreen.setVisible(false);
+                }
+                splashScreen.dispose();
+            }
+
+            main.invoke(null, new Object[] { args } );
+
+            return app;
+        }
+        catch (LaunchException lex) {
+            throw launchError(lex);
+        }
+        catch (Exception ex) {
+            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
+        }
+    }
+
+    /**
+     * Set the classloader as the context classloader for all threads. This is
+     * required to make some applications work. For example, an application that
+     * provides a custom Swing LnF may ask the swing thread to load resources
+     * from their JNLP, which would only work if the Swing thread knows about
+     * the JNLPClassLoader.
+     * 
+     * @param classLoader the classloader to set as the context classloader
+     */
+    private void setContextClassLoaderForAllThreads(ClassLoader classLoader) {
+        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
+        ThreadGroup root;
+        
+        root = Thread.currentThread().getThreadGroup();
+        while (root.getParent() != null) {
+            root = root.getParent();
+        }
+
+        /* be prepared for change in thread size */
+        int threadCountGuess = threadBean.getThreadCount();
+        Thread[] threads;
+        do {
+            threadCountGuess = threadCountGuess * 2;
+            threads = new Thread[threadCountGuess];
+            root.enumerate(threads, true);
+        } while (threads[threadCountGuess-1] != null);
+        
+        
+        for (Thread thread: threads) {
+            if (thread != null) {
+                if (JNLPRuntime.isDebug()) {
+                    System.err.println("Setting " + classLoader + " as the classloader for thread " + thread.getName());
+                }
+                thread.setContextClassLoader(classLoader);
+            }
+        }
+        
+    }
+
+    /** 
+     * Launches a JNLP applet. This method should be called from a
+     * thread in the application's thread group.<p>
+     *
+     * The enableCodeBase parameter adds the applet's codebase to
+     * the locations searched for resources and classes.  This can
+     * slow down the applet loading but allows browser-style applets
+     * that don't use JAR files exclusively to be run from a applet
+     * JNLP file.  If the applet JNLP file does not specify any
+     * resources then the code base will be enabled regardless of
+     * the specified value.<p>
+     *
+     * @param file the JNLP file
+     * @param enableCodeBase whether to add the codebase URL to the classloader
+     */
+    protected ApplicationInstance launchApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
+        if (!file.isApplet())
+            throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo")));
+
+        try {
+            AppletInstance applet = createApplet(file, enableCodeBase, cont);
+            applet.initialize();
+            
+            applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance
+            return applet;
+        }
+        catch (LaunchException lex) {
+            throw launchError(lex);
+        }
+        catch (Exception ex) {
+            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
+        }
+    }
+    
+    /**
+     * Gets an ApplicationInstance, but does not launch the applet.
+     */
+    protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
+        if (!file.isApplet())
+            throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo")));
+
+        try {
+            AppletInstance applet = createApplet(file, enableCodeBase, cont);
+            applet.initialize();
+            return applet;
+        }
+        catch (LaunchException lex) {
+            throw launchError(lex);
+        }
+        catch (Exception ex) {
+            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo")));
+        }
+    }
+
+    /** 
+     * Launches a JNLP installer.  This method should be called from
+     * a thread in the application's thread group.
+     */
+    protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException {
+        throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo")));
+    }
+
+    /**
+     * Create an AppletInstance.
+     *
+     * @param enableCodeBase whether to add the code base URL to the classloader
+     */
+    protected AppletInstance createApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
+        try {
+            JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
+
+            if (enableCodeBase || file.getResources().getJARs().length == 0)
+                loader.enableCodeBase();
+
+            AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup();
+
+            String appletName = file.getApplet().getMainClass();
+
+			//Classloader chokes if there's '/' in the path to the main class.
+			//Must replace with '.' instead.
+			appletName = appletName.replace('/', '.');
+            Class appletClass = loader.loadClass(appletName);
+            Applet applet = (Applet) appletClass.newInstance();
+
+            AppletInstance appletInstance;
+            if (cont == null)
+              appletInstance = new AppletInstance(file, group, loader, applet);
+            else
+              appletInstance = new AppletInstance(file, group, loader, applet, cont);
+
+            group.setApplication(appletInstance);
+            loader.setApplication(appletInstance);
+
+            return appletInstance;
+        }
+        catch (Exception ex) {
+            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo")));
+        }
+    }
+
+    /**
+     * Creates an Applet object from a JNLPFile. This is mainly to be used with
+     * gcjwebplugin.
+     * @param file the PluginBridge to be used.
+     * @param enableCodeBase whether to add the code base URL to the classloader.
+     */
+    protected Applet createAppletObject(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException {
+        try {
+            JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
+
+            if (enableCodeBase || file.getResources().getJARs().length == 0)
+                loader.enableCodeBase();
+
+            String appletName = file.getApplet().getMainClass();
+
+			//Classloader chokes if there's '/' in the path to the main class.
+			//Must replace with '.' instead.
+			appletName = appletName.replace('/', '.');
+            Class appletClass = loader.loadClass(appletName);
+            Applet applet = (Applet) appletClass.newInstance();
+
+            return applet;
+        }
+        catch (Exception ex) {
+            throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo")));
+        }
+    }
+    
+    /**
+     * Creates an Application.
+     */
+    protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException {
+        try {
+            JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
+            AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup();
+
+            ApplicationInstance app = new ApplicationInstance(file, group, loader);
+            group.setApplication(app);
+            loader.setApplication(app);
+
+            return app;
+        }
+        catch (Exception ex) {
+            throw new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplication"), R("LInitApplicationInfo"));
+        }
+    }
+
+    /**
+     * Create a thread group for the JNLP file.
+     */
+    protected AppThreadGroup createThreadGroup(JNLPFile file) {
+        return new AppThreadGroup(mainGroup, file.getTitle());
+    }
+
+    /**
+     * Send n launch error to the handler, if set, and also to the
+     * caller.
+     */
+    private LaunchException launchError(LaunchException ex) {
+        if (handler != null)
+            handler.launchError(ex);
+
+        return ex;
+    }
+
+    /**
+     * Send a launch error to the handler, if set, and to the
+     * caller only if the handler indicated that the launch should
+     * continue despite the warning.
+     *
+     * @return an exception to throw if the launch should be aborted, or null otherwise
+     */
+    private LaunchException launchWarning(LaunchException ex) {
+        if (handler != null)
+            if (!handler.launchWarning(ex))
+                // no need to destroy the app b/c it hasn't started
+                return ex; // chose to abort
+
+        return null; // chose to continue, or no handler
+    }
+
+
+
+    /**
+     * This runnable is used to call the appropriate launch method
+     * for the application, applet, or installer in its thread group.
+     */
+    private class TgThread extends Thread { // ThreadGroupThread
+        private JNLPFile file;
+        private ApplicationInstance application;
+        private LaunchException exception;
+        private Container cont;
+        private boolean isPlugin = false;
+
+        TgThread(JNLPFile file) {
+            this(file, null);
+        }
+
+        TgThread(JNLPFile file, Container cont) {
+            super(createThreadGroup(file), file.getTitle());
+
+            this.file = file;
+            this.cont = cont;
+        }
+        
+        TgThread(JNLPFile file, Container cont, boolean isPlugin) {
+            super(createThreadGroup(file), file.getTitle());
+            this.file = file;
+            this.cont = cont;
+            this.isPlugin = isPlugin;
+        }
+
+        public void run() {
+            try {
+                // Do not create new AppContext if we're using NetX and gcjwebplugin.
+                if (context && !isPlugin)
+                	new Reflect().invokeStatic("sun.awt.SunToolkit", "createNewAppContext");
+
+                if (isPlugin) {
+                	// Do not display download indicators if we're using gcjwebplugin.
+                	JNLPRuntime.setDefaultDownloadIndicator(null);
+                	application = getApplet(file, true, cont);
+                } else {
+                	if (file.isApplication())
+                		application = launchApplication(file);
+                	else if (file.isApplet())
+                		application = launchApplet(file, true, cont); // enable applet code base
+                	else if (file.isInstaller())
+                		application = launchInstaller(file);
+                	else 
+                		throw launchError(new LaunchException(file, null, 
+                				R("LSFatal"), R("LCClient"), R("LNotLaunchable"), 
+                				R("LNotLaunchableInfo")));
+                }
+            }
+            catch (LaunchException ex) {
+                ex.printStackTrace();
+                exception = ex;
+                // Exit if we can't launch the application.
+                if (exitOnFailure)
+                	System.exit(0);
+            }
+        }
+
+        public LaunchException getException() {
+            return exception;
+        }
+
+        public ApplicationInstance getApplication() {
+            return application;
+        }
+        
+    };
+
+
+    /**
+     * This runnable is used by the <code>launchBackground</code>
+     * methods to launch a JNLP file from a separate thread.
+     */
+    private class BgRunner implements Runnable {
+        private JNLPFile file;
+        private URL location;
+
+        BgRunner(JNLPFile file, URL location) {
+            this.file = file;
+            this.location = location;
+        }
+
+        public void run() {
+            try {
+                if (file != null)
+                    launch(file);
+                if (location != null)
+                    launch(location);
+            }
+            catch (LaunchException ex) {
+                // launch method communicates error conditions to the
+                // handler if it exists, otherwise we don't care because
+                // there's nothing that can be done about the exception.
+            }
+        }
+    };
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/ParseException.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/ParseException.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,94 +1,94 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-
-/**
- * Thrown to indicate that an error has occurred while parsing a
- * JNLP file.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.7 $
- */
-public class ParseException extends Exception {
-
-    // todo: add meaningful information, such as the invalid
-    // element, parse position, etc.
-    
-    /** the original exception */
-    private Throwable cause = null;
-
-
-    /**
-     * Create a parse exception with the specified message.
-     */
-    public ParseException(String message) {
-        super(message);
-    }
-
-    /**
-     * Create a parse exception with the specified message and
-     * cause.
-     */
-    public ParseException(String message, Throwable cause) {
-        super(message);
-
-        // replace with setCause when no longer 1.3 compatible
-        this.cause = cause;
-    }
-
-    /**
-     * Return the cause of the launch exception or null if there
-     * is no cause exception.
-     */
-    public Throwable getCause() {
-        return cause;
-    }
-
-    /**
-     * Print the stack trace and the cause exception (1.3
-     * compatible)
-     */
-    public void printStackTrace(PrintStream stream) {
-        super.printStackTrace(stream);
-
-        if (cause != null) {
-            stream.println("Caused by: ");
-            cause.printStackTrace(stream);
-        }
-    }
-
-    /**
-     * Print the stack trace and the cause exception (1.3
-     * compatible)
-     */
-    public void printStackTrace(PrintWriter stream) {
-        super.printStackTrace(stream);
-
-        if (cause != null) {
-            stream.println("Caused by: ");
-            cause.printStackTrace(stream);
-        }
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * Thrown to indicate that an error has occurred while parsing a
+ * JNLP file.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.7 $
+ */
+public class ParseException extends Exception {
+
+    // todo: add meaningful information, such as the invalid
+    // element, parse position, etc.
+    
+    /** the original exception */
+    private Throwable cause = null;
+
+
+    /**
+     * Create a parse exception with the specified message.
+     */
+    public ParseException(String message) {
+        super(message);
+    }
+
+    /**
+     * Create a parse exception with the specified message and
+     * cause.
+     */
+    public ParseException(String message, Throwable cause) {
+        super(message);
+
+        // replace with setCause when no longer 1.3 compatible
+        this.cause = cause;
+    }
+
+    /**
+     * Return the cause of the launch exception or null if there
+     * is no cause exception.
+     */
+    public Throwable getCause() {
+        return cause;
+    }
+
+    /**
+     * Print the stack trace and the cause exception (1.3
+     * compatible)
+     */
+    public void printStackTrace(PrintStream stream) {
+        super.printStackTrace(stream);
+
+        if (cause != null) {
+            stream.println("Caused by: ");
+            cause.printStackTrace(stream);
+        }
+    }
+
+    /**
+     * Print the stack trace and the cause exception (1.3
+     * compatible)
+     */
+    public void printStackTrace(PrintWriter stream) {
+        super.printStackTrace(stream);
+
+        if (cause != null) {
+            stream.println("Caused by: ");
+            cause.printStackTrace(stream);
+        }
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/Parser.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/Parser.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,1206 +1,1206 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// Copyright (C) 2009 Red Hat, Inc.
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-//import javax.xml.parsers.*; // commented to use right Node
-//import org.w3c.dom.*;       // class for using Tiny XML | NanoXML
-//import org.xml.sax.*;
-//import gd.xml.tiny.*;
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
-import net.sourceforge.nanoxml.*;
-
-
-/**
- * Contains methods to parse an XML document into a JNLPFile.
- * Implements JNLP specification version 1.0.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.13 $ 
- */
-class Parser {
-
-    private static String R(String key) { return JNLPRuntime.getMessage(key); }
-    private static String R(String key, Object p1) { return R(key, p1, null); }
-    private static String R(String key, Object p1, Object p2) { return R(key, p1, p2, null); }
-    private static String R(String key, Object p1, Object p2, Object p3) { return JNLPRuntime.getMessage(key, new Object[] { p1, p2, p3 }); }
-
-
-    // defines netx.jnlp.Node class if using Tiny XML or Nano XML
-
-    // Currently uses the Nano XML parse.  Search for "SAX" or
-    // "TINY" or "NANO" and uncomment those blocks and comment the
-    // active ones (if any) to switch XML parsers.  Also
-    // (un)comment appropriate Node class at end of this file and
-    // do a clean build.
-
-    /**
-     * Ensure consistent error handling.
-     */
-    /* SAX
-    static ErrorHandler errorHandler = new ErrorHandler() {
-        public void error(SAXParseException exception) throws SAXParseException {
-            //throw exception;
-        }
-        public void fatalError(SAXParseException exception) throws SAXParseException {
-            //throw exception;
-        }
-        public void warning(SAXParseException exception) {
-            System.err.println("XML parse warning:");
-            exception.printStackTrace();
-        }
-    };
-    */
-
-
-    /** the supported JNLP file versions */
-    private static Version supportedVersions = new Version("1.0 1.5 6.0");
-
-    // fix: some descriptors need to use the jnlp file at a later
-    // date and having file ref lets us pass it to their
-    // constructors
-    //
-    /** the file reference */
-    private JNLPFile file; // do not use (uninitialized)
-
-    /** the root node */
-    private Node root;
-
-    /** the specification version */
-    private Version spec;
-
-    /** the base URL that all hrefs are relative to */
-    private URL base;
-
-    /** the codebase URL */
-    private URL codebase;
-
-    /** the file URL */
-    private URL fileLocation;
-
-    /** whether to throw errors on non-fatal errors. */
-    private boolean strict; // if strict==true parses a file with no error then strict==false should also
-
-    /** whether to allow extensions to the JNLP specification */
-    private boolean allowExtensions; // true if extensions to JNLP spec are ok
-
-
-    /**
-     * Create a parser for the JNLP file.  If the location
-     * parameters is not null it is used as the default codebase
-     * (does not override value of jnlp element's href
-     * attribute).<p>
-     *
-     * The root node may be normalized as a side effect of this
-     * constructor.
-     *
-     * @param file the (uninitialized) file reference
-     * @param base if codebase is not specified, a default base for relative URLs 
-     * @param root the root node
-     * @param strict whether to enforce strict compliance with the JNLP spec
-     * @param allowExtensions whether to allow extensions to the JNLP spec
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public Parser(JNLPFile file, URL base, Node root, boolean strict, boolean allowExtensions) throws ParseException {
-        this.file = file;
-        this.root = root;
-        this.strict = strict;
-        this.allowExtensions = allowExtensions;
-
-        // ensure it's a JNLP node
-        if (root == null || !root.getNodeName().equals("jnlp"))
-            throw new ParseException(R("PInvalidRoot"));
-
-        // JNLP tag information
-        this.spec = getVersion(root, "spec", "1.0+");
-        this.codebase = addSlash(getURL(root, "codebase", base));
-        this.base = (codebase!=null) ? codebase : base; // if codebase not specified use default codebase
-        fileLocation = getURL(root, "href", this.base);
-
-        // ensure version is supported
-        if (!supportedVersions.matchesAny(spec))
-            throw new ParseException(R("PSpecUnsupported", supportedVersions));
-
-        // normalize the text nodes
-        root.normalize();
-    }
-
-    /**
-     * Return the JNLP specification versions supported.
-     */
-    public static Version getSupportedVersions() {
-        return supportedVersions;
-    }
-
-    /** 
-     * Returns the file version.
-     */
-    public Version getFileVersion() {
-        return getVersion(root, "version", null);
-    }
-
-    /** 
-     * Returns the file location.
-     */
-    public URL getFileLocation() {
-        return fileLocation;
-    }
-
-    /** 
-     * Returns the codebase.
-     */
-    public URL getCodeBase() {
-        return codebase;
-    }
-
-    /** 
-     * Returns the specification version.
-     */
-    public Version getSpecVersion() {
-        return spec;
-    }
-
-    // 
-    // This section loads the resources elements
-    // 
-
-    /**
-     * Returns all of the ResourcesDesc elements under the specified
-     * node (jnlp or j2se).
-     *
-     * @param parent the parent node (either jnlp or j2se)
-     * @param j2se true if the resources are located under a j2se or java node
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public List getResources(Node parent, boolean j2se) throws ParseException {
-        List result = new ArrayList();
-        Node resources[] = getChildNodes(parent, "resources");
-
-        // ensure that there are at least one information section present
-        if (resources.length == 0 && !j2se)
-            throw new ParseException(R("PNoResources"));
-
-        // create objects from the resources sections
-        for (int i=0; i < resources.length; i++)
-            result.add(getResourcesDesc(resources[i], j2se));
-
-        return result;
-    }
-
-    /**
-     * Returns the ResourcesDesc element at the specified node.
-     *
-     * @param node the resources node
-     * @param j2se true if the resources are located under a j2se or java node
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public ResourcesDesc getResourcesDesc(Node node, boolean j2se) throws ParseException {
-        boolean mainFlag = false; // if found a main tag
-
-        // create resources
-        ResourcesDesc resources = 
-            new ResourcesDesc(file, 
-                              getLocales(node), 
-                              splitString(getAttribute(node, "os", null)),
-                              splitString(getAttribute(node, "arch", null)));
-
-        // step through the elements
-        Node child = node.getFirstChild();
-        while (child != null) {
-            String name = child.getNodeName();
-
-            // check for nativelib but no trusted environment 
-            if ("nativelib".equals(name))
-                if (!isTrustedEnvironment())
-                    throw new ParseException(R("PUntrustedNative"));
-
-            if ("j2se".equals(name) || "java".equals(name)) {
-                if (getChildNode(root, "component-desc") != null)
-                    if (strict)
-                        throw new ParseException(R("PExtensionHasJ2SE"));
-                if (!j2se)
-                    resources.addResource( getJRE(child) );
-                else
-                    throw new ParseException(R("PInnerJ2SE"));
-            }
-
-            if ("jar".equals(name) || "nativelib".equals(name)) {
-                JARDesc jar = getJAR(child);
-
-                // check for duplicate main entries
-                if (jar.isMain()) {
-                    if (mainFlag == true)
-                        if (strict)
-                            throw new ParseException(R("PTwoMains"));
-                    mainFlag = true;
-                }
-
-                resources.addResource(jar);
-            }
-
-            if ("extension".equals(name))
-                resources.addResource( getExtension(child) );
-
-            if ("property".equals(name)) 
-                resources.addResource( getProperty(child) );
-
-            if ("package".equals(name))
-                resources.addResource( getPackage(child) );
-
-            child = child.getNextSibling();
-        }
-
-        return resources;
-    }
-
-    /**
-     * Returns the JRE element at the specified node.
-     *
-     * @param node the j2se/java node 
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public JREDesc getJRE(Node node) throws ParseException {
-        Version version = getVersion(node, "version", null);
-        URL location = getURL(node, "href", base);
-        String vmArgs = getAttribute(node, "java-vm-args",null);
-        try {
-            checkVMArgs(vmArgs);
-        } catch (IllegalArgumentException argumentException) {
-            vmArgs = null;
-        }
-        String initialHeap = getAttribute(node, "initial-heap-size", null);
-        String maxHeap = getAttribute(node, "max-heap-size", null);
-        List resources = getResources(node, true);
-
-        // require version attribute
-        getRequiredAttribute(node, "version", null);
-
-        return new JREDesc(version, location, vmArgs, initialHeap, maxHeap, resources);
-    }
-
-
-
-    /**
-     * Returns the JAR element at the specified node.
-     *
-     * @param node the jar or nativelib node 
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public JARDesc getJAR(Node node) throws ParseException {
-        boolean nativeJar = "nativelib".equals(node.getNodeName());
-        URL location = getRequiredURL(node, "href", base);
-        Version version = getVersion(node, "version", null);
-        String part = getAttribute(node, "part", null);
-        boolean main = "true".equals(getAttribute(node, "main", "false"));
-        boolean lazy = "lazy".equals(getAttribute(node, "download", "eager"));
-        int size = Integer.parseInt(getAttribute(node, "size", "0"));
-
-        if (nativeJar && main)
-            if (strict) 
-                throw new ParseException(R("PNativeHasMain"));
-
-        return new JARDesc(location, version, part, lazy, main, nativeJar, true);
-
-    }
-
-    /**
-     * Returns the Extension element at the specified node.
-     *
-     * @param node the extension node 
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public ExtensionDesc getExtension(Node node) throws ParseException {
-        String name = getAttribute(node, "name", null);
-        Version version = getVersion(node, "version", null);
-        URL location = getRequiredURL(node, "href", base);
-
-        ExtensionDesc ext = new ExtensionDesc(name, version, location);
-
-        Node dload[] = getChildNodes(node, "ext-download");
-        for (int i=0; i < dload.length; i++) {
-            boolean lazy = "lazy".equals(getAttribute(dload[i], "download", "eager"));
-
-            ext.addPart(getRequiredAttribute(dload[i], "ext-part", null),
-                        getAttribute(dload[i], "part", null),
-                        lazy);
-        }
-
-        return ext;
-    }
-
-    /**
-     * Returns the Property element at the specified node.
-     *
-     * @param node the property node 
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public PropertyDesc getProperty(Node node) throws ParseException {
-        String name = getRequiredAttribute(node, "name", null);
-        String value = getRequiredAttribute(node, "value", "");
-
-        return new PropertyDesc(name, value);
-    }
-
-    /**
-     * Returns the Package element at the specified node.
-     *
-     * @param node the package node 
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public PackageDesc getPackage(Node node) throws ParseException {
-        String name = getRequiredAttribute(node, "name", null);
-        String part = getRequiredAttribute(node, "part", "");
-        boolean recursive = getAttribute(node, "recursive", "false").equals("true");
-
-        return new PackageDesc(name, part, recursive);
-    }
-
-    // 
-    // This section loads the information elements
-    // 
-
-    /**
-     * Returns all of the information elements under the specified
-     * node.
-     *
-     * @param parent the parent node (jnlp)
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public List getInfo(Node parent) throws ParseException {
-        List result = new ArrayList();
-        Node info[] = getChildNodes(parent, "information");
-
-        // ensure that there are at least one information section present
-        if (info.length == 0)
-            throw new ParseException(R("PNoInfoElement"));
-
-        // create objects from the info sections
-        for (int i=0; i < info.length; i++)
-            result.add(getInformationDesc(info[i]));
-
-        return result;
-    }
-
-    /**
-     * Returns the information element at the specified node.
-     *
-     * @param node the information node
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public InformationDesc getInformationDesc(Node node) throws ParseException {
-        List descriptionsUsed = new ArrayList();
-
-        // locale
-        Locale locales[] = getLocales(node);
-
-        // create information
-        InformationDesc info = new InformationDesc(file, locales);
-
-        // step through the elements
-        Node child = node.getFirstChild();
-        while (child != null) {
-            String name = child.getNodeName();
-
-            if ("title".equals(name))
-                addInfo(info, child, null, getSpanText(child));
-            if ("vendor".equals(name))
-                addInfo(info, child, null, getSpanText(child));
-            if ("description".equals(name)) {
-                String kind = getAttribute(child, "kind", "default");
-                if (descriptionsUsed.contains(kind))
-                    if (strict)
-                        throw new ParseException(R("PTwoDescriptions", kind));
-
-                descriptionsUsed.add(kind);
-                addInfo(info, child, kind, getSpanText(child));
-            }
-            if ("homepage".equals(name))
-                addInfo(info, child, null, getRequiredURL(child, "href", base));
-            if ("icon".equals(name))
-                addInfo(info, child, getAttribute(child, "kind", "default"), getIcon(child));
-            if ("offline-allowed".equals(name))
-                addInfo(info, child, null, Boolean.TRUE);
-            if ("sharing-allowed".equals(name)) {
-                if (strict && !allowExtensions)
-                    throw new ParseException(R("PSharing"));
-                addInfo(info, child, null, Boolean.TRUE);
-            }
-            if ("association".equals(name)) {
-                addInfo(info, child, null, getAssociation(child));
-            }
-            if ("shortcut".equals(name)) {
-                addInfo(info, child, null, getShortcut(child));
-            }
-            if ("related-content".equals(name)) {
-                addInfo(info, child, null, getRelatedContent(child));
-            }
-
-            child = child.getNextSibling();
-        }
-
-        return info;
-    }
-
-    /**
-     * Adds a key,value pair to the information object.
-     *
-     * @param info the information object 
-     * @param node node name to be used as the key
-     * @param mod key name appended with "-"+mod if not null
-     * @param value the info object to add (icon or string)
-     */
-    protected void addInfo(InformationDesc info, Node node, String mod, Object value) {
-        String modStr = (mod == null) ? "" : "-"+mod;
-
-        if (node == null)
-            return;
-
-        info.addItem(node.getNodeName()+modStr, value);
-    }
-
-    /**
-     * Returns the icon element at the specified node.
-     *
-     * @param node the icon node
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public IconDesc getIcon(Node node) throws ParseException {
-        int width = Integer.parseInt(getAttribute(node, "width", "-1"));
-        int height = Integer.parseInt(getAttribute(node, "height", "-1"));
-        int size = Integer.parseInt(getAttribute(node, "size", "-1"));
-        int depth = Integer.parseInt(getAttribute(node, "depth", "-1"));
-        URL location = getRequiredURL(node, "href", base);
-        Object kind = getAttribute(node, "kind", "default"); 
-
-        return new IconDesc(location, kind, width, height, depth, size);
-    }
-
-    // 
-    // This section loads the security descriptor element
-    // 
-
-    /**
-     * Returns the security descriptor element.  If no security
-     * element was specified in the JNLP file then a SecurityDesc
-     * with applet permissions is returned.
-     *
-     * @param parent the parent node
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public SecurityDesc getSecurity(Node parent) throws ParseException {
-        Node nodes[] = getChildNodes(parent, "security");
-
-        // test for too many security elements
-        if (nodes.length > 1) 
-            if (strict)
-                throw new ParseException(R("PTwoSecurity"));
-
-        Object type = SecurityDesc.SANDBOX_PERMISSIONS;
-
-        if (nodes.length == 0)
-            type = SecurityDesc.SANDBOX_PERMISSIONS;
-        else if (null != getChildNode(nodes[0], "all-permissions"))
-            type = SecurityDesc.ALL_PERMISSIONS;
-        else if (null != getChildNode(nodes[0], "j2ee-application-client-permissions"))
-            type = SecurityDesc.J2EE_PERMISSIONS;
-        else if (strict)
-            throw new ParseException(R("PEmptySecurity"));
-
-        if (base != null)
-            return new SecurityDesc(file, type, base.getHost());
-        else
-            return new SecurityDesc(file, type, null);
-    }
-
-    /**
-     * Returns whether the JNLP file requests a trusted execution
-     * environment.
-     */
-    protected boolean isTrustedEnvironment() {
-        Node security = getChildNode(root, "security");
-
-        if (security != null)
-            if (getChildNode(security, "all-permissions") != null
-                || getChildNode(security, "j2ee-application-client-permissions") != null)
-                return true;
-
-        return false;
-    }
-
-    // 
-    // This section loads the launch descriptor element
-    // 
-
-    /**
-     * Returns the launch descriptor element, either AppletDesc,
-     * ApplicationDesc, ComponentDesc, or InstallerDesc.
-     *
-     * @param parent the parent node
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public Object getLauncher(Node parent) throws ParseException {
-        // check for other than one application type
-        if (1 != getChildNodes(parent, "applet-desc").length
-            + getChildNodes(parent, "application-desc").length
-            + getChildNodes(parent, "component-desc").length
-            + getChildNodes(parent, "installer-desc").length)
-            throw new ParseException(R("PTwoDescriptors"));
-
-        Node child = parent.getFirstChild();
-        while (child != null) {
-            String name = child.getNodeName();
-
-            if ("applet-desc".equals(name))
-                return getApplet(child);
-            if ("application-desc".equals(name))
-                return getApplication(child);
-            if ("component-desc".equals(name))
-                return getComponent(child);
-            if ("installer-desc".equals(name))
-                return getInstaller(child);
-
-            child = child.getNextSibling();
-        }
-
-        // not reached
-        return null;
-    }
-
-    /**
-     * Returns the applet descriptor.
-     *
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public AppletDesc getApplet(Node node) throws ParseException {
-        String name = getRequiredAttribute(node, "name", R("PUnknownApplet"));
-        String main = getRequiredAttribute(node, "main-class", null);
-        URL docbase = getURL(node, "documentbase", base);
-        Map paramMap = new HashMap();
-        int width = 0;
-        int height = 0;
-
-        try {
-            width = Integer.parseInt(getRequiredAttribute(node, "width", "100"));
-            height = Integer.parseInt(getRequiredAttribute(node, "height", "100"));
-        }
-        catch (NumberFormatException nfe) {
-            if (width <= 0)
-                throw new ParseException(R("PBadWidth"));
-            throw new ParseException(R("PBadWidth"));
-        }
-
-        // read params
-        Node params[] = getChildNodes(node, "param");
-        for (int i=0; i < params.length; i++) {
-            paramMap.put(getRequiredAttribute(params[i], "name", null),
-                         getRequiredAttribute(params[i], "value", ""));
-        }
-
-        return new AppletDesc(name, main, docbase, width, height, paramMap);
-    }
-
-    /**
-     * Returns the application descriptor.
-     *
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public ApplicationDesc getApplication(Node node) throws ParseException {
-        String main = getAttribute(node, "main-class", null);
-        List argsList = new ArrayList();
-
-        // if (main == null)
-        //   only ok if can be found in main jar file (can't check here but make a note)
-
-        // read parameters
-        Node args[] = getChildNodes(node, "argument");
-        for (int i=0; i < args.length; i++) {
-            //argsList.add( args[i].getNodeValue() );
-            
-            //This approach was not finding the argument text
-            argsList.add( getSpanText(args[i]) );
-        }
-
-        String argStrings[] = 
-            (String[]) argsList.toArray( new String[argsList.size()] );
-
-        return new ApplicationDesc(main, argStrings);
-    }
-
-    /**
-     * Returns the component descriptor.
-     */
-    public ComponentDesc getComponent(Node node) {
-        return new ComponentDesc();
-    }
-
-    /**
-     * Returns the installer descriptor.
-     */
-    public InstallerDesc getInstaller(Node node) {
-        String main = getAttribute(node, "main-class", null);
-
-        return new InstallerDesc(main);
-    }
-
-    /**
-     * Returns the association descriptor.
-     */
-    public AssociationDesc getAssociation(Node node) throws ParseException {
-        String[] extensions = getRequiredAttribute(node, "extensions", null).split(" ");
-        String mimeType = getRequiredAttribute(node, "mime-type", null);
-
-        return new AssociationDesc(mimeType, extensions);
-    }
-    
-    /**
-     * Returns the shortcut descriptor.
-     */
-    public ShortcutDesc getShortcut(Node node) throws ParseException {
-        
-        String online = getAttribute(node, "online", "true");
-        boolean shortcutIsOnline = Boolean.valueOf(online);
-        
-        boolean showOnDesktop = false;
-        MenuDesc menu = null;
-        
-        // step through the elements
-        Node child = node.getFirstChild();
-        while (child != null) {
-            String name = child.getNodeName();
-
-            if ("desktop".equals(name)) {
-                if (showOnDesktop && strict) {
-                    throw new ParseException(R("PTwoDesktops"));
-                }
-                showOnDesktop = true;
-            } else if ("menu".equals(name)){
-                if (menu != null && strict) {
-                    throw new ParseException(R("PTwoMenus"));
-                }
-                menu = getMenu(child);
-            }
-            
-            child = child.getNextSibling();
-        }
-        
-        ShortcutDesc shortcut = new ShortcutDesc(shortcutIsOnline, showOnDesktop);
-        if (menu != null) {
-            shortcut.addMenu(menu);
-        }
-        return shortcut;
-    }
-    
-    /**
-     * Returns the menu descriptor.
-     */
-    public MenuDesc getMenu(Node node) {
-        String subMenu = getAttribute(node, "submenu", null);
-        
-        return new MenuDesc(subMenu);
-    }
-
-    
-    /**
-     * Returns the related-content descriptor.
-     */
-    public RelatedContentDesc getRelatedContent(Node node) throws ParseException {
-        
-        getRequiredAttribute(node, "href", null);
-        URL location = getURL(node, "href", base);
-        
-        String title = null;
-        String description = null;
-        IconDesc icon = null;
-        
-        // step through the elements
-        Node child = node.getFirstChild();
-        while (child != null) {
-            String name = child.getNodeName();
-            
-            if ("title".equals(name)) {
-                if (title != null && strict) {
-                    throw new ParseException(R("PTwoTitles"));
-                }
-                title = getSpanText(child);
-            } else if ("description".equals(name)) {
-                if (description != null && strict) {
-                    throw new ParseException(R("PTwoDescriptions"));
-                }
-                description = getSpanText(child);
-            } else if ("icon".equals(name)) {
-                if (icon != null && strict) {
-                    throw new ParseException(R("PTwoIcons"));
-                }
-                icon = getIcon(child);
-            }
-            
-            child = child.getNextSibling();
-        }
-        
-        RelatedContentDesc relatedContent = new RelatedContentDesc(location);
-        relatedContent.setDescription(description);
-        relatedContent.setIconDesc(icon);
-        relatedContent.setTitle(title);
-        
-        return relatedContent;
-        
-    }
-    
-    // other methods
-
-    /**
-     * Returns an array of substrings seperated by spaces (spaces
-     * escaped with backslash do not separate strings).  This method
-     * splits strings as per the spec except that it does replace
-     * escaped other characters with their own value.
-     */
-    public String[] splitString(String source) {
-        if (source == null)
-            return new String[0];
-
-        List result = new ArrayList();
-        StringTokenizer st = new StringTokenizer(source, " ");
-        StringBuffer part = new StringBuffer();
-        while (st.hasMoreTokens()) {
-            part.setLength(0);
-
-            // tack together tokens joined by backslash
-            while (true) {
-                part.append(st.nextToken());
-
-                if (st.hasMoreTokens() && part.charAt(part.length()-1) == '\\')
-                    part.setCharAt(part.length()-1, ' '); // join with the space
-                else
-                    break; // bizarre while format gets \ at end of string right (no extra space added at end)
-            } 
-
-            // delete \ quote chars
-            for (int i = part.length(); i-- > 0;) // sweet syntax for reverse loop
-                if (part.charAt(i) == '\\')
-                    part.deleteCharAt(i--); // and skip previous char so \\ becomes \
-
-            result.add( part.toString() );
-        }
-
-        return (String[]) result.toArray(new String[result.size()] );
-    }
-
-    /**
-     * Returns the Locale object(s) from a node's locale attribute.
-     *
-     * @param node the node with a locale attribute
-     */
-    public Locale[] getLocales(Node node) {
-        List locales = new ArrayList();
-        String localeParts[] = 
-            splitString(getAttribute(node, "locale", ""));
-
-        for (int i=0; i < localeParts.length; i++) {
-            Locale l = getLocale( localeParts[i] );
-            if (l != null)
-                locales.add(l);
-        }
-
-        return (Locale[]) locales.toArray(new Locale[locales.size()] );
-    }
-
-    /**
-     * Returns a Locale from a single locale.
-     *
-     * @param locale the locale string
-     */
-    public Locale getLocale(String localeStr) {
-        if (localeStr.length() < 2)
-            return null;
-
-        String language = localeStr.substring(0, 2);
-        String country = (localeStr.length()<5) ? "" : localeStr.substring(3, 5);
-        String variant = (localeStr.length()<7) ? "" : localeStr.substring(6, 8);
-
-        // null is not allowed n locale but "" is
-        return new Locale(language, country, variant);
-    }
-
-
-
-    // XML junk
-
-    /**
-     * Returns the implied text under a node, for example "text" in
-     * "<description>text</description>".
-     *
-     * @param node the node with text under it
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public String getSpanText(Node node) throws ParseException {
-        if (node == null)
-            return null;
-
-        // NANO
-        return node.getNodeValue();
-
-        /* TINY
-        Node child = node.getFirstChild();
-
-        if (child == null) {
-            if (strict)
-                // not sure if this is an error or whether "" is proper
-                throw new ParseException("No text specified (node="+node.getNodeName()+")");
-            else
-                return "";
-        }
-
-        return child.getNodeValue();
-        */
-    }
-
-    /**
-     * Returns the first child node with the specified name.
-     */
-    public static Node getChildNode(Node node, String name) {
-        Node[] result = getChildNodes(node, name);
-        if (result.length == 0)
-            return null;
-        else
-            return result[0];
-    }
-
-    /**
-     * Returns all child nodes with the specified name.
-     */
-    public static Node[] getChildNodes(Node node, String name) {
-        List result = new ArrayList();
-
-        Node child = node.getFirstChild();
-        while (child != null) {
-            if (child.getNodeName().equals(name))
-                result.add(child);
-            child = child.getNextSibling();
-        }
-
-        return (Node[]) result.toArray( new Node[result.size()] );
-    }
-
-
-    /**
-     * Returns a URL with a trailing / appended to it if there is no
-     * trailing slash on the specifed URL.
-     */
-    private URL addSlash(URL source) {
-        if (source == null)
-            return null;
-
-        if (!source.toString().endsWith("/")) {
-            try { 
-                source = new URL(source.toString()+"/"); 
-            }
-            catch (MalformedURLException ex) {
-            }
-        }
-
-        return source;
-    }
-
-
-    /**
-     * Returns the same result as getURL except that a
-     * ParseException is thrown if the attribute is null or empty.
-     *
-     * @param node the node
-     * @param name the attribute containing an href
-     * @param base the base URL
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public URL getRequiredURL(Node node, String name, URL base) throws ParseException {
-        // probably should change "" to null so that url is always
-        // required even if !strict
-        getRequiredAttribute(node, name, "");
-
-        return getURL(node, name, base);
-    }
-
-
-    /**
-     * Returns a URL object from a href string relative to the
-     * code base.  If the href denotes a relative URL, it must
-     * reference a location that is a subdirectory of the
-     * codebase.<p>
-     *
-     * @param node the node
-     * @param name the attribute containing an href
-     * @param base the base URL
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public URL getURL(Node node, String name, URL base) throws ParseException {
-        String href = getAttribute(node, name, null);
-        if (href == null)
-            return null; // so that code can throw an exception if attribute was required
-
-        try {
-            if (base == null)
-                return new URL(href);
-            else {
-                try {
-                    return new URL(href);
-                }
-                catch (MalformedURLException ex) {
-                    // is relative
-                }
-
-                URL result = new URL(base, href);
-
-                // check for going above the codebase
-                if (! result.toString().startsWith( base.toString()) )
-                    if (strict)
-                        throw new ParseException(R("PUrlNotInCodebase", node.getNodeName(), href, base));
-
-                return result;
-            }
-
-        }
-        catch (MalformedURLException ex) {
-            if (base == null)
-                throw new ParseException(R("PBadNonrelativeUrl", node.getNodeName(), href));
-            else
-                throw new ParseException(R("PBadRelativeUrl", node.getNodeName(), href, base));
-        }
-    }
-
-    /**
-     * Returns a Version from the specified attribute and default
-     * value.
-     *
-     * @param node the node
-     * @param name the attribute
-     * @param defaultValue default if no such attribute
-     * @return a Version, or null if no such attribute and default is null
-     */
-    public Version getVersion(Node node, String name, String defaultValue) {
-        String version = getAttribute(node, name, defaultValue);
-        if (version == null)
-            return null;
-        else
-            return new Version(version);
-    }
-    
-    /**
-     * Check that the VM args are valid and safe
-     * @param vmArgs a string containing the args
-     * @throws ParseException if the VM arguments are invalid or dangerous
-     */
-    private void checkVMArgs(String vmArgs) throws IllegalArgumentException {
-        if (vmArgs == null) {
-            return;
-        }
-        
-        List<String> validArguments = Arrays.asList(getValidVMArguments());
-        List<String> validStartingArguments = Arrays.asList(getValidStartingVMArguments());
-        
-        String[] arguments = vmArgs.split(" ");
-        boolean argumentIsValid = false;
-        for (String argument: arguments) {
-            argumentIsValid = false;
-            
-            if (validArguments.contains(argument)) {
-                argumentIsValid = true;
-            } else {
-                for (String validStartingArgument: validStartingArguments) {
-                    if (argument.startsWith(validStartingArgument)) {
-                        argumentIsValid = true;
-                        break;
-                    }
-                }
-            }
-            
-            if (!argumentIsValid) {
-                throw new IllegalArgumentException(argument);
-            }
-        }
-        
-    }
-    
-    /**
-     * Returns an array of valid (ie safe and supported) arguments for the JVM
-     * 
-     * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
-     */
-    private String[] getValidVMArguments() {
-        return new String[] {
-        "-d32",                             /* use a 32-bit data model if available */
-        "-client",                          /* to select the client VM */
-        "-server",                          /* to select the server VM */
-        "-verbose",                         /* enable verbose output */
-        "-version",                         /* print product version and exit */
-        "-showversion",                     /* print product version and continue */                              
-        "-help",                            /* print this help message */
-        "-X",                               /* print help on non-standard options */
-        "-ea",                              /* enable assertions */
-        "-enableassertions",                /* enable assertions */
-        "-da",                              /* disable assertions */
-        "-disableassertions",               /* disable assertions */
-        "-esa",                             /* enable system assertions */
-        "-enablesystemassertions",          /* enable system assertions */
-        "-dsa",                             /* disable system assertione */
-        "-disablesystemassertions",         /* disable system assertione */
-        "-Xmixed",                          /* mixed mode execution (default) */
-        "-Xint",                            /* interpreted mode execution only */
-        "-Xnoclassgc",                      /* disable class garbage collection */
-        "-Xincgc",                          /* enable incremental garbage collection */
-        "-Xbatch",                          /* disable background compilation */
-        "-Xprof",                           /* output cpu profiling data */
-        "-Xdebug",                          /* enable remote debugging */
-        "-Xfuture",                         /* enable strictest checks, anticipating future default */
-        "-Xrs",                             /* reduce use of OS signals by Java/VM (see documentation) */
-        "-XX:+ForceTimeHighResolution",     /* use high resolution timer */
-        "-XX:-ForceTimeHighResolution",     /* use low resolution (default) */
-        };
-    }
-
-    /**
-     * Returns an array containing the starts of valid (ie safe and supported)
-     * arguments for the JVM
-     * 
-     * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
-     */
-    private String[] getValidStartingVMArguments() {
-        return new String[] {
-        "-ea",                          /* enable assertions for classes */
-        "-enableassertions",            /* enable assertions for classes */
-        "-da",                          /* disable assertions for classes */
-        "-disableassertions",           /* disable assertions for classes */
-        "-verbose",                     /* enable verbose output */
-        "-Xms",                         /* set initial Java heap size */
-        "-Xmx",                         /* set maximum Java heap size */
-        "-Xss",                         /* set java thread stack size */
-        "-XX:NewRatio",                 /* set Ratio of new/old gen sizes */
-        "-XX:NewSize",                  /* set initial size of new generation */
-        "-XX:MaxNewSize",               /* set max size of new generation */
-        "-XX:PermSize",                 /* set initial size of permanent gen */
-        "-XX:MaxPermSize",              /* set max size of permanent gen */
-        "-XX:MaxHeapFreeRatio",         /* heap free percentage (default 70) */
-        "-XX:MinHeapFreeRatio",         /* heap free percentage (default 40) */
-        "-XX:UseSerialGC",              /* use serial garbage collection */
-        "-XX:ThreadStackSize",          /* thread stack size (in KB) */
-        "-XX:MaxInlineSize",            /* set max num of bytecodes to inline */
-        "-XX:ReservedCodeCacheSize",    /* Reserved code cache size (bytes) */
-        "-XX:MaxDirectMemorySize",
-
-        };
-    }
-
-    /**
-     * Returns the same result as getAttribute except that if strict
-     * mode is enabled or the default value is null a parse
-     * exception is thrown instead of returning the default value.
-     *
-     * @param node the node
-     * @param name the attribute
-     * @param defaultValue default value
-     * @throws ParseException if the attribute does not exist or is empty
-     */
-    public String getRequiredAttribute(Node node, String name, String defaultValue) throws ParseException {
-        String result = getAttribute(node, name, null);
-
-        if (result == null || result.length() == 0)
-            if (strict || defaultValue == null)
-                throw new ParseException(R("PNeedsAttribute", node.getNodeName(), name));
-
-        if (result == null)
-            return defaultValue;
-        else
-            return result;
-    }
-
-    /**
-     * Retuns an attribute or the specified defaultValue if there is
-     * no such attribute.
-     *
-     * @param node the node
-     * @param name the attribute
-     * @param defaultValue default if no such attribute
-     */
-    public String getAttribute(Node node, String name, String defaultValue) {
-        // SAX
-        // String result = ((Element) node).getAttribute(name);
-        String result = node.getAttribute(name);
-
-        if (result == null || result.length()==0) 
-            return defaultValue;
-
-        return result;
-    }
-
-    /**
-     * Return the root node from the XML document in the specified
-     * input stream.
-     *
-     * @throws ParseException if the JNLP file is invalid
-     */
-    public static Node getRootNode(InputStream input) throws ParseException {
-        try {
-            /* SAX
-            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-            factory.setValidating(false);
-            factory.setNamespaceAware(true);
-            DocumentBuilder builder = factory.newDocumentBuilder();
-            builder.setErrorHandler(errorHandler);
-
-            Document doc = builder.parse(input);
-            return doc.getDocumentElement();
-            */
-
-            /* TINY
-            Node document = new Node(TinyParser.parseXML(input));
-            Node jnlpNode = getChildNode(document, "jnlp"); // skip comments
-            */
-
-            /* NANO */
-            final XMLElement xml = new XMLElement();
-            final PipedInputStream pin = new PipedInputStream();
-            final PipedOutputStream pout = new PipedOutputStream(pin);
-            final InputStreamReader isr = new InputStreamReader(input);
-            
-            // Clean the jnlp xml file of all comments before passing
-            // it to the parser.
-            new Thread(
-                new Runnable(){
-                    public void run(){
-                        (new XMLElement()).sanitizeInput(isr, pout);
-                        try {
-                            pout.close();
-                        } catch (IOException ioe) {
-                            ioe.printStackTrace();
-                        }
-                    }
-                }
-            ).start();
-            xml.parseFromReader(new InputStreamReader(pin));
-            Node jnlpNode = new Node(xml);
-            return jnlpNode;
-        }
-        catch(Exception ex) {
-            throw new ParseException(R("PBadXML"), ex);
-        }
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// Copyright (C) 2009 Red Hat, Inc.
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+//import javax.xml.parsers.*; // commented to use right Node
+//import org.w3c.dom.*;       // class for using Tiny XML | NanoXML
+//import org.xml.sax.*;
+//import gd.xml.tiny.*;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.nanoxml.*;
+
+
+/**
+ * Contains methods to parse an XML document into a JNLPFile.
+ * Implements JNLP specification version 1.0.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.13 $ 
+ */
+class Parser {
+
+    private static String R(String key) { return JNLPRuntime.getMessage(key); }
+    private static String R(String key, Object p1) { return R(key, p1, null); }
+    private static String R(String key, Object p1, Object p2) { return R(key, p1, p2, null); }
+    private static String R(String key, Object p1, Object p2, Object p3) { return JNLPRuntime.getMessage(key, new Object[] { p1, p2, p3 }); }
+
+
+    // defines netx.jnlp.Node class if using Tiny XML or Nano XML
+
+    // Currently uses the Nano XML parse.  Search for "SAX" or
+    // "TINY" or "NANO" and uncomment those blocks and comment the
+    // active ones (if any) to switch XML parsers.  Also
+    // (un)comment appropriate Node class at end of this file and
+    // do a clean build.
+
+    /**
+     * Ensure consistent error handling.
+     */
+    /* SAX
+    static ErrorHandler errorHandler = new ErrorHandler() {
+        public void error(SAXParseException exception) throws SAXParseException {
+            //throw exception;
+        }
+        public void fatalError(SAXParseException exception) throws SAXParseException {
+            //throw exception;
+        }
+        public void warning(SAXParseException exception) {
+            System.err.println("XML parse warning:");
+            exception.printStackTrace();
+        }
+    };
+    */
+
+
+    /** the supported JNLP file versions */
+    private static Version supportedVersions = new Version("1.0 1.5 6.0");
+
+    // fix: some descriptors need to use the jnlp file at a later
+    // date and having file ref lets us pass it to their
+    // constructors
+    //
+    /** the file reference */
+    private JNLPFile file; // do not use (uninitialized)
+
+    /** the root node */
+    private Node root;
+
+    /** the specification version */
+    private Version spec;
+
+    /** the base URL that all hrefs are relative to */
+    private URL base;
+
+    /** the codebase URL */
+    private URL codebase;
+
+    /** the file URL */
+    private URL fileLocation;
+
+    /** whether to throw errors on non-fatal errors. */
+    private boolean strict; // if strict==true parses a file with no error then strict==false should also
+
+    /** whether to allow extensions to the JNLP specification */
+    private boolean allowExtensions; // true if extensions to JNLP spec are ok
+
+
+    /**
+     * Create a parser for the JNLP file.  If the location
+     * parameters is not null it is used as the default codebase
+     * (does not override value of jnlp element's href
+     * attribute).<p>
+     *
+     * The root node may be normalized as a side effect of this
+     * constructor.
+     *
+     * @param file the (uninitialized) file reference
+     * @param base if codebase is not specified, a default base for relative URLs 
+     * @param root the root node
+     * @param strict whether to enforce strict compliance with the JNLP spec
+     * @param allowExtensions whether to allow extensions to the JNLP spec
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public Parser(JNLPFile file, URL base, Node root, boolean strict, boolean allowExtensions) throws ParseException {
+        this.file = file;
+        this.root = root;
+        this.strict = strict;
+        this.allowExtensions = allowExtensions;
+
+        // ensure it's a JNLP node
+        if (root == null || !root.getNodeName().equals("jnlp"))
+            throw new ParseException(R("PInvalidRoot"));
+
+        // JNLP tag information
+        this.spec = getVersion(root, "spec", "1.0+");
+        this.codebase = addSlash(getURL(root, "codebase", base));
+        this.base = (codebase!=null) ? codebase : base; // if codebase not specified use default codebase
+        fileLocation = getURL(root, "href", this.base);
+
+        // ensure version is supported
+        if (!supportedVersions.matchesAny(spec))
+            throw new ParseException(R("PSpecUnsupported", supportedVersions));
+
+        // normalize the text nodes
+        root.normalize();
+    }
+
+    /**
+     * Return the JNLP specification versions supported.
+     */
+    public static Version getSupportedVersions() {
+        return supportedVersions;
+    }
+
+    /** 
+     * Returns the file version.
+     */
+    public Version getFileVersion() {
+        return getVersion(root, "version", null);
+    }
+
+    /** 
+     * Returns the file location.
+     */
+    public URL getFileLocation() {
+        return fileLocation;
+    }
+
+    /** 
+     * Returns the codebase.
+     */
+    public URL getCodeBase() {
+        return codebase;
+    }
+
+    /** 
+     * Returns the specification version.
+     */
+    public Version getSpecVersion() {
+        return spec;
+    }
+
+    // 
+    // This section loads the resources elements
+    // 
+
+    /**
+     * Returns all of the ResourcesDesc elements under the specified
+     * node (jnlp or j2se).
+     *
+     * @param parent the parent node (either jnlp or j2se)
+     * @param j2se true if the resources are located under a j2se or java node
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public List getResources(Node parent, boolean j2se) throws ParseException {
+        List result = new ArrayList();
+        Node resources[] = getChildNodes(parent, "resources");
+
+        // ensure that there are at least one information section present
+        if (resources.length == 0 && !j2se)
+            throw new ParseException(R("PNoResources"));
+
+        // create objects from the resources sections
+        for (int i=0; i < resources.length; i++)
+            result.add(getResourcesDesc(resources[i], j2se));
+
+        return result;
+    }
+
+    /**
+     * Returns the ResourcesDesc element at the specified node.
+     *
+     * @param node the resources node
+     * @param j2se true if the resources are located under a j2se or java node
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public ResourcesDesc getResourcesDesc(Node node, boolean j2se) throws ParseException {
+        boolean mainFlag = false; // if found a main tag
+
+        // create resources
+        ResourcesDesc resources = 
+            new ResourcesDesc(file, 
+                              getLocales(node), 
+                              splitString(getAttribute(node, "os", null)),
+                              splitString(getAttribute(node, "arch", null)));
+
+        // step through the elements
+        Node child = node.getFirstChild();
+        while (child != null) {
+            String name = child.getNodeName();
+
+            // check for nativelib but no trusted environment 
+            if ("nativelib".equals(name))
+                if (!isTrustedEnvironment())
+                    throw new ParseException(R("PUntrustedNative"));
+
+            if ("j2se".equals(name) || "java".equals(name)) {
+                if (getChildNode(root, "component-desc") != null)
+                    if (strict)
+                        throw new ParseException(R("PExtensionHasJ2SE"));
+                if (!j2se)
+                    resources.addResource( getJRE(child) );
+                else
+                    throw new ParseException(R("PInnerJ2SE"));
+            }
+
+            if ("jar".equals(name) || "nativelib".equals(name)) {
+                JARDesc jar = getJAR(child);
+
+                // check for duplicate main entries
+                if (jar.isMain()) {
+                    if (mainFlag == true)
+                        if (strict)
+                            throw new ParseException(R("PTwoMains"));
+                    mainFlag = true;
+                }
+
+                resources.addResource(jar);
+            }
+
+            if ("extension".equals(name))
+                resources.addResource( getExtension(child) );
+
+            if ("property".equals(name)) 
+                resources.addResource( getProperty(child) );
+
+            if ("package".equals(name))
+                resources.addResource( getPackage(child) );
+
+            child = child.getNextSibling();
+        }
+
+        return resources;
+    }
+
+    /**
+     * Returns the JRE element at the specified node.
+     *
+     * @param node the j2se/java node 
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public JREDesc getJRE(Node node) throws ParseException {
+        Version version = getVersion(node, "version", null);
+        URL location = getURL(node, "href", base);
+        String vmArgs = getAttribute(node, "java-vm-args",null);
+        try {
+            checkVMArgs(vmArgs);
+        } catch (IllegalArgumentException argumentException) {
+            vmArgs = null;
+        }
+        String initialHeap = getAttribute(node, "initial-heap-size", null);
+        String maxHeap = getAttribute(node, "max-heap-size", null);
+        List resources = getResources(node, true);
+
+        // require version attribute
+        getRequiredAttribute(node, "version", null);
+
+        return new JREDesc(version, location, vmArgs, initialHeap, maxHeap, resources);
+    }
+
+
+
+    /**
+     * Returns the JAR element at the specified node.
+     *
+     * @param node the jar or nativelib node 
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public JARDesc getJAR(Node node) throws ParseException {
+        boolean nativeJar = "nativelib".equals(node.getNodeName());
+        URL location = getRequiredURL(node, "href", base);
+        Version version = getVersion(node, "version", null);
+        String part = getAttribute(node, "part", null);
+        boolean main = "true".equals(getAttribute(node, "main", "false"));
+        boolean lazy = "lazy".equals(getAttribute(node, "download", "eager"));
+        int size = Integer.parseInt(getAttribute(node, "size", "0"));
+
+        if (nativeJar && main)
+            if (strict) 
+                throw new ParseException(R("PNativeHasMain"));
+
+        return new JARDesc(location, version, part, lazy, main, nativeJar, true);
+
+    }
+
+    /**
+     * Returns the Extension element at the specified node.
+     *
+     * @param node the extension node 
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public ExtensionDesc getExtension(Node node) throws ParseException {
+        String name = getAttribute(node, "name", null);
+        Version version = getVersion(node, "version", null);
+        URL location = getRequiredURL(node, "href", base);
+
+        ExtensionDesc ext = new ExtensionDesc(name, version, location);
+
+        Node dload[] = getChildNodes(node, "ext-download");
+        for (int i=0; i < dload.length; i++) {
+            boolean lazy = "lazy".equals(getAttribute(dload[i], "download", "eager"));
+
+            ext.addPart(getRequiredAttribute(dload[i], "ext-part", null),
+                        getAttribute(dload[i], "part", null),
+                        lazy);
+        }
+
+        return ext;
+    }
+
+    /**
+     * Returns the Property element at the specified node.
+     *
+     * @param node the property node 
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public PropertyDesc getProperty(Node node) throws ParseException {
+        String name = getRequiredAttribute(node, "name", null);
+        String value = getRequiredAttribute(node, "value", "");
+
+        return new PropertyDesc(name, value);
+    }
+
+    /**
+     * Returns the Package element at the specified node.
+     *
+     * @param node the package node 
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public PackageDesc getPackage(Node node) throws ParseException {
+        String name = getRequiredAttribute(node, "name", null);
+        String part = getRequiredAttribute(node, "part", "");
+        boolean recursive = getAttribute(node, "recursive", "false").equals("true");
+
+        return new PackageDesc(name, part, recursive);
+    }
+
+    // 
+    // This section loads the information elements
+    // 
+
+    /**
+     * Returns all of the information elements under the specified
+     * node.
+     *
+     * @param parent the parent node (jnlp)
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public List getInfo(Node parent) throws ParseException {
+        List result = new ArrayList();
+        Node info[] = getChildNodes(parent, "information");
+
+        // ensure that there are at least one information section present
+        if (info.length == 0)
+            throw new ParseException(R("PNoInfoElement"));
+
+        // create objects from the info sections
+        for (int i=0; i < info.length; i++)
+            result.add(getInformationDesc(info[i]));
+
+        return result;
+    }
+
+    /**
+     * Returns the information element at the specified node.
+     *
+     * @param node the information node
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public InformationDesc getInformationDesc(Node node) throws ParseException {
+        List descriptionsUsed = new ArrayList();
+
+        // locale
+        Locale locales[] = getLocales(node);
+
+        // create information
+        InformationDesc info = new InformationDesc(file, locales);
+
+        // step through the elements
+        Node child = node.getFirstChild();
+        while (child != null) {
+            String name = child.getNodeName();
+
+            if ("title".equals(name))
+                addInfo(info, child, null, getSpanText(child));
+            if ("vendor".equals(name))
+                addInfo(info, child, null, getSpanText(child));
+            if ("description".equals(name)) {
+                String kind = getAttribute(child, "kind", "default");
+                if (descriptionsUsed.contains(kind))
+                    if (strict)
+                        throw new ParseException(R("PTwoDescriptions", kind));
+
+                descriptionsUsed.add(kind);
+                addInfo(info, child, kind, getSpanText(child));
+            }
+            if ("homepage".equals(name))
+                addInfo(info, child, null, getRequiredURL(child, "href", base));
+            if ("icon".equals(name))
+                addInfo(info, child, getAttribute(child, "kind", "default"), getIcon(child));
+            if ("offline-allowed".equals(name))
+                addInfo(info, child, null, Boolean.TRUE);
+            if ("sharing-allowed".equals(name)) {
+                if (strict && !allowExtensions)
+                    throw new ParseException(R("PSharing"));
+                addInfo(info, child, null, Boolean.TRUE);
+            }
+            if ("association".equals(name)) {
+                addInfo(info, child, null, getAssociation(child));
+            }
+            if ("shortcut".equals(name)) {
+                addInfo(info, child, null, getShortcut(child));
+            }
+            if ("related-content".equals(name)) {
+                addInfo(info, child, null, getRelatedContent(child));
+            }
+
+            child = child.getNextSibling();
+        }
+
+        return info;
+    }
+
+    /**
+     * Adds a key,value pair to the information object.
+     *
+     * @param info the information object 
+     * @param node node name to be used as the key
+     * @param mod key name appended with "-"+mod if not null
+     * @param value the info object to add (icon or string)
+     */
+    protected void addInfo(InformationDesc info, Node node, String mod, Object value) {
+        String modStr = (mod == null) ? "" : "-"+mod;
+
+        if (node == null)
+            return;
+
+        info.addItem(node.getNodeName()+modStr, value);
+    }
+
+    /**
+     * Returns the icon element at the specified node.
+     *
+     * @param node the icon node
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public IconDesc getIcon(Node node) throws ParseException {
+        int width = Integer.parseInt(getAttribute(node, "width", "-1"));
+        int height = Integer.parseInt(getAttribute(node, "height", "-1"));
+        int size = Integer.parseInt(getAttribute(node, "size", "-1"));
+        int depth = Integer.parseInt(getAttribute(node, "depth", "-1"));
+        URL location = getRequiredURL(node, "href", base);
+        Object kind = getAttribute(node, "kind", "default"); 
+
+        return new IconDesc(location, kind, width, height, depth, size);
+    }
+
+    // 
+    // This section loads the security descriptor element
+    // 
+
+    /**
+     * Returns the security descriptor element.  If no security
+     * element was specified in the JNLP file then a SecurityDesc
+     * with applet permissions is returned.
+     *
+     * @param parent the parent node
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public SecurityDesc getSecurity(Node parent) throws ParseException {
+        Node nodes[] = getChildNodes(parent, "security");
+
+        // test for too many security elements
+        if (nodes.length > 1) 
+            if (strict)
+                throw new ParseException(R("PTwoSecurity"));
+
+        Object type = SecurityDesc.SANDBOX_PERMISSIONS;
+
+        if (nodes.length == 0)
+            type = SecurityDesc.SANDBOX_PERMISSIONS;
+        else if (null != getChildNode(nodes[0], "all-permissions"))
+            type = SecurityDesc.ALL_PERMISSIONS;
+        else if (null != getChildNode(nodes[0], "j2ee-application-client-permissions"))
+            type = SecurityDesc.J2EE_PERMISSIONS;
+        else if (strict)
+            throw new ParseException(R("PEmptySecurity"));
+
+        if (base != null)
+            return new SecurityDesc(file, type, base.getHost());
+        else
+            return new SecurityDesc(file, type, null);
+    }
+
+    /**
+     * Returns whether the JNLP file requests a trusted execution
+     * environment.
+     */
+    protected boolean isTrustedEnvironment() {
+        Node security = getChildNode(root, "security");
+
+        if (security != null)
+            if (getChildNode(security, "all-permissions") != null
+                || getChildNode(security, "j2ee-application-client-permissions") != null)
+                return true;
+
+        return false;
+    }
+
+    // 
+    // This section loads the launch descriptor element
+    // 
+
+    /**
+     * Returns the launch descriptor element, either AppletDesc,
+     * ApplicationDesc, ComponentDesc, or InstallerDesc.
+     *
+     * @param parent the parent node
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public Object getLauncher(Node parent) throws ParseException {
+        // check for other than one application type
+        if (1 != getChildNodes(parent, "applet-desc").length
+            + getChildNodes(parent, "application-desc").length
+            + getChildNodes(parent, "component-desc").length
+            + getChildNodes(parent, "installer-desc").length)
+            throw new ParseException(R("PTwoDescriptors"));
+
+        Node child = parent.getFirstChild();
+        while (child != null) {
+            String name = child.getNodeName();
+
+            if ("applet-desc".equals(name))
+                return getApplet(child);
+            if ("application-desc".equals(name))
+                return getApplication(child);
+            if ("component-desc".equals(name))
+                return getComponent(child);
+            if ("installer-desc".equals(name))
+                return getInstaller(child);
+
+            child = child.getNextSibling();
+        }
+
+        // not reached
+        return null;
+    }
+
+    /**
+     * Returns the applet descriptor.
+     *
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public AppletDesc getApplet(Node node) throws ParseException {
+        String name = getRequiredAttribute(node, "name", R("PUnknownApplet"));
+        String main = getRequiredAttribute(node, "main-class", null);
+        URL docbase = getURL(node, "documentbase", base);
+        Map paramMap = new HashMap();
+        int width = 0;
+        int height = 0;
+
+        try {
+            width = Integer.parseInt(getRequiredAttribute(node, "width", "100"));
+            height = Integer.parseInt(getRequiredAttribute(node, "height", "100"));
+        }
+        catch (NumberFormatException nfe) {
+            if (width <= 0)
+                throw new ParseException(R("PBadWidth"));
+            throw new ParseException(R("PBadWidth"));
+        }
+
+        // read params
+        Node params[] = getChildNodes(node, "param");
+        for (int i=0; i < params.length; i++) {
+            paramMap.put(getRequiredAttribute(params[i], "name", null),
+                         getRequiredAttribute(params[i], "value", ""));
+        }
+
+        return new AppletDesc(name, main, docbase, width, height, paramMap);
+    }
+
+    /**
+     * Returns the application descriptor.
+     *
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public ApplicationDesc getApplication(Node node) throws ParseException {
+        String main = getAttribute(node, "main-class", null);
+        List argsList = new ArrayList();
+
+        // if (main == null)
+        //   only ok if can be found in main jar file (can't check here but make a note)
+
+        // read parameters
+        Node args[] = getChildNodes(node, "argument");
+        for (int i=0; i < args.length; i++) {
+            //argsList.add( args[i].getNodeValue() );
+            
+            //This approach was not finding the argument text
+            argsList.add( getSpanText(args[i]) );
+        }
+
+        String argStrings[] = 
+            (String[]) argsList.toArray( new String[argsList.size()] );
+
+        return new ApplicationDesc(main, argStrings);
+    }
+
+    /**
+     * Returns the component descriptor.
+     */
+    public ComponentDesc getComponent(Node node) {
+        return new ComponentDesc();
+    }
+
+    /**
+     * Returns the installer descriptor.
+     */
+    public InstallerDesc getInstaller(Node node) {
+        String main = getAttribute(node, "main-class", null);
+
+        return new InstallerDesc(main);
+    }
+
+    /**
+     * Returns the association descriptor.
+     */
+    public AssociationDesc getAssociation(Node node) throws ParseException {
+        String[] extensions = getRequiredAttribute(node, "extensions", null).split(" ");
+        String mimeType = getRequiredAttribute(node, "mime-type", null);
+
+        return new AssociationDesc(mimeType, extensions);
+    }
+    
+    /**
+     * Returns the shortcut descriptor.
+     */
+    public ShortcutDesc getShortcut(Node node) throws ParseException {
+        
+        String online = getAttribute(node, "online", "true");
+        boolean shortcutIsOnline = Boolean.valueOf(online);
+        
+        boolean showOnDesktop = false;
+        MenuDesc menu = null;
+        
+        // step through the elements
+        Node child = node.getFirstChild();
+        while (child != null) {
+            String name = child.getNodeName();
+
+            if ("desktop".equals(name)) {
+                if (showOnDesktop && strict) {
+                    throw new ParseException(R("PTwoDesktops"));
+                }
+                showOnDesktop = true;
+            } else if ("menu".equals(name)){
+                if (menu != null && strict) {
+                    throw new ParseException(R("PTwoMenus"));
+                }
+                menu = getMenu(child);
+            }
+            
+            child = child.getNextSibling();
+        }
+        
+        ShortcutDesc shortcut = new ShortcutDesc(shortcutIsOnline, showOnDesktop);
+        if (menu != null) {
+            shortcut.addMenu(menu);
+        }
+        return shortcut;
+    }
+    
+    /**
+     * Returns the menu descriptor.
+     */
+    public MenuDesc getMenu(Node node) {
+        String subMenu = getAttribute(node, "submenu", null);
+        
+        return new MenuDesc(subMenu);
+    }
+
+    
+    /**
+     * Returns the related-content descriptor.
+     */
+    public RelatedContentDesc getRelatedContent(Node node) throws ParseException {
+        
+        getRequiredAttribute(node, "href", null);
+        URL location = getURL(node, "href", base);
+        
+        String title = null;
+        String description = null;
+        IconDesc icon = null;
+        
+        // step through the elements
+        Node child = node.getFirstChild();
+        while (child != null) {
+            String name = child.getNodeName();
+            
+            if ("title".equals(name)) {
+                if (title != null && strict) {
+                    throw new ParseException(R("PTwoTitles"));
+                }
+                title = getSpanText(child);
+            } else if ("description".equals(name)) {
+                if (description != null && strict) {
+                    throw new ParseException(R("PTwoDescriptions"));
+                }
+                description = getSpanText(child);
+            } else if ("icon".equals(name)) {
+                if (icon != null && strict) {
+                    throw new ParseException(R("PTwoIcons"));
+                }
+                icon = getIcon(child);
+            }
+            
+            child = child.getNextSibling();
+        }
+        
+        RelatedContentDesc relatedContent = new RelatedContentDesc(location);
+        relatedContent.setDescription(description);
+        relatedContent.setIconDesc(icon);
+        relatedContent.setTitle(title);
+        
+        return relatedContent;
+        
+    }
+    
+    // other methods
+
+    /**
+     * Returns an array of substrings seperated by spaces (spaces
+     * escaped with backslash do not separate strings).  This method
+     * splits strings as per the spec except that it does replace
+     * escaped other characters with their own value.
+     */
+    public String[] splitString(String source) {
+        if (source == null)
+            return new String[0];
+
+        List result = new ArrayList();
+        StringTokenizer st = new StringTokenizer(source, " ");
+        StringBuffer part = new StringBuffer();
+        while (st.hasMoreTokens()) {
+            part.setLength(0);
+
+            // tack together tokens joined by backslash
+            while (true) {
+                part.append(st.nextToken());
+
+                if (st.hasMoreTokens() && part.charAt(part.length()-1) == '\\')
+                    part.setCharAt(part.length()-1, ' '); // join with the space
+                else
+                    break; // bizarre while format gets \ at end of string right (no extra space added at end)
+            } 
+
+            // delete \ quote chars
+            for (int i = part.length(); i-- > 0;) // sweet syntax for reverse loop
+                if (part.charAt(i) == '\\')
+                    part.deleteCharAt(i--); // and skip previous char so \\ becomes \
+
+            result.add( part.toString() );
+        }
+
+        return (String[]) result.toArray(new String[result.size()] );
+    }
+
+    /**
+     * Returns the Locale object(s) from a node's locale attribute.
+     *
+     * @param node the node with a locale attribute
+     */
+    public Locale[] getLocales(Node node) {
+        List locales = new ArrayList();
+        String localeParts[] = 
+            splitString(getAttribute(node, "locale", ""));
+
+        for (int i=0; i < localeParts.length; i++) {
+            Locale l = getLocale( localeParts[i] );
+            if (l != null)
+                locales.add(l);
+        }
+
+        return (Locale[]) locales.toArray(new Locale[locales.size()] );
+    }
+
+    /**
+     * Returns a Locale from a single locale.
+     *
+     * @param locale the locale string
+     */
+    public Locale getLocale(String localeStr) {
+        if (localeStr.length() < 2)
+            return null;
+
+        String language = localeStr.substring(0, 2);
+        String country = (localeStr.length()<5) ? "" : localeStr.substring(3, 5);
+        String variant = (localeStr.length()<7) ? "" : localeStr.substring(6, 8);
+
+        // null is not allowed n locale but "" is
+        return new Locale(language, country, variant);
+    }
+
+
+
+    // XML junk
+
+    /**
+     * Returns the implied text under a node, for example "text" in
+     * "<description>text</description>".
+     *
+     * @param node the node with text under it
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public String getSpanText(Node node) throws ParseException {
+        if (node == null)
+            return null;
+
+        // NANO
+        return node.getNodeValue();
+
+        /* TINY
+        Node child = node.getFirstChild();
+
+        if (child == null) {
+            if (strict)
+                // not sure if this is an error or whether "" is proper
+                throw new ParseException("No text specified (node="+node.getNodeName()+")");
+            else
+                return "";
+        }
+
+        return child.getNodeValue();
+        */
+    }
+
+    /**
+     * Returns the first child node with the specified name.
+     */
+    public static Node getChildNode(Node node, String name) {
+        Node[] result = getChildNodes(node, name);
+        if (result.length == 0)
+            return null;
+        else
+            return result[0];
+    }
+
+    /**
+     * Returns all child nodes with the specified name.
+     */
+    public static Node[] getChildNodes(Node node, String name) {
+        List result = new ArrayList();
+
+        Node child = node.getFirstChild();
+        while (child != null) {
+            if (child.getNodeName().equals(name))
+                result.add(child);
+            child = child.getNextSibling();
+        }
+
+        return (Node[]) result.toArray( new Node[result.size()] );
+    }
+
+
+    /**
+     * Returns a URL with a trailing / appended to it if there is no
+     * trailing slash on the specifed URL.
+     */
+    private URL addSlash(URL source) {
+        if (source == null)
+            return null;
+
+        if (!source.toString().endsWith("/")) {
+            try { 
+                source = new URL(source.toString()+"/"); 
+            }
+            catch (MalformedURLException ex) {
+            }
+        }
+
+        return source;
+    }
+
+
+    /**
+     * Returns the same result as getURL except that a
+     * ParseException is thrown if the attribute is null or empty.
+     *
+     * @param node the node
+     * @param name the attribute containing an href
+     * @param base the base URL
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public URL getRequiredURL(Node node, String name, URL base) throws ParseException {
+        // probably should change "" to null so that url is always
+        // required even if !strict
+        getRequiredAttribute(node, name, "");
+
+        return getURL(node, name, base);
+    }
+
+
+    /**
+     * Returns a URL object from a href string relative to the
+     * code base.  If the href denotes a relative URL, it must
+     * reference a location that is a subdirectory of the
+     * codebase.<p>
+     *
+     * @param node the node
+     * @param name the attribute containing an href
+     * @param base the base URL
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public URL getURL(Node node, String name, URL base) throws ParseException {
+        String href = getAttribute(node, name, null);
+        if (href == null)
+            return null; // so that code can throw an exception if attribute was required
+
+        try {
+            if (base == null)
+                return new URL(href);
+            else {
+                try {
+                    return new URL(href);
+                }
+                catch (MalformedURLException ex) {
+                    // is relative
+                }
+
+                URL result = new URL(base, href);
+
+                // check for going above the codebase
+                if (! result.toString().startsWith( base.toString()) )
+                    if (strict)
+                        throw new ParseException(R("PUrlNotInCodebase", node.getNodeName(), href, base));
+
+                return result;
+            }
+
+        }
+        catch (MalformedURLException ex) {
+            if (base == null)
+                throw new ParseException(R("PBadNonrelativeUrl", node.getNodeName(), href));
+            else
+                throw new ParseException(R("PBadRelativeUrl", node.getNodeName(), href, base));
+        }
+    }
+
+    /**
+     * Returns a Version from the specified attribute and default
+     * value.
+     *
+     * @param node the node
+     * @param name the attribute
+     * @param defaultValue default if no such attribute
+     * @return a Version, or null if no such attribute and default is null
+     */
+    public Version getVersion(Node node, String name, String defaultValue) {
+        String version = getAttribute(node, name, defaultValue);
+        if (version == null)
+            return null;
+        else
+            return new Version(version);
+    }
+    
+    /**
+     * Check that the VM args are valid and safe
+     * @param vmArgs a string containing the args
+     * @throws ParseException if the VM arguments are invalid or dangerous
+     */
+    private void checkVMArgs(String vmArgs) throws IllegalArgumentException {
+        if (vmArgs == null) {
+            return;
+        }
+        
+        List<String> validArguments = Arrays.asList(getValidVMArguments());
+        List<String> validStartingArguments = Arrays.asList(getValidStartingVMArguments());
+        
+        String[] arguments = vmArgs.split(" ");
+        boolean argumentIsValid = false;
+        for (String argument: arguments) {
+            argumentIsValid = false;
+            
+            if (validArguments.contains(argument)) {
+                argumentIsValid = true;
+            } else {
+                for (String validStartingArgument: validStartingArguments) {
+                    if (argument.startsWith(validStartingArgument)) {
+                        argumentIsValid = true;
+                        break;
+                    }
+                }
+            }
+            
+            if (!argumentIsValid) {
+                throw new IllegalArgumentException(argument);
+            }
+        }
+        
+    }
+    
+    /**
+     * Returns an array of valid (ie safe and supported) arguments for the JVM
+     * 
+     * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
+     */
+    private String[] getValidVMArguments() {
+        return new String[] {
+        "-d32",                             /* use a 32-bit data model if available */
+        "-client",                          /* to select the client VM */
+        "-server",                          /* to select the server VM */
+        "-verbose",                         /* enable verbose output */
+        "-version",                         /* print product version and exit */
+        "-showversion",                     /* print product version and continue */                              
+        "-help",                            /* print this help message */
+        "-X",                               /* print help on non-standard options */
+        "-ea",                              /* enable assertions */
+        "-enableassertions",                /* enable assertions */
+        "-da",                              /* disable assertions */
+        "-disableassertions",               /* disable assertions */
+        "-esa",                             /* enable system assertions */
+        "-enablesystemassertions",          /* enable system assertions */
+        "-dsa",                             /* disable system assertione */
+        "-disablesystemassertions",         /* disable system assertione */
+        "-Xmixed",                          /* mixed mode execution (default) */
+        "-Xint",                            /* interpreted mode execution only */
+        "-Xnoclassgc",                      /* disable class garbage collection */
+        "-Xincgc",                          /* enable incremental garbage collection */
+        "-Xbatch",                          /* disable background compilation */
+        "-Xprof",                           /* output cpu profiling data */
+        "-Xdebug",                          /* enable remote debugging */
+        "-Xfuture",                         /* enable strictest checks, anticipating future default */
+        "-Xrs",                             /* reduce use of OS signals by Java/VM (see documentation) */
+        "-XX:+ForceTimeHighResolution",     /* use high resolution timer */
+        "-XX:-ForceTimeHighResolution",     /* use low resolution (default) */
+        };
+    }
+
+    /**
+     * Returns an array containing the starts of valid (ie safe and supported)
+     * arguments for the JVM
+     * 
+     * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html
+     */
+    private String[] getValidStartingVMArguments() {
+        return new String[] {
+        "-ea",                          /* enable assertions for classes */
+        "-enableassertions",            /* enable assertions for classes */
+        "-da",                          /* disable assertions for classes */
+        "-disableassertions",           /* disable assertions for classes */
+        "-verbose",                     /* enable verbose output */
+        "-Xms",                         /* set initial Java heap size */
+        "-Xmx",                         /* set maximum Java heap size */
+        "-Xss",                         /* set java thread stack size */
+        "-XX:NewRatio",                 /* set Ratio of new/old gen sizes */
+        "-XX:NewSize",                  /* set initial size of new generation */
+        "-XX:MaxNewSize",               /* set max size of new generation */
+        "-XX:PermSize",                 /* set initial size of permanent gen */
+        "-XX:MaxPermSize",              /* set max size of permanent gen */
+        "-XX:MaxHeapFreeRatio",         /* heap free percentage (default 70) */
+        "-XX:MinHeapFreeRatio",         /* heap free percentage (default 40) */
+        "-XX:UseSerialGC",              /* use serial garbage collection */
+        "-XX:ThreadStackSize",          /* thread stack size (in KB) */
+        "-XX:MaxInlineSize",            /* set max num of bytecodes to inline */
+        "-XX:ReservedCodeCacheSize",    /* Reserved code cache size (bytes) */
+        "-XX:MaxDirectMemorySize",
+
+        };
+    }
+
+    /**
+     * Returns the same result as getAttribute except that if strict
+     * mode is enabled or the default value is null a parse
+     * exception is thrown instead of returning the default value.
+     *
+     * @param node the node
+     * @param name the attribute
+     * @param defaultValue default value
+     * @throws ParseException if the attribute does not exist or is empty
+     */
+    public String getRequiredAttribute(Node node, String name, String defaultValue) throws ParseException {
+        String result = getAttribute(node, name, null);
+
+        if (result == null || result.length() == 0)
+            if (strict || defaultValue == null)
+                throw new ParseException(R("PNeedsAttribute", node.getNodeName(), name));
+
+        if (result == null)
+            return defaultValue;
+        else
+            return result;
+    }
+
+    /**
+     * Retuns an attribute or the specified defaultValue if there is
+     * no such attribute.
+     *
+     * @param node the node
+     * @param name the attribute
+     * @param defaultValue default if no such attribute
+     */
+    public String getAttribute(Node node, String name, String defaultValue) {
+        // SAX
+        // String result = ((Element) node).getAttribute(name);
+        String result = node.getAttribute(name);
+
+        if (result == null || result.length()==0) 
+            return defaultValue;
+
+        return result;
+    }
+
+    /**
+     * Return the root node from the XML document in the specified
+     * input stream.
+     *
+     * @throws ParseException if the JNLP file is invalid
+     */
+    public static Node getRootNode(InputStream input) throws ParseException {
+        try {
+            /* SAX
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setValidating(false);
+            factory.setNamespaceAware(true);
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.setErrorHandler(errorHandler);
+
+            Document doc = builder.parse(input);
+            return doc.getDocumentElement();
+            */
+
+            /* TINY
+            Node document = new Node(TinyParser.parseXML(input));
+            Node jnlpNode = getChildNode(document, "jnlp"); // skip comments
+            */
+
+            /* NANO */
+            final XMLElement xml = new XMLElement();
+            final PipedInputStream pin = new PipedInputStream();
+            final PipedOutputStream pout = new PipedOutputStream(pin);
+            final InputStreamReader isr = new InputStreamReader(input);
+            
+            // Clean the jnlp xml file of all comments before passing
+            // it to the parser.
+            new Thread(
+                new Runnable(){
+                    public void run(){
+                        (new XMLElement()).sanitizeInput(isr, pout);
+                        try {
+                            pout.close();
+                        } catch (IOException ioe) {
+                            ioe.printStackTrace();
+                        }
+                    }
+                }
+            ).start();
+            xml.parseFromReader(new InputStreamReader(pin));
+            Node jnlpNode = new Node(xml);
+            return jnlpNode;
+        }
+        catch(Exception ex) {
+            throw new ParseException(R("PBadXML"), ex);
+        }
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/cache/CacheEntry.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/cache/CacheEntry.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,174 +1,174 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.cache;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.lang.reflect.*;
-import java.security.*;
-import javax.jnlp.*;
-
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.runtime.*;
-import net.sourceforge.jnlp.util.*;
-
-/**
- * Describes an entry in the cache.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.10 $ 
- */
-public class CacheEntry {
-
-    /** the remote resource location */
-    private URL location;
-
-    /** the requested version */
-    private Version version;
-
-    /** info about the cached file */
-    private PropertiesFile properties;
-
-
-    /**
-     * Create a CacheEntry for the resources specified as a remote
-     * URL.
-     *
-     * @param location the remote resource location
-     * @param version the version of the resource
-     */
-    public CacheEntry(URL location, Version version) {
-        this.location = location;
-        this.version = version;
-
-        File infoFile = CacheUtil.getCacheFile(location, version);
-        infoFile = new File(infoFile.getPath()+".info"); // replace with something that can't be clobbered
-
-        properties = new PropertiesFile(infoFile, JNLPRuntime.getMessage("CAutoGen"));
-    }
-
-    /**
-     * Initialize the cache entry data from a connection to the
-     * remote resource (does not store data).
-     */
-    void initialize(URLConnection connection) {
-        long modified = connection.getLastModified();
-        long length = connection.getContentLength(); // an int
-
-        properties.setProperty("content-length", Long.toString(length));
-        properties.setProperty("last-modified", Long.toString(modified));
-    }
-
-    /**
-     * Returns the remote location this entry caches.
-     */
-    public URL getLocation() {
-        return location;
-    }
-
-    /**
-     * Returns the time in the local system clock that the file was
-     * most recently checked for an update.
-     */
-    public long getLastUpdated() {
-        try {
-            return Long.parseLong(properties.getProperty("last-updated"));
-        }
-        catch (Exception ex) {
-            return 0;
-        }
-    }
-
-    /**
-     * Sets the time in the local system clock that the file was
-     * most recently checked for an update.
-     */
-    public void setLastUpdated(long updatedTime) {
-        properties.setProperty("last-updated", Long.toString(updatedTime));
-    }
-
-    /**
-     * Returns whether there is a version of the URL contents in
-     * the cache and it is up to date.  This method may not return
-     * immediately.
-     *
-     * @param connection a connection to the remote URL
-     * @return whether the cache contains the version
-     */
-    public boolean isCurrent(URLConnection connection) {
-        boolean cached = isCached();
-
-        if (!cached)
-            return false;
-
-        try {
-            long remoteModified = connection.getLastModified();
-            long cachedModified = Long.parseLong(properties.getProperty("last-modified"));
-
-            if (remoteModified > 0 && remoteModified <= cachedModified)
-                return true;
-            else
-                return false;
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            return cached; // if can't connect return whether already in cache
-        }
-    }
-
-    /**
-     * Returns true if the cache has a local copy of the contents
-     * of the URL matching the specified version string.
-     *
-     * @return true if the resource is in the cache
-     */
-    public boolean isCached() {
-        File localFile = CacheUtil.getCacheFile(location, version);
-        if (!localFile.exists())
-            return false;
-
-        try {
-            long cachedLength = localFile.length();
-            long remoteLength = Long.parseLong(properties.getProperty("content-length", "-1"));
-
-            if (remoteLength >= 0 && cachedLength != remoteLength)
-                return false;
-            else
-                return true;
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            return false; // should throw?
-        }
-    }
-
-    /**
-     * Save the current information for the cache entry.
-     */
-    protected void store() {
-        properties.store();
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.security.*;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+import net.sourceforge.jnlp.util.*;
+
+/**
+ * Describes an entry in the cache.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.10 $ 
+ */
+public class CacheEntry {
+
+    /** the remote resource location */
+    private URL location;
+
+    /** the requested version */
+    private Version version;
+
+    /** info about the cached file */
+    private PropertiesFile properties;
+
+
+    /**
+     * Create a CacheEntry for the resources specified as a remote
+     * URL.
+     *
+     * @param location the remote resource location
+     * @param version the version of the resource
+     */
+    public CacheEntry(URL location, Version version) {
+        this.location = location;
+        this.version = version;
+
+        File infoFile = CacheUtil.getCacheFile(location, version);
+        infoFile = new File(infoFile.getPath()+".info"); // replace with something that can't be clobbered
+
+        properties = new PropertiesFile(infoFile, JNLPRuntime.getMessage("CAutoGen"));
+    }
+
+    /**
+     * Initialize the cache entry data from a connection to the
+     * remote resource (does not store data).
+     */
+    void initialize(URLConnection connection) {
+        long modified = connection.getLastModified();
+        long length = connection.getContentLength(); // an int
+
+        properties.setProperty("content-length", Long.toString(length));
+        properties.setProperty("last-modified", Long.toString(modified));
+    }
+
+    /**
+     * Returns the remote location this entry caches.
+     */
+    public URL getLocation() {
+        return location;
+    }
+
+    /**
+     * Returns the time in the local system clock that the file was
+     * most recently checked for an update.
+     */
+    public long getLastUpdated() {
+        try {
+            return Long.parseLong(properties.getProperty("last-updated"));
+        }
+        catch (Exception ex) {
+            return 0;
+        }
+    }
+
+    /**
+     * Sets the time in the local system clock that the file was
+     * most recently checked for an update.
+     */
+    public void setLastUpdated(long updatedTime) {
+        properties.setProperty("last-updated", Long.toString(updatedTime));
+    }
+
+    /**
+     * Returns whether there is a version of the URL contents in
+     * the cache and it is up to date.  This method may not return
+     * immediately.
+     *
+     * @param connection a connection to the remote URL
+     * @return whether the cache contains the version
+     */
+    public boolean isCurrent(URLConnection connection) {
+        boolean cached = isCached();
+
+        if (!cached)
+            return false;
+
+        try {
+            long remoteModified = connection.getLastModified();
+            long cachedModified = Long.parseLong(properties.getProperty("last-modified"));
+
+            if (remoteModified > 0 && remoteModified <= cachedModified)
+                return true;
+            else
+                return false;
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            return cached; // if can't connect return whether already in cache
+        }
+    }
+
+    /**
+     * Returns true if the cache has a local copy of the contents
+     * of the URL matching the specified version string.
+     *
+     * @return true if the resource is in the cache
+     */
+    public boolean isCached() {
+        File localFile = CacheUtil.getCacheFile(location, version);
+        if (!localFile.exists())
+            return false;
+
+        try {
+            long cachedLength = localFile.length();
+            long remoteLength = Long.parseLong(properties.getProperty("content-length", "-1"));
+
+            if (remoteLength >= 0 && cachedLength != remoteLength)
+                return false;
+            else
+                return true;
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            return false; // should throw?
+        }
+    }
+
+    /**
+     * Save the current information for the cache entry.
+     */
+    protected void store() {
+        properties.store();
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/cache/CacheUtil.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/cache/CacheUtil.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,394 +1,394 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.cache;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.lang.reflect.*;
-import java.security.*;
-import javax.jnlp.*;
-
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.runtime.*;
-
-/**
- * Provides static methods to interact with the cache, download
- * indicator, and other utility methods.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.17 $
- */
-public class CacheUtil {
-
-    private static String R(String key, Object param) { 
-        return JNLPRuntime.getMessage(key, new Object[] {param}); 
-    }
-
-    /**
-     * Compares a URL using string compare of its protocol, host,
-     * port, path, query, and anchor.  This method avoids the host
-     * name lookup that URL.equals does for http: protocol URLs.
-     * It may not return the same value as the URL.equals method
-     * (different hostnames that resolve to the same IP address,
-     * ie sourceforge.net and www.sourceforge.net).
-     */
-    public static boolean urlEquals(URL u1, URL u2) {
-        if (u1==u2)
-            return true;
-        if (u1==null || u2==null)
-            return false;
-
-        if (!compare(u1.getProtocol(), u2.getProtocol(), true) ||
-            !compare(u1.getHost(), u2.getHost(), true) ||
-            //u1.getDefaultPort() != u2.getDefaultPort() || // only in 1.4
-            !compare(u1.getPath(), u2.getPath(), false) ||
-            !compare(u1.getQuery(), u2.getQuery(), false) ||
-            !compare(u1.getRef(), u2.getRef(), false))
-            return false;
-        else
-            return true;
-    }
-
-    /**
-     * Caches a resource and returns a URL for it in the cache;
-     * blocks until resource is cached.  If the resource location is
-     * not cacheable (points to a local file, etc) then the original
-     * URL is returned.<p>
-     *
-     * @param location location of the resource
-     * @param version the version, or null
-     * @return either the location in the cache or the original location
-     */
-    public static URL getCachedResource(URL location, Version version, UpdatePolicy policy) {
-        ResourceTracker rt = new ResourceTracker();
-        rt.addResource(location, version, policy);
-        try {
-            File f = rt.getCacheFile(location);
-            return f.toURL();
-        }
-        catch (MalformedURLException ex) {
-            return location;
-        }
-    }
-
-    /**
-     * Compare strings that can be null.
-     */
-    private static boolean compare(String s1, String s2, boolean ignore) {
-        if (s1==s2)
-            return true;
-        if (s1==null || s2==null)
-            return false;
-
-        if (ignore)
-            return s1.equalsIgnoreCase(s2);
-        else
-            return s1.equals(s2);
-    }
-
-    /**
-     * Returns the Permission object necessary to access the
-     * resource, or null if no permission is needed.
-     */
-    public static Permission getReadPermission(URL location, Version version) {
-        if (CacheUtil.isCacheable(location, version)) {
-            File file = CacheUtil.getCacheFile(location, version);
-
-            return new FilePermission(file.getPath(), "read");
-        }
-        else {
-            try {
-                // this is what URLClassLoader does
-                return location.openConnection().getPermission();
-            }
-            catch (java.io.IOException ioe) {
-                // should try to figure out the permission
-            	if (JNLPRuntime.isDebug())
-            		ioe.printStackTrace();
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns whether there is a version of the URL contents in the
-     * cache and it is up to date.  This method may not return
-     * immediately.
-     *
-     * @param source the source URL
-     * @param version the versions to check for
-     * @param connection a connection to the URL, or null
-     * @return whether the cache contains the version
-     * @throws IllegalArgumentException if the source is not cacheable
-     */
-    public static boolean isCurrent(URL source, Version version, URLConnection connection) {
-
-        if (!isCacheable(source, version))
-            throw new IllegalArgumentException(R("CNotCacheable", source));
-
-        try {
-            if (connection == null)
-                connection = source.openConnection();
-
-            connection.connect();
-
-            CacheEntry entry = new CacheEntry(source, version); // could pool this
-            boolean result = entry.isCurrent(connection);
-
-            if (JNLPRuntime.isDebug())
-                System.out.println("isCurrent: "+source+" = "+result);
-
-            return result;
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            return isCached(source, version); // if can't connect return whether already in cache
-        }
-    }
-
-    /**
-     * Returns true if the cache has a local copy of the contents of
-     * the URL matching the specified version string.
-     *
-     * @param source the source URL
-     * @param version the versions to check for
-     * @return true if the source is in the cache
-     * @throws IllegalArgumentException if the source is not cacheable
-     */
-    public static boolean isCached(URL source, Version version) {
-        if (!isCacheable(source, version))
-            throw new IllegalArgumentException(R("CNotCacheable", source));
-
-        CacheEntry entry = new CacheEntry(source, version); // could pool this
-        boolean result = entry.isCached();
-
-        if (JNLPRuntime.isDebug())
-            System.out.println("isCached: "+source+" = "+result);
-
-        return result;
-    }
-
-    /**
-     * Returns whether the resource can be cached as a local file;
-     * if not, then URLConnection.openStream can be used to obtain
-     * the contents.
-     */
-    public static boolean isCacheable(URL source, Version version) {
-        if (source == null)
-            return false;
-
-        if (source.getProtocol().equals("file"))
-            return false;
-
-        if (source.getProtocol().equals("jar"))
-            return false;
-
-        return true;
-    }
-
-    /**
-     * Returns the file for the locally cached contents of the
-     * source.  This method returns the file location only and does
-     * not download the resource.  The latest version of the
-     * resource that matches the specified version will be returned.
-     *
-     * @param source the source URL
-     * @param version the version id of the local file
-     * @return the file location in the cache, or null if no versions cached
-     * @throws IllegalArgumentException if the source is not cacheable
-     */
-    public static File getCacheFile(URL source, Version version) {
-        // ensure that version is an version id not version string
-
-        if (!isCacheable(source, version))
-            throw new IllegalArgumentException(R("CNotCacheable", source));
-
-        try {
-            File localFile = urlToPath(source, "cache");
-            localFile.getParentFile().mkdirs();
-
-            return localFile;
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            return null;
-        }
-    }
-
-    /**
-     * Returns a buffered output stream open for writing to the
-     * cache file.
-     *
-     * @param source the remote location
-     * @param version the file version to write to
-     */
-    public static OutputStream getOutputStream(URL source, Version version) throws IOException {
-        File localFile = getCacheFile(source, version);
-        OutputStream out = new FileOutputStream(localFile);
-
-        return new BufferedOutputStream(out);
-    }
-
-    /**
-     * Copies from an input stream to an output stream.  On
-     * completion, both streams will be closed.  Streams are
-     * buffered automatically.
-     */
-    public static void streamCopy(InputStream is, OutputStream os) throws IOException {
-        if (!(is instanceof BufferedInputStream))
-            is = new BufferedInputStream(is);
-
-        if (!(os instanceof BufferedOutputStream))
-            os = new BufferedOutputStream(os);
-
-        try {
-            byte b[] = new byte[4096];
-            while (true) {
-                int c = is.read(b, 0, b.length);
-                if (c == -1)
-                    break;
-
-                os.write(b, 0, c);
-            }
-        }
-        finally {
-            is.close();
-            os.close();
-        }
-    }
-
-    /**
-     * Converts a URL into a local path string within the runtime's
-     * base directory.
-     *
-     * @param location the url
-     * @param subdir subdirectory under the base directory
-     * @return the file
-     */
-    public static File urlToPath(URL location, String subdir) {
-        StringBuffer path = new StringBuffer();
-
-        if (subdir != null) {
-            path.append(subdir);
-            path.append(File.separatorChar);
-        }
-
-        path.append(location.getProtocol());
-        path.append(File.separatorChar);
-        path.append(location.getHost());
-        path.append(File.separatorChar);
-        path.append(location.getPath().replace('/', File.separatorChar));
-
-        return new File(JNLPRuntime.getBaseDir(), fixPath(path.toString()));
-    }
-
-    /**
-     * Clean up a string by removing characters that can't appear in
-     * a local file name.
-     */
-    private static String fixPath(String path) {
-        char badChars[] = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
-
-        for (int i=0; i < badChars.length; i++)
-            if (badChars[i] != File.separatorChar)
-                if (-1 != path.indexOf(badChars[i]))
-                    path = path.replace(badChars[i], 'X');
-
-        return path;
-    }
-
-    /**
-     * Waits until the resources are downloaded, while showing a
-     * progress indicator.
-     *
-     * @param tracker the resource tracker
-     * @param resources the resources to wait for
-     * @param title name of the download
-     */
-    public static void waitForResources(ApplicationInstance app, ResourceTracker tracker, URL resources[], String title) {
-        DownloadIndicator indicator = JNLPRuntime.getDefaultDownloadIndicator();
-        DownloadServiceListener listener = null;
-
-        try {
-            if (indicator == null) {
-                tracker.waitForResources(resources, 0);
-                return;
-            }
-
-            // see if resources can be downloaded very quickly; avoids
-            // overhead of creating display components for the resources
-            if (tracker.waitForResources(resources, indicator.getInitialDelay())) 
-                return;
-
-            // only resources not starting out downloaded are displayed
-            List urlList = new ArrayList();
-            for (int i=0; i < resources.length; i++) {
-                if (!tracker.checkResource(resources[i]))
-                    urlList.add(resources[i]);
-            }
-            URL undownloaded[] = (URL[]) urlList.toArray( new URL[urlList.size()] );
-
-            listener = indicator.getListener(app, title, undownloaded);
-
-            do {
-                long read = 0;
-                long total = 0;
-
-                for (int i=0; i < undownloaded.length; i++) {
-                    // add in any -1's; they're insignificant
-                    total += tracker.getTotalSize(undownloaded[i]);
-                    read += tracker.getAmountRead(undownloaded[i]);
-                }
-
-                int percent = (int)( (100*read)/Math.max(1,total) );
-
-                for (int i=0; i < undownloaded.length; i++)
-                    listener.progress(undownloaded[i], "version",
-                                      tracker.getAmountRead(undownloaded[i]),
-                                      tracker.getTotalSize(undownloaded[i]),
-                                      percent);
-            }
-            while (!tracker.waitForResources(resources, indicator.getUpdateRate()));
-
-            // make sure they read 100% until indicator closes
-            for (int i=0; i < undownloaded.length; i++)
-                listener.progress(undownloaded[i], "version",
-                                  tracker.getTotalSize(undownloaded[i]),
-                                  tracker.getTotalSize(undownloaded[i]),
-                                  100);
-
-        }
-        catch (InterruptedException ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-        }
-        finally {
-            if (listener != null)
-                indicator.disposeListener(listener);
-        }
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.security.*;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+
+/**
+ * Provides static methods to interact with the cache, download
+ * indicator, and other utility methods.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.17 $
+ */
+public class CacheUtil {
+
+    private static String R(String key, Object param) { 
+        return JNLPRuntime.getMessage(key, new Object[] {param}); 
+    }
+
+    /**
+     * Compares a URL using string compare of its protocol, host,
+     * port, path, query, and anchor.  This method avoids the host
+     * name lookup that URL.equals does for http: protocol URLs.
+     * It may not return the same value as the URL.equals method
+     * (different hostnames that resolve to the same IP address,
+     * ie sourceforge.net and www.sourceforge.net).
+     */
+    public static boolean urlEquals(URL u1, URL u2) {
+        if (u1==u2)
+            return true;
+        if (u1==null || u2==null)
+            return false;
+
+        if (!compare(u1.getProtocol(), u2.getProtocol(), true) ||
+            !compare(u1.getHost(), u2.getHost(), true) ||
+            //u1.getDefaultPort() != u2.getDefaultPort() || // only in 1.4
+            !compare(u1.getPath(), u2.getPath(), false) ||
+            !compare(u1.getQuery(), u2.getQuery(), false) ||
+            !compare(u1.getRef(), u2.getRef(), false))
+            return false;
+        else
+            return true;
+    }
+
+    /**
+     * Caches a resource and returns a URL for it in the cache;
+     * blocks until resource is cached.  If the resource location is
+     * not cacheable (points to a local file, etc) then the original
+     * URL is returned.<p>
+     *
+     * @param location location of the resource
+     * @param version the version, or null
+     * @return either the location in the cache or the original location
+     */
+    public static URL getCachedResource(URL location, Version version, UpdatePolicy policy) {
+        ResourceTracker rt = new ResourceTracker();
+        rt.addResource(location, version, policy);
+        try {
+            File f = rt.getCacheFile(location);
+            return f.toURL();
+        }
+        catch (MalformedURLException ex) {
+            return location;
+        }
+    }
+
+    /**
+     * Compare strings that can be null.
+     */
+    private static boolean compare(String s1, String s2, boolean ignore) {
+        if (s1==s2)
+            return true;
+        if (s1==null || s2==null)
+            return false;
+
+        if (ignore)
+            return s1.equalsIgnoreCase(s2);
+        else
+            return s1.equals(s2);
+    }
+
+    /**
+     * Returns the Permission object necessary to access the
+     * resource, or null if no permission is needed.
+     */
+    public static Permission getReadPermission(URL location, Version version) {
+        if (CacheUtil.isCacheable(location, version)) {
+            File file = CacheUtil.getCacheFile(location, version);
+
+            return new FilePermission(file.getPath(), "read");
+        }
+        else {
+            try {
+                // this is what URLClassLoader does
+                return location.openConnection().getPermission();
+            }
+            catch (java.io.IOException ioe) {
+                // should try to figure out the permission
+            	if (JNLPRuntime.isDebug())
+            		ioe.printStackTrace();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns whether there is a version of the URL contents in the
+     * cache and it is up to date.  This method may not return
+     * immediately.
+     *
+     * @param source the source URL
+     * @param version the versions to check for
+     * @param connection a connection to the URL, or null
+     * @return whether the cache contains the version
+     * @throws IllegalArgumentException if the source is not cacheable
+     */
+    public static boolean isCurrent(URL source, Version version, URLConnection connection) {
+
+        if (!isCacheable(source, version))
+            throw new IllegalArgumentException(R("CNotCacheable", source));
+
+        try {
+            if (connection == null)
+                connection = source.openConnection();
+
+            connection.connect();
+
+            CacheEntry entry = new CacheEntry(source, version); // could pool this
+            boolean result = entry.isCurrent(connection);
+
+            if (JNLPRuntime.isDebug())
+                System.out.println("isCurrent: "+source+" = "+result);
+
+            return result;
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            return isCached(source, version); // if can't connect return whether already in cache
+        }
+    }
+
+    /**
+     * Returns true if the cache has a local copy of the contents of
+     * the URL matching the specified version string.
+     *
+     * @param source the source URL
+     * @param version the versions to check for
+     * @return true if the source is in the cache
+     * @throws IllegalArgumentException if the source is not cacheable
+     */
+    public static boolean isCached(URL source, Version version) {
+        if (!isCacheable(source, version))
+            throw new IllegalArgumentException(R("CNotCacheable", source));
+
+        CacheEntry entry = new CacheEntry(source, version); // could pool this
+        boolean result = entry.isCached();
+
+        if (JNLPRuntime.isDebug())
+            System.out.println("isCached: "+source+" = "+result);
+
+        return result;
+    }
+
+    /**
+     * Returns whether the resource can be cached as a local file;
+     * if not, then URLConnection.openStream can be used to obtain
+     * the contents.
+     */
+    public static boolean isCacheable(URL source, Version version) {
+        if (source == null)
+            return false;
+
+        if (source.getProtocol().equals("file"))
+            return false;
+
+        if (source.getProtocol().equals("jar"))
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Returns the file for the locally cached contents of the
+     * source.  This method returns the file location only and does
+     * not download the resource.  The latest version of the
+     * resource that matches the specified version will be returned.
+     *
+     * @param source the source URL
+     * @param version the version id of the local file
+     * @return the file location in the cache, or null if no versions cached
+     * @throws IllegalArgumentException if the source is not cacheable
+     */
+    public static File getCacheFile(URL source, Version version) {
+        // ensure that version is an version id not version string
+
+        if (!isCacheable(source, version))
+            throw new IllegalArgumentException(R("CNotCacheable", source));
+
+        try {
+            File localFile = urlToPath(source, "cache");
+            localFile.getParentFile().mkdirs();
+
+            return localFile;
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            return null;
+        }
+    }
+
+    /**
+     * Returns a buffered output stream open for writing to the
+     * cache file.
+     *
+     * @param source the remote location
+     * @param version the file version to write to
+     */
+    public static OutputStream getOutputStream(URL source, Version version) throws IOException {
+        File localFile = getCacheFile(source, version);
+        OutputStream out = new FileOutputStream(localFile);
+
+        return new BufferedOutputStream(out);
+    }
+
+    /**
+     * Copies from an input stream to an output stream.  On
+     * completion, both streams will be closed.  Streams are
+     * buffered automatically.
+     */
+    public static void streamCopy(InputStream is, OutputStream os) throws IOException {
+        if (!(is instanceof BufferedInputStream))
+            is = new BufferedInputStream(is);
+
+        if (!(os instanceof BufferedOutputStream))
+            os = new BufferedOutputStream(os);
+
+        try {
+            byte b[] = new byte[4096];
+            while (true) {
+                int c = is.read(b, 0, b.length);
+                if (c == -1)
+                    break;
+
+                os.write(b, 0, c);
+            }
+        }
+        finally {
+            is.close();
+            os.close();
+        }
+    }
+
+    /**
+     * Converts a URL into a local path string within the runtime's
+     * base directory.
+     *
+     * @param location the url
+     * @param subdir subdirectory under the base directory
+     * @return the file
+     */
+    public static File urlToPath(URL location, String subdir) {
+        StringBuffer path = new StringBuffer();
+
+        if (subdir != null) {
+            path.append(subdir);
+            path.append(File.separatorChar);
+        }
+
+        path.append(location.getProtocol());
+        path.append(File.separatorChar);
+        path.append(location.getHost());
+        path.append(File.separatorChar);
+        path.append(location.getPath().replace('/', File.separatorChar));
+
+        return new File(JNLPRuntime.getBaseDir(), fixPath(path.toString()));
+    }
+
+    /**
+     * Clean up a string by removing characters that can't appear in
+     * a local file name.
+     */
+    private static String fixPath(String path) {
+        char badChars[] = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
+
+        for (int i=0; i < badChars.length; i++)
+            if (badChars[i] != File.separatorChar)
+                if (-1 != path.indexOf(badChars[i]))
+                    path = path.replace(badChars[i], 'X');
+
+        return path;
+    }
+
+    /**
+     * Waits until the resources are downloaded, while showing a
+     * progress indicator.
+     *
+     * @param tracker the resource tracker
+     * @param resources the resources to wait for
+     * @param title name of the download
+     */
+    public static void waitForResources(ApplicationInstance app, ResourceTracker tracker, URL resources[], String title) {
+        DownloadIndicator indicator = JNLPRuntime.getDefaultDownloadIndicator();
+        DownloadServiceListener listener = null;
+
+        try {
+            if (indicator == null) {
+                tracker.waitForResources(resources, 0);
+                return;
+            }
+
+            // see if resources can be downloaded very quickly; avoids
+            // overhead of creating display components for the resources
+            if (tracker.waitForResources(resources, indicator.getInitialDelay())) 
+                return;
+
+            // only resources not starting out downloaded are displayed
+            List urlList = new ArrayList();
+            for (int i=0; i < resources.length; i++) {
+                if (!tracker.checkResource(resources[i]))
+                    urlList.add(resources[i]);
+            }
+            URL undownloaded[] = (URL[]) urlList.toArray( new URL[urlList.size()] );
+
+            listener = indicator.getListener(app, title, undownloaded);
+
+            do {
+                long read = 0;
+                long total = 0;
+
+                for (int i=0; i < undownloaded.length; i++) {
+                    // add in any -1's; they're insignificant
+                    total += tracker.getTotalSize(undownloaded[i]);
+                    read += tracker.getAmountRead(undownloaded[i]);
+                }
+
+                int percent = (int)( (100*read)/Math.max(1,total) );
+
+                for (int i=0; i < undownloaded.length; i++)
+                    listener.progress(undownloaded[i], "version",
+                                      tracker.getAmountRead(undownloaded[i]),
+                                      tracker.getTotalSize(undownloaded[i]),
+                                      percent);
+            }
+            while (!tracker.waitForResources(resources, indicator.getUpdateRate()));
+
+            // make sure they read 100% until indicator closes
+            for (int i=0; i < undownloaded.length; i++)
+                listener.progress(undownloaded[i], "version",
+                                  tracker.getTotalSize(undownloaded[i]),
+                                  tracker.getTotalSize(undownloaded[i]),
+                                  100);
+
+        }
+        catch (InterruptedException ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+        }
+        finally {
+            if (listener != null)
+                indicator.disposeListener(listener);
+        }
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,321 +1,321 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.cache;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.net.*;
-import java.util.*;
-import java.util.List;
-import javax.swing.*;
-import javax.swing.Timer;
-import javax.jnlp.*;
-
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.runtime.*;
-
-/**
- * Show the progress of downloads.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.3 $ 
- */
-public class DefaultDownloadIndicator implements DownloadIndicator {
-
-    // todo: rewrite this to cut down on size/complexity; smarter
-    // panels (JList, renderer) understand resources instead of
-    // nested panels and grid-bag mess.
-
-    // todo: fix bug where user closes download box and it
-    // never(?) reappears.
-
-    // todo: UI for user to cancel/restart download
-
-    // todo: this should be synchronized at some point but conflicts
-    // aren't very likely.
-
-    private static String downloading = JNLPRuntime.getMessage("CDownloading");
-    private static String complete = JNLPRuntime.getMessage("CComplete");
-
-    /** time to wait after completing but before window closes */
-    private static final int CLOSE_DELAY = 750;
-
-    /** the display window */
-    private static JFrame frame; 
-
-    /** shared constraint */
-    static GridBagConstraints vertical;
-    static GridBagConstraints verticalIndent;
-    static { 
-        vertical = new GridBagConstraints();
-        vertical.gridwidth = GridBagConstraints.REMAINDER;
-        vertical.weightx = 1.0;
-        vertical.fill = GridBagConstraints.HORIZONTAL;
-        vertical.anchor = GridBagConstraints.WEST;
-
-        verticalIndent = (GridBagConstraints) vertical.clone();
-        verticalIndent.insets = new Insets(0, 10, 3, 0);
-    }
-
-    /**
-     * Return the update rate.
-     */
-    public int getUpdateRate() {
-        return 150; //ms
-    }
-
-    /**
-     * Return the initial delay before obtaining a listener.
-     */
-    public int getInitialDelay() {
-        return 300; //ms
-    }
-
-    /**
-     * Return a download service listener that displays the progress
-     * in a shared download info window.
-     *
-     * @param app the downloading application, or null if N/A
-     * @param downloadName name identifying the download to the user
-     * @param resources initial urls to display (not required)
-     */
-    public DownloadServiceListener getListener(ApplicationInstance app, String downloadName, URL resources[]) {
-        DownloadPanel result = new DownloadPanel(downloadName);
-
-        if (frame == null) {
-            frame = new JFrame(downloading+"...");
-            frame.getContentPane().setLayout(new GridBagLayout());
-        }
-
-        if (resources != null)
-            for (int i=0; i < resources.length; i++)
-                result.addProgressPanel(resources[i], null);
-
-        frame.getContentPane().add(result, vertical);
-        frame.pack();
-
-        if (!frame.isVisible()) {
-            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
-            Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());
-            Dimension screen = new Dimension(screenSize.width - insets.left , 
-                    screenSize.height - insets.top);
-            frame.setLocation(screen.width-frame.getWidth(),
-                              screen.height-frame.getHeight());
-        }
-
-        frame.show();
-
-        return result;
-    }
-
-    /**
-     * Remove a download service listener that was obtained by
-     * calling the getDownloadListener method from the shared
-     * download info window.
-     */
-    public void disposeListener(final DownloadServiceListener listener) {
-        if (!(listener instanceof DownloadPanel))
-            return;
-
-        ActionListener hider = new ActionListener() {
-            public void actionPerformed(ActionEvent evt) {
-                if (frame.getContentPane().getComponentCount() == 1)
-                    frame.hide();
-
-                frame.getContentPane().remove((DownloadPanel) listener);
-                frame.pack();
-            }
-        };
-
-        Timer timer = new Timer(CLOSE_DELAY, hider);
-        timer.setRepeats(false);
-        timer.start();
-    }
-
-
-
-    /**
-     * Groups the url progress in a panel.
-     */
-    static class DownloadPanel extends JPanel implements DownloadServiceListener {
-
-        /** the download name */
-        private String downloadName;
-
-        /** Downloading part: */
-        private JLabel header = new JLabel();
-
-        /** list of URLs being downloaded */
-        private List urls = new ArrayList();
-
-        /** list of ProgressPanels */
-        private List panels = new ArrayList();
-
-
-        /**
-         * Create a new download panel for with the specified download
-         * name.
-         */
-        protected DownloadPanel(String downloadName) {
-            setLayout(new GridBagLayout());
-
-            this.downloadName = downloadName;
-            this.add(header, vertical);
-            header.setFont(header.getFont().deriveFont(Font.BOLD));
-
-            setOverallPercent(0);
-        }
-
-        /**
-         * Add a ProgressPanel for a URL.
-         */
-        protected void addProgressPanel(URL url, String version) {
-            if (!urls.contains(url)) {
-                ProgressPanel panel = new ProgressPanel(url, version);
-
-                add(panel, verticalIndent);
-                frame.pack();
-
-                urls.add(url);
-                panels.add(panel);
-            }
-        }
-
-        /**
-         * Update the download progress of a url.
-         */
-        protected void update(final URL url, final String version, final long readSoFar, final long total, final int overallPercent) {
-            Runnable r = new Runnable() {
-                public void run() {
-                    if (!urls.contains(url))
-                        addProgressPanel(url, version);
-
-                    setOverallPercent(overallPercent);
-
-                    ProgressPanel panel = (ProgressPanel) panels.get(urls.indexOf(url));
-                    panel.setProgress(readSoFar, total);
-                    panel.repaint();
-                }
-            };
-            SwingUtilities.invokeLater(r);
-        }
-
-        /**
-         * Sets the overall percent completed.
-         */
-        public void setOverallPercent(int percent) {
-            // don't get whole string from resource and sub in
-            // values because it'll be doing a MessageFormat for
-            // each update.
-            header.setText(downloading+" "+downloadName+": "+percent+"% "+complete+".");
-        }
-
-        /**
-         * Called when a download failed.
-         */
-        public void downloadFailed(URL url, String version) {
-            update(url, version, -1, -1, -1);
-        }
-
-        /**
-         * Called when a download has progressed.
-         */
-        public void progress(URL url, String version, long readSoFar, long total, int overallPercent) {
-            update(url, version, readSoFar, total, overallPercent);
-        }
-
-        /**
-         * Called when an archive is patched.
-         */
-        public void upgradingArchive(URL url, String version, int patchPercent, int overallPercent) {
-            update(url, version, patchPercent, 100, overallPercent);
-        }
-
-        /**
-         * Called when a download is being validated.
-         */
-        public void validating(URL url, String version, long entry, long total, int overallPercent) {
-            update(url, version, entry, total, overallPercent);
-        }
-
-    };
-
-
-
-    /**
-     * A progress bar with the URL next to it.
-     */
-    static class ProgressPanel extends JPanel {
-        private JPanel bar = new JPanel();
-
-        private long total;
-        private long readSoFar;
-
-        ProgressPanel(URL url, String version) {
-            JLabel location = new JLabel(" "+url.getHost()+"/"+url.getFile());
-
-            bar.setMinimumSize(new Dimension(80,15));
-            bar.setPreferredSize(new Dimension(80,15));
-            bar.setOpaque(false);
-
-            setLayout(new GridBagLayout());
-
-            GridBagConstraints gbc = new GridBagConstraints();
-            gbc.weightx = 0.0;
-            gbc.fill = GridBagConstraints.NONE;
-            gbc.gridwidth = GridBagConstraints.RELATIVE;
-            add(bar, gbc);
-
-            gbc.insets = new Insets(0, 3, 0, 0);
-            gbc.weightx = 1.0;
-            gbc.fill = GridBagConstraints.HORIZONTAL;
-            gbc.gridwidth = GridBagConstraints.REMAINDER;
-            gbc.anchor = GridBagConstraints.WEST;
-            add(location, gbc);
-        }
-
-        public void setProgress(long readSoFar, long total) {
-            this.readSoFar = readSoFar;
-            this.total = total;
-        }
-
-        public void paintComponent(Graphics g) {
-            super.paintComponent(g);
-
-            int x = bar.getX();
-            int y = bar.getY();
-            int h = bar.getHeight();
-            int w = bar.getWidth();
-
-            if (readSoFar <= 0 || total <= 0) {
-                // make barber pole
-            }
-            else {
-                double progress = (double)readSoFar / (double)total;
-                int divide = (int)(w * progress);
-
-                g.setColor(Color.white);
-                g.fillRect(x, y, w, h);
-                g.setColor(Color.blue);
-                g.fillRect(x+1, y+1, divide-1, h-1);
-            }
-        }
-    };
-
-}
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.util.*;
+import java.util.List;
+import javax.swing.*;
+import javax.swing.Timer;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.runtime.*;
+
+/**
+ * Show the progress of downloads.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.3 $ 
+ */
+public class DefaultDownloadIndicator implements DownloadIndicator {
+
+    // todo: rewrite this to cut down on size/complexity; smarter
+    // panels (JList, renderer) understand resources instead of
+    // nested panels and grid-bag mess.
+
+    // todo: fix bug where user closes download box and it
+    // never(?) reappears.
+
+    // todo: UI for user to cancel/restart download
+
+    // todo: this should be synchronized at some point but conflicts
+    // aren't very likely.
+
+    private static String downloading = JNLPRuntime.getMessage("CDownloading");
+    private static String complete = JNLPRuntime.getMessage("CComplete");
+
+    /** time to wait after completing but before window closes */
+    private static final int CLOSE_DELAY = 750;
+
+    /** the display window */
+    private static JFrame frame; 
+
+    /** shared constraint */
+    static GridBagConstraints vertical;
+    static GridBagConstraints verticalIndent;
+    static { 
+        vertical = new GridBagConstraints();
+        vertical.gridwidth = GridBagConstraints.REMAINDER;
+        vertical.weightx = 1.0;
+        vertical.fill = GridBagConstraints.HORIZONTAL;
+        vertical.anchor = GridBagConstraints.WEST;
+
+        verticalIndent = (GridBagConstraints) vertical.clone();
+        verticalIndent.insets = new Insets(0, 10, 3, 0);
+    }
+
+    /**
+     * Return the update rate.
+     */
+    public int getUpdateRate() {
+        return 150; //ms
+    }
+
+    /**
+     * Return the initial delay before obtaining a listener.
+     */
+    public int getInitialDelay() {
+        return 300; //ms
+    }
+
+    /**
+     * Return a download service listener that displays the progress
+     * in a shared download info window.
+     *
+     * @param app the downloading application, or null if N/A
+     * @param downloadName name identifying the download to the user
+     * @param resources initial urls to display (not required)
+     */
+    public DownloadServiceListener getListener(ApplicationInstance app, String downloadName, URL resources[]) {
+        DownloadPanel result = new DownloadPanel(downloadName);
+
+        if (frame == null) {
+            frame = new JFrame(downloading+"...");
+            frame.getContentPane().setLayout(new GridBagLayout());
+        }
+
+        if (resources != null)
+            for (int i=0; i < resources.length; i++)
+                result.addProgressPanel(resources[i], null);
+
+        frame.getContentPane().add(result, vertical);
+        frame.pack();
+
+        if (!frame.isVisible()) {
+            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+            Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration());
+            Dimension screen = new Dimension(screenSize.width - insets.left , 
+                    screenSize.height - insets.top);
+            frame.setLocation(screen.width-frame.getWidth(),
+                              screen.height-frame.getHeight());
+        }
+
+        frame.show();
+
+        return result;
+    }
+
+    /**
+     * Remove a download service listener that was obtained by
+     * calling the getDownloadListener method from the shared
+     * download info window.
+     */
+    public void disposeListener(final DownloadServiceListener listener) {
+        if (!(listener instanceof DownloadPanel))
+            return;
+
+        ActionListener hider = new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                if (frame.getContentPane().getComponentCount() == 1)
+                    frame.hide();
+
+                frame.getContentPane().remove((DownloadPanel) listener);
+                frame.pack();
+            }
+        };
+
+        Timer timer = new Timer(CLOSE_DELAY, hider);
+        timer.setRepeats(false);
+        timer.start();
+    }
+
+
+
+    /**
+     * Groups the url progress in a panel.
+     */
+    static class DownloadPanel extends JPanel implements DownloadServiceListener {
+
+        /** the download name */
+        private String downloadName;
+
+        /** Downloading part: */
+        private JLabel header = new JLabel();
+
+        /** list of URLs being downloaded */
+        private List urls = new ArrayList();
+
+        /** list of ProgressPanels */
+        private List panels = new ArrayList();
+
+
+        /**
+         * Create a new download panel for with the specified download
+         * name.
+         */
+        protected DownloadPanel(String downloadName) {
+            setLayout(new GridBagLayout());
+
+            this.downloadName = downloadName;
+            this.add(header, vertical);
+            header.setFont(header.getFont().deriveFont(Font.BOLD));
+
+            setOverallPercent(0);
+        }
+
+        /**
+         * Add a ProgressPanel for a URL.
+         */
+        protected void addProgressPanel(URL url, String version) {
+            if (!urls.contains(url)) {
+                ProgressPanel panel = new ProgressPanel(url, version);
+
+                add(panel, verticalIndent);
+                frame.pack();
+
+                urls.add(url);
+                panels.add(panel);
+            }
+        }
+
+        /**
+         * Update the download progress of a url.
+         */
+        protected void update(final URL url, final String version, final long readSoFar, final long total, final int overallPercent) {
+            Runnable r = new Runnable() {
+                public void run() {
+                    if (!urls.contains(url))
+                        addProgressPanel(url, version);
+
+                    setOverallPercent(overallPercent);
+
+                    ProgressPanel panel = (ProgressPanel) panels.get(urls.indexOf(url));
+                    panel.setProgress(readSoFar, total);
+                    panel.repaint();
+                }
+            };
+            SwingUtilities.invokeLater(r);
+        }
+
+        /**
+         * Sets the overall percent completed.
+         */
+        public void setOverallPercent(int percent) {
+            // don't get whole string from resource and sub in
+            // values because it'll be doing a MessageFormat for
+            // each update.
+            header.setText(downloading+" "+downloadName+": "+percent+"% "+complete+".");
+        }
+
+        /**
+         * Called when a download failed.
+         */
+        public void downloadFailed(URL url, String version) {
+            update(url, version, -1, -1, -1);
+        }
+
+        /**
+         * Called when a download has progressed.
+         */
+        public void progress(URL url, String version, long readSoFar, long total, int overallPercent) {
+            update(url, version, readSoFar, total, overallPercent);
+        }
+
+        /**
+         * Called when an archive is patched.
+         */
+        public void upgradingArchive(URL url, String version, int patchPercent, int overallPercent) {
+            update(url, version, patchPercent, 100, overallPercent);
+        }
+
+        /**
+         * Called when a download is being validated.
+         */
+        public void validating(URL url, String version, long entry, long total, int overallPercent) {
+            update(url, version, entry, total, overallPercent);
+        }
+
+    };
+
+
+
+    /**
+     * A progress bar with the URL next to it.
+     */
+    static class ProgressPanel extends JPanel {
+        private JPanel bar = new JPanel();
+
+        private long total;
+        private long readSoFar;
+
+        ProgressPanel(URL url, String version) {
+            JLabel location = new JLabel(" "+url.getHost()+"/"+url.getFile());
+
+            bar.setMinimumSize(new Dimension(80,15));
+            bar.setPreferredSize(new Dimension(80,15));
+            bar.setOpaque(false);
+
+            setLayout(new GridBagLayout());
+
+            GridBagConstraints gbc = new GridBagConstraints();
+            gbc.weightx = 0.0;
+            gbc.fill = GridBagConstraints.NONE;
+            gbc.gridwidth = GridBagConstraints.RELATIVE;
+            add(bar, gbc);
+
+            gbc.insets = new Insets(0, 3, 0, 0);
+            gbc.weightx = 1.0;
+            gbc.fill = GridBagConstraints.HORIZONTAL;
+            gbc.gridwidth = GridBagConstraints.REMAINDER;
+            gbc.anchor = GridBagConstraints.WEST;
+            add(location, gbc);
+        }
+
+        public void setProgress(long readSoFar, long total) {
+            this.readSoFar = readSoFar;
+            this.total = total;
+        }
+
+        public void paintComponent(Graphics g) {
+            super.paintComponent(g);
+
+            int x = bar.getX();
+            int y = bar.getY();
+            int h = bar.getHeight();
+            int w = bar.getWidth();
+
+            if (readSoFar <= 0 || total <= 0) {
+                // make barber pole
+            }
+            else {
+                double progress = (double)readSoFar / (double)total;
+                int divide = (int)(w * progress);
+
+                g.setColor(Color.white);
+                g.fillRect(x, y, w, h);
+                g.setColor(Color.blue);
+                g.fillRect(x+1, y+1, divide-1, h-1);
+            }
+        }
+    };
+
+}
+
--- a/rt/net/sourceforge/jnlp/cache/ResourceTracker.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/cache/ResourceTracker.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,1051 +1,1051 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.cache;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Pack200;
-import java.util.jar.Pack200.Unpacker;
-import java.util.zip.GZIPInputStream;
-
-import net.sourceforge.jnlp.Version;
-import net.sourceforge.jnlp.event.DownloadEvent;
-import net.sourceforge.jnlp.event.DownloadListener;
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
-import net.sourceforge.jnlp.util.WeakList;
-
-/**
- * This class tracks the downloading of various resources of a
- * JNLP file to local files in the cache.  It can be used to
- * download icons, jnlp and extension files, jars, and jardiff
- * files using the version based protocol or any file using the
- * basic download protocol (jardiff and version not implemented
- * yet).<p>
- *
- * The resource tracker can be configured to prefetch resources,
- * which are downloaded in the order added to the media
- * tracker.<p>
- *
- * Multiple threads are used to download and cache resources that
- * are actively being waited for (blocking a caller) or those that
- * have been started downloading by calling the startDownload
- * method.  Resources that are prefetched are downloaded one at a
- * time and only if no other trackers have requested downloads.
- * This allows the tracker to start downloading many items without
- * using many system resources, but still quickly download items
- * as needed.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.22 $ 
- */
-public class ResourceTracker {
-
-    // todo: use event listener arrays instead of lists
-
-    // todo: see if there is a way to set the socket options just
-    // for use by the tracker so checks for updates don't hang for
-    // a long time.
-
-    // todo: ability to restart/retry a hung download
-
-    // todo: move resource downloading/processing code into Resource
-    // class, threading stays in ResourceTracker
-
-    // todo: get status method? and some way to convey error status
-    // to the caller.
-
-    // todo: might make a tracker be able to download more than one
-    // version of a resource, but probably not very useful.
-
-
-    // defines 
-    //    ResourceTracker.Downloader (download threads)
-
-    // separately locks on (in order of aquire order, ie, sync on prefetch never syncs on lock):
-    //   lock, prefetch, this.resources, each resource, listeners
-
-    /** notified on initialization or download of a resource */
-    private static Object lock = new Integer(0); // used to lock static structures
-
-    // shortcuts
-    private static final int UNINITIALIZED = Resource.UNINITIALIZED;
-    private static final int CONNECT = Resource.CONNECT;
-    private static final int CONNECTING = Resource.CONNECTING;
-    private static final int CONNECTED = Resource.CONNECTED;
-    private static final int DOWNLOAD = Resource.DOWNLOAD;
-    private static final int DOWNLOADING = Resource.DOWNLOADING;
-    private static final int DOWNLOADED = Resource.DOWNLOADED;
-    private static final int ERROR = Resource.ERROR;
-    private static final int STARTED = Resource.STARTED;
-
-    /** max threads */
-    private static final int maxThreads = 5;
-
-    /** running threads */
-    private static int threads = 0;
-
-    /** weak list of resource trackers with resources to prefetch */
-    private static WeakList prefetchTrackers = new WeakList();
-
-    /** resources requested to be downloaded */
-    private static ArrayList queue = new ArrayList();
-
-    /** resource trackers threads are working for (used for load balancing across multi-tracker downloads) */
-    private static ArrayList active = new ArrayList(); // 
-
-    /** the resources known about by this resource tracker */
-    private List resources = new ArrayList();
-
-    /** download listeners for this tracker */
-    private List listeners = new ArrayList();
-
-    /** whether to download parts before requested */ 
-    private boolean prefetch;
-
-
-    /**
-     * Creates a resource tracker that does not prefetch resources.
-     */
-    public ResourceTracker() {
-        this(false);
-    }
-
-    /**
-     * Creates a resource tracker.
-     *
-     * @param prefetch whether to download resources before requested. 
-     */
-    public ResourceTracker(boolean prefetch) {
-        this.prefetch = prefetch;
-
-        if (prefetch) {
-            synchronized (prefetchTrackers) {
-                prefetchTrackers.add(this);
-                prefetchTrackers.trimToSize();
-            }
-        }
-    }
-
-    /**
-     * Add a resource identified by the specified location and
-     * version.  The tracker only downloads one version of a given
-     * resource per instance (ie cannot download both versions 1 and
-     * 2 of a resource in the same tracker).
-     *
-     * @param location the location of the resource
-     * @param version the resource version
-     * @param updatePolicy whether to check for updates if already in cache
-     */
-    public void addResource(URL location, Version version, UpdatePolicy updatePolicy) {
-        if (location == null)
-            throw new IllegalArgumentException("location==null");
-
-        Resource resource = Resource.getResource(location, version, updatePolicy);
-        boolean downloaded = false;
-
-        synchronized (resources) {
-            if (resources.contains(resource))
-                return;
-            resource.addTracker(this);
-            resources.add(resource);
-        }
-
-        // checkCache may take a while (loads properties file).  this
-        // should really be synchronized on resources, but the worst
-        // case should be that the resource will be updated once even
-        // if unnecessary.
-        downloaded = checkCache(resource, updatePolicy);
-
-        synchronized (lock) {
-            if (!downloaded)
-                if (prefetch && threads == 0) // existing threads do pre-fetch when queue empty
-                    startThread();
-        }
-    }
-
-    /**
-     * Removes a resource from the tracker.  This method is useful
-     * to allow memory to be reclaimed, but calling this method is
-     * not required as resources are reclaimed when the tracker is
-     * collected.
-     *
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public void removeResource(URL location) {
-        synchronized (resources) {
-            Resource resource = getResource(location);
-
-            if (resource != null) {
-                resources.remove(resource);
-                resource.removeTracker(this);
-            }
-
-            // should remove from queue? probably doesn't matter
-        }
-    }
-
-    /**
-     * Check the cache for a resource, and initialize the resource
-     * as already downloaded if found. <p>
-     *
-     * @param updatePolicy whether to check for updates if already in cache
-     * @return whether the resource are already downloaded 
-     */
-    private boolean checkCache(Resource resource, UpdatePolicy updatePolicy) {
-        if (!CacheUtil.isCacheable(resource.location, resource.downloadVersion)) {
-            // pretend that they are already downloaded; essentially
-            // they will just 'pass through' the tracker as if they were
-            // never added (for example, not affecting the total download size).
-            synchronized (resource) {
-                resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED);
-            }
-            fireDownloadEvent(resource);
-            return true;
-        }
-
-        if (updatePolicy != UpdatePolicy.ALWAYS && updatePolicy != UpdatePolicy.FORCE) { // save loading entry props file
-            CacheEntry entry = new CacheEntry(resource.location, resource.downloadVersion);
-
-            if (entry.isCached() && !updatePolicy.shouldUpdate(entry)) {
-                if (JNLPRuntime.isDebug())
-                    System.out.println("not updating: "+resource.location);
-
-                synchronized (resource) {
-                    resource.localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion);
-                    resource.size = resource.localFile.length();
-                    resource.transferred = resource.localFile.length();
-                    resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED);
-                }
-                fireDownloadEvent(resource);
-                return true;
-            }
-        }
-        
-        if (updatePolicy == UpdatePolicy.FORCE) { // ALWAYS update
-            // When we are "always" updating, we update for each instance. Reset resource status.
-            resource.changeStatus(Integer.MAX_VALUE, 0);
-        }
-
-        // may or may not be cached, but check update when connection
-        // is open to possibly save network communication time if it
-        // has to be downloaded, and allow this call to return quickly
-        return false;
-    }
-
-    /**
-     * Adds the listener to the list of objects interested in
-     * receivind DownloadEvents.<p>
-     *
-     * @param location the resource to add a callback for
-     * @param runnable the runnable to call when resource is completed
-     */
-    public void addDownloadListener(DownloadListener listener) {
-        synchronized (listeners) {
-            if (!listeners.contains(listener))
-                listeners.add(listener);
-        }
-    }
-
-    /**
-     * Removes a download listener.
-     */
-    public void removeDownloadListener(DownloadListener listener) {
-        synchronized (listeners) {
-            listeners.remove(listener);
-        }
-    }
-
-    /**
-     * Fires the download event corresponding to the resource's
-     * state.  This method is typicall called by the Resource itself
-     * on each tracker that is monitoring the resource.  Do not call
-     * this method with any locks because the listeners may call
-     * back to this ResourceTracker.
-     */
-    protected void fireDownloadEvent(Resource resource) {
-        DownloadListener l[] = null;
-        synchronized (listeners) {
-            l = (DownloadListener[]) listeners.toArray(new DownloadListener[0]);
-        }
-
-        int status;
-        synchronized (resource) {
-            status = resource.status;
-        }
-
-        DownloadEvent event = new DownloadEvent(this, resource);
-        for (int i=0; i < l.length; i++) {
-            if (0 != ((ERROR|DOWNLOADED) & status))
-                l[i].downloadCompleted(event);
-            else if (0 != (DOWNLOADING & status))
-                l[i].downloadStarted(event);
-            else if (0 != (CONNECTING & status))
-                l[i].updateStarted(event);
-        }
-    }
-
-    /**
-     * Returns a URL pointing to the cached location of the
-     * resource, or the resource itself if it is a non-cacheable
-     * resource.<p>
-     *
-     * If the resource has not downloaded yet, the method will block
-     * until it has been transferred to the cache.<p>
-     *
-     * @param location the resource location
-     * @return the resource, or null if it could not be downloaded
-     * @throws IllegalArgumentException if the resource is not being tracked
-     * @see CacheUtil#isCacheable
-     */
-    public URL getCacheURL(URL location) {
-        try {
-            File f = getCacheFile(location);
-            if (f != null)
-                return f.toURL();
-        }
-        catch (MalformedURLException ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-        }
-
-        return location;
-    }
-
-    /**
-     * Returns a file containing the downloaded resource.  If the
-     * resource is non-cacheable then null is returned unless the
-     * resource is a local file (the original file is returned).<p>
-     *
-     * If the resource has not downloaded yet, the method will block
-     * until it has been transferred to the cache.<p>
-     *
-     * @param location the resource location
-     * @return a local file containing the resource, or null
-     * @throws IllegalArgumentException if the resource is not being tracked
-     * @see CacheUtil#isCacheable
-     */
-    public File getCacheFile(URL location) {
-        try {
-            Resource resource = getResource(location);
-            if (!resource.isSet(DOWNLOADED|ERROR))
-                waitForResource(location, 0);
-
-            if (resource.isSet(ERROR))
-                return null;
-
-            if (resource.localFile != null)
-                return resource.localFile;
-
-            if (location.getProtocol().equalsIgnoreCase("file")) {
-                File file = new File(location.getFile());
-                if (file.exists())
-                    return file;
-            }
-
-            return null;
-        }
-        catch (InterruptedException ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            return null; // need an error exception to throw
-        }
-    }
-
-    /**
-     * Returns an input stream that reads the contents of the
-     * resource.  For non-cacheable resources, an InputStream that
-     * reads from the source location is returned.  Otherwise the
-     * InputStream reads the cached resource.<p>
-     *
-     * This method will block while the resource is downloaded to
-     * the cache.
-     *
-     * @throws IOException if there was an error opening the stream
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public InputStream getInputStream(URL location) throws IOException {
-        try {
-            Resource resource = getResource(location);
-            if (!resource.isSet(DOWNLOADED|ERROR))
-                waitForResource(location, 0);
-
-            if (resource.localFile != null)
-                return new FileInputStream(resource.localFile);
-
-            return resource.location.openStream();
-        }
-        catch (InterruptedException ex) {
-            throw new IOException("wait was interrupted");
-        }
-    }
-
-    /**
-     * Wait for a group of resources to be downloaded and made
-     * available locally.
-     *
-     * @param urls the resources to wait for
-     * @param timeout the time in ms to wait before returning, 0 for no timeout
-     * @return whether the resources downloaded before the timeout
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public boolean waitForResources(URL urls[], long timeout) throws InterruptedException {
-        Resource resources[] = new Resource[ urls.length ];
-
-        synchronized(resources) { 
-            // keep the lock so getResource doesn't have to aquire it each time
-            for (int i=0; i < urls.length; i++)
-                resources[i] = getResource(urls[i]);
-        }
-
-        if (resources.length > 0)
-            return wait(resources, timeout);
-
-        return true;
-    }
-
-    /**
-     * Wait for a particular resource to be downloaded and made
-     * available.
-     *
-     * @param location the resource to wait for
-     * @param timeout the timeout, or 0 to wait until completed
-     * @return whether the resource downloaded before the timeout
-     * @throws InterruptedException if another thread interrupted the wait
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public boolean waitForResource(URL location, long timeout) throws InterruptedException {
-        return wait(new Resource[] { getResource(location) }, timeout);
-    }
-
-    /**
-     * Returns the number of bytes downloaded for a resource.
-     *
-     * @param location the resource location
-     * @return the number of bytes transferred
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public long getAmountRead(URL location) {
-        // not atomic b/c transferred is a long, but so what (each
-        // byte atomic? so probably won't affect anything...)
-        return getResource(location).transferred;
-    }
-
-    /**
-     * Returns whether a resource is available for use (ie, can be
-     * accessed with the getCacheFile method).
-     *
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public boolean checkResource(URL location) {
-        return getResource(location).isSet(DOWNLOADED|ERROR); // isSet atomic
-    }
-
-    /**
-     * Starts loading the resource if it is not already being
-     * downloaded or already cached.  Resources started downloading
-     * using this method may download faster than those prefetched
-     * by the tracker because the tracker will only prefetch one
-     * resource at a time to conserve system resources.
-     *
-     * @return true if the resource is already downloaded (or an error occurred) 
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public boolean startResource(URL location) {
-        Resource resource = getResource(location);
-
-        return startResource(resource);
-    }
-
-    /**
-     * Sets the resource status to connect and download, and
-     * enqueues the resource if not already started.
-     *
-     * @return true if the resource is already downloaded (or an error occurred) 
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    private boolean startResource(Resource resource) {
-        boolean enqueue = false;
-
-        synchronized (resource) {
-            if (resource.isSet(ERROR))
-                return true;
-
-            enqueue = !resource.isSet(STARTED);
-
-            if (!resource.isSet(CONNECTED | CONNECTING))
-                resource.changeStatus(0, CONNECT|STARTED);
-            if (!resource.isSet(DOWNLOADED | DOWNLOADING))
-                resource.changeStatus(0, DOWNLOAD|STARTED);
-
-            if (!resource.isSet(DOWNLOAD|CONNECT))
-                enqueue = false;
-        }
-
-        if (enqueue)
-            queueResource(resource);
-
-        return !enqueue;
-    }
-
-    /**
-     * Returns the number of total size in bytes of a resource, or
-     * -1 it the size is not known.
-     *
-     * @param location the resource location
-     * @return the number of bytes, or -1
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    public long getTotalSize(URL location) {
-        return getResource(location).size; // atomic
-    }
-
-    /**
-     * Start a new download thread if there are not too many threads
-     * already running.<p>
-     *
-     * Calls to this method should be synchronized on lock.
-     */
-    protected void startThread() {
-        if (threads < maxThreads) {
-            threads++;
-
-            Thread thread = new Thread(new Downloader());
-            thread.start();
-        }
-    }
-
-    /**
-     * A thread is ending, called by the thread itself.<p>
-     *
-     * Calls to this method should be synchronized.
-     */
-    private void endThread() {
-        threads--;
-
-        if (threads < 0) {
-            // this should never happen but try to recover
-            threads = 0;
-
-            if (queue.size() > 0) // if any on queue make sure a thread is running
-                startThread(); // look into whether this could create a loop
-
-            throw new RuntimeException("tracker threads < 0");
-        }
-
-        if (threads == 0) {
-            synchronized (prefetchTrackers) { 
-                queue.trimToSize(); // these only accessed by threads so no sync needed
-                active.clear(); // no threads so no trackers actively downloading
-                active.trimToSize();
-                prefetchTrackers.trimToSize();
-            }
-        }
-    }
-
-    /**
-     * Add a resource to the queue and start a thread to download or
-     * initialize it.
-     */
-    private void queueResource(Resource resource) {
-        synchronized (lock) {
-            if (!resource.isSet(CONNECT|DOWNLOAD))
-                throw new IllegalArgumentException("Invalid resource state (resource: "+resource+")");
-
-            queue.add(resource);
-            startThread();
-        }
-    }
-
-    /**
-     * Process the resource by either downloading it or initializing
-     * it.
-     */
-    private void processResource(Resource resource) {
-        boolean doConnect = false;
-        boolean doDownload = false;
-
-        synchronized (resource) {
-            if (resource.isSet(CONNECTING))
-                doConnect = true;
-        }
-        if (doConnect)
-            initializeResource(resource);
-
-        synchronized (resource) {
-            // return to queue if we just initalized but it still needs
-            // to download (not cached locally / out of date)
-            if (resource.isSet(DOWNLOAD)) // would be DOWNLOADING if connected before this method
-                queueResource(resource);
-
-            if (resource.isSet(DOWNLOADING))
-                doDownload = true;
-        }
-        if (doDownload)
-            downloadResource(resource);
-    }
-
-    /**
-     * Downloads a resource to a file, uncompressing it if required
-     * 
-     * @param resource the resource to download
-     */
-    private void downloadResource(Resource resource) {
-        resource.fireDownloadEvent(); // fire DOWNLOADING
-
-        try {
-            // create out second in case in does not exist
-            URLConnection con = getVersionedResourceURL(resource).openConnection();
-            con.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
-            
-            con.connect();
-
-            /*
-             * We dont really know what we are downloading. If we ask for
-             * foo.jar, the server might send us foo.jar.pack.gz or foo.jar.gz
-             * instead. So we save the file with the appropriate extension
-             */
-            URL downloadLocation = resource.location;
-
-            String contentEncoding = con.getContentEncoding();
-
-            if (JNLPRuntime.isDebug()) {
-                System.err.println("Content encoding for " + resource.location + ": "
-                        + contentEncoding);
-            }
-
-            if (contentEncoding != null) {
-                if (contentEncoding.equals("gzip")) {
-                    downloadLocation = new URL(downloadLocation.toString() + ".gz");
-                } else if (contentEncoding.equals("pack200-gzip")) {
-                    downloadLocation = new URL(downloadLocation.toString() + ".pack.gz");
-                }
-            }
-
-            InputStream in = new BufferedInputStream(con.getInputStream());
-            OutputStream out = CacheUtil.getOutputStream(downloadLocation, resource.downloadVersion);
-            byte buf[] = new byte[1024];
-            int rlen;
-
-            while (-1 != (rlen = in.read(buf))) {
-                resource.transferred += rlen;
-                out.write(buf, 0, rlen);
-            }
-
-            in.close();
-            out.close();
-
-            // explicitly close the URLConnection.
-            if (con instanceof HttpURLConnection)
-                ((HttpURLConnection)con).disconnect();
-            
-            /*
-             * If the file was compressed, uncompress it.
-             */
-            if (contentEncoding != null) {
-                if (contentEncoding.equals("gzip")) {
-                    GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil
-                            .getCacheFile(downloadLocation, resource.downloadVersion)));
-                    InputStream inputStream = new BufferedInputStream(gzInputStream);
-
-                    BufferedOutputStream outputStream = new BufferedOutputStream(
-                            new FileOutputStream(CacheUtil.getCacheFile(resource.location,
-                                    resource.downloadVersion)));
-
-                    while (-1 != (rlen = inputStream.read(buf))) {
-                        outputStream.write(buf, 0, rlen);
-                    }
-
-                    outputStream.close();
-                    inputStream.close();
-                    gzInputStream.close();
-                    
-                } else if (contentEncoding.equals("pack200-gzip")) {
-                    GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(
-                            CacheUtil.getCacheFile(downloadLocation, resource.downloadVersion)));
-                    InputStream inputStream = new BufferedInputStream(gzInputStream);
-
-                    JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(
-                            CacheUtil.getCacheFile(resource.location, resource.downloadVersion)));
-
-                    Unpacker unpacker = Pack200.newUnpacker();
-                    unpacker.unpack(inputStream, outputStream);
-
-                    outputStream.close();
-                    inputStream.close();
-                    gzInputStream.close();
-                }
-            }
-            
-            resource.changeStatus(DOWNLOADING, DOWNLOADED);
-            synchronized(lock) {
-                lock.notifyAll(); // wake up wait's to check for completion
-            }
-            resource.fireDownloadEvent(); // fire DOWNLOADED
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            resource.changeStatus(0, ERROR);
-            synchronized(lock) {
-                lock.notifyAll(); // wake up wait's to check for completion
-            }
-            resource.fireDownloadEvent(); // fire ERROR
-        }
-    }
-
-    /**
-     * Open a URL connection and get the content length and other
-     * fields.
-     */
-    private void initializeResource(Resource resource) {
-        resource.fireDownloadEvent(); // fire CONNECTING
-
-        try {
-            File localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion);
-
-            // connect
-            URLConnection connection = getVersionedResourceURL(resource).openConnection(); // this won't change so should be okay unsynchronized
-            connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
-
-            int size = connection.getContentLength();
-            boolean current = CacheUtil.isCurrent(resource.location, resource.requestVersion, connection) && resource.getUpdatePolicy() != UpdatePolicy.FORCE;
-
-            synchronized(resource) {
-                resource.localFile = localFile;
-                // resource.connection = connection;
-                resource.size = size;
-                resource.changeStatus(CONNECT|CONNECTING, CONNECTED);
-
-                // check if up-to-date; if so set as downloaded
-                if (current)
-                    resource.changeStatus(DOWNLOAD|DOWNLOADING, DOWNLOADED);
-            }
-
-            // update cache entry
-            CacheEntry entry = new CacheEntry(resource.location, resource.requestVersion);
-            if (!current)
-                entry.initialize(connection);
-
-            entry.setLastUpdated(System.currentTimeMillis());
-            entry.store();
-
-            synchronized(lock) {
-                lock.notifyAll(); // wake up wait's to check for completion
-            }
-            resource.fireDownloadEvent(); // fire CONNECTED
-
-            // explicitly close the URLConnection.
-			if (connection instanceof HttpURLConnection)
-                ((HttpURLConnection)connection).disconnect();
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            resource.changeStatus(0, ERROR);
-            synchronized(lock) {
-                lock.notifyAll(); // wake up wait's to check for completion
-            }
-            resource.fireDownloadEvent(); // fire ERROR
-        }
-    }
-
-    /**
-     * Returns the versioned url for a resource
-     * @param resource the resource to get the url for 
-     */
-    private URL getVersionedResourceURL(Resource resource) {
-        String actualLocation = resource.location.getProtocol() + "://"
-                + resource.location.getHost();
-        if (resource.location.getPort() != -1) {
-            actualLocation += ":" + resource.location.getPort();
-        }
-        actualLocation += resource.location.getPath();
-        if (resource.requestVersion != null
-                && resource.requestVersion.isVersionId()) {
-            actualLocation += "?version-id=" + resource.requestVersion;
-        }
-        URL versionedURL;
-        try {
-            versionedURL = new URL(actualLocation);
-        } catch (MalformedURLException e) {
-            return resource.location;
-        }
-        return versionedURL;
-    }
- 
-    
-    /**
-     * Pick the next resource to download or initialize.  If there
-     * are no more resources requested then one is taken from a
-     * resource tracker with prefetch enabled.<p>
-     *
-     * The resource state is advanced before it is returned
-     * (CONNECT-&gt;CONNECTING).<p>
-     *
-     * Calls to this method should be synchronized on lock.<p>
-     *
-     * @return the resource to initialize or download, or null
-     */
-    private static Resource selectNextResource() {
-        Resource result;
-
-        // pick from queue
-        result = selectByFlag(queue, CONNECT, ERROR); // connect but not error
-        if (result == null)
-            result = selectByFlag(queue, DOWNLOAD, ERROR|CONNECT|CONNECTING);
-
-        // remove from queue if found
-        if (result != null)
-            queue.remove(result);
-
-        // prefetch if nothing found so far and this is the last thread
-        if (result == null && threads == 1)
-            result = getPrefetch();
-
-        if (result == null)
-            return null;
-
-        synchronized (result) {
-            if (result.isSet(CONNECT)) {
-                result.changeStatus(CONNECT, CONNECTING);
-            }
-            else if (result.isSet(DOWNLOAD)) {
-                // only download if *not* connecting, when done connecting
-                // select next will pick up the download part.  This makes
-                // all requested connects happen before any downloads, so
-                // the size is known as early as possible.
-                result.changeStatus(DOWNLOAD, DOWNLOADING);
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Returns the next resource to be prefetched before
-     * requested.<p>
-     *
-     * Calls to this method should be synchronized on lock.<p>
-     */
-    private static Resource getPrefetch() {
-        Resource result = null;
-        Resource alternate = null;
-
-        // first find one to initialize 
-        synchronized (prefetchTrackers) {
-            for (int i=0; i < prefetchTrackers.size() && result == null; i++) {
-                ResourceTracker tracker = (ResourceTracker) prefetchTrackers.get(i);
-                if (tracker == null)
-                    continue;
-
-                synchronized (tracker.resources) {
-                    result = selectByFlag(tracker.resources, UNINITIALIZED, ERROR);
-
-                    if (result == null && alternate == null)
-                        alternate = selectByFlag(tracker.resources, CONNECTED, ERROR|DOWNLOADED|DOWNLOADING|DOWNLOAD);
-                }
-            }
-        }
-
-        // if none to initialize, switch to download
-        if (result == null)
-            result = alternate;
-
-        if (result == null)
-            return null;
-
-        synchronized (result) {
-            ResourceTracker tracker = result.getTracker();
-            if (tracker == null)
-                return null; // GC of tracker happened between above code and here
-
-            // prevents startResource from putting it on queue since
-            // we're going to return it.
-            result.changeStatus(0, STARTED);
-
-            tracker.startResource(result);
-        }
-
-        return result;
-    }
-
-    /**
-     * Selects a resource from the source list that has the
-     * specified flag set.<p>
-     *
-     * Calls to this method should be synchronized on lock and
-     * source list.<p>
-     */
-    private static Resource selectByFlag(List source, int flag, int notflag) {
-        Resource result = null;
-        int score = Integer.MAX_VALUE;
-
-        for (int i=0; i < source.size(); i++) {
-            Resource resource = (Resource) source.get(i);
-            boolean selectable = false;
-
-            synchronized (resource) {
-                if (resource.isSet(flag) && !resource.isSet(notflag))
-                    selectable = true;
-            }
-
-            if (selectable) {
-                int activeCount = 0;
-
-                for (int j=0; j < active.size(); j++)
-                    if ((ResourceTracker)active.get(j) == resource.getTracker())
-                        activeCount++;
-
-                // try to spread out the downloads so that a slow host
-                // won't monopolize the downloads
-                if (activeCount < score) {
-                    result = resource;
-                    score = activeCount;
-                }
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Return the resource matching the specified URL.
-     *
-     * @throws IllegalArgumentException if the resource is not being tracked
-     */
-    private Resource getResource(URL location) {
-        synchronized (resources) {
-            for (int i=0; i < resources.size(); i++) {
-                Resource resource = (Resource) resources.get(i);
-
-                if (CacheUtil.urlEquals(resource.location, location))
-                    return resource;
-            }
-        }
-
-        throw new IllegalArgumentException("Location does not specify a resource being tracked.");
-    }
-
-    /**
-     * Wait for some resources.
-     *
-     * @param resources the resources to wait for
-     * @param timeout the timeout, or 0 to wait until completed
-     * @returns true if the resources were downloaded or had errors,
-     * false if the timeout was reached
-     * @throws InterruptedException if another thread interrupted the wait
-     */
-    private boolean wait(Resource resources[], long timeout) throws InterruptedException {
-        long startTime = System.currentTimeMillis();
-
-        // start them downloading / connecting in background
-        for (int i=0; i < resources.length; i++)
-            startResource(resources[i]);
-
-        // wait for completion
-        while (true) {
-            boolean finished = true;
-
-            synchronized (lock) {
-                // check for completion 
-                for (int i=0; i < resources.length; i++) {
-                	//NetX Deadlocking may be solved by removing this 
-                	//synch block.
-                    synchronized (resources[i]) {
-                        if (!resources[i].isSet(DOWNLOADED | ERROR)) {
-                            finished = false;
-                            break;
-                        }
-                    }
-                }
-                if (finished)
-                    return true;
-
-                // wait
-                long waitTime = 0;
-
-                if (timeout > 0) {
-                    waitTime = timeout - (System.currentTimeMillis()-startTime);
-                    if (waitTime <= 0)
-                        return false;
-                }
-
-                lock.wait(waitTime);
-            }
-        }
-    }
-
-
-    // inner classes
-
-    /** 
-     * This class downloads and initializes the queued resources.
-     */
-    class Downloader implements Runnable {
-        Resource resource = null;
-
-        public void run() {
-            while (true) {
-                synchronized (lock) {
-                    // remove from active list, used for load balancing
-                    if (resource != null)
-                        active.remove(resource.getTracker());
-
-                    resource = selectNextResource();
-
-                    if (resource == null) {
-                        endThread();
-                        break;
-                    }
-
-                    // add to active list, used for load balancing
-                    active.add(resource.getTracker());
-                }
-
-                try {
-                    processResource(resource);
-                }
-                catch (Exception ex) {
-                    if (JNLPRuntime.isDebug())
-                        ex.printStackTrace();
-                }
-            }
-            // should have a finally in case some exception is thrown by
-            // selectNextResource();
-        }
-    };
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.cache;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+import java.util.jar.Pack200.Unpacker;
+import java.util.zip.GZIPInputStream;
+
+import net.sourceforge.jnlp.Version;
+import net.sourceforge.jnlp.event.DownloadEvent;
+import net.sourceforge.jnlp.event.DownloadListener;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.util.WeakList;
+
+/**
+ * This class tracks the downloading of various resources of a
+ * JNLP file to local files in the cache.  It can be used to
+ * download icons, jnlp and extension files, jars, and jardiff
+ * files using the version based protocol or any file using the
+ * basic download protocol (jardiff and version not implemented
+ * yet).<p>
+ *
+ * The resource tracker can be configured to prefetch resources,
+ * which are downloaded in the order added to the media
+ * tracker.<p>
+ *
+ * Multiple threads are used to download and cache resources that
+ * are actively being waited for (blocking a caller) or those that
+ * have been started downloading by calling the startDownload
+ * method.  Resources that are prefetched are downloaded one at a
+ * time and only if no other trackers have requested downloads.
+ * This allows the tracker to start downloading many items without
+ * using many system resources, but still quickly download items
+ * as needed.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.22 $ 
+ */
+public class ResourceTracker {
+
+    // todo: use event listener arrays instead of lists
+
+    // todo: see if there is a way to set the socket options just
+    // for use by the tracker so checks for updates don't hang for
+    // a long time.
+
+    // todo: ability to restart/retry a hung download
+
+    // todo: move resource downloading/processing code into Resource
+    // class, threading stays in ResourceTracker
+
+    // todo: get status method? and some way to convey error status
+    // to the caller.
+
+    // todo: might make a tracker be able to download more than one
+    // version of a resource, but probably not very useful.
+
+
+    // defines 
+    //    ResourceTracker.Downloader (download threads)
+
+    // separately locks on (in order of aquire order, ie, sync on prefetch never syncs on lock):
+    //   lock, prefetch, this.resources, each resource, listeners
+
+    /** notified on initialization or download of a resource */
+    private static Object lock = new Integer(0); // used to lock static structures
+
+    // shortcuts
+    private static final int UNINITIALIZED = Resource.UNINITIALIZED;
+    private static final int CONNECT = Resource.CONNECT;
+    private static final int CONNECTING = Resource.CONNECTING;
+    private static final int CONNECTED = Resource.CONNECTED;
+    private static final int DOWNLOAD = Resource.DOWNLOAD;
+    private static final int DOWNLOADING = Resource.DOWNLOADING;
+    private static final int DOWNLOADED = Resource.DOWNLOADED;
+    private static final int ERROR = Resource.ERROR;
+    private static final int STARTED = Resource.STARTED;
+
+    /** max threads */
+    private static final int maxThreads = 5;
+
+    /** running threads */
+    private static int threads = 0;
+
+    /** weak list of resource trackers with resources to prefetch */
+    private static WeakList prefetchTrackers = new WeakList();
+
+    /** resources requested to be downloaded */
+    private static ArrayList queue = new ArrayList();
+
+    /** resource trackers threads are working for (used for load balancing across multi-tracker downloads) */
+    private static ArrayList active = new ArrayList(); // 
+
+    /** the resources known about by this resource tracker */
+    private List resources = new ArrayList();
+
+    /** download listeners for this tracker */
+    private List listeners = new ArrayList();
+
+    /** whether to download parts before requested */ 
+    private boolean prefetch;
+
+
+    /**
+     * Creates a resource tracker that does not prefetch resources.
+     */
+    public ResourceTracker() {
+        this(false);
+    }
+
+    /**
+     * Creates a resource tracker.
+     *
+     * @param prefetch whether to download resources before requested. 
+     */
+    public ResourceTracker(boolean prefetch) {
+        this.prefetch = prefetch;
+
+        if (prefetch) {
+            synchronized (prefetchTrackers) {
+                prefetchTrackers.add(this);
+                prefetchTrackers.trimToSize();
+            }
+        }
+    }
+
+    /**
+     * Add a resource identified by the specified location and
+     * version.  The tracker only downloads one version of a given
+     * resource per instance (ie cannot download both versions 1 and
+     * 2 of a resource in the same tracker).
+     *
+     * @param location the location of the resource
+     * @param version the resource version
+     * @param updatePolicy whether to check for updates if already in cache
+     */
+    public void addResource(URL location, Version version, UpdatePolicy updatePolicy) {
+        if (location == null)
+            throw new IllegalArgumentException("location==null");
+
+        Resource resource = Resource.getResource(location, version, updatePolicy);
+        boolean downloaded = false;
+
+        synchronized (resources) {
+            if (resources.contains(resource))
+                return;
+            resource.addTracker(this);
+            resources.add(resource);
+        }
+
+        // checkCache may take a while (loads properties file).  this
+        // should really be synchronized on resources, but the worst
+        // case should be that the resource will be updated once even
+        // if unnecessary.
+        downloaded = checkCache(resource, updatePolicy);
+
+        synchronized (lock) {
+            if (!downloaded)
+                if (prefetch && threads == 0) // existing threads do pre-fetch when queue empty
+                    startThread();
+        }
+    }
+
+    /**
+     * Removes a resource from the tracker.  This method is useful
+     * to allow memory to be reclaimed, but calling this method is
+     * not required as resources are reclaimed when the tracker is
+     * collected.
+     *
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public void removeResource(URL location) {
+        synchronized (resources) {
+            Resource resource = getResource(location);
+
+            if (resource != null) {
+                resources.remove(resource);
+                resource.removeTracker(this);
+            }
+
+            // should remove from queue? probably doesn't matter
+        }
+    }
+
+    /**
+     * Check the cache for a resource, and initialize the resource
+     * as already downloaded if found. <p>
+     *
+     * @param updatePolicy whether to check for updates if already in cache
+     * @return whether the resource are already downloaded 
+     */
+    private boolean checkCache(Resource resource, UpdatePolicy updatePolicy) {
+        if (!CacheUtil.isCacheable(resource.location, resource.downloadVersion)) {
+            // pretend that they are already downloaded; essentially
+            // they will just 'pass through' the tracker as if they were
+            // never added (for example, not affecting the total download size).
+            synchronized (resource) {
+                resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED);
+            }
+            fireDownloadEvent(resource);
+            return true;
+        }
+
+        if (updatePolicy != UpdatePolicy.ALWAYS && updatePolicy != UpdatePolicy.FORCE) { // save loading entry props file
+            CacheEntry entry = new CacheEntry(resource.location, resource.downloadVersion);
+
+            if (entry.isCached() && !updatePolicy.shouldUpdate(entry)) {
+                if (JNLPRuntime.isDebug())
+                    System.out.println("not updating: "+resource.location);
+
+                synchronized (resource) {
+                    resource.localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion);
+                    resource.size = resource.localFile.length();
+                    resource.transferred = resource.localFile.length();
+                    resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED);
+                }
+                fireDownloadEvent(resource);
+                return true;
+            }
+        }
+        
+        if (updatePolicy == UpdatePolicy.FORCE) { // ALWAYS update
+            // When we are "always" updating, we update for each instance. Reset resource status.
+            resource.changeStatus(Integer.MAX_VALUE, 0);
+        }
+
+        // may or may not be cached, but check update when connection
+        // is open to possibly save network communication time if it
+        // has to be downloaded, and allow this call to return quickly
+        return false;
+    }
+
+    /**
+     * Adds the listener to the list of objects interested in
+     * receivind DownloadEvents.<p>
+     *
+     * @param location the resource to add a callback for
+     * @param runnable the runnable to call when resource is completed
+     */
+    public void addDownloadListener(DownloadListener listener) {
+        synchronized (listeners) {
+            if (!listeners.contains(listener))
+                listeners.add(listener);
+        }
+    }
+
+    /**
+     * Removes a download listener.
+     */
+    public void removeDownloadListener(DownloadListener listener) {
+        synchronized (listeners) {
+            listeners.remove(listener);
+        }
+    }
+
+    /**
+     * Fires the download event corresponding to the resource's
+     * state.  This method is typicall called by the Resource itself
+     * on each tracker that is monitoring the resource.  Do not call
+     * this method with any locks because the listeners may call
+     * back to this ResourceTracker.
+     */
+    protected void fireDownloadEvent(Resource resource) {
+        DownloadListener l[] = null;
+        synchronized (listeners) {
+            l = (DownloadListener[]) listeners.toArray(new DownloadListener[0]);
+        }
+
+        int status;
+        synchronized (resource) {
+            status = resource.status;
+        }
+
+        DownloadEvent event = new DownloadEvent(this, resource);
+        for (int i=0; i < l.length; i++) {
+            if (0 != ((ERROR|DOWNLOADED) & status))
+                l[i].downloadCompleted(event);
+            else if (0 != (DOWNLOADING & status))
+                l[i].downloadStarted(event);
+            else if (0 != (CONNECTING & status))
+                l[i].updateStarted(event);
+        }
+    }
+
+    /**
+     * Returns a URL pointing to the cached location of the
+     * resource, or the resource itself if it is a non-cacheable
+     * resource.<p>
+     *
+     * If the resource has not downloaded yet, the method will block
+     * until it has been transferred to the cache.<p>
+     *
+     * @param location the resource location
+     * @return the resource, or null if it could not be downloaded
+     * @throws IllegalArgumentException if the resource is not being tracked
+     * @see CacheUtil#isCacheable
+     */
+    public URL getCacheURL(URL location) {
+        try {
+            File f = getCacheFile(location);
+            if (f != null)
+                return f.toURL();
+        }
+        catch (MalformedURLException ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+        }
+
+        return location;
+    }
+
+    /**
+     * Returns a file containing the downloaded resource.  If the
+     * resource is non-cacheable then null is returned unless the
+     * resource is a local file (the original file is returned).<p>
+     *
+     * If the resource has not downloaded yet, the method will block
+     * until it has been transferred to the cache.<p>
+     *
+     * @param location the resource location
+     * @return a local file containing the resource, or null
+     * @throws IllegalArgumentException if the resource is not being tracked
+     * @see CacheUtil#isCacheable
+     */
+    public File getCacheFile(URL location) {
+        try {
+            Resource resource = getResource(location);
+            if (!resource.isSet(DOWNLOADED|ERROR))
+                waitForResource(location, 0);
+
+            if (resource.isSet(ERROR))
+                return null;
+
+            if (resource.localFile != null)
+                return resource.localFile;
+
+            if (location.getProtocol().equalsIgnoreCase("file")) {
+                File file = new File(location.getFile());
+                if (file.exists())
+                    return file;
+            }
+
+            return null;
+        }
+        catch (InterruptedException ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            return null; // need an error exception to throw
+        }
+    }
+
+    /**
+     * Returns an input stream that reads the contents of the
+     * resource.  For non-cacheable resources, an InputStream that
+     * reads from the source location is returned.  Otherwise the
+     * InputStream reads the cached resource.<p>
+     *
+     * This method will block while the resource is downloaded to
+     * the cache.
+     *
+     * @throws IOException if there was an error opening the stream
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public InputStream getInputStream(URL location) throws IOException {
+        try {
+            Resource resource = getResource(location);
+            if (!resource.isSet(DOWNLOADED|ERROR))
+                waitForResource(location, 0);
+
+            if (resource.localFile != null)
+                return new FileInputStream(resource.localFile);
+
+            return resource.location.openStream();
+        }
+        catch (InterruptedException ex) {
+            throw new IOException("wait was interrupted");
+        }
+    }
+
+    /**
+     * Wait for a group of resources to be downloaded and made
+     * available locally.
+     *
+     * @param urls the resources to wait for
+     * @param timeout the time in ms to wait before returning, 0 for no timeout
+     * @return whether the resources downloaded before the timeout
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public boolean waitForResources(URL urls[], long timeout) throws InterruptedException {
+        Resource resources[] = new Resource[ urls.length ];
+
+        synchronized(resources) { 
+            // keep the lock so getResource doesn't have to aquire it each time
+            for (int i=0; i < urls.length; i++)
+                resources[i] = getResource(urls[i]);
+        }
+
+        if (resources.length > 0)
+            return wait(resources, timeout);
+
+        return true;
+    }
+
+    /**
+     * Wait for a particular resource to be downloaded and made
+     * available.
+     *
+     * @param location the resource to wait for
+     * @param timeout the timeout, or 0 to wait until completed
+     * @return whether the resource downloaded before the timeout
+     * @throws InterruptedException if another thread interrupted the wait
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public boolean waitForResource(URL location, long timeout) throws InterruptedException {
+        return wait(new Resource[] { getResource(location) }, timeout);
+    }
+
+    /**
+     * Returns the number of bytes downloaded for a resource.
+     *
+     * @param location the resource location
+     * @return the number of bytes transferred
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public long getAmountRead(URL location) {
+        // not atomic b/c transferred is a long, but so what (each
+        // byte atomic? so probably won't affect anything...)
+        return getResource(location).transferred;
+    }
+
+    /**
+     * Returns whether a resource is available for use (ie, can be
+     * accessed with the getCacheFile method).
+     *
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public boolean checkResource(URL location) {
+        return getResource(location).isSet(DOWNLOADED|ERROR); // isSet atomic
+    }
+
+    /**
+     * Starts loading the resource if it is not already being
+     * downloaded or already cached.  Resources started downloading
+     * using this method may download faster than those prefetched
+     * by the tracker because the tracker will only prefetch one
+     * resource at a time to conserve system resources.
+     *
+     * @return true if the resource is already downloaded (or an error occurred) 
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public boolean startResource(URL location) {
+        Resource resource = getResource(location);
+
+        return startResource(resource);
+    }
+
+    /**
+     * Sets the resource status to connect and download, and
+     * enqueues the resource if not already started.
+     *
+     * @return true if the resource is already downloaded (or an error occurred) 
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    private boolean startResource(Resource resource) {
+        boolean enqueue = false;
+
+        synchronized (resource) {
+            if (resource.isSet(ERROR))
+                return true;
+
+            enqueue = !resource.isSet(STARTED);
+
+            if (!resource.isSet(CONNECTED | CONNECTING))
+                resource.changeStatus(0, CONNECT|STARTED);
+            if (!resource.isSet(DOWNLOADED | DOWNLOADING))
+                resource.changeStatus(0, DOWNLOAD|STARTED);
+
+            if (!resource.isSet(DOWNLOAD|CONNECT))
+                enqueue = false;
+        }
+
+        if (enqueue)
+            queueResource(resource);
+
+        return !enqueue;
+    }
+
+    /**
+     * Returns the number of total size in bytes of a resource, or
+     * -1 it the size is not known.
+     *
+     * @param location the resource location
+     * @return the number of bytes, or -1
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    public long getTotalSize(URL location) {
+        return getResource(location).size; // atomic
+    }
+
+    /**
+     * Start a new download thread if there are not too many threads
+     * already running.<p>
+     *
+     * Calls to this method should be synchronized on lock.
+     */
+    protected void startThread() {
+        if (threads < maxThreads) {
+            threads++;
+
+            Thread thread = new Thread(new Downloader());
+            thread.start();
+        }
+    }
+
+    /**
+     * A thread is ending, called by the thread itself.<p>
+     *
+     * Calls to this method should be synchronized.
+     */
+    private void endThread() {
+        threads--;
+
+        if (threads < 0) {
+            // this should never happen but try to recover
+            threads = 0;
+
+            if (queue.size() > 0) // if any on queue make sure a thread is running
+                startThread(); // look into whether this could create a loop
+
+            throw new RuntimeException("tracker threads < 0");
+        }
+
+        if (threads == 0) {
+            synchronized (prefetchTrackers) { 
+                queue.trimToSize(); // these only accessed by threads so no sync needed
+                active.clear(); // no threads so no trackers actively downloading
+                active.trimToSize();
+                prefetchTrackers.trimToSize();
+            }
+        }
+    }
+
+    /**
+     * Add a resource to the queue and start a thread to download or
+     * initialize it.
+     */
+    private void queueResource(Resource resource) {
+        synchronized (lock) {
+            if (!resource.isSet(CONNECT|DOWNLOAD))
+                throw new IllegalArgumentException("Invalid resource state (resource: "+resource+")");
+
+            queue.add(resource);
+            startThread();
+        }
+    }
+
+    /**
+     * Process the resource by either downloading it or initializing
+     * it.
+     */
+    private void processResource(Resource resource) {
+        boolean doConnect = false;
+        boolean doDownload = false;
+
+        synchronized (resource) {
+            if (resource.isSet(CONNECTING))
+                doConnect = true;
+        }
+        if (doConnect)
+            initializeResource(resource);
+
+        synchronized (resource) {
+            // return to queue if we just initalized but it still needs
+            // to download (not cached locally / out of date)
+            if (resource.isSet(DOWNLOAD)) // would be DOWNLOADING if connected before this method
+                queueResource(resource);
+
+            if (resource.isSet(DOWNLOADING))
+                doDownload = true;
+        }
+        if (doDownload)
+            downloadResource(resource);
+    }
+
+    /**
+     * Downloads a resource to a file, uncompressing it if required
+     * 
+     * @param resource the resource to download
+     */
+    private void downloadResource(Resource resource) {
+        resource.fireDownloadEvent(); // fire DOWNLOADING
+
+        try {
+            // create out second in case in does not exist
+            URLConnection con = getVersionedResourceURL(resource).openConnection();
+            con.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
+            
+            con.connect();
+
+            /*
+             * We dont really know what we are downloading. If we ask for
+             * foo.jar, the server might send us foo.jar.pack.gz or foo.jar.gz
+             * instead. So we save the file with the appropriate extension
+             */
+            URL downloadLocation = resource.location;
+
+            String contentEncoding = con.getContentEncoding();
+
+            if (JNLPRuntime.isDebug()) {
+                System.err.println("Content encoding for " + resource.location + ": "
+                        + contentEncoding);
+            }
+
+            if (contentEncoding != null) {
+                if (contentEncoding.equals("gzip")) {
+                    downloadLocation = new URL(downloadLocation.toString() + ".gz");
+                } else if (contentEncoding.equals("pack200-gzip")) {
+                    downloadLocation = new URL(downloadLocation.toString() + ".pack.gz");
+                }
+            }
+
+            InputStream in = new BufferedInputStream(con.getInputStream());
+            OutputStream out = CacheUtil.getOutputStream(downloadLocation, resource.downloadVersion);
+            byte buf[] = new byte[1024];
+            int rlen;
+
+            while (-1 != (rlen = in.read(buf))) {
+                resource.transferred += rlen;
+                out.write(buf, 0, rlen);
+            }
+
+            in.close();
+            out.close();
+
+            // explicitly close the URLConnection.
+            if (con instanceof HttpURLConnection)
+                ((HttpURLConnection)con).disconnect();
+            
+            /*
+             * If the file was compressed, uncompress it.
+             */
+            if (contentEncoding != null) {
+                if (contentEncoding.equals("gzip")) {
+                    GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil
+                            .getCacheFile(downloadLocation, resource.downloadVersion)));
+                    InputStream inputStream = new BufferedInputStream(gzInputStream);
+
+                    BufferedOutputStream outputStream = new BufferedOutputStream(
+                            new FileOutputStream(CacheUtil.getCacheFile(resource.location,
+                                    resource.downloadVersion)));
+
+                    while (-1 != (rlen = inputStream.read(buf))) {
+                        outputStream.write(buf, 0, rlen);
+                    }
+
+                    outputStream.close();
+                    inputStream.close();
+                    gzInputStream.close();
+                    
+                } else if (contentEncoding.equals("pack200-gzip")) {
+                    GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(
+                            CacheUtil.getCacheFile(downloadLocation, resource.downloadVersion)));
+                    InputStream inputStream = new BufferedInputStream(gzInputStream);
+
+                    JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(
+                            CacheUtil.getCacheFile(resource.location, resource.downloadVersion)));
+
+                    Unpacker unpacker = Pack200.newUnpacker();
+                    unpacker.unpack(inputStream, outputStream);
+
+                    outputStream.close();
+                    inputStream.close();
+                    gzInputStream.close();
+                }
+            }
+            
+            resource.changeStatus(DOWNLOADING, DOWNLOADED);
+            synchronized(lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire DOWNLOADED
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            resource.changeStatus(0, ERROR);
+            synchronized(lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire ERROR
+        }
+    }
+
+    /**
+     * Open a URL connection and get the content length and other
+     * fields.
+     */
+    private void initializeResource(Resource resource) {
+        resource.fireDownloadEvent(); // fire CONNECTING
+
+        try {
+            File localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion);
+
+            // connect
+            URLConnection connection = getVersionedResourceURL(resource).openConnection(); // this won't change so should be okay unsynchronized
+            connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
+
+            int size = connection.getContentLength();
+            boolean current = CacheUtil.isCurrent(resource.location, resource.requestVersion, connection) && resource.getUpdatePolicy() != UpdatePolicy.FORCE;
+
+            synchronized(resource) {
+                resource.localFile = localFile;
+                // resource.connection = connection;
+                resource.size = size;
+                resource.changeStatus(CONNECT|CONNECTING, CONNECTED);
+
+                // check if up-to-date; if so set as downloaded
+                if (current)
+                    resource.changeStatus(DOWNLOAD|DOWNLOADING, DOWNLOADED);
+            }
+
+            // update cache entry
+            CacheEntry entry = new CacheEntry(resource.location, resource.requestVersion);
+            if (!current)
+                entry.initialize(connection);
+
+            entry.setLastUpdated(System.currentTimeMillis());
+            entry.store();
+
+            synchronized(lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire CONNECTED
+
+            // explicitly close the URLConnection.
+			if (connection instanceof HttpURLConnection)
+                ((HttpURLConnection)connection).disconnect();
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            resource.changeStatus(0, ERROR);
+            synchronized(lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire ERROR
+        }
+    }
+
+    /**
+     * Returns the versioned url for a resource
+     * @param resource the resource to get the url for 
+     */
+    private URL getVersionedResourceURL(Resource resource) {
+        String actualLocation = resource.location.getProtocol() + "://"
+                + resource.location.getHost();
+        if (resource.location.getPort() != -1) {
+            actualLocation += ":" + resource.location.getPort();
+        }
+        actualLocation += resource.location.getPath();
+        if (resource.requestVersion != null
+                && resource.requestVersion.isVersionId()) {
+            actualLocation += "?version-id=" + resource.requestVersion;
+        }
+        URL versionedURL;
+        try {
+            versionedURL = new URL(actualLocation);
+        } catch (MalformedURLException e) {
+            return resource.location;
+        }
+        return versionedURL;
+    }
+ 
+    
+    /**
+     * Pick the next resource to download or initialize.  If there
+     * are no more resources requested then one is taken from a
+     * resource tracker with prefetch enabled.<p>
+     *
+     * The resource state is advanced before it is returned
+     * (CONNECT-&gt;CONNECTING).<p>
+     *
+     * Calls to this method should be synchronized on lock.<p>
+     *
+     * @return the resource to initialize or download, or null
+     */
+    private static Resource selectNextResource() {
+        Resource result;
+
+        // pick from queue
+        result = selectByFlag(queue, CONNECT, ERROR); // connect but not error
+        if (result == null)
+            result = selectByFlag(queue, DOWNLOAD, ERROR|CONNECT|CONNECTING);
+
+        // remove from queue if found
+        if (result != null)
+            queue.remove(result);
+
+        // prefetch if nothing found so far and this is the last thread
+        if (result == null && threads == 1)
+            result = getPrefetch();
+
+        if (result == null)
+            return null;
+
+        synchronized (result) {
+            if (result.isSet(CONNECT)) {
+                result.changeStatus(CONNECT, CONNECTING);
+            }
+            else if (result.isSet(DOWNLOAD)) {
+                // only download if *not* connecting, when done connecting
+                // select next will pick up the download part.  This makes
+                // all requested connects happen before any downloads, so
+                // the size is known as early as possible.
+                result.changeStatus(DOWNLOAD, DOWNLOADING);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the next resource to be prefetched before
+     * requested.<p>
+     *
+     * Calls to this method should be synchronized on lock.<p>
+     */
+    private static Resource getPrefetch() {
+        Resource result = null;
+        Resource alternate = null;
+
+        // first find one to initialize 
+        synchronized (prefetchTrackers) {
+            for (int i=0; i < prefetchTrackers.size() && result == null; i++) {
+                ResourceTracker tracker = (ResourceTracker) prefetchTrackers.get(i);
+                if (tracker == null)
+                    continue;
+
+                synchronized (tracker.resources) {
+                    result = selectByFlag(tracker.resources, UNINITIALIZED, ERROR);
+
+                    if (result == null && alternate == null)
+                        alternate = selectByFlag(tracker.resources, CONNECTED, ERROR|DOWNLOADED|DOWNLOADING|DOWNLOAD);
+                }
+            }
+        }
+
+        // if none to initialize, switch to download
+        if (result == null)
+            result = alternate;
+
+        if (result == null)
+            return null;
+
+        synchronized (result) {
+            ResourceTracker tracker = result.getTracker();
+            if (tracker == null)
+                return null; // GC of tracker happened between above code and here
+
+            // prevents startResource from putting it on queue since
+            // we're going to return it.
+            result.changeStatus(0, STARTED);
+
+            tracker.startResource(result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Selects a resource from the source list that has the
+     * specified flag set.<p>
+     *
+     * Calls to this method should be synchronized on lock and
+     * source list.<p>
+     */
+    private static Resource selectByFlag(List source, int flag, int notflag) {
+        Resource result = null;
+        int score = Integer.MAX_VALUE;
+
+        for (int i=0; i < source.size(); i++) {
+            Resource resource = (Resource) source.get(i);
+            boolean selectable = false;
+
+            synchronized (resource) {
+                if (resource.isSet(flag) && !resource.isSet(notflag))
+                    selectable = true;
+            }
+
+            if (selectable) {
+                int activeCount = 0;
+
+                for (int j=0; j < active.size(); j++)
+                    if ((ResourceTracker)active.get(j) == resource.getTracker())
+                        activeCount++;
+
+                // try to spread out the downloads so that a slow host
+                // won't monopolize the downloads
+                if (activeCount < score) {
+                    result = resource;
+                    score = activeCount;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Return the resource matching the specified URL.
+     *
+     * @throws IllegalArgumentException if the resource is not being tracked
+     */
+    private Resource getResource(URL location) {
+        synchronized (resources) {
+            for (int i=0; i < resources.size(); i++) {
+                Resource resource = (Resource) resources.get(i);
+
+                if (CacheUtil.urlEquals(resource.location, location))
+                    return resource;
+            }
+        }
+
+        throw new IllegalArgumentException("Location does not specify a resource being tracked.");
+    }
+
+    /**
+     * Wait for some resources.
+     *
+     * @param resources the resources to wait for
+     * @param timeout the timeout, or 0 to wait until completed
+     * @returns true if the resources were downloaded or had errors,
+     * false if the timeout was reached
+     * @throws InterruptedException if another thread interrupted the wait
+     */
+    private boolean wait(Resource resources[], long timeout) throws InterruptedException {
+        long startTime = System.currentTimeMillis();
+
+        // start them downloading / connecting in background
+        for (int i=0; i < resources.length; i++)
+            startResource(resources[i]);
+
+        // wait for completion
+        while (true) {
+            boolean finished = true;
+
+            synchronized (lock) {
+                // check for completion 
+                for (int i=0; i < resources.length; i++) {
+                	//NetX Deadlocking may be solved by removing this 
+                	//synch block.
+                    synchronized (resources[i]) {
+                        if (!resources[i].isSet(DOWNLOADED | ERROR)) {
+                            finished = false;
+                            break;
+                        }
+                    }
+                }
+                if (finished)
+                    return true;
+
+                // wait
+                long waitTime = 0;
+
+                if (timeout > 0) {
+                    waitTime = timeout - (System.currentTimeMillis()-startTime);
+                    if (waitTime <= 0)
+                        return false;
+                }
+
+                lock.wait(waitTime);
+            }
+        }
+    }
+
+
+    // inner classes
+
+    /** 
+     * This class downloads and initializes the queued resources.
+     */
+    class Downloader implements Runnable {
+        Resource resource = null;
+
+        public void run() {
+            while (true) {
+                synchronized (lock) {
+                    // remove from active list, used for load balancing
+                    if (resource != null)
+                        active.remove(resource.getTracker());
+
+                    resource = selectNextResource();
+
+                    if (resource == null) {
+                        endThread();
+                        break;
+                    }
+
+                    // add to active list, used for load balancing
+                    active.add(resource.getTracker());
+                }
+
+                try {
+                    processResource(resource);
+                }
+                catch (Exception ex) {
+                    if (JNLPRuntime.isDebug())
+                        ex.printStackTrace();
+                }
+            }
+            // should have a finally in case some exception is thrown by
+            // selectNextResource();
+        }
+    };
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,354 +1,354 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.applet.*;
-import java.awt.*;
-import java.awt.event.*;
-import java.util.*;
-import java.util.List;
-import java.lang.reflect.InvocationTargetException;
-import java.net.*;
-import java.io.*;
-import javax.swing.*;
-
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.util.*;
-
-/**
- * The applet environment including stub, context, and frame.  The
- * default environment puts the applet in a non-resiable frame;
- * this can be changed by obtaining the frame and setting it
- * resizable.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.12 $ 
- */
-public class AppletEnvironment implements AppletContext, AppletStub {
-
-    /** the JNLP file */
-    private JNLPFile file;
-
-    /** the applet instance */
-    private AppletInstance appletInstance;
-
-    /** the applet */
-    private Applet applet;
-
-    /** the parameters */
-    private Map parameters;
-
-    /** the applet container */
-    private Container cont;
-
-    /** weak references to the audio clips */
-    private WeakList weakClips = new WeakList();
-
-    /** whether the applet has been started / displayed */
-    private boolean appletStarted = false;
-
-    /** whether the applet has been destroyed */
-    private boolean destroyed = false;
-
-
-    /**
-     * Create a new applet environment for the applet specified by
-     * the JNLP file.
-     */
-    public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance, Container cont) {
-        this.file = file;
-        this.appletInstance = appletInstance;
-        this.applet = appletInstance.getApplet();
-
-        parameters = file.getApplet().getParameters();
-        this.cont = cont;
-    }
-
-    /**
-     * Create a new applet environment for the applet specified by
-     * the JNLP file, in a new frame.
-     */
-    public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance) {
-        this(file, appletInstance, null);
-
-        Frame frame = new Frame(file.getApplet().getName() + " - Applet");
-        frame.setResizable(false);
-
-        appletInstance.addWindow(frame);
-        // may not need this once security manager can close windows
-        // that do not have app code on the stack
-        WindowListener closer = new WindowAdapter() {
-            public void windowClosing(WindowEvent event) {
-                appletInstance.destroy();
-                System.exit(0);
-            }
-        };
-        frame.addWindowListener(closer);
-        this.cont = frame;
-    }
-
-    /**
-     * Checks whether the applet has been destroyed, and throws an
-     * IllegalStateException if the applet has been destroyed of.
-     *
-     * @throws IllegalStateException
-     */
-    private void checkDestroyed() {
-        if (destroyed)
-            throw new IllegalStateException("Illegal applet stub/context access: applet destroyed.");
-    }
-
-    /**
-     * Disposes the applet's resources and disables the applet
-     * environment from further use; after calling this method the
-     * applet stub and context methods throw IllegalStateExceptions.
-     */
-    public void destroy() {
-        destroyed = true;
-
-        List clips = weakClips.hardList();
-        for (int i = 0; i < clips.size(); i++) {
-            ((AppletAudioClip)clips.get(i)).dispose();
-        }
-    }
-
-    /**
-     * Returns the frame that contains the applet.  Disposing this
-     * frame will destroy the applet.
-     */
-    public Container getAppletFrame() {
-        // TODO: rename this method to getAppletContainer ?
-        return cont;
-    }
-
-    /**
-     * Initialize, start, and show the applet.
-     */
-    public void startApplet() {
-        checkDestroyed();
-
-        if (appletStarted)
-            return;
-
-        appletStarted = true;
-
-        try {
-            AppletDesc appletDesc = file.getApplet();
-
-            if (cont instanceof AppletStub)
-                applet.setStub((AppletStub)cont);
-            else
-                applet.setStub(this);
-
-            cont.setLayout(new BorderLayout());
-            cont.add("Center", applet);
-            cont.validate();
-
-            // This is only needed if the applet is in its own frame.
-            if (cont instanceof Frame) {
-                Frame frame = (Frame) cont;
-                frame.pack(); // cause insets to be calculated
-
-                Insets insets = frame.getInsets();
-                frame.setSize(appletDesc.getWidth() + insets.left + insets.right,
-                              appletDesc.getHeight() + insets.top + insets.bottom);
-            }
-    
-            try {
-            	SwingUtilities.invokeAndWait(new Runnable() {
-            		public void run() {
-            			// do first because some applets need to be displayed before
-            			// starting (they use Component.getImage or something)
-            			cont.setVisible(true);
-
-            			applet.init();
-            			applet.start();
-
-            			cont.invalidate(); // this should force the applet to
-            			cont.validate();   // the correct size and to repaint
-            			cont.repaint();
-            		}
-            	});
-            } catch (InterruptedException ie) {
-
-            } catch (InvocationTargetException ite) {
-
-            }
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            // should also kill the applet?
-        }
-    }
-
-    // applet context methods 
-
-    /**
-     * Returns the applet if the applet's name is specified,
-     * otherwise return null.
-     */
-    public Applet getApplet(String name) {
-        checkDestroyed();
-
-        if (name != null && name.equals(file.getApplet().getName()))
-            return applet;
-        else
-            return null;
-    }
-
-    /**
-     * Returns an enumeration that contains only the applet
-     * from the JNLP file.
-     */
-    public Enumeration getApplets() {
-        checkDestroyed();
-
-        return Collections.enumeration( Arrays.asList(new Applet[] { applet }) );
-    }
-
-    /**
-     * Returns an audio clip.
-     */
-    public AudioClip getAudioClip(URL location) {
-        checkDestroyed();
-
-        AppletAudioClip clip = new AppletAudioClip(location);
-
-        weakClips.add(clip);
-        weakClips.trimToSize();
-
-        return clip;
-    }
-
-    /**
-     * Return an image loaded from the specified location.
-     */
-    public Image getImage(URL location) {
-        checkDestroyed();
-
-        //return Toolkit.getDefaultToolkit().createImage(location);
-        Image image = (new ImageIcon(location)).getImage();
-
-        return image;
-    }
-
-    /**
-     * Not implemented yet.
-     */
-    public void showDocument(java.net.URL uRL) {
-        checkDestroyed();
-
-    }
-
-    /**
-     * Not implemented yet.
-     */
-    public void showDocument(java.net.URL uRL, java.lang.String str) {
-        checkDestroyed();
-
-    }
-
-    /**
-     * Not implemented yet.
-     */
-    public void showStatus(java.lang.String str) {
-        checkDestroyed();
-
-    }
-
-    /**
-     * Required for JRE1.4, but not implemented yet.
-     */
-    public void setStream(String key, InputStream stream) {
-        checkDestroyed();
-
-    }
-
-    /**
-     * Required for JRE1.4, but not implemented yet.
-     */
-    public InputStream getStream(String key) {
-        checkDestroyed();
-
-        return null;
-    }
-
-    /**
-     * Required for JRE1.4, but not implemented yet.
-     */
-    public Iterator getStreamKeys()  {
-        checkDestroyed();
-
-        return null;
-    }
-
-    // stub methods
-
-    public void appletResize(int width, int height) {
-        checkDestroyed();
-
-        if (cont instanceof Frame) {
-            Frame frame = (Frame) cont;
-            Insets insets = frame.getInsets();
-
-            frame.setSize(width + insets.left + insets.right,
-                          height + insets.top + insets.bottom);
-        }
-    }
-
-    public AppletContext getAppletContext() {
-        checkDestroyed();
-
-        return this;
-    }
-
-    public URL getCodeBase() {
-        checkDestroyed();
-
-        return file.getCodeBase();
-    }
-
-    public URL getDocumentBase() {
-        checkDestroyed();
-
-        return file.getApplet().getDocumentBase();
-    }
-
-    // FIXME: Sun's applet code forces all parameters to lower case.
-    // Does Netx's JNLP code do the same, so we can remove the first lookup?
-    public String getParameter(String name) {
-        checkDestroyed();
-
-        String s = (String) parameters.get(name);
-        if (s != null)
-            return s;
-
-        return (String) parameters.get(name.toLowerCase());
-    }
-
-    public boolean isActive() {
-        checkDestroyed();
-
-        // it won't be started or stopped, so if it can call it's running
-        return true;
-    }
-
-
-}
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.util.List;
+import java.lang.reflect.InvocationTargetException;
+import java.net.*;
+import java.io.*;
+import javax.swing.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.util.*;
+
+/**
+ * The applet environment including stub, context, and frame.  The
+ * default environment puts the applet in a non-resiable frame;
+ * this can be changed by obtaining the frame and setting it
+ * resizable.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.12 $ 
+ */
+public class AppletEnvironment implements AppletContext, AppletStub {
+
+    /** the JNLP file */
+    private JNLPFile file;
+
+    /** the applet instance */
+    private AppletInstance appletInstance;
+
+    /** the applet */
+    private Applet applet;
+
+    /** the parameters */
+    private Map parameters;
+
+    /** the applet container */
+    private Container cont;
+
+    /** weak references to the audio clips */
+    private WeakList weakClips = new WeakList();
+
+    /** whether the applet has been started / displayed */
+    private boolean appletStarted = false;
+
+    /** whether the applet has been destroyed */
+    private boolean destroyed = false;
+
+
+    /**
+     * Create a new applet environment for the applet specified by
+     * the JNLP file.
+     */
+    public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance, Container cont) {
+        this.file = file;
+        this.appletInstance = appletInstance;
+        this.applet = appletInstance.getApplet();
+
+        parameters = file.getApplet().getParameters();
+        this.cont = cont;
+    }
+
+    /**
+     * Create a new applet environment for the applet specified by
+     * the JNLP file, in a new frame.
+     */
+    public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance) {
+        this(file, appletInstance, null);
+
+        Frame frame = new Frame(file.getApplet().getName() + " - Applet");
+        frame.setResizable(false);
+
+        appletInstance.addWindow(frame);
+        // may not need this once security manager can close windows
+        // that do not have app code on the stack
+        WindowListener closer = new WindowAdapter() {
+            public void windowClosing(WindowEvent event) {
+                appletInstance.destroy();
+                System.exit(0);
+            }
+        };
+        frame.addWindowListener(closer);
+        this.cont = frame;
+    }
+
+    /**
+     * Checks whether the applet has been destroyed, and throws an
+     * IllegalStateException if the applet has been destroyed of.
+     *
+     * @throws IllegalStateException
+     */
+    private void checkDestroyed() {
+        if (destroyed)
+            throw new IllegalStateException("Illegal applet stub/context access: applet destroyed.");
+    }
+
+    /**
+     * Disposes the applet's resources and disables the applet
+     * environment from further use; after calling this method the
+     * applet stub and context methods throw IllegalStateExceptions.
+     */
+    public void destroy() {
+        destroyed = true;
+
+        List clips = weakClips.hardList();
+        for (int i = 0; i < clips.size(); i++) {
+            ((AppletAudioClip)clips.get(i)).dispose();
+        }
+    }
+
+    /**
+     * Returns the frame that contains the applet.  Disposing this
+     * frame will destroy the applet.
+     */
+    public Container getAppletFrame() {
+        // TODO: rename this method to getAppletContainer ?
+        return cont;
+    }
+
+    /**
+     * Initialize, start, and show the applet.
+     */
+    public void startApplet() {
+        checkDestroyed();
+
+        if (appletStarted)
+            return;
+
+        appletStarted = true;
+
+        try {
+            AppletDesc appletDesc = file.getApplet();
+
+            if (cont instanceof AppletStub)
+                applet.setStub((AppletStub)cont);
+            else
+                applet.setStub(this);
+
+            cont.setLayout(new BorderLayout());
+            cont.add("Center", applet);
+            cont.validate();
+
+            // This is only needed if the applet is in its own frame.
+            if (cont instanceof Frame) {
+                Frame frame = (Frame) cont;
+                frame.pack(); // cause insets to be calculated
+
+                Insets insets = frame.getInsets();
+                frame.setSize(appletDesc.getWidth() + insets.left + insets.right,
+                              appletDesc.getHeight() + insets.top + insets.bottom);
+            }
+    
+            try {
+            	SwingUtilities.invokeAndWait(new Runnable() {
+            		public void run() {
+            			// do first because some applets need to be displayed before
+            			// starting (they use Component.getImage or something)
+            			cont.setVisible(true);
+
+            			applet.init();
+            			applet.start();
+
+            			cont.invalidate(); // this should force the applet to
+            			cont.validate();   // the correct size and to repaint
+            			cont.repaint();
+            		}
+            	});
+            } catch (InterruptedException ie) {
+
+            } catch (InvocationTargetException ite) {
+
+            }
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            // should also kill the applet?
+        }
+    }
+
+    // applet context methods 
+
+    /**
+     * Returns the applet if the applet's name is specified,
+     * otherwise return null.
+     */
+    public Applet getApplet(String name) {
+        checkDestroyed();
+
+        if (name != null && name.equals(file.getApplet().getName()))
+            return applet;
+        else
+            return null;
+    }
+
+    /**
+     * Returns an enumeration that contains only the applet
+     * from the JNLP file.
+     */
+    public Enumeration getApplets() {
+        checkDestroyed();
+
+        return Collections.enumeration( Arrays.asList(new Applet[] { applet }) );
+    }
+
+    /**
+     * Returns an audio clip.
+     */
+    public AudioClip getAudioClip(URL location) {
+        checkDestroyed();
+
+        AppletAudioClip clip = new AppletAudioClip(location);
+
+        weakClips.add(clip);
+        weakClips.trimToSize();
+
+        return clip;
+    }
+
+    /**
+     * Return an image loaded from the specified location.
+     */
+    public Image getImage(URL location) {
+        checkDestroyed();
+
+        //return Toolkit.getDefaultToolkit().createImage(location);
+        Image image = (new ImageIcon(location)).getImage();
+
+        return image;
+    }
+
+    /**
+     * Not implemented yet.
+     */
+    public void showDocument(java.net.URL uRL) {
+        checkDestroyed();
+
+    }
+
+    /**
+     * Not implemented yet.
+     */
+    public void showDocument(java.net.URL uRL, java.lang.String str) {
+        checkDestroyed();
+
+    }
+
+    /**
+     * Not implemented yet.
+     */
+    public void showStatus(java.lang.String str) {
+        checkDestroyed();
+
+    }
+
+    /**
+     * Required for JRE1.4, but not implemented yet.
+     */
+    public void setStream(String key, InputStream stream) {
+        checkDestroyed();
+
+    }
+
+    /**
+     * Required for JRE1.4, but not implemented yet.
+     */
+    public InputStream getStream(String key) {
+        checkDestroyed();
+
+        return null;
+    }
+
+    /**
+     * Required for JRE1.4, but not implemented yet.
+     */
+    public Iterator getStreamKeys()  {
+        checkDestroyed();
+
+        return null;
+    }
+
+    // stub methods
+
+    public void appletResize(int width, int height) {
+        checkDestroyed();
+
+        if (cont instanceof Frame) {
+            Frame frame = (Frame) cont;
+            Insets insets = frame.getInsets();
+
+            frame.setSize(width + insets.left + insets.right,
+                          height + insets.top + insets.bottom);
+        }
+    }
+
+    public AppletContext getAppletContext() {
+        checkDestroyed();
+
+        return this;
+    }
+
+    public URL getCodeBase() {
+        checkDestroyed();
+
+        return file.getCodeBase();
+    }
+
+    public URL getDocumentBase() {
+        checkDestroyed();
+
+        return file.getApplet().getDocumentBase();
+    }
+
+    // FIXME: Sun's applet code forces all parameters to lower case.
+    // Does Netx's JNLP code do the same, so we can remove the first lookup?
+    public String getParameter(String name) {
+        checkDestroyed();
+
+        String s = (String) parameters.get(name);
+        if (s != null)
+            return s;
+
+        return (String) parameters.get(name.toLowerCase());
+    }
+
+    public boolean isActive() {
+        checkDestroyed();
+
+        // it won't be started or stopped, so if it can call it's running
+        return true;
+    }
+
+
+}
--- a/rt/net/sourceforge/jnlp/runtime/AppletInstance.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/AppletInstance.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,139 +1,139 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.applet.*;
-import java.awt.*;
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.util.List;
-import java.security.*;
-import java.lang.reflect.*;
-import java.lang.ref.*;
-
-import net.sourceforge.jnlp.*;
-
-
-/**
- * Represents a launched application instance created from a JNLP
- * file.  This class does not control the operation of the applet,
- * use the AppletEnvironment class to start and stop the applet.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.9 $ 
- */
-public class AppletInstance extends ApplicationInstance {
-
-    /** whether the applet's stop and destroy methods have been called */
-    private boolean appletStopped = false;
-
-    /** the applet */
-    private Applet applet;
-
-    /** the applet environment */
-    private AppletEnvironment environment;
-
-
-    /**
-     * Create a New Task based on the Specified URL
-     */
-    public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet) {
-        super(file, group, loader);
-
-        this.applet = applet;
-
-        this.environment = new AppletEnvironment(file, this);
-    }
-
-    /**
-     * 
-     */
-    public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet, Container cont) {
-        super(file, group, loader);
-        this.applet = applet;
-        this.environment = new AppletEnvironment(file, this, cont);
-    }
-
-    /**
-     * Sets whether the applet is resizable or not.  Applets default
-     * to being not resizable.
-     */
-    public void setResizable(boolean resizable) {
-        Container c = environment.getAppletFrame();
-        if (c instanceof Frame)
-            ((Frame) c).setResizable(resizable);
-    }
-
-    /**
-     * Returns whether the applet is resizable.
-     */
-    public boolean isResizable() {
-        Container c = environment.getAppletFrame();
-        if (c instanceof Frame)
-            return ((Frame) c).isResizable();
-
-        return false;
-    }
-
-    /**
-     * Returns the application title.
-     */
-    public String getTitle() {
-        return getJNLPFile().getApplet().getName();
-    }
-
-    /**
-     * Returns the applet environment.
-     */
-    public AppletEnvironment getAppletEnvironment() {
-        return environment;
-    }
-
-    /**
-     * Returns the applet.
-     */
-    public Applet getApplet() {
-        return applet;
-    }
-
-    /**
-     * Stop the application and destroy its resources.
-     */
-    public void destroy() {
-        if (appletStopped)
-            return;
-
-        appletStopped = true;
-
-        try {
-            applet.stop();
-            applet.destroy();
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-        }
-
-        environment.destroy();
-
-        super.destroy();
-    }
-
-}
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.applet.*;
+import java.awt.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.List;
+import java.security.*;
+import java.lang.reflect.*;
+import java.lang.ref.*;
+
+import net.sourceforge.jnlp.*;
+
+
+/**
+ * Represents a launched application instance created from a JNLP
+ * file.  This class does not control the operation of the applet,
+ * use the AppletEnvironment class to start and stop the applet.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.9 $ 
+ */
+public class AppletInstance extends ApplicationInstance {
+
+    /** whether the applet's stop and destroy methods have been called */
+    private boolean appletStopped = false;
+
+    /** the applet */
+    private Applet applet;
+
+    /** the applet environment */
+    private AppletEnvironment environment;
+
+
+    /**
+     * Create a New Task based on the Specified URL
+     */
+    public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet) {
+        super(file, group, loader);
+
+        this.applet = applet;
+
+        this.environment = new AppletEnvironment(file, this);
+    }
+
+    /**
+     * 
+     */
+    public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet, Container cont) {
+        super(file, group, loader);
+        this.applet = applet;
+        this.environment = new AppletEnvironment(file, this, cont);
+    }
+
+    /**
+     * Sets whether the applet is resizable or not.  Applets default
+     * to being not resizable.
+     */
+    public void setResizable(boolean resizable) {
+        Container c = environment.getAppletFrame();
+        if (c instanceof Frame)
+            ((Frame) c).setResizable(resizable);
+    }
+
+    /**
+     * Returns whether the applet is resizable.
+     */
+    public boolean isResizable() {
+        Container c = environment.getAppletFrame();
+        if (c instanceof Frame)
+            return ((Frame) c).isResizable();
+
+        return false;
+    }
+
+    /**
+     * Returns the application title.
+     */
+    public String getTitle() {
+        return getJNLPFile().getApplet().getName();
+    }
+
+    /**
+     * Returns the applet environment.
+     */
+    public AppletEnvironment getAppletEnvironment() {
+        return environment;
+    }
+
+    /**
+     * Returns the applet.
+     */
+    public Applet getApplet() {
+        return applet;
+    }
+
+    /**
+     * Stop the application and destroy its resources.
+     */
+    public void destroy() {
+        if (appletStopped)
+            return;
+
+        appletStopped = true;
+
+        try {
+            applet.stop();
+            applet.destroy();
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+        }
+
+        environment.destroy();
+
+        super.destroy();
+    }
+
+}
+
--- a/rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,293 +1,293 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.awt.*;
-import java.util.*;
-import java.util.List;
-import java.security.*;
-import javax.swing.event.EventListenerList;
-
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.event.*;
-import net.sourceforge.jnlp.security.SecurityWarningDialog.AccessType;
-import net.sourceforge.jnlp.services.ServiceUtil;
-import net.sourceforge.jnlp.util.*;
-
-/**
- * Represents a running instance of an application described in a
- * JNLPFile.  This class provides a way to track the application's
- * resources and destroy the application.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.15 $ 
- */
-public class ApplicationInstance {
-
-    // todo: should attempt to unload the environment variables
-    // installed by the application.
-
-
-    /** the file */
-    private JNLPFile file;
-
-    /** the thread group */
-    private ThreadGroup group;
-
-    /** the classloader */
-    private ClassLoader loader;
-
-    /** whether the application has stopped running */
-    private boolean stopped = false;
-
-    /** weak list of windows opened by the application */
-    private WeakList weakWindows = new WeakList();
-
-    /** list of application listeners  */
-    private EventListenerList listeners = new EventListenerList();
-
-	/** whether or not this application is signed */
-	private boolean isSigned = false;
-
-    /**
-     * Create an application instance for the file.
-     */
-    public ApplicationInstance(JNLPFile file, ThreadGroup group, ClassLoader loader) {
-        this.file = file;
-        this.group = group;
-        this.loader = loader;
-        this.isSigned = ((JNLPClassLoader) loader).getSigning();
-    }
-
-    /**
-     * Add an Application listener
-     */
-    public void addApplicationListener(ApplicationListener listener) {
-        listeners.add(ApplicationListener.class, listener);
-    }
-
-    /**
-     * Remove an Application Listener
-     */
-    public void removeApplicationListener(ApplicationListener listener) {
-        listeners.remove(ApplicationListener.class, listener);
-    }
-
-    /**
-     * Notify listeners that the application has been terminated.
-     */
-    protected void fireDestroyed() {
-        Object list[] = listeners.getListenerList();
-        ApplicationEvent event = null;
-
-        for (int i=list.length-1; i>0; i-=2) { // last to first required
-            if (event == null)
-                event = new ApplicationEvent(this);
-
-            ((ApplicationListener)list[i]).applicationDestroyed(event);
-        }
-    }
-
-    /**
-     * Initialize the application's environment (installs
-     * environment variables, etc).
-     */
-    public void initialize() {
-        installEnvironment();
-        
-        /*
-         * FIXME: Disable creating desktop entries for now
-         * 
-         * there are some major issues we need to work out before we can enable them
-         * 1. Playing nice with the altnatives system
-         *   - use the system preferred jdk (/usr/bin/javaws)
-         *   - dont assume what jdk javaws corresponds to
-         *   - make sure our shortcuts work with the sun jdk and vice versa 
-         *     (may not be possible since sun's javaws creates a launcher that
-         *     links to /usr/java/${ver}/bin/javaws)
-         *   - we should use the same options and arguments as sun's javaws
-         * 2. Make shortcuts work offline 
-         *   - make the cache updates and replacements work properly
-         *   - shortcuts should use the cache
-         *   
-         *  addMenuAndDesktopEntries();
-         */
-    }
-
-    /**
-     * Creates menu and desktop entries if required by the jnlp file
-     */
-
-    private void addMenuAndDesktopEntries() {
-        XDesktopEntry entry = new XDesktopEntry(file);
-
-        if (file.getInformation().getShortcut().onDesktop()) {
-            if (ServiceUtil.checkAccess(this, AccessType.CREATE_DESTKOP_SHORTCUT)) {
-                entry.createDesktopShortcut();
-            }
-        }
-
-        if (file.getInformation().getShortcut().getMenu() != null) {
-            /*
-             * Sun's WebStart implementation doesnt seem to do anything under GNOME
-             */
-            if (JNLPRuntime.isDebug()) {
-                System.err.println("ApplicationInstance.addMenuAndDesktopEntries():"
-                        + " Adding menu entries NOT IMPLEMENTED");
-            }
-        }
-
-    }
-
-    /**
-     * Releases the application's resources before it is collected.
-     * Only collectable if classloader and thread group are
-     * also collectable so basically is almost never called (an
-     * application would have to close its windows and exit its
-     * threads but not call System.exit).
-     */
-    public void finalize() {
-        destroy();
-    }
-
-    /**
-     * Install the environment variables.
-     */
-    void installEnvironment() {
-        final PropertyDesc props[] = file.getResources().getProperties();
-
-        PrivilegedAction installProps = new PrivilegedAction() {
-            public Object run() {
-                for (int i=0; i < props.length; i++) {
-                    System.setProperty(props[i].getKey(), props[i].getValue());
-                }
-
-                return null;
-            }
-        };
-        AccessController.doPrivileged(installProps);
-    }
-
-    /** 
-     * Returns the JNLP file for this task.
-     */
-    public JNLPFile getJNLPFile() {
-        return file;
-    }
-
-    /**
-     * Returns the application title.
-     */
-    public String getTitle() {
-        return file.getTitle();
-    }
-
-    /**
-     * Returns whether the application is running.
-     */
-    public boolean isRunning() {
-        return !stopped;
-    }
-
-    /**
-     * Stop the application and destroy its resources.
-     */
-    public void destroy() {
-        if (stopped)
-            return;
-
-        try {
-            // destroy resources
-            for (int i=0; i < weakWindows.size(); i++) {
-                Window w = (Window) weakWindows.get(i);
-                if (w != null)
-                    w.dispose();
-            }
-
-            weakWindows.clear();
-
-             // interrupt threads
-            Thread threads[] = new Thread[ group.activeCount() * 2 ];
-            int nthreads = group.enumerate(threads);
-            for (int i=0; i < nthreads; i++) {
-                if (JNLPRuntime.isDebug())
-                    System.out.println("Interrupt thread: "+threads[i]);
-
-                threads[i].interrupt();
-            }
-
-            // then stop
-            Thread.currentThread().yield();
-            nthreads = group.enumerate(threads);
-            for (int i=0; i < nthreads; i++) {
-                if (JNLPRuntime.isDebug())
-                    System.out.println("Stop thread: "+threads[i]);
-
-                threads[i].stop();
-            }
-
-            // then destroy - except Thread.destroy() not implemented in jdk
-
-       }
-        finally {
-            stopped = true;
-            fireDestroyed();
-        }
-    }
-
-    /**
-     * Returns the thread group.
-     *
-     * @throws IllegalStateException if the app is not running
-     */
-    public ThreadGroup getThreadGroup() throws IllegalStateException {
-        if (stopped)
-            throw new IllegalStateException();
-
-        return group;
-    }
-
-    /** 
-     * Returns the classloader.
-     *
-     * @throws IllegalStateException if the app is not running
-     */
-    public ClassLoader getClassLoader() throws IllegalStateException {
-        if (stopped)
-            throw new IllegalStateException();
-
-        return loader;
-    }
-
-    /**
-     * Adds a window that this application opened.  When the
-     * application is disposed, these windows will also be disposed.
-     */
-    protected void addWindow(Window window) {
-        weakWindows.add(window);
-        weakWindows.trimToSize();
-    }
-
-	/**
-	 * Returns whether or not this jar is signed.
-	 */
-	public boolean isSigned() {
-		return isSigned;
-	}
-}
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+import java.security.*;
+import javax.swing.event.EventListenerList;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.event.*;
+import net.sourceforge.jnlp.security.SecurityWarningDialog.AccessType;
+import net.sourceforge.jnlp.services.ServiceUtil;
+import net.sourceforge.jnlp.util.*;
+
+/**
+ * Represents a running instance of an application described in a
+ * JNLPFile.  This class provides a way to track the application's
+ * resources and destroy the application.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.15 $ 
+ */
+public class ApplicationInstance {
+
+    // todo: should attempt to unload the environment variables
+    // installed by the application.
+
+
+    /** the file */
+    private JNLPFile file;
+
+    /** the thread group */
+    private ThreadGroup group;
+
+    /** the classloader */
+    private ClassLoader loader;
+
+    /** whether the application has stopped running */
+    private boolean stopped = false;
+
+    /** weak list of windows opened by the application */
+    private WeakList weakWindows = new WeakList();
+
+    /** list of application listeners  */
+    private EventListenerList listeners = new EventListenerList();
+
+	/** whether or not this application is signed */
+	private boolean isSigned = false;
+
+    /**
+     * Create an application instance for the file.
+     */
+    public ApplicationInstance(JNLPFile file, ThreadGroup group, ClassLoader loader) {
+        this.file = file;
+        this.group = group;
+        this.loader = loader;
+        this.isSigned = ((JNLPClassLoader) loader).getSigning();
+    }
+
+    /**
+     * Add an Application listener
+     */
+    public void addApplicationListener(ApplicationListener listener) {
+        listeners.add(ApplicationListener.class, listener);
+    }
+
+    /**
+     * Remove an Application Listener
+     */
+    public void removeApplicationListener(ApplicationListener listener) {
+        listeners.remove(ApplicationListener.class, listener);
+    }
+
+    /**
+     * Notify listeners that the application has been terminated.
+     */
+    protected void fireDestroyed() {
+        Object list[] = listeners.getListenerList();
+        ApplicationEvent event = null;
+
+        for (int i=list.length-1; i>0; i-=2) { // last to first required
+            if (event == null)
+                event = new ApplicationEvent(this);
+
+            ((ApplicationListener)list[i]).applicationDestroyed(event);
+        }
+    }
+
+    /**
+     * Initialize the application's environment (installs
+     * environment variables, etc).
+     */
+    public void initialize() {
+        installEnvironment();
+        
+        /*
+         * FIXME: Disable creating desktop entries for now
+         * 
+         * there are some major issues we need to work out before we can enable them
+         * 1. Playing nice with the altnatives system
+         *   - use the system preferred jdk (/usr/bin/javaws)
+         *   - dont assume what jdk javaws corresponds to
+         *   - make sure our shortcuts work with the sun jdk and vice versa 
+         *     (may not be possible since sun's javaws creates a launcher that
+         *     links to /usr/java/${ver}/bin/javaws)
+         *   - we should use the same options and arguments as sun's javaws
+         * 2. Make shortcuts work offline 
+         *   - make the cache updates and replacements work properly
+         *   - shortcuts should use the cache
+         *   
+         *  addMenuAndDesktopEntries();
+         */
+    }
+
+    /**
+     * Creates menu and desktop entries if required by the jnlp file
+     */
+
+    private void addMenuAndDesktopEntries() {
+        XDesktopEntry entry = new XDesktopEntry(file);
+
+        if (file.getInformation().getShortcut().onDesktop()) {
+            if (ServiceUtil.checkAccess(this, AccessType.CREATE_DESTKOP_SHORTCUT)) {
+                entry.createDesktopShortcut();
+            }
+        }
+
+        if (file.getInformation().getShortcut().getMenu() != null) {
+            /*
+             * Sun's WebStart implementation doesnt seem to do anything under GNOME
+             */
+            if (JNLPRuntime.isDebug()) {
+                System.err.println("ApplicationInstance.addMenuAndDesktopEntries():"
+                        + " Adding menu entries NOT IMPLEMENTED");
+            }
+        }
+
+    }
+
+    /**
+     * Releases the application's resources before it is collected.
+     * Only collectable if classloader and thread group are
+     * also collectable so basically is almost never called (an
+     * application would have to close its windows and exit its
+     * threads but not call System.exit).
+     */
+    public void finalize() {
+        destroy();
+    }
+
+    /**
+     * Install the environment variables.
+     */
+    void installEnvironment() {
+        final PropertyDesc props[] = file.getResources().getProperties();
+
+        PrivilegedAction installProps = new PrivilegedAction() {
+            public Object run() {
+                for (int i=0; i < props.length; i++) {
+                    System.setProperty(props[i].getKey(), props[i].getValue());
+                }
+
+                return null;
+            }
+        };
+        AccessController.doPrivileged(installProps);
+    }
+
+    /** 
+     * Returns the JNLP file for this task.
+     */
+    public JNLPFile getJNLPFile() {
+        return file;
+    }
+
+    /**
+     * Returns the application title.
+     */
+    public String getTitle() {
+        return file.getTitle();
+    }
+
+    /**
+     * Returns whether the application is running.
+     */
+    public boolean isRunning() {
+        return !stopped;
+    }
+
+    /**
+     * Stop the application and destroy its resources.
+     */
+    public void destroy() {
+        if (stopped)
+            return;
+
+        try {
+            // destroy resources
+            for (int i=0; i < weakWindows.size(); i++) {
+                Window w = (Window) weakWindows.get(i);
+                if (w != null)
+                    w.dispose();
+            }
+
+            weakWindows.clear();
+
+             // interrupt threads
+            Thread threads[] = new Thread[ group.activeCount() * 2 ];
+            int nthreads = group.enumerate(threads);
+            for (int i=0; i < nthreads; i++) {
+                if (JNLPRuntime.isDebug())
+                    System.out.println("Interrupt thread: "+threads[i]);
+
+                threads[i].interrupt();
+            }
+
+            // then stop
+            Thread.currentThread().yield();
+            nthreads = group.enumerate(threads);
+            for (int i=0; i < nthreads; i++) {
+                if (JNLPRuntime.isDebug())
+                    System.out.println("Stop thread: "+threads[i]);
+
+                threads[i].stop();
+            }
+
+            // then destroy - except Thread.destroy() not implemented in jdk
+
+       }
+        finally {
+            stopped = true;
+            fireDestroyed();
+        }
+    }
+
+    /**
+     * Returns the thread group.
+     *
+     * @throws IllegalStateException if the app is not running
+     */
+    public ThreadGroup getThreadGroup() throws IllegalStateException {
+        if (stopped)
+            throw new IllegalStateException();
+
+        return group;
+    }
+
+    /** 
+     * Returns the classloader.
+     *
+     * @throws IllegalStateException if the app is not running
+     */
+    public ClassLoader getClassLoader() throws IllegalStateException {
+        if (stopped)
+            throw new IllegalStateException();
+
+        return loader;
+    }
+
+    /**
+     * Adds a window that this application opened.  When the
+     * application is disposed, these windows will also be disposed.
+     */
+    protected void addWindow(Window window) {
+        weakWindows.add(window);
+        weakWindows.trimToSize();
+    }
+
+	/**
+	 * Returns whether or not this jar is signed.
+	 */
+	public boolean isSigned() {
+		return isSigned;
+	}
+}
+
--- a/rt/net/sourceforge/jnlp/runtime/Boot.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/Boot.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,446 +1,446 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-
-import net.sourceforge.jnlp.AppletDesc;
-import net.sourceforge.jnlp.ApplicationDesc;
-import net.sourceforge.jnlp.JNLPFile;
-import net.sourceforge.jnlp.LaunchException;
-import net.sourceforge.jnlp.Launcher;
-import net.sourceforge.jnlp.ParseException;
-import net.sourceforge.jnlp.PropertyDesc;
-import net.sourceforge.jnlp.ResourcesDesc;
-import net.sourceforge.jnlp.cache.UpdatePolicy;
-import net.sourceforge.jnlp.security.VariableX509TrustManager;
-import net.sourceforge.jnlp.security.viewer.CertificateViewer;
-import net.sourceforge.jnlp.services.ServiceUtil;
-
-/**
- * This is the main entry point for the JNLP client.  The main
- * method parses the command line parameters and loads a JNLP
- * file into the secure runtime environment.  This class is meant
- * to be called from the command line or file association; to
- * initialize the netx engine from other code invoke the
- * <code>JNLPRuntime.initialize</code> method after configuring
- * the runtime.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.21 $
- */
-public final class Boot implements PrivilegedAction {
-
-    // todo: decide whether a spawned netx (external launch)
-    // should inherit the same options as this instance (store argv?)
-    
-    private static String R(String key) { return JNLPRuntime.getMessage(key); }
-    private static String R(String key, Object param) { return JNLPRuntime.getMessage(key, new Object[] {param}); }
-
-    private static final String version = "0.5";
-
-    /** the text to display before launching the about link */
-    private static final String aboutMessage = ""
-        + "netx v"+version+" - (C)2001-2003 Jon A. Maxwell (jmaxwell@users.sourceforge.net)\n"
-        + "\n"
-        + R("BLaunchAbout");
-
-    private static final String miniLicense = "\n"
-        + "   netx - an open-source JNLP client.\n"
-        + "   Copyright (C) 2001-2003 Jon A. Maxwell (JAM)\n"
-        + "\n"
-        + "   // This library is free software; you can redistribute it and/or\n"
-        + "   modify it under the terms of the GNU Lesser General Public\n"
-        + "   License as published by the Free Software Foundation; either\n"
-        + "   version 2.1 of the License, or (at your option) any later version.\n"
-        + "\n"
-        + "   This library is distributed in the hope that it will be useful,\n"
-        + "   but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
-        + "   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
-        + "   Lesser General Public License for more details.\n"
-        + "\n"
-        + "   You should have received a copy of the GNU Lesser General Public\n"
-        + "   License along with this library; if not, write to the Free Software\n"
-        + "   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n"
-        + "\n";
-
-    private static final String helpMessage = "\n"
-        + "Usage:   " + R("BOUsage")+"\n"
-        + "         " + R("BOUsage2")+"\n"
-        + "\n"
-        + "control-options:"+"\n"
-        + "  -about                "+R("BOAbout")+"\n"
-        + "  -viewer               "+R("BOViewer")+"\n"
-        + "\n"
-        + "run-options:"+"\n"
-        + "  -basedir dir          "+R("BOBasedir")+"\n"
-        + "  -arg arg              "+R("BOArg")+"\n"
-        + "  -param name=value     "+R("BOParam")+"\n"
-        + "  -property name=value  "+R("BOProperty")+"\n"
-        + "  -update seconds       "+R("BOUpdate")+"\n"
-        + "  -license              "+R("BOLicense")+"\n"
-        + "  -verbose              "+R("BOVerbose")+"\n"
-        + "  -nosecurity           "+R("BONosecurity")+"\n"
-        + "  -noupdate             "+R("BONoupdate")+"\n"
-        + "  -headless             "+R("BOHeadless")+"\n"
-        + "  -strict               "+R("BOStrict")+"\n"
-        + "  -umask=value          "+R("BOUmask")+"\n"
-        + "  -Xnofork              "+R("BXnofork")+"\n"
-        + "  -help                 "+R("BOHelp")+"\n";
-
-    private static final String doubleArgs = "-basedir -jnlp -arg -param -property -update";
-
-    private static String args[]; // avoid the hot potato
-
-
-    /**
-     * Launch the JNLP file specified by the command-line arguments.
-     */
-    public static void main(String[] argsIn) {
-        args = argsIn;
-        
-        if (null != getOption("-viewer")) {
-
-            try {
-                CertificateViewer.main(null);
-                System.exit(0);
-            } catch (Exception e) {
-                // TODO Auto-generated catch block
-                e.printStackTrace();
-            }
-
-        }
-
-        if (null != getOption("-license")) {
-            System.out.println(miniLicense);
-            System.exit(0);
-        }
-
-        if (null != getOption("-help")) {
-            System.out.println(helpMessage);
-            System.exit(0);
-        }
-
-        if (null != getOption("-about"))
-            System.out.println(aboutMessage);
-
-        if (null != getOption("-verbose"))
-            JNLPRuntime.setDebug(true);
-
-        if (null != getOption("-update")) {
-            int value = Integer.parseInt(getOption("-update"));
-            JNLPRuntime.setDefaultUpdatePolicy(new UpdatePolicy(value*1000l));
-        }
-
-        if (null != getOption("-headless"))
-            JNLPRuntime.setHeadless(true);
-
-
-        if (null != getOption("-noupdate"))
-            JNLPRuntime.setDefaultUpdatePolicy(UpdatePolicy.NEVER);
-        
-        if (null != getOption("-Xnofork")) {
-            JNLPRuntime.setForksAllowed(false);
-        }
-        
-        // wire in custom authenticator
-        try {
-            SSLSocketFactory sslSocketFactory;
-            SSLContext context = SSLContext.getInstance("SSL");
-            TrustManager[] trust = new TrustManager[] { VariableX509TrustManager.getInstance() };
-            context.init(null, trust, null);
-            sslSocketFactory = context.getSocketFactory();
-            
-            HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
-        } catch (Exception e) {
-            System.err.println("Unable to set SSLSocketfactory (may _prevent_ access to sites that should be trusted)! Continuing anyway...");
-            e.printStackTrace();
-        }
-
-        JNLPRuntime.setInitialArgments(Arrays.asList(argsIn));
-        
-        // do in a privileged action to clear the security context of
-        // the Boot13 class, which doesn't have any privileges in
-        // JRE1.3; JRE1.4 works without Boot13 or this PrivilegedAction.
-        AccessController.doPrivileged(new Boot());
-
-    }
-
-    /**
-     * The privileged part (jdk1.3 compatibility).
-     */
-    public Object run() {
-        JNLPRuntime.setBaseDir(getBaseDir());
-        JNLPRuntime.setSecurityEnabled(null == getOption("-nosecurity"));
-        JNLPRuntime.initialize(true);
-
-        try {
-            new Launcher().launch(getFile());
-        }
-        catch (LaunchException ex) {
-            // default handler prints this
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-
-            fatalError(JNLPRuntime.getMessage("RUnexpected", 
-                        new Object[] {ex.toString(), ex.getStackTrace()[0]} ));
-        }
-
-        return null;
-    }
-
-    private static void fatalError(String message) {
-        System.err.println("netx: "+message);
-        System.exit(1);
-    }
-
-    /**
-     * Returns the about.jnlp file in {java.home}/lib or null if this file
-     * does not exist.
-     */
-    private static String getAboutFile() {
-
-        if (new File(JNLPRuntime.NETX_ABOUT_FILE).exists())
-            return JNLPRuntime.NETX_ABOUT_FILE;
-        else
-            return null;
-    }
-
-    /**
-     * Returns the file to open; does not return if no file was
-     * specified.
-     */
-    private static JNLPFile getFile() throws ParseException, MalformedURLException, IOException {
-    
-        String location = getJNLPFile();
-        
-        // override -jnlp with aboutFile
-        if (getOption("-about") != null) {
-            location = getAboutFile();
-            if (location == null)
-                fatalError("Unable to find about.jnlp in {java.home}/lib/");
-        } else {
-            location = getJNLPFile();
-        }
-
-        if (location == null) {
-            System.out.println(helpMessage);
-            System.exit(1);
-        }
-        
-        if (JNLPRuntime.isDebug())
-            System.out.println(R("BFileLoc")+": "+location);
-
-        URL url = null;
-
-        try {
-            if (new File(location).exists())
-                url = new File(location).toURL(); // Why use file.getCanonicalFile?
-            else 
-                url = new URL(ServiceUtil.getBasicService().getCodeBase(), location);
-        } catch (Exception e) {
-            fatalError("Invalid jnlp file " + location);
-            if (JNLPRuntime.isDebug())
-                e.printStackTrace();
-        }
-        
-        boolean strict = (null != getOption("-strict"));
-
-        JNLPFile file = new JNLPFile(url, strict);
-
-        // add in extra params from command line
-        addProperties(file);
-
-        if (file.isApplet())
-            addParameters(file);
-
-        if (file.isApplication())
-            addArguments(file);
-
-        if (JNLPRuntime.isDebug()) {
-            if (getOption("-arg") != null)
-                if (file.isInstaller() || file.isApplet())
-                    System.out.println(R("BArgsNA"));
-
-            if (getOption("-param") != null)
-                if (file.isApplication())
-                    System.out.println(R("BParamNA"));
-        }
-
-        return file;
-    }
-
-    /**
-     * Add the properties to the JNLP file.
-     */
-    private static void addProperties(JNLPFile file) {
-        String props[] = getOptions("-property");
-        ResourcesDesc resources = file.getResources();
-
-        for (int i=0; i < props.length; i++) {
-            // allows empty property, not sure about validity of that.
-            int equals = props[i].indexOf("=");
-            if (equals == -1)
-                fatalError(R("BBadProp", props[i]));
-
-            String key = props[i].substring(0, equals);
-            String value = props[i].substring(equals+1, props[i].length());
-
-            resources.addResource(new PropertyDesc(key, value));
-        }
-    }
-
-    /**
-     * Add the params to the JNLP file; only call if file is
-     * actually an applet file.
-     */
-    private static void addParameters(JNLPFile file) {
-        String params[] = getOptions("-param");
-        AppletDesc applet = file.getApplet();
-
-        for (int i=0; i < params.length; i++) {
-            // allows empty param, not sure about validity of that.
-            int equals = params[i].indexOf("=");
-            if (equals == -1)
-                fatalError(R("BBadParam", params[i]));
-
-            String name = params[i].substring(0, equals);
-            String value = params[i].substring(equals+1, params[i].length());
-
-            applet.addParameter(name, value);
-        }
-    }
-
-    /**
-     * Add the arguments to the JNLP file; only call if file is
-     * actually an application (not installer).
-     */
-    private static void addArguments(JNLPFile file) {
-        String args[] = getOptions("-arg");  // FYI args also global variable
-        ApplicationDesc app = file.getApplication();
-
-        for (int i=0; i < args.length; i++) {
-            app.addArgument(args[i]);
-        }
-    }
-
-    /**
-     * Gets the JNLP file from the command line arguments, or exits upon error.
-     */
-    private static String getJNLPFile() {
-
-        if (args.length == 0) {
-            System.out.println(helpMessage);
-            System.exit(0);
-        } else if (args.length == 1) {
-            return args[args.length - 1];
-        } else {
-            String lastArg = args[args.length - 1];
-            String secondLastArg = args[args.length - 2];
-
-            if (doubleArgs.indexOf(secondLastArg) == -1) {
-                return lastArg;
-            } else {
-                System.out.println(helpMessage);
-                System.exit(0);
-            }
-        }
-        return null;
-    }
-    
-    /**
-     * Return value of the first occurence of the specified
-     * option, or null if the option is not present.  If the
-     * option is a flag (0-parameter) and is present then the
-     * option name is returned.
-     */
-    private static String getOption(String option) {
-        String result[] = getOptions(option);
-
-        if (result.length == 0)
-            return null;
-        else
-            return result[0];
-    }
-
-    /**
-     * Return all the values of the specified option, or an empty
-     * array if the option is not present.  If the option is a
-     * flag (0-parameter) and is present then the option name is
-     * returned once for each occurrence.
-     */
-    private static String[] getOptions(String option) {
-        List result = new ArrayList();
-
-        for (int i=0; i < args.length; i++) {
-            if (option.equals(args[i])) {
-                if (-1 == doubleArgs.indexOf(option))
-                    result.add(option);
-                else
-                    if (i+1 < args.length)
-                        result.add(args[i+1]);
-            }
-
-            if (args[i].startsWith("-") && -1 != doubleArgs.indexOf(args[i]))
-                i++;
-        }
-
-        return (String[]) result.toArray( new String[result.size()] );
-    }
-
-    /**
-     * Return the base dir.  If the base dir parameter is not set
-     * the value is read from JNLPRuntime.NETX_ABOUT_FILE file.
-     * If that file does not exist, an install dialog is displayed 
-     * to select the base directory.
-     */
-    private static File getBaseDir() {
-        if (getOption("-basedir") != null) {
-            File basedir = new File(getOption("-basedir"));
-
-            if (!basedir.exists() || !basedir.isDirectory())
-                fatalError(R("BNoDir", basedir));
-
-            return basedir;
-        }
-
-        // check .netxrc
-        File basedir = JNLPRuntime.getDefaultBaseDir();
-        if (basedir == null)
-            fatalError(R("BNoBase"));
-
-        return basedir;
-    }
-
-}
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+import net.sourceforge.jnlp.AppletDesc;
+import net.sourceforge.jnlp.ApplicationDesc;
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.LaunchException;
+import net.sourceforge.jnlp.Launcher;
+import net.sourceforge.jnlp.ParseException;
+import net.sourceforge.jnlp.PropertyDesc;
+import net.sourceforge.jnlp.ResourcesDesc;
+import net.sourceforge.jnlp.cache.UpdatePolicy;
+import net.sourceforge.jnlp.security.VariableX509TrustManager;
+import net.sourceforge.jnlp.security.viewer.CertificateViewer;
+import net.sourceforge.jnlp.services.ServiceUtil;
+
+/**
+ * This is the main entry point for the JNLP client.  The main
+ * method parses the command line parameters and loads a JNLP
+ * file into the secure runtime environment.  This class is meant
+ * to be called from the command line or file association; to
+ * initialize the netx engine from other code invoke the
+ * <code>JNLPRuntime.initialize</code> method after configuring
+ * the runtime.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.21 $
+ */
+public final class Boot implements PrivilegedAction {
+
+    // todo: decide whether a spawned netx (external launch)
+    // should inherit the same options as this instance (store argv?)
+    
+    private static String R(String key) { return JNLPRuntime.getMessage(key); }
+    private static String R(String key, Object param) { return JNLPRuntime.getMessage(key, new Object[] {param}); }
+
+    private static final String version = "0.5";
+
+    /** the text to display before launching the about link */
+    private static final String aboutMessage = ""
+        + "netx v"+version+" - (C)2001-2003 Jon A. Maxwell (jmaxwell@users.sourceforge.net)\n"
+        + "\n"
+        + R("BLaunchAbout");
+
+    private static final String miniLicense = "\n"
+        + "   netx - an open-source JNLP client.\n"
+        + "   Copyright (C) 2001-2003 Jon A. Maxwell (JAM)\n"
+        + "\n"
+        + "   // This library is free software; you can redistribute it and/or\n"
+        + "   modify it under the terms of the GNU Lesser General Public\n"
+        + "   License as published by the Free Software Foundation; either\n"
+        + "   version 2.1 of the License, or (at your option) any later version.\n"
+        + "\n"
+        + "   This library is distributed in the hope that it will be useful,\n"
+        + "   but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+        + "   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
+        + "   Lesser General Public License for more details.\n"
+        + "\n"
+        + "   You should have received a copy of the GNU Lesser General Public\n"
+        + "   License along with this library; if not, write to the Free Software\n"
+        + "   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\n"
+        + "\n";
+
+    private static final String helpMessage = "\n"
+        + "Usage:   " + R("BOUsage")+"\n"
+        + "         " + R("BOUsage2")+"\n"
+        + "\n"
+        + "control-options:"+"\n"
+        + "  -about                "+R("BOAbout")+"\n"
+        + "  -viewer               "+R("BOViewer")+"\n"
+        + "\n"
+        + "run-options:"+"\n"
+        + "  -basedir dir          "+R("BOBasedir")+"\n"
+        + "  -arg arg              "+R("BOArg")+"\n"
+        + "  -param name=value     "+R("BOParam")+"\n"
+        + "  -property name=value  "+R("BOProperty")+"\n"
+        + "  -update seconds       "+R("BOUpdate")+"\n"
+        + "  -license              "+R("BOLicense")+"\n"
+        + "  -verbose              "+R("BOVerbose")+"\n"
+        + "  -nosecurity           "+R("BONosecurity")+"\n"
+        + "  -noupdate             "+R("BONoupdate")+"\n"
+        + "  -headless             "+R("BOHeadless")+"\n"
+        + "  -strict               "+R("BOStrict")+"\n"
+        + "  -umask=value          "+R("BOUmask")+"\n"
+        + "  -Xnofork              "+R("BXnofork")+"\n"
+        + "  -help                 "+R("BOHelp")+"\n";
+
+    private static final String doubleArgs = "-basedir -jnlp -arg -param -property -update";
+
+    private static String args[]; // avoid the hot potato
+
+
+    /**
+     * Launch the JNLP file specified by the command-line arguments.
+     */
+    public static void main(String[] argsIn) {
+        args = argsIn;
+        
+        if (null != getOption("-viewer")) {
+
+            try {
+                CertificateViewer.main(null);
+                System.exit(0);
+            } catch (Exception e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+
+        }
+
+        if (null != getOption("-license")) {
+            System.out.println(miniLicense);
+            System.exit(0);
+        }
+
+        if (null != getOption("-help")) {
+            System.out.println(helpMessage);
+            System.exit(0);
+        }
+
+        if (null != getOption("-about"))
+            System.out.println(aboutMessage);
+
+        if (null != getOption("-verbose"))
+            JNLPRuntime.setDebug(true);
+
+        if (null != getOption("-update")) {
+            int value = Integer.parseInt(getOption("-update"));
+            JNLPRuntime.setDefaultUpdatePolicy(new UpdatePolicy(value*1000l));
+        }
+
+        if (null != getOption("-headless"))
+            JNLPRuntime.setHeadless(true);
+
+
+        if (null != getOption("-noupdate"))
+            JNLPRuntime.setDefaultUpdatePolicy(UpdatePolicy.NEVER);
+        
+        if (null != getOption("-Xnofork")) {
+            JNLPRuntime.setForksAllowed(false);
+        }
+        
+        // wire in custom authenticator
+        try {
+            SSLSocketFactory sslSocketFactory;
+            SSLContext context = SSLContext.getInstance("SSL");
+            TrustManager[] trust = new TrustManager[] { VariableX509TrustManager.getInstance() };
+            context.init(null, trust, null);
+            sslSocketFactory = context.getSocketFactory();
+            
+            HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
+        } catch (Exception e) {
+            System.err.println("Unable to set SSLSocketfactory (may _prevent_ access to sites that should be trusted)! Continuing anyway...");
+            e.printStackTrace();
+        }
+
+        JNLPRuntime.setInitialArgments(Arrays.asList(argsIn));
+        
+        // do in a privileged action to clear the security context of
+        // the Boot13 class, which doesn't have any privileges in
+        // JRE1.3; JRE1.4 works without Boot13 or this PrivilegedAction.
+        AccessController.doPrivileged(new Boot());
+
+    }
+
+    /**
+     * The privileged part (jdk1.3 compatibility).
+     */
+    public Object run() {
+        JNLPRuntime.setBaseDir(getBaseDir());
+        JNLPRuntime.setSecurityEnabled(null == getOption("-nosecurity"));
+        JNLPRuntime.initialize(true);
+
+        try {
+            new Launcher().launch(getFile());
+        }
+        catch (LaunchException ex) {
+            // default handler prints this
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+
+            fatalError(JNLPRuntime.getMessage("RUnexpected", 
+                        new Object[] {ex.toString(), ex.getStackTrace()[0]} ));
+        }
+
+        return null;
+    }
+
+    private static void fatalError(String message) {
+        System.err.println("netx: "+message);
+        System.exit(1);
+    }
+
+    /**
+     * Returns the about.jnlp file in {java.home}/lib or null if this file
+     * does not exist.
+     */
+    private static String getAboutFile() {
+
+        if (new File(JNLPRuntime.NETX_ABOUT_FILE).exists())
+            return JNLPRuntime.NETX_ABOUT_FILE;
+        else
+            return null;
+    }
+
+    /**
+     * Returns the file to open; does not return if no file was
+     * specified.
+     */
+    private static JNLPFile getFile() throws ParseException, MalformedURLException, IOException {
+    
+        String location = getJNLPFile();
+        
+        // override -jnlp with aboutFile
+        if (getOption("-about") != null) {
+            location = getAboutFile();
+            if (location == null)
+                fatalError("Unable to find about.jnlp in {java.home}/lib/");
+        } else {
+            location = getJNLPFile();
+        }
+
+        if (location == null) {
+            System.out.println(helpMessage);
+            System.exit(1);
+        }
+        
+        if (JNLPRuntime.isDebug())
+            System.out.println(R("BFileLoc")+": "+location);
+
+        URL url = null;
+
+        try {
+            if (new File(location).exists())
+                url = new File(location).toURL(); // Why use file.getCanonicalFile?
+            else 
+                url = new URL(ServiceUtil.getBasicService().getCodeBase(), location);
+        } catch (Exception e) {
+            fatalError("Invalid jnlp file " + location);
+            if (JNLPRuntime.isDebug())
+                e.printStackTrace();
+        }
+        
+        boolean strict = (null != getOption("-strict"));
+
+        JNLPFile file = new JNLPFile(url, strict);
+
+        // add in extra params from command line
+        addProperties(file);
+
+        if (file.isApplet())
+            addParameters(file);
+
+        if (file.isApplication())
+            addArguments(file);
+
+        if (JNLPRuntime.isDebug()) {
+            if (getOption("-arg") != null)
+                if (file.isInstaller() || file.isApplet())
+                    System.out.println(R("BArgsNA"));
+
+            if (getOption("-param") != null)
+                if (file.isApplication())
+                    System.out.println(R("BParamNA"));
+        }
+
+        return file;
+    }
+
+    /**
+     * Add the properties to the JNLP file.
+     */
+    private static void addProperties(JNLPFile file) {
+        String props[] = getOptions("-property");
+        ResourcesDesc resources = file.getResources();
+
+        for (int i=0; i < props.length; i++) {
+            // allows empty property, not sure about validity of that.
+            int equals = props[i].indexOf("=");
+            if (equals == -1)
+                fatalError(R("BBadProp", props[i]));
+
+            String key = props[i].substring(0, equals);
+            String value = props[i].substring(equals+1, props[i].length());
+
+            resources.addResource(new PropertyDesc(key, value));
+        }
+    }
+
+    /**
+     * Add the params to the JNLP file; only call if file is
+     * actually an applet file.
+     */
+    private static void addParameters(JNLPFile file) {
+        String params[] = getOptions("-param");
+        AppletDesc applet = file.getApplet();
+
+        for (int i=0; i < params.length; i++) {
+            // allows empty param, not sure about validity of that.
+            int equals = params[i].indexOf("=");
+            if (equals == -1)
+                fatalError(R("BBadParam", params[i]));
+
+            String name = params[i].substring(0, equals);
+            String value = params[i].substring(equals+1, params[i].length());
+
+            applet.addParameter(name, value);
+        }
+    }
+
+    /**
+     * Add the arguments to the JNLP file; only call if file is
+     * actually an application (not installer).
+     */
+    private static void addArguments(JNLPFile file) {
+        String args[] = getOptions("-arg");  // FYI args also global variable
+        ApplicationDesc app = file.getApplication();
+
+        for (int i=0; i < args.length; i++) {
+            app.addArgument(args[i]);
+        }
+    }
+
+    /**
+     * Gets the JNLP file from the command line arguments, or exits upon error.
+     */
+    private static String getJNLPFile() {
+
+        if (args.length == 0) {
+            System.out.println(helpMessage);
+            System.exit(0);
+        } else if (args.length == 1) {
+            return args[args.length - 1];
+        } else {
+            String lastArg = args[args.length - 1];
+            String secondLastArg = args[args.length - 2];
+
+            if (doubleArgs.indexOf(secondLastArg) == -1) {
+                return lastArg;
+            } else {
+                System.out.println(helpMessage);
+                System.exit(0);
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Return value of the first occurence of the specified
+     * option, or null if the option is not present.  If the
+     * option is a flag (0-parameter) and is present then the
+     * option name is returned.
+     */
+    private static String getOption(String option) {
+        String result[] = getOptions(option);
+
+        if (result.length == 0)
+            return null;
+        else
+            return result[0];
+    }
+
+    /**
+     * Return all the values of the specified option, or an empty
+     * array if the option is not present.  If the option is a
+     * flag (0-parameter) and is present then the option name is
+     * returned once for each occurrence.
+     */
+    private static String[] getOptions(String option) {
+        List result = new ArrayList();
+
+        for (int i=0; i < args.length; i++) {
+            if (option.equals(args[i])) {
+                if (-1 == doubleArgs.indexOf(option))
+                    result.add(option);
+                else
+                    if (i+1 < args.length)
+                        result.add(args[i+1]);
+            }
+
+            if (args[i].startsWith("-") && -1 != doubleArgs.indexOf(args[i]))
+                i++;
+        }
+
+        return (String[]) result.toArray( new String[result.size()] );
+    }
+
+    /**
+     * Return the base dir.  If the base dir parameter is not set
+     * the value is read from JNLPRuntime.NETX_ABOUT_FILE file.
+     * If that file does not exist, an install dialog is displayed 
+     * to select the base directory.
+     */
+    private static File getBaseDir() {
+        if (getOption("-basedir") != null) {
+            File basedir = new File(getOption("-basedir"));
+
+            if (!basedir.exists() || !basedir.isDirectory())
+                fatalError(R("BNoDir", basedir));
+
+            return basedir;
+        }
+
+        // check .netxrc
+        File basedir = JNLPRuntime.getDefaultBaseDir();
+        if (basedir == null)
+            fatalError(R("BNoBase"));
+
+        return basedir;
+    }
+
+}
+
--- a/rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,991 +1,991 @@
-
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.AccessControlContext;
-import java.security.AccessController;
-import java.security.CodeSource;
-import java.security.Permission;
-import java.security.PermissionCollection;
-import java.security.Permissions;
-import java.security.PrivilegedAction;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.TreeSet;
-import java.util.Vector;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-import net.sourceforge.jnlp.ExtensionDesc;
-import net.sourceforge.jnlp.JARDesc;
-import net.sourceforge.jnlp.JNLPFile;
-import net.sourceforge.jnlp.LaunchException;
-import net.sourceforge.jnlp.ParseException;
-import net.sourceforge.jnlp.PluginBridge;
-import net.sourceforge.jnlp.ResourcesDesc;
-import net.sourceforge.jnlp.SecurityDesc;
-import net.sourceforge.jnlp.Version;
-import net.sourceforge.jnlp.cache.CacheUtil;
-import net.sourceforge.jnlp.cache.ResourceTracker;
-import net.sourceforge.jnlp.cache.UpdatePolicy;
-import net.sourceforge.jnlp.security.SecurityWarningDialog;
-import net.sourceforge.jnlp.tools.JarSigner;
-import sun.misc.JarIndex;
-
-/**
- * Classloader that takes it's resources from a JNLP file.  If the
- * JNLP file defines extensions, separate classloaders for these
- * will be created automatically.  Classes are loaded with the
- * security context when the classloader was created.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.20 $ 
- */
-public class JNLPClassLoader extends URLClassLoader {
-
-    // todo: initializePermissions should get the permissions from
-    // extension classes too so that main file classes can load
-    // resources in an extension.
-
-    /** shortcut for resources */
-    private static String R(String key) { return JNLPRuntime.getMessage(key); }
-
-    /** map from JNLPFile url to shared classloader */
-    private static Map urlToLoader = new HashMap(); // never garbage collected!
-
-    /** number of times a classloader with native code is created */
-    private static int nativeCounter = 0;
-
-    /** the directory for native code */
-    private File nativeDir = null; // if set, some native code exists
-
-    /** security context */
-    private AccessControlContext acc = AccessController.getContext();
-
-    /** the permissions for the cached jar files */
-    private List resourcePermissions;
-
-    /** the app */
-    private ApplicationInstance app = null; // here for faster lookup in security manager
-
-    /** list of this, local and global loaders this loader uses */
-    private JNLPClassLoader loaders[] = null; // ..[0]==this
-
-    /** whether to strictly adhere to the spec or not */
-    private boolean strict = true;
-
-    /** loads the resources */
-    private ResourceTracker tracker = new ResourceTracker(true); // prefetch
-
-    /** the update policy for resources */
-    private UpdatePolicy updatePolicy;
-
-    /** the JNLP file */
-    private JNLPFile file;
-
-    /** the resources section */
-    private ResourcesDesc resources;
-
-    /** the security section */
-    private SecurityDesc security;
-    
-    /** Permissions granted by the user during runtime. */
-    private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>();
-
-    /** all jars not yet part of classloader or active */
-    private List available = new ArrayList();
-
-	/** all of the jar files that were verified */
-	private ArrayList<String> verifiedJars = null;
-
-	/** all of the jar files that were not verified */
-	private ArrayList<String> unverifiedJars = null;
-
-	/** the jarsigner tool to verify our jars */
-	private JarSigner js = null;
-
-	private boolean signing = false;
-	
-	/** ArrayList containing jar indexes for various jars available to this classloader */
-	private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>();
-	
-	/** File entries in the jar files available to this classloader */
-	private TreeSet jarEntries = new TreeSet();
-
-    /**
-     * Create a new JNLPClassLoader from the specified file.
-     *
-     * @param file the JNLP file
-     */
-    protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException {
-        super(new URL[0], JNLPClassLoader.class.getClassLoader());
-
-        if (JNLPRuntime.isDebug())
-            System.out.println("New classloader: "+file.getFileLocation());
-
-        this.file = file;
-        this.updatePolicy = policy;
-        this.resources = file.getResources();
-
-        // initialize extensions
-        initializeExtensions();
-
-        // initialize permissions
-        initializePermissions();
-
-        initializeResources();
-
-        setSecurity();
-
-    }
-
-    private void setSecurity() {
-		/**
-		 * When we're trying to load an applet, file.getSecurity() will return
-		 * null since there is no jnlp file to specify permissions. We
-		 * determine security settings here, after trying to verify jars.
-		 */
-		if (file instanceof PluginBridge) {
-			if (signing == true) {
-				this.security = new SecurityDesc(file, 
-					SecurityDesc.ALL_PERMISSIONS,
-					file.getCodeBase().getHost());
-			} else {
-				this.security = new SecurityDesc(file, 
-					SecurityDesc.SANDBOX_PERMISSIONS, 
-					file.getCodeBase().getHost());
-			}
-		} else { //regular jnlp file
-			
-			/**
-			 * If the application is signed, then we set the SecurityDesc to the
-			 * <security> tag in the jnlp file. Note that if an application is
-			 * signed, but there is no <security> tag in the jnlp file, the
-			 * application will get sandbox permissions.
-			 * If the application is unsigned, we ignore the <security> tag and 
-			 * use a sandbox instead. 
-			 */
-			if (signing == true) {
-				this.security = file.getSecurity();
-			} else {
-				this.security = new SecurityDesc(file, 
-						SecurityDesc.SANDBOX_PERMISSIONS, 
-						file.getCodeBase().getHost());
-			}
-		}
-    }
-    
-    /**
-     * Returns a JNLP classloader for the specified JNLP file.
-     *
-     * @param file the file to load classes for
-     * @param policy the update policy to use when downloading resources
-     */
-    public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException {
-        JNLPClassLoader loader = null;
-        URL location = file.getFileLocation();
-
-        if (location != null)
-            loader = (JNLPClassLoader) urlToLoader.get(location);
-
-		try {
-        	if (loader == null)
-            	loader = new JNLPClassLoader(file, policy);
-		} catch (LaunchException e) {
-			throw e;
-		}
-
-        if (file.getInformation().isSharingAllowed())
-            urlToLoader.put(location, loader);
-
-        return loader;
-    }
-
-    /**
-     * Returns a JNLP classloader for the JNLP file at the specified
-     * location. 
-     *
-     * @param location the file's location
-     * @param version the file's version
-     * @param policy the update policy to use when downloading resources
-     */
-    public static JNLPClassLoader getInstance(URL location, Version version, UpdatePolicy policy)
-            throws IOException, ParseException, LaunchException {
-        JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(location);
-
-        if (loader == null)
-            loader = getInstance(new JNLPFile(location, version, false, policy), policy);
-
-        return loader;
-    }
-
-    /**
-     * Load the extensions specified in the JNLP file.
-     */
-    void initializeExtensions() {
-        ExtensionDesc[] ext = resources.getExtensions();
-
-        List loaderList = new ArrayList();
-
-        loaderList.add(this);
-
-		//if (ext != null) {
-        	for (int i=0; i < ext.length; i++) {
-            	try {
-               		JNLPClassLoader loader = getInstance(ext[i].getLocation(), ext[i].getVersion(), updatePolicy);
-                	loaderList.add(loader);
-            	}
-            	catch (Exception ex) {
-                	ex.printStackTrace();
-            	}
-        	}
-		//}
-
-        loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]);
-    }
-
-    /**
-     * Make permission objects for the classpath.
-     */
-    void initializePermissions() {
-        resourcePermissions = new ArrayList();
-
-        JARDesc jars[] = resources.getJARs();
-        for (int i=0; i < jars.length; i++) {
-            Permission p = CacheUtil.getReadPermission(jars[i].getLocation(),
-                                                       jars[i].getVersion());
-
-            if (JNLPRuntime.isDebug()) {
-            	if (p == null)
-            		System.out.println("Unable to add permission for " + jars[i].getLocation());
-            	else
-            		System.out.println("Permission added: " + p.toString());
-            }
-            if (p != null)
-                resourcePermissions.add(p);
-        }
-    }
-
-    /**
-     * Load all of the JARs used in this JNLP file into the
-     * ResourceTracker for downloading.
-     */
-    void initializeResources() throws LaunchException {
-        JARDesc jars[] = resources.getJARs();
-		if (jars == null || jars.length == 0)
-			return;
-		/*
-		if (jars == null || jars.length == 0) {
-			throw new LaunchException(null, null, R("LSFatal"),
-			                    R("LCInit"), R("LFatalVerification"), "No jars!");
-		}
-		*/
-        List initialJars = new ArrayList();
-
-        for (int i=0; i < jars.length; i++) {
-
-            available.add(jars[i]);
-
-            if (jars[i].isEager())
-                initialJars.add(jars[i]); // regardless of part
-
-            tracker.addResource(jars[i].getLocation(),
-                                jars[i].getVersion(), 
-                                jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE
-                               );
-        }
-
-        if (strict)
-            fillInPartJars(initialJars); // add in each initial part's lazy jars
-
-		if (JNLPRuntime.isVerifying()) {
-
-			JarSigner js;
-			waitForJars(initialJars); //download the jars first.
-
-			try {
-				js = verifyJars(initialJars);
-			} catch (Exception e) {
-				//we caught an Exception from the JarSigner class.
-				//Note: one of these exceptions could be from not being able
-				//to read the cacerts or trusted.certs files.
-				e.printStackTrace();
-				throw new LaunchException(null, null, R("LSFatal"),
-					R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo"));
-			}
-
-			//Case when at least one jar has some signing
-			if (js.anyJarsSigned()){
-				signing = true;
-
-				//user does not trust this publisher
-				if (!js.getAlreadyTrustPublisher()) {
-				    checkTrustWithUser(js);
-				} else {
-					/**
-					 * If the user trusts this publisher (i.e. the publisher's certificate
-					 * is in the user's trusted.certs file), we do not show any dialogs.
-					 */
-				}
-			} else {
-
-				signing = false;
-				//otherwise this jar is simply unsigned -- make sure to ask
-				//for permission on certain actions
-			}
-		}
-
-        activateJars(initialJars);
-    }
-
-    private void checkTrustWithUser(JarSigner js) throws LaunchException {
-        if (!js.getRootInCacerts()) { //root cert is not in cacerts
-            boolean b = SecurityWarningDialog.showCertWarningDialog(
-                SecurityWarningDialog.AccessType.UNVERIFIED, file, js);
-            if (!b)
-                throw new LaunchException(null, null, R("LSFatal"), 
-                    R("LCLaunching"), R("LNotVerified"), "");
-        } else if (js.getRootInCacerts()) { //root cert is in cacerts
-            boolean b = false;
-            if (js.noSigningIssues())
-                b = SecurityWarningDialog.showCertWarningDialog(
-                        SecurityWarningDialog.AccessType.VERIFIED, file, js);
-            else if (!js.noSigningIssues())
-                b = SecurityWarningDialog.showCertWarningDialog(
-                        SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js);
-            if (!b)
-                throw new LaunchException(null, null, R("LSFatal"),
-                    R("LCLaunching"), R("LCancelOnUserRequest"), "");
-        }
-    }
-
-    /**
-     * Add applet's codebase URL.  This allows compatibility with
-     * applets that load resources from their codebase instead of
-     * through JARs, but can slow down resource loading.  Resources
-     * loaded from the codebase are not cached.
-     */
-    public void enableCodeBase() {
-        addURL( file.getCodeBase() ); // nothing happens if called more that once?
-    }
-
-    /**
-     * Sets the JNLP app this group is for; can only be called once.
-     */
-    public void setApplication(ApplicationInstance app) {
-        if (this.app != null) {
-            if (JNLPRuntime.isDebug()) {
-                Exception ex = new IllegalStateException("Application can only be set once");
-                ex.printStackTrace();
-            }
-            return;
-        }
-
-        this.app = app;
-    }
-
-    /**
-     * Returns the JNLP app for this classloader
-     */
-    public ApplicationInstance getApplication() {
-        return app;
-    }
-
-    /**
-     * Returns the JNLP file the classloader was created from.
-     */
-    public JNLPFile getJNLPFile() {
-        return file;
-    }
-
-    /**
-     * Returns the permissions for the CodeSource.
-     */
-    protected PermissionCollection getPermissions(CodeSource cs) {
-        Permissions result = new Permissions();
-
-        // should check for extensions or boot, automatically give all
-        // access w/o security dialog once we actually check certificates.
-
-        // copy security permissions from SecurityDesc element
-		if (security != null) {
-        	Enumeration e = security.getPermissions().elements();
-        	while (e.hasMoreElements())
-            	result.add((Permission) e.nextElement());
-		}
-
-        // add in permission to read the cached JAR files
-        for (int i=0; i < resourcePermissions.size(); i++)
-            result.add((Permission) resourcePermissions.get(i));
-
-        // add in the permissions that the user granted.
-        for (int i=0; i < runtimePermissions.size(); i++)
-        	result.add(runtimePermissions.get(i));
-
-        return result;
-    }
-
-    protected void addPermission(Permission p) {
-    	runtimePermissions.add(p);
-    }
-    
-    /**
-     * Adds to the specified list of JARS any other JARs that need
-     * to be loaded at the same time as the JARs specified (ie, are
-     * in the same part).
-     */
-    protected void fillInPartJars(List jars) {
-        for (int i=0; i < jars.size(); i++) {
-            String part = ((JARDesc) jars.get(i)).getPart();
-
-            for (int a=0; a < available.size(); a++) {
-                JARDesc jar = (JARDesc) available.get(a);
-
-                if (part != null && part.equals(jar.getPart()))
-                    if (!jars.contains(jar))
-                        jars.add(jar);
-            }
-        }
-    }
-
-    /**
-     * Ensures that the list of jars have all been transferred, and
-     * makes them available to the classloader.  If a jar contains
-     * native code, the libraries will be extracted and placed in
-     * the path.
-     *
-     * @param jars the list of jars to load
-     */
-    protected void activateJars(final List jars) {
-        PrivilegedAction activate = new PrivilegedAction() {
-
-            public Object run() {
-                // transfer the Jars
-                waitForJars(jars);
-
-                for (int i=0; i < jars.size(); i++) {
-                    JARDesc jar = (JARDesc) jars.get(i);
-
-                    available.remove(jar);
-
-                    // add jar
-                    File localFile = tracker.getCacheFile(jar.getLocation());
-                    try {
-                        URL location = jar.getLocation(); // non-cacheable, use source location
-                        if (localFile != null) {
-                            location = localFile.toURL(); // cached file
-                            
-                            // This is really not the best way.. but we need some way for 
-                            // PluginAppletViewer::getCachedImageRef() to check if the image 
-                            // is available locally, and it cannot use getResources() because 
-                            // that prefetches the resource, which confuses MediaTracker.waitForAll() 
-                            // which does a wait(), waiting for notification (presumably 
-                            // thrown after a resource is fetched). This bug manifests itself
-                            // particularly when using The FileManager applet from Webmin.
-                            
-                            JarFile jarFile = new JarFile(localFile);
-                            Enumeration e = jarFile.entries();
-                            while (e.hasMoreElements()) {
-                                
-                                JarEntry je = (JarEntry) e.nextElement();
-                                
-                                // another jar in my jar? it is more likely than you think  
-                                if (je.getName().endsWith(".jar")) {
-                                    // We need to extract that jar so that it can be loaded 
-                                    // (inline loading with "jar:..!/..." path will not work 
-                                    // with standard classloader methods)
-
-                                    String extractedJarLocation = localFile.getParent() + "/" + je.getName();
-                                    File parentDir = new File(extractedJarLocation).getParentFile();
-                                    if (!parentDir.isDirectory() && !parentDir.mkdirs()) {
-                                        throw new RuntimeException(R("RNestedJarExtration"));
-                                    }
-                                    FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation);
-                                    InputStream is = jarFile.getInputStream(je);
-
-                                    byte[] bytes = new byte[1024];
-                                    int read = is.read(bytes);
-                                    while (read > 0) {
-                                        extractedJar.write(bytes, 0, read);
-                                        read = is.read(bytes);
-                                    }
-
-                                    is.close();
-                                    extractedJar.close();
-
-                                    JarSigner signer = new JarSigner();
-                                    signer.verifyJar(extractedJarLocation);
-
-                                    if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) {
-                                        checkTrustWithUser(signer);
-                                    }
-
-                                    try {
-                                        addURL(new URL("file://" + extractedJarLocation));
-                                    } catch (MalformedURLException mfue) {
-                                        if (JNLPRuntime.isDebug())
-                                            System.err.println("Unable to add extracted nested jar to classpath");
-
-                                        mfue.printStackTrace();
-                                    }
-                                }
-
-                                jarEntries.add(je.getName());
-                            }
-
-                        }
-
-                        addURL(location);
-
-                        // there is currently no mechanism to cache files per 
-                        // instance.. so only index cached files
-                        if (localFile != null) {
-                            JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null);
-
-                            if (index != null)
-                                jarIndexes.add(index);
-                        }
-
-                        if (JNLPRuntime.isDebug())
-                            System.err.println("Activate jar: "+location);
-                    }
-                    catch (Exception ex) {
-                        if (JNLPRuntime.isDebug())
-                            ex.printStackTrace();
-                    }
-
-                    if (jar.isNative())
-                        activateNative(jar);
-                }
-
-                return null;
-            }
-        };
-
-        AccessController.doPrivileged(activate, acc);
-    }
-
-    /**
-     * Enable the native code contained in a JAR by copying the
-     * native files into the filesystem.  Called in the security
-     * context of the classloader.
-     */
-    protected void activateNative(JARDesc jar) {
-        if (JNLPRuntime.isDebug())
-            System.out.println("Activate native: "+jar.getLocation());
-
-        File localFile = tracker.getCacheFile(jar.getLocation());
-        if (localFile == null)
-            return;
-
-        if (nativeDir == null)
-            nativeDir = getNativeDir();
-
-        try {
-            JarFile jarFile = new JarFile(localFile, false);
-            Enumeration entries = jarFile.entries();
-
-            while (entries.hasMoreElements()) {
-                JarEntry e = (JarEntry) entries.nextElement();
-
-                if (e.isDirectory() || e.getName().indexOf('/') != -1)
-                    continue;
-
-                File outFile = new File(nativeDir, e.getName());
-
-                CacheUtil.streamCopy(jarFile.getInputStream(e),
-                                     new FileOutputStream(outFile));
-            }
-        }
-        catch (IOException ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-        }
-    }
-
-    /**
-     * Return the base directory to store native code files in.
-     * This method does not need to return the same directory across
-     * calls.
-     */
-    protected File getNativeDir() {
-        nativeDir = new File(System.getProperty("java.io.tmpdir") 
-                             + File.separator + "netx-native-" 
-                             + (new Random().nextInt() & 0xFFFF));
-
-        if (!nativeDir.mkdirs()) 
-            return null;
-        else
-            return nativeDir;
-    }
-
-    /**
-     * Return the absolute path to the native library.
-     */
-    protected String findLibrary(String lib) {
-        if (nativeDir == null)
-            return null;
-
-        String syslib = System.mapLibraryName(lib);
-
-        File target = new File(nativeDir, syslib);
-        if (target.exists())
-            return target.toString();
-        else {
-            String result = super.findLibrary(lib);
-            if (result != null)
-                return result;
-
-            return findLibraryExt(lib);
-        }
-    }
-
-    /**
-     * Try to find the library path from another peer classloader.
-     */
-    protected String findLibraryExt(String lib) {
-        for (int i=0; i < loaders.length; i++) {
-            String result = null;
-
-            if (loaders[i] != this)
-                result = loaders[i].findLibrary(lib);
-
-            if (result != null)
-                return result;
-        }
-
-        return null;
-    }
-
-    /**
-     * Wait for a group of JARs, and send download events if there
-     * is a download listener or display a progress window otherwise.
-     *
-     * @param jars the jars
-     */
-    private void waitForJars(List jars) {
-        URL urls[] = new URL[jars.size()];
-
-        for (int i=0; i < jars.size(); i++) {
-            JARDesc jar = (JARDesc) jars.get(i);
-
-            urls[i] = jar.getLocation();
-        }
-
-        CacheUtil.waitForResources(app, tracker, urls, file.getTitle());
-    }
-
-    /**
-	 * Verifies code signing of jars to be used.
-	 *
-	 * @param jars the jars to be verified.
-	 */
-	private JarSigner verifyJars(List<JARDesc> jars) throws Exception {
-	
-		js = new JarSigner();
-		js.verifyJars(jars, tracker);
-		return js;
-	}
-
-    /**
-     * Find the loaded class in this loader or any of its extension loaders.
-     */
-    protected Class findLoadedClassAll(String name) {
-        for (int i=0; i < loaders.length; i++) {
-            Class result = null;
-
-            if (loaders[i] == this)
-                result = super.findLoadedClass(name);
-            else
-                result = loaders[i].findLoadedClassAll(name);
-
-            if (result != null)
-                return result;
-        }
-
-        return null;
-    }
-
-    /**
-     * Find a JAR in the shared 'extension' classloaders, this
-     * classloader, or one of the classloaders for the JNLP file's
-     * extensions.
-     */
-    public Class loadClass(String name) throws ClassNotFoundException {
-
-        Class result = findLoadedClassAll(name);
-
-        // try parent classloader
-        if (result == null) {
-            try {
-                ClassLoader parent = getParent();
-                if (parent == null)
-                    parent = ClassLoader.getSystemClassLoader();
-
-                return parent.loadClass(name);
-            }
-            catch (ClassNotFoundException ex) { }
-        }
-
-        // filter out 'bad' package names like java, javax
-        // validPackage(name);
-
-        // search this and the extension loaders
-        if (result == null)
-            try {
-                result = loadClassExt(name);
-            } catch (ClassNotFoundException cnfe) {
-
-                // Not found in external loader either. As a last resort, look in any available indexes
-
-                // Currently this loads jars directly from the site. We cannot cache it because this 
-                // call is initiated from within the applet, which does not have disk read/write permissions
-                for (JarIndex index: jarIndexes) {
-                    LinkedList<String> jarList = index.get(name.replace('.', '/'));
-
-                    if (jarList != null) {
-                        for (String jarName: jarList) {
-                            JARDesc desc;
-                            try {
-                                desc = new JARDesc(new URL(file.getCodeBase(), jarName),
-                                        null, null, false, true, false, true);
-                            } catch (MalformedURLException mfe) {
-                                throw new ClassNotFoundException(name);
-                            }
-
-                            available.add(desc);
-
-                            tracker.addResource(desc.getLocation(),
-                                    desc.getVersion(), 
-                                    JNLPRuntime.getDefaultUpdatePolicy()
-                            );
-
-                            URL remoteURL;
-                            try {
-                                remoteURL = new URL(file.getCodeBase() + jarName);
-                            } catch (MalformedURLException mfe) {
-                                throw new ClassNotFoundException(name);
-                            }
-
-                            URL u;
-
-                            try {
-                                u = tracker.getCacheURL(remoteURL);
-                            } catch (Exception e) {
-                                throw new ClassNotFoundException(name);
-                            }
-
-                            if (u != null)
-                                addURL(u);
-
-                        }
-
-                        // If it still fails, let it error out                        
-                        result = loadClassExt(name);
-                    }
-                }
-            }
-
-        return result;
-    }
-
-    /**
-     * Find the class in this loader or any of its extension loaders.
-     */
-    protected Class findClass(String name) throws ClassNotFoundException {
-        for (int i=0; i < loaders.length; i++) {
-            try {
-                if (loaders[i] == this)
-                    return super.findClass(name);
-                else
-                    return loaders[i].findClass(name);
-            }
-            catch(ClassNotFoundException ex) { }
-            catch(ClassFormatError cfe) {}
-        }
-
-        throw new ClassNotFoundException(name);
-    }
-
-    /**
-     * Search for the class by incrementally adding resources to the
-     * classloader and its extension classloaders until the resource
-     * is found.
-     */
-    private Class loadClassExt(String name) throws ClassNotFoundException {
-        // make recursive
-        addAvailable();
-
-        // find it
-        try {
-            return findClass(name);
-        }
-        catch(ClassNotFoundException ex) {
-        }
-
-        // add resources until found
-        while (true) {
-            JNLPClassLoader addedTo = addNextResource();
-
-            if (addedTo == null)
-                throw new ClassNotFoundException(name);
-
-            try {
-                return addedTo.findClass(name);
-            }
-            catch(ClassNotFoundException ex) {
-            }
-        }
-    }
-
-    /**
-     * Finds the resource in this, the parent, or the extension
-     * class loaders.
-     */
-    public URL getResource(String name) {
-        URL result = super.getResource(name);
-
-        for (int i=1; i < loaders.length; i++)
-            if (result == null)
-                result = loaders[i].getResource(name);
-
-        return result;
-    }
-
-    /**
-     * Finds the resource in this, the parent, or the extension
-     * class loaders.
-     */
-    public Enumeration findResources(String name) throws IOException {
-        Vector resources = new Vector();
-
-        for (int i=0; i < loaders.length; i++) {
-            Enumeration e;
-
-            if (loaders[i] == this)
-                e = super.findResources(name);
-            else 
-                e = loaders[i].findResources(name);
-
-            while (e.hasMoreElements())
-                resources.add(e.nextElement());
-        }
-
-        return resources.elements();
-    }
-    
-    /**
-     * Returns if the specified resource is available locally from a cached jar
-     * 
-     * @param s The name of the resource
-     * @return Whether or not the resource is available locally
-     */
-    public boolean resourceAvailableLocally(String s) {
-        return jarEntries.contains(s);
-    }
-
-    /**
-     * Adds whatever resources have already been downloaded in the
-     * background.
-     */
-    protected void addAvailable() {
-        // go through available, check tracker for it and all of its
-        // part brothers being available immediately, add them.
-
-        for (int i=1; i < loaders.length; i++) {
-            loaders[i].addAvailable();
-        }
-    }
-
-    /**
-     * Adds the next unused resource to the classloader.  That
-     * resource and all those in the same part will be downloaded
-     * and added to the classloader before returning.  If there are
-     * no more resources to add, the method returns immediately.
-     *
-     * @return the classloader that resources were added to, or null
-     */
-    protected JNLPClassLoader addNextResource() {
-        if (available.size() == 0) {
-            for (int i=1; i < loaders.length; i++) {
-                JNLPClassLoader result = loaders[i].addNextResource();
-
-                if (result != null)
-                    return result;
-            }
-            return null;
-        }
-
-        // add jar
-        List jars = new ArrayList();
-        jars.add(available.get(0));
-
-        fillInPartJars(jars);
-
-		
-		activateJars(jars);
-
-        return this;
-    }
-
-    // this part compatibility with previous classloader
-    /**
-     * @deprecated
-     */
-    public String getExtensionName() {
-        String result = file.getInformation().getTitle();
-
-        if (result == null)
-            result = file.getInformation().getDescription();
-        if (result == null && file.getFileLocation() != null)
-            result = file.getFileLocation().toString();
-        if (result == null && file.getCodeBase() != null)
-            result = file.getCodeBase().toString();
-
-        return result;
-    }
-
-    /**
-     * @deprecated
-     */
-    public String getExtensionHREF() {
-        return file.getFileLocation().toString();
-    }
-
-	public boolean getSigning() {
-		return signing;
-	}
-
-	protected SecurityDesc getSecurity() {
-		return security;
-	}
-}
-
-
+
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.TreeSet;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import net.sourceforge.jnlp.ExtensionDesc;
+import net.sourceforge.jnlp.JARDesc;
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.LaunchException;
+import net.sourceforge.jnlp.ParseException;
+import net.sourceforge.jnlp.PluginBridge;
+import net.sourceforge.jnlp.ResourcesDesc;
+import net.sourceforge.jnlp.SecurityDesc;
+import net.sourceforge.jnlp.Version;
+import net.sourceforge.jnlp.cache.CacheUtil;
+import net.sourceforge.jnlp.cache.ResourceTracker;
+import net.sourceforge.jnlp.cache.UpdatePolicy;
+import net.sourceforge.jnlp.security.SecurityWarningDialog;
+import net.sourceforge.jnlp.tools.JarSigner;
+import sun.misc.JarIndex;
+
+/**
+ * Classloader that takes it's resources from a JNLP file.  If the
+ * JNLP file defines extensions, separate classloaders for these
+ * will be created automatically.  Classes are loaded with the
+ * security context when the classloader was created.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.20 $ 
+ */
+public class JNLPClassLoader extends URLClassLoader {
+
+    // todo: initializePermissions should get the permissions from
+    // extension classes too so that main file classes can load
+    // resources in an extension.
+
+    /** shortcut for resources */
+    private static String R(String key) { return JNLPRuntime.getMessage(key); }
+
+    /** map from JNLPFile url to shared classloader */
+    private static Map urlToLoader = new HashMap(); // never garbage collected!
+
+    /** number of times a classloader with native code is created */
+    private static int nativeCounter = 0;
+
+    /** the directory for native code */
+    private File nativeDir = null; // if set, some native code exists
+
+    /** security context */
+    private AccessControlContext acc = AccessController.getContext();
+
+    /** the permissions for the cached jar files */
+    private List resourcePermissions;
+
+    /** the app */
+    private ApplicationInstance app = null; // here for faster lookup in security manager
+
+    /** list of this, local and global loaders this loader uses */
+    private JNLPClassLoader loaders[] = null; // ..[0]==this
+
+    /** whether to strictly adhere to the spec or not */
+    private boolean strict = true;
+
+    /** loads the resources */
+    private ResourceTracker tracker = new ResourceTracker(true); // prefetch
+
+    /** the update policy for resources */
+    private UpdatePolicy updatePolicy;
+
+    /** the JNLP file */
+    private JNLPFile file;
+
+    /** the resources section */
+    private ResourcesDesc resources;
+
+    /** the security section */
+    private SecurityDesc security;
+    
+    /** Permissions granted by the user during runtime. */
+    private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>();
+
+    /** all jars not yet part of classloader or active */
+    private List available = new ArrayList();
+
+	/** all of the jar files that were verified */
+	private ArrayList<String> verifiedJars = null;
+
+	/** all of the jar files that were not verified */
+	private ArrayList<String> unverifiedJars = null;
+
+	/** the jarsigner tool to verify our jars */
+	private JarSigner js = null;
+
+	private boolean signing = false;
+	
+	/** ArrayList containing jar indexes for various jars available to this classloader */
+	private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>();
+	
+	/** File entries in the jar files available to this classloader */
+	private TreeSet jarEntries = new TreeSet();
+
+    /**
+     * Create a new JNLPClassLoader from the specified file.
+     *
+     * @param file the JNLP file
+     */
+    protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException {
+        super(new URL[0], JNLPClassLoader.class.getClassLoader());
+
+        if (JNLPRuntime.isDebug())
+            System.out.println("New classloader: "+file.getFileLocation());
+
+        this.file = file;
+        this.updatePolicy = policy;
+        this.resources = file.getResources();
+
+        // initialize extensions
+        initializeExtensions();
+
+        // initialize permissions
+        initializePermissions();
+
+        initializeResources();
+
+        setSecurity();
+
+    }
+
+    private void setSecurity() {
+		/**
+		 * When we're trying to load an applet, file.getSecurity() will return
+		 * null since there is no jnlp file to specify permissions. We
+		 * determine security settings here, after trying to verify jars.
+		 */
+		if (file instanceof PluginBridge) {
+			if (signing == true) {
+				this.security = new SecurityDesc(file, 
+					SecurityDesc.ALL_PERMISSIONS,
+					file.getCodeBase().getHost());
+			} else {
+				this.security = new SecurityDesc(file, 
+					SecurityDesc.SANDBOX_PERMISSIONS, 
+					file.getCodeBase().getHost());
+			}
+		} else { //regular jnlp file
+			
+			/**
+			 * If the application is signed, then we set the SecurityDesc to the
+			 * <security> tag in the jnlp file. Note that if an application is
+			 * signed, but there is no <security> tag in the jnlp file, the
+			 * application will get sandbox permissions.
+			 * If the application is unsigned, we ignore the <security> tag and 
+			 * use a sandbox instead. 
+			 */
+			if (signing == true) {
+				this.security = file.getSecurity();
+			} else {
+				this.security = new SecurityDesc(file, 
+						SecurityDesc.SANDBOX_PERMISSIONS, 
+						file.getCodeBase().getHost());
+			}
+		}
+    }
+    
+    /**
+     * Returns a JNLP classloader for the specified JNLP file.
+     *
+     * @param file the file to load classes for
+     * @param policy the update policy to use when downloading resources
+     */
+    public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException {
+        JNLPClassLoader loader = null;
+        URL location = file.getFileLocation();
+
+        if (location != null)
+            loader = (JNLPClassLoader) urlToLoader.get(location);
+
+		try {
+        	if (loader == null)
+            	loader = new JNLPClassLoader(file, policy);
+		} catch (LaunchException e) {
+			throw e;
+		}
+
+        if (file.getInformation().isSharingAllowed())
+            urlToLoader.put(location, loader);
+
+        return loader;
+    }
+
+    /**
+     * Returns a JNLP classloader for the JNLP file at the specified
+     * location. 
+     *
+     * @param location the file's location
+     * @param version the file's version
+     * @param policy the update policy to use when downloading resources
+     */
+    public static JNLPClassLoader getInstance(URL location, Version version, UpdatePolicy policy)
+            throws IOException, ParseException, LaunchException {
+        JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(location);
+
+        if (loader == null)
+            loader = getInstance(new JNLPFile(location, version, false, policy), policy);
+
+        return loader;
+    }
+
+    /**
+     * Load the extensions specified in the JNLP file.
+     */
+    void initializeExtensions() {
+        ExtensionDesc[] ext = resources.getExtensions();
+
+        List loaderList = new ArrayList();
+
+        loaderList.add(this);
+
+		//if (ext != null) {
+        	for (int i=0; i < ext.length; i++) {
+            	try {
+               		JNLPClassLoader loader = getInstance(ext[i].getLocation(), ext[i].getVersion(), updatePolicy);
+                	loaderList.add(loader);
+            	}
+            	catch (Exception ex) {
+                	ex.printStackTrace();
+            	}
+        	}
+		//}
+
+        loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]);
+    }
+
+    /**
+     * Make permission objects for the classpath.
+     */
+    void initializePermissions() {
+        resourcePermissions = new ArrayList();
+
+        JARDesc jars[] = resources.getJARs();
+        for (int i=0; i < jars.length; i++) {
+            Permission p = CacheUtil.getReadPermission(jars[i].getLocation(),
+                                                       jars[i].getVersion());
+
+            if (JNLPRuntime.isDebug()) {
+            	if (p == null)
+            		System.out.println("Unable to add permission for " + jars[i].getLocation());
+            	else
+            		System.out.println("Permission added: " + p.toString());
+            }
+            if (p != null)
+                resourcePermissions.add(p);
+        }
+    }
+
+    /**
+     * Load all of the JARs used in this JNLP file into the
+     * ResourceTracker for downloading.
+     */
+    void initializeResources() throws LaunchException {
+        JARDesc jars[] = resources.getJARs();
+		if (jars == null || jars.length == 0)
+			return;
+		/*
+		if (jars == null || jars.length == 0) {
+			throw new LaunchException(null, null, R("LSFatal"),
+			                    R("LCInit"), R("LFatalVerification"), "No jars!");
+		}
+		*/
+        List initialJars = new ArrayList();
+
+        for (int i=0; i < jars.length; i++) {
+
+            available.add(jars[i]);
+
+            if (jars[i].isEager())
+                initialJars.add(jars[i]); // regardless of part
+
+            tracker.addResource(jars[i].getLocation(),
+                                jars[i].getVersion(), 
+                                jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE
+                               );
+        }
+
+        if (strict)
+            fillInPartJars(initialJars); // add in each initial part's lazy jars
+
+		if (JNLPRuntime.isVerifying()) {
+
+			JarSigner js;
+			waitForJars(initialJars); //download the jars first.
+
+			try {
+				js = verifyJars(initialJars);
+			} catch (Exception e) {
+				//we caught an Exception from the JarSigner class.
+				//Note: one of these exceptions could be from not being able
+				//to read the cacerts or trusted.certs files.
+				e.printStackTrace();
+				throw new LaunchException(null, null, R("LSFatal"),
+					R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo"));
+			}
+
+			//Case when at least one jar has some signing
+			if (js.anyJarsSigned()){
+				signing = true;
+
+				//user does not trust this publisher
+				if (!js.getAlreadyTrustPublisher()) {
+				    checkTrustWithUser(js);
+				} else {
+					/**
+					 * If the user trusts this publisher (i.e. the publisher's certificate
+					 * is in the user's trusted.certs file), we do not show any dialogs.
+					 */
+				}
+			} else {
+
+				signing = false;
+				//otherwise this jar is simply unsigned -- make sure to ask
+				//for permission on certain actions
+			}
+		}
+
+        activateJars(initialJars);
+    }
+
+    private void checkTrustWithUser(JarSigner js) throws LaunchException {
+        if (!js.getRootInCacerts()) { //root cert is not in cacerts
+            boolean b = SecurityWarningDialog.showCertWarningDialog(
+                SecurityWarningDialog.AccessType.UNVERIFIED, file, js);
+            if (!b)
+                throw new LaunchException(null, null, R("LSFatal"), 
+                    R("LCLaunching"), R("LNotVerified"), "");
+        } else if (js.getRootInCacerts()) { //root cert is in cacerts
+            boolean b = false;
+            if (js.noSigningIssues())
+                b = SecurityWarningDialog.showCertWarningDialog(
+                        SecurityWarningDialog.AccessType.VERIFIED, file, js);
+            else if (!js.noSigningIssues())
+                b = SecurityWarningDialog.showCertWarningDialog(
+                        SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js);
+            if (!b)
+                throw new LaunchException(null, null, R("LSFatal"),
+                    R("LCLaunching"), R("LCancelOnUserRequest"), "");
+        }
+    }
+
+    /**
+     * Add applet's codebase URL.  This allows compatibility with
+     * applets that load resources from their codebase instead of
+     * through JARs, but can slow down resource loading.  Resources
+     * loaded from the codebase are not cached.
+     */
+    public void enableCodeBase() {
+        addURL( file.getCodeBase() ); // nothing happens if called more that once?
+    }
+
+    /**
+     * Sets the JNLP app this group is for; can only be called once.
+     */
+    public void setApplication(ApplicationInstance app) {
+        if (this.app != null) {
+            if (JNLPRuntime.isDebug()) {
+                Exception ex = new IllegalStateException("Application can only be set once");
+                ex.printStackTrace();
+            }
+            return;
+        }
+
+        this.app = app;
+    }
+
+    /**
+     * Returns the JNLP app for this classloader
+     */
+    public ApplicationInstance getApplication() {
+        return app;
+    }
+
+    /**
+     * Returns the JNLP file the classloader was created from.
+     */
+    public JNLPFile getJNLPFile() {
+        return file;
+    }
+
+    /**
+     * Returns the permissions for the CodeSource.
+     */
+    protected PermissionCollection getPermissions(CodeSource cs) {
+        Permissions result = new Permissions();
+
+        // should check for extensions or boot, automatically give all
+        // access w/o security dialog once we actually check certificates.
+
+        // copy security permissions from SecurityDesc element
+		if (security != null) {
+        	Enumeration e = security.getPermissions().elements();
+        	while (e.hasMoreElements())
+            	result.add((Permission) e.nextElement());
+		}
+
+        // add in permission to read the cached JAR files
+        for (int i=0; i < resourcePermissions.size(); i++)
+            result.add((Permission) resourcePermissions.get(i));
+
+        // add in the permissions that the user granted.
+        for (int i=0; i < runtimePermissions.size(); i++)
+        	result.add(runtimePermissions.get(i));
+
+        return result;
+    }
+
+    protected void addPermission(Permission p) {
+    	runtimePermissions.add(p);
+    }
+    
+    /**
+     * Adds to the specified list of JARS any other JARs that need
+     * to be loaded at the same time as the JARs specified (ie, are
+     * in the same part).
+     */
+    protected void fillInPartJars(List jars) {
+        for (int i=0; i < jars.size(); i++) {
+            String part = ((JARDesc) jars.get(i)).getPart();
+
+            for (int a=0; a < available.size(); a++) {
+                JARDesc jar = (JARDesc) available.get(a);
+
+                if (part != null && part.equals(jar.getPart()))
+                    if (!jars.contains(jar))
+                        jars.add(jar);
+            }
+        }
+    }
+
+    /**
+     * Ensures that the list of jars have all been transferred, and
+     * makes them available to the classloader.  If a jar contains
+     * native code, the libraries will be extracted and placed in
+     * the path.
+     *
+     * @param jars the list of jars to load
+     */
+    protected void activateJars(final List jars) {
+        PrivilegedAction activate = new PrivilegedAction() {
+
+            public Object run() {
+                // transfer the Jars
+                waitForJars(jars);
+
+                for (int i=0; i < jars.size(); i++) {
+                    JARDesc jar = (JARDesc) jars.get(i);
+
+                    available.remove(jar);
+
+                    // add jar
+                    File localFile = tracker.getCacheFile(jar.getLocation());
+                    try {
+                        URL location = jar.getLocation(); // non-cacheable, use source location
+                        if (localFile != null) {
+                            location = localFile.toURL(); // cached file
+                            
+                            // This is really not the best way.. but we need some way for 
+                            // PluginAppletViewer::getCachedImageRef() to check if the image 
+                            // is available locally, and it cannot use getResources() because 
+                            // that prefetches the resource, which confuses MediaTracker.waitForAll() 
+                            // which does a wait(), waiting for notification (presumably 
+                            // thrown after a resource is fetched). This bug manifests itself
+                            // particularly when using The FileManager applet from Webmin.
+                            
+                            JarFile jarFile = new JarFile(localFile);
+                            Enumeration e = jarFile.entries();
+                            while (e.hasMoreElements()) {
+                                
+                                JarEntry je = (JarEntry) e.nextElement();
+                                
+                                // another jar in my jar? it is more likely than you think  
+                                if (je.getName().endsWith(".jar")) {
+                                    // We need to extract that jar so that it can be loaded 
+                                    // (inline loading with "jar:..!/..." path will not work 
+                                    // with standard classloader methods)
+
+                                    String extractedJarLocation = localFile.getParent() + "/" + je.getName();
+                                    File parentDir = new File(extractedJarLocation).getParentFile();
+                                    if (!parentDir.isDirectory() && !parentDir.mkdirs()) {
+                                        throw new RuntimeException(R("RNestedJarExtration"));
+                                    }
+                                    FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation);
+                                    InputStream is = jarFile.getInputStream(je);
+
+                                    byte[] bytes = new byte[1024];
+                                    int read = is.read(bytes);
+                                    while (read > 0) {
+                                        extractedJar.write(bytes, 0, read);
+                                        read = is.read(bytes);
+                                    }
+
+                                    is.close();
+                                    extractedJar.close();
+
+                                    JarSigner signer = new JarSigner();
+                                    signer.verifyJar(extractedJarLocation);
+
+                                    if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) {
+                                        checkTrustWithUser(signer);
+                                    }
+
+                                    try {
+                                        addURL(new URL("file://" + extractedJarLocation));
+                                    } catch (MalformedURLException mfue) {
+                                        if (JNLPRuntime.isDebug())
+                                            System.err.println("Unable to add extracted nested jar to classpath");
+
+                                        mfue.printStackTrace();
+                                    }
+                                }
+
+                                jarEntries.add(je.getName());
+                            }
+
+                        }
+
+                        addURL(location);
+
+                        // there is currently no mechanism to cache files per 
+                        // instance.. so only index cached files
+                        if (localFile != null) {
+                            JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null);
+
+                            if (index != null)
+                                jarIndexes.add(index);
+                        }
+
+                        if (JNLPRuntime.isDebug())
+                            System.err.println("Activate jar: "+location);
+                    }
+                    catch (Exception ex) {
+                        if (JNLPRuntime.isDebug())
+                            ex.printStackTrace();
+                    }
+
+                    if (jar.isNative())
+                        activateNative(jar);
+                }
+
+                return null;
+            }
+        };
+
+        AccessController.doPrivileged(activate, acc);
+    }
+
+    /**
+     * Enable the native code contained in a JAR by copying the
+     * native files into the filesystem.  Called in the security
+     * context of the classloader.
+     */
+    protected void activateNative(JARDesc jar) {
+        if (JNLPRuntime.isDebug())
+            System.out.println("Activate native: "+jar.getLocation());
+
+        File localFile = tracker.getCacheFile(jar.getLocation());
+        if (localFile == null)
+            return;
+
+        if (nativeDir == null)
+            nativeDir = getNativeDir();
+
+        try {
+            JarFile jarFile = new JarFile(localFile, false);
+            Enumeration entries = jarFile.entries();
+
+            while (entries.hasMoreElements()) {
+                JarEntry e = (JarEntry) entries.nextElement();
+
+                if (e.isDirectory() || e.getName().indexOf('/') != -1)
+                    continue;
+
+                File outFile = new File(nativeDir, e.getName());
+
+                CacheUtil.streamCopy(jarFile.getInputStream(e),
+                                     new FileOutputStream(outFile));
+            }
+        }
+        catch (IOException ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+        }
+    }
+
+    /**
+     * Return the base directory to store native code files in.
+     * This method does not need to return the same directory across
+     * calls.
+     */
+    protected File getNativeDir() {
+        nativeDir = new File(System.getProperty("java.io.tmpdir") 
+                             + File.separator + "netx-native-" 
+                             + (new Random().nextInt() & 0xFFFF));
+
+        if (!nativeDir.mkdirs()) 
+            return null;
+        else
+            return nativeDir;
+    }
+
+    /**
+     * Return the absolute path to the native library.
+     */
+    protected String findLibrary(String lib) {
+        if (nativeDir == null)
+            return null;
+
+        String syslib = System.mapLibraryName(lib);
+
+        File target = new File(nativeDir, syslib);
+        if (target.exists())
+            return target.toString();
+        else {
+            String result = super.findLibrary(lib);
+            if (result != null)
+                return result;
+
+            return findLibraryExt(lib);
+        }
+    }
+
+    /**
+     * Try to find the library path from another peer classloader.
+     */
+    protected String findLibraryExt(String lib) {
+        for (int i=0; i < loaders.length; i++) {
+            String result = null;
+
+            if (loaders[i] != this)
+                result = loaders[i].findLibrary(lib);
+
+            if (result != null)
+                return result;
+        }
+
+        return null;
+    }
+
+    /**
+     * Wait for a group of JARs, and send download events if there
+     * is a download listener or display a progress window otherwise.
+     *
+     * @param jars the jars
+     */
+    private void waitForJars(List jars) {
+        URL urls[] = new URL[jars.size()];
+
+        for (int i=0; i < jars.size(); i++) {
+            JARDesc jar = (JARDesc) jars.get(i);
+
+            urls[i] = jar.getLocation();
+        }
+
+        CacheUtil.waitForResources(app, tracker, urls, file.getTitle());
+    }
+
+    /**
+	 * Verifies code signing of jars to be used.
+	 *
+	 * @param jars the jars to be verified.
+	 */
+	private JarSigner verifyJars(List<JARDesc> jars) throws Exception {
+	
+		js = new JarSigner();
+		js.verifyJars(jars, tracker);
+		return js;
+	}
+
+    /**
+     * Find the loaded class in this loader or any of its extension loaders.
+     */
+    protected Class findLoadedClassAll(String name) {
+        for (int i=0; i < loaders.length; i++) {
+            Class result = null;
+
+            if (loaders[i] == this)
+                result = super.findLoadedClass(name);
+            else
+                result = loaders[i].findLoadedClassAll(name);
+
+            if (result != null)
+                return result;
+        }
+
+        return null;
+    }
+
+    /**
+     * Find a JAR in the shared 'extension' classloaders, this
+     * classloader, or one of the classloaders for the JNLP file's
+     * extensions.
+     */
+    public Class loadClass(String name) throws ClassNotFoundException {
+
+        Class result = findLoadedClassAll(name);
+
+        // try parent classloader
+        if (result == null) {
+            try {
+                ClassLoader parent = getParent();
+                if (parent == null)
+                    parent = ClassLoader.getSystemClassLoader();
+
+                return parent.loadClass(name);
+            }
+            catch (ClassNotFoundException ex) { }
+        }
+
+        // filter out 'bad' package names like java, javax
+        // validPackage(name);
+
+        // search this and the extension loaders
+        if (result == null)
+            try {
+                result = loadClassExt(name);
+            } catch (ClassNotFoundException cnfe) {
+
+                // Not found in external loader either. As a last resort, look in any available indexes
+
+                // Currently this loads jars directly from the site. We cannot cache it because this 
+                // call is initiated from within the applet, which does not have disk read/write permissions
+                for (JarIndex index: jarIndexes) {
+                    LinkedList<String> jarList = index.get(name.replace('.', '/'));
+
+                    if (jarList != null) {
+                        for (String jarName: jarList) {
+                            JARDesc desc;
+                            try {
+                                desc = new JARDesc(new URL(file.getCodeBase(), jarName),
+                                        null, null, false, true, false, true);
+                            } catch (MalformedURLException mfe) {
+                                throw new ClassNotFoundException(name);
+                            }
+
+                            available.add(desc);
+
+                            tracker.addResource(desc.getLocation(),
+                                    desc.getVersion(), 
+                                    JNLPRuntime.getDefaultUpdatePolicy()
+                            );
+
+                            URL remoteURL;
+                            try {
+                                remoteURL = new URL(file.getCodeBase() + jarName);
+                            } catch (MalformedURLException mfe) {
+                                throw new ClassNotFoundException(name);
+                            }
+
+                            URL u;
+
+                            try {
+                                u = tracker.getCacheURL(remoteURL);
+                            } catch (Exception e) {
+                                throw new ClassNotFoundException(name);
+                            }
+
+                            if (u != null)
+                                addURL(u);
+
+                        }
+
+                        // If it still fails, let it error out                        
+                        result = loadClassExt(name);
+                    }
+                }
+            }
+
+        return result;
+    }
+
+    /**
+     * Find the class in this loader or any of its extension loaders.
+     */
+    protected Class findClass(String name) throws ClassNotFoundException {
+        for (int i=0; i < loaders.length; i++) {
+            try {
+                if (loaders[i] == this)
+                    return super.findClass(name);
+                else
+                    return loaders[i].findClass(name);
+            }
+            catch(ClassNotFoundException ex) { }
+            catch(ClassFormatError cfe) {}
+        }
+
+        throw new ClassNotFoundException(name);
+    }
+
+    /**
+     * Search for the class by incrementally adding resources to the
+     * classloader and its extension classloaders until the resource
+     * is found.
+     */
+    private Class loadClassExt(String name) throws ClassNotFoundException {
+        // make recursive
+        addAvailable();
+
+        // find it
+        try {
+            return findClass(name);
+        }
+        catch(ClassNotFoundException ex) {
+        }
+
+        // add resources until found
+        while (true) {
+            JNLPClassLoader addedTo = addNextResource();
+
+            if (addedTo == null)
+                throw new ClassNotFoundException(name);
+
+            try {
+                return addedTo.findClass(name);
+            }
+            catch(ClassNotFoundException ex) {
+            }
+        }
+    }
+
+    /**
+     * Finds the resource in this, the parent, or the extension
+     * class loaders.
+     */
+    public URL getResource(String name) {
+        URL result = super.getResource(name);
+
+        for (int i=1; i < loaders.length; i++)
+            if (result == null)
+                result = loaders[i].getResource(name);
+
+        return result;
+    }
+
+    /**
+     * Finds the resource in this, the parent, or the extension
+     * class loaders.
+     */
+    public Enumeration findResources(String name) throws IOException {
+        Vector resources = new Vector();
+
+        for (int i=0; i < loaders.length; i++) {
+            Enumeration e;
+
+            if (loaders[i] == this)
+                e = super.findResources(name);
+            else 
+                e = loaders[i].findResources(name);
+
+            while (e.hasMoreElements())
+                resources.add(e.nextElement());
+        }
+
+        return resources.elements();
+    }
+    
+    /**
+     * Returns if the specified resource is available locally from a cached jar
+     * 
+     * @param s The name of the resource
+     * @return Whether or not the resource is available locally
+     */
+    public boolean resourceAvailableLocally(String s) {
+        return jarEntries.contains(s);
+    }
+
+    /**
+     * Adds whatever resources have already been downloaded in the
+     * background.
+     */
+    protected void addAvailable() {
+        // go through available, check tracker for it and all of its
+        // part brothers being available immediately, add them.
+
+        for (int i=1; i < loaders.length; i++) {
+            loaders[i].addAvailable();
+        }
+    }
+
+    /**
+     * Adds the next unused resource to the classloader.  That
+     * resource and all those in the same part will be downloaded
+     * and added to the classloader before returning.  If there are
+     * no more resources to add, the method returns immediately.
+     *
+     * @return the classloader that resources were added to, or null
+     */
+    protected JNLPClassLoader addNextResource() {
+        if (available.size() == 0) {
+            for (int i=1; i < loaders.length; i++) {
+                JNLPClassLoader result = loaders[i].addNextResource();
+
+                if (result != null)
+                    return result;
+            }
+            return null;
+        }
+
+        // add jar
+        List jars = new ArrayList();
+        jars.add(available.get(0));
+
+        fillInPartJars(jars);
+
+		
+		activateJars(jars);
+
+        return this;
+    }
+
+    // this part compatibility with previous classloader
+    /**
+     * @deprecated
+     */
+    public String getExtensionName() {
+        String result = file.getInformation().getTitle();
+
+        if (result == null)
+            result = file.getInformation().getDescription();
+        if (result == null && file.getFileLocation() != null)
+            result = file.getFileLocation().toString();
+        if (result == null && file.getCodeBase() != null)
+            result = file.getCodeBase().toString();
+
+        return result;
+    }
+
+    /**
+     * @deprecated
+     */
+    public String getExtensionHREF() {
+        return file.getFileLocation().toString();
+    }
+
+	public boolean getSigning() {
+		return signing;
+	}
+
+	protected SecurityDesc getSecurity() {
+		return security;
+	}
+}
+
+
--- a/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,559 +1,559 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.io.*;
-import java.awt.*;
-import java.text.*;
-import java.util.*;
-import java.util.List;
-import java.security.*;
-import javax.jnlp.*;
-
-import net.sourceforge.jnlp.*;
-import net.sourceforge.jnlp.cache.*;
-import net.sourceforge.jnlp.services.*;
-import net.sourceforge.jnlp.util.*;
-
-
-/**
- * Configure and access the runtime environment.  This class
- * stores global jnlp properties such as default download
- * indicators, the install/base directory, the default resource
- * update policy, etc.  Some settings, such as the base directory,
- * cannot be changed once the runtime has been initialized.<p>
- *
- * The JNLP runtime can be locked to prevent further changes to
- * the runtime environment except by a specified class.  If set,
- * only instances of the <i>exit class</i> can exit the JVM or
- * change the JNLP runtime settings once the runtime has been
- * initialized.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.19 $
- */
-public class JNLPRuntime {
-
-    static {
-        loadResources();
-    }
-
-    /** the localized resource strings */
-    private static ResourceBundle resources;
-
-    /** the security manager */
-    private static JNLPSecurityManager security;
-
-    /** the security policy */
-    private static JNLPPolicy policy;
-
-    /** the base dir for cache, etc */
-    private static File baseDir;
-
-    /** a default launch handler */
-    private static LaunchHandler handler = null;
-
-    /** default download indicator */
-    private static DownloadIndicator indicator = null;
-
-    /** update policy that controls when to check for updates */
-    private static UpdatePolicy updatePolicy = UpdatePolicy.ALWAYS;
-
-    /** netx window icon */
-    private static Image windowIcon = null;
-
-    /** whether initialized */
-    private static boolean initialized = false;
-
-    /** whether netx is in command-line mode (headless) */
-    private static boolean headless = false;
-
-	/** whether we'll be checking for jar signing */
-	private static boolean verify = true;
-
-    /** whether the runtime uses security */
-    private static boolean securityEnabled = true;
-
-    /** whether debug mode is on */
-    private static boolean debug = false; // package access by Boot
-    
-    /** mutex to wait on, for initialization */
-    public static Object initMutex = new Object();
-
-    /** set to true if this is a webstart application. */
-    private static boolean isWebstartApplication; 
-    
-    /** set to false to indicate another JVM should not be spawned, even if necessary */
-    private static boolean forksAllowed = true;
-
-    /** contains the arguments passed to the jnlp runtime */
-    private static List<String> initialArguments;
-
-    /** Username */
-    public static final String USER = System.getProperty("user.name");
-
-    /** User's home directory */
-    public static final String HOME_DIR = System.getProperty("user.home");
-
-    /** the ~/.netxrc file containing netx settings */
-    public static final String NETXRC_FILE = HOME_DIR + File.separator + ".netxrc";
-
-    /** the ~/.netx directory containing user-specific data */
-    public static final String NETX_DIR = HOME_DIR + File.separator + ".netx";
-
-    /** the ~/.netx/security directory containing security related information */
-    public static final String SECURITY_DIR = NETX_DIR + File.separator + "security";
-
-    /** the ~/.netx/security/trusted.certs file containing trusted certificates */
-    public static final String CERTIFICATES_FILE = SECURITY_DIR + File.separator + "trusted.certs";
-
-    /** the /tmp/ directory used for temporary files */
-    public static final String TMP_DIR = System.getProperty("java.io.tmpdir");
-
-    /**
-     * the /tmp/$USER/netx/locks/ directory containing locks for single instance
-     * applications
-     */
-    public static final String LOCKS_DIR = TMP_DIR + File.separator + USER + File.separator
-            + "netx" + File.separator + "locks";
-
-    /** the java.home directory */
-    public static final String JAVA_HOME_DIR = System.getProperty("java.home");
-    
-    /** the JNLP file to open to display the network-based about window */
-    public static final String NETX_ABOUT_FILE = JAVA_HOME_DIR + File.separator + "lib"
-            + File.separator + "about.jnlp";
-
-    
-    
-    /**
-     * Returns whether the JNLP runtime environment has been
-     * initialized.  Once initialized, some properties such as the
-     * base directory cannot be changed.  Before 
-     */
-    public static boolean isInitialized() {
-        return initialized;
-    }
-
-    /**
-     * Initialize the JNLP runtime environment by installing the
-     * security manager and security policy, initializing the JNLP
-     * standard services, etc.<p>
-     *
-     * This method cannot be called more than once.  Once
-     * initialized, methods that alter the runtime can only be
-     * called by the exit class.<p>
-     *
-     * @param isApplication is true if a webstart application is being initialized
-     *
-     * @throws IllegalStateException if the runtime was previously initialized
-     */
-    public static void initialize(boolean isApplication) throws IllegalStateException {
-        checkInitialized();
-     
-        isWebstartApplication = isApplication;
-
-        if (headless == false)
-            checkHeadless();
-
-        if (!headless && windowIcon == null)
-            loadWindowIcon();
-
-        if (!headless && indicator == null)
-            indicator = new DefaultDownloadIndicator();
-
-        if (handler == null)
-            handler = new DefaultLaunchHandler();
-
-        if (baseDir == null)
-            baseDir = getDefaultBaseDir();
-
-        if (baseDir == null)
-            throw new IllegalStateException(JNLPRuntime.getMessage("BNoBase"));
-
-        ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start
-	
-        policy = new JNLPPolicy();
-        security = new JNLPSecurityManager(); // side effect: create JWindow
-
-        if (securityEnabled) {
-            Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy
-            System.setSecurityManager(security);
-        }
-
-        initialized = true;
-    }
-
-    /**
-     * Returns true if a webstart application has been initialized, and false
-     * for a plugin applet.
-     */
-    public static boolean isWebstartApplication() {
-        return isWebstartApplication;
-    }
-
-    /**
-     * Returns the window icon.
-     */
-    public static Image getWindowIcon() {
-        return windowIcon;
-    }
-
-    /**
-     * Sets the window icon that is displayed in Java applications
-     * and applets instead of the default Java icon.
-     *
-     * @throws IllegalStateException if caller is not the exit class
-     */
-    public static void setWindowIcon(Image image) {
-        checkExitClass();
-        windowIcon = image;
-    }
-
-    /**
-     * Returns whether the JNLP client will use any AWT/Swing
-     * components.
-     */
-    public static boolean isHeadless() {
-        return headless;
-    }
-
-	/**
-	 * Returns whether we are verifying code signing.
-	 */
-	public static boolean isVerifying() {
-		return verify;
-	}
-    /**
-     * Sets whether the JNLP client will use any AWT/Swing
-     * components.  In headless mode, client features that use the
-     * AWT are disabled such that the client can be used in
-     * headless mode (<code>java.awt.headless=true</code>).
-     *
-     * @throws IllegalStateException if the runtime was previously initialized
-     */
-    public static void setHeadless(boolean enabled) {
-        checkInitialized();
-        headless = enabled;
-    }
-
-   /**
-	* Sets whether we will verify code signing.
-	* @throws IllegalStateException if the runtime was previously initialized
-	*/
-    public static void setVerify(boolean enabled) {
-		checkInitialized();
-		verify = enabled;
-    }
-
-    /**
-     * Return the base directory containing the cache, persistence
-     * store, etc.
-     */
-    public static File getBaseDir() {
-        return baseDir;
-    }
-
-    /**
-     * Sets the base directory containing the cache, persistence
-     * store, etc.
-     *
-     * @throws IllegalStateException if caller is not the exit class
-     */
-    public static void setBaseDir(File baseDirectory) {
-        checkInitialized();
-        baseDir = baseDirectory;
-    }
-
-    /**
-     * Returns whether the secure runtime environment is enabled.
-     */
-    public static boolean isSecurityEnabled() {
-        return securityEnabled;
-    }
-
-    /**
-     * Sets whether to enable the secure runtime environment.
-     * Disabling security can increase performance for some
-     * applications, and can be used to use netx with other code
-     * that uses its own security manager or policy.
-     *
-     * Disabling security is not recommended and should only be
-     * used if the JNLP files opened are trusted.  This method can
-     * only be called before initalizing the runtime.<p>
-     *
-     * @param enabled whether security should be enabled
-     * @throws IllegalStateException if the runtime is already initialized
-     */
-    public static void setSecurityEnabled(boolean enabled) {
-        checkInitialized();
-        securityEnabled = enabled;
-    }
-
-    /**
-     * Returns the system default base dir for or if not set,
-     * prompts the user for the location.
-     *
-     * @return the base dir, or null if the user canceled the dialog
-     * @throws IOException if there was an io exception
-     */
-    public static File getDefaultBaseDir() {
-        PropertiesFile props = JNLPRuntime.getProperties();
-
-        String baseStr = props.getProperty("basedir");
-        if (baseStr != null)
-            return new File(baseStr);
-
-        String homeDir = HOME_DIR;
-        File baseDir = new File(NETX_DIR);
-        if (homeDir == null || (!baseDir.isDirectory() && !baseDir.mkdir()))
-            return null;
-
-        props.setProperty("basedir", baseDir.toString());
-        props.store();
-
-        return baseDir;
-    }
-
-    /**
-     * Set a class that can exit the JVM; if not set then any class
-     * can exit the JVM.
-     *
-     * @throws IllegalStateException if caller is not the exit class
-     */
-    public static void setExitClass(Class exitClass) {
-        checkExitClass();
-        security.setExitClass(exitClass);
-    }
-    
-    /**
-     * Disables applets from calling exit.
-     * 
-     * Once disabled, exit cannot be re-enabled for the duration of the JVM instance
-     */
-    public static void disableExit() {
-    	security.disableExit();
-    }
-
-    /**
-     * Return the current Application, or null if none can be
-     * determined.
-     */
-    public static ApplicationInstance getApplication() {
-        return security.getApplication();
-    }
-
-    /**
-     * Return a PropertiesFile object backed by the runtime's
-     * properties file.
-     */
-    public static PropertiesFile getProperties() {
-        File netxrc = new File(NETXRC_FILE);
-        return new PropertiesFile(netxrc);
-    }
-
-    /**
-     * Return whether debug statements for the JNLP client code
-     * should be printed.
-     */
-    public static boolean isDebug() {
-        return debug;
-    }
-
-    /**
-     * Sets whether debug statements for the JNLP client code
-     * should be printed to the standard output.
-     *
-     * @throws IllegalStateException if caller is not the exit class
-     */
-    public static void setDebug(boolean enabled) {
-        checkExitClass();
-        debug = enabled;
-    }
-
-    /**
-     * Sets the default update policy.
-     *
-     * @throws IllegalStateException if caller is not the exit class
-     */
-    public static void setDefaultUpdatePolicy(UpdatePolicy policy) {
-        checkExitClass();
-        updatePolicy = policy;
-    }
-
-    /**
-     * Returns the default update policy.
-     */
-    public static UpdatePolicy getDefaultUpdatePolicy() {
-        return updatePolicy;
-    }
-
-    /**
-     * Sets the default launch handler.
-     */
-    public static void setDefaultLaunchHandler(LaunchHandler handler) {
-        checkExitClass();
-        JNLPRuntime.handler = handler;
-    }
-
-    /**
-     * Returns the default launch handler.
-     */
-    public static LaunchHandler getDefaultLaunchHandler() {
-        return handler;
-    }
-
-    /**
-     * Sets the default download indicator.
-     *
-     * @throws IllegalStateException if caller is not the exit class
-     */
-    public static void setDefaultDownloadIndicator(DownloadIndicator indicator) {
-        checkExitClass();
-        JNLPRuntime.indicator = indicator;
-    }
-
-    /**
-     * Returns the default download indicator.
-     */
-    public static DownloadIndicator getDefaultDownloadIndicator() {
-        return indicator;
-    }
-
-    /**
-     * Returns the localized resource string identified by the
-     * specified key.  If the message is empty, a null is
-     * returned.
-     */
-    public static String getMessage(String key) {
-        try {
-            String result = resources.getString(key);
-            if (result.length() == 0)
-                return null;
-            else
-                return result;
-        }
-        catch (Exception ex) {
-            if (!key.equals("RNoResource"))
-                return getMessage("RNoResource", new Object[] {key});
-            else
-                return "Missing resource: "+key;
-        }
-    }
-
-    /**
-     * Returns the localized resource string using the specified
-     * arguments.
-     *
-     * @param args the formatting arguments to the resource string
-     */
-    public static String getMessage(String key, Object args[]) {
-        return MessageFormat.format(getMessage(key), args);
-    }
-
-    /**
-     * Returns true if the current runtime will fork
-     */
-    public static boolean getForksAllowed() {
-        return forksAllowed;
-    }
-    
-    public static void setForksAllowed(boolean value) {
-        checkInitialized();
-        forksAllowed = value;
-    }
-    
-    /**
-     * Throws an exception if called when the runtime is
-     * already initialized.
-     */
-    private static void checkInitialized() {
-        if (initialized)
-            throw new IllegalStateException("JNLPRuntime already initialized.");
-    }
-
-    /**
-     * Throws an exception if called with security enabled but
-     * a caller is not the exit class and the runtime has been
-     * initialized.
-     */
-    private static void checkExitClass() {
-        if (securityEnabled && initialized)
-            if (!security.isExitClass())
-                throw new IllegalStateException("Caller is not the exit class");
-    }
-
-    /**
-     * Check whether the VM is in headless mode.
-     */
-    private static void checkHeadless() {
-        //if (GraphicsEnvironment.isHeadless()) // jdk1.4+ only
-        //    headless = true;
-        try {
-            if ("true".equalsIgnoreCase(System.getProperty("java.awt.headless")))
-                headless = true;
-        }
-        catch (SecurityException ex) {
-        }
-    }
-
-    /**
-     * Load the resources.
-     */
-    private static void loadResources() {
-        try {
-            resources = ResourceBundle.getBundle("net.sourceforge.jnlp.resources.Messages");
-        }
-        catch (Exception ex) {
-            throw new IllegalStateException("Missing resource bundle in netx.jar:net/sourceforge/jnlp/resource/Messages.properties");
-        }
-    }
-
-    /**
-     * Load the window icon.
-     */
-    private static void loadWindowIcon() {
-        if (windowIcon != null)
-            return;
-
-        try {
-            windowIcon = new javax.swing.ImageIcon((new sun.misc.Launcher())
-            		.getClassLoader().getResource("net/sourceforge/jnlp/resources/netx-icon.png")).getImage();
-        }
-        catch (Exception ex) {
-            if (JNLPRuntime.isDebug())
-                ex.printStackTrace();
-        }
-    }
-
-
-    public static void setInitialArgments(List<String> args) {
-        checkInitialized();
-        SecurityManager securityManager = System.getSecurityManager();
-        if (securityManager != null)
-            securityManager.checkPermission(new AllPermission());
-        initialArguments = args;
-    }
-    
-    public static List<String> getInitialArguments() {
-        return initialArguments;
-    }
-    
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.io.*;
+import java.awt.*;
+import java.text.*;
+import java.util.*;
+import java.util.List;
+import java.security.*;
+import javax.jnlp.*;
+
+import net.sourceforge.jnlp.*;
+import net.sourceforge.jnlp.cache.*;
+import net.sourceforge.jnlp.services.*;
+import net.sourceforge.jnlp.util.*;
+
+
+/**
+ * Configure and access the runtime environment.  This class
+ * stores global jnlp properties such as default download
+ * indicators, the install/base directory, the default resource
+ * update policy, etc.  Some settings, such as the base directory,
+ * cannot be changed once the runtime has been initialized.<p>
+ *
+ * The JNLP runtime can be locked to prevent further changes to
+ * the runtime environment except by a specified class.  If set,
+ * only instances of the <i>exit class</i> can exit the JVM or
+ * change the JNLP runtime settings once the runtime has been
+ * initialized.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.19 $
+ */
+public class JNLPRuntime {
+
+    static {
+        loadResources();
+    }
+
+    /** the localized resource strings */
+    private static ResourceBundle resources;
+
+    /** the security manager */
+    private static JNLPSecurityManager security;
+
+    /** the security policy */
+    private static JNLPPolicy policy;
+
+    /** the base dir for cache, etc */
+    private static File baseDir;
+
+    /** a default launch handler */
+    private static LaunchHandler handler = null;
+
+    /** default download indicator */
+    private static DownloadIndicator indicator = null;
+
+    /** update policy that controls when to check for updates */
+    private static UpdatePolicy updatePolicy = UpdatePolicy.ALWAYS;
+
+    /** netx window icon */
+    private static Image windowIcon = null;
+
+    /** whether initialized */
+    private static boolean initialized = false;
+
+    /** whether netx is in command-line mode (headless) */
+    private static boolean headless = false;
+
+	/** whether we'll be checking for jar signing */
+	private static boolean verify = true;
+
+    /** whether the runtime uses security */
+    private static boolean securityEnabled = true;
+
+    /** whether debug mode is on */
+    private static boolean debug = false; // package access by Boot
+    
+    /** mutex to wait on, for initialization */
+    public static Object initMutex = new Object();
+
+    /** set to true if this is a webstart application. */
+    private static boolean isWebstartApplication; 
+    
+    /** set to false to indicate another JVM should not be spawned, even if necessary */
+    private static boolean forksAllowed = true;
+
+    /** contains the arguments passed to the jnlp runtime */
+    private static List<String> initialArguments;
+
+    /** Username */
+    public static final String USER = System.getProperty("user.name");
+
+    /** User's home directory */
+    public static final String HOME_DIR = System.getProperty("user.home");
+
+    /** the ~/.netxrc file containing netx settings */
+    public static final String NETXRC_FILE = HOME_DIR + File.separator + ".netxrc";
+
+    /** the ~/.netx directory containing user-specific data */
+    public static final String NETX_DIR = HOME_DIR + File.separator + ".netx";
+
+    /** the ~/.netx/security directory containing security related information */
+    public static final String SECURITY_DIR = NETX_DIR + File.separator + "security";
+
+    /** the ~/.netx/security/trusted.certs file containing trusted certificates */
+    public static final String CERTIFICATES_FILE = SECURITY_DIR + File.separator + "trusted.certs";
+
+    /** the /tmp/ directory used for temporary files */
+    public static final String TMP_DIR = System.getProperty("java.io.tmpdir");
+
+    /**
+     * the /tmp/$USER/netx/locks/ directory containing locks for single instance
+     * applications
+     */
+    public static final String LOCKS_DIR = TMP_DIR + File.separator + USER + File.separator
+            + "netx" + File.separator + "locks";
+
+    /** the java.home directory */
+    public static final String JAVA_HOME_DIR = System.getProperty("java.home");
+    
+    /** the JNLP file to open to display the network-based about window */
+    public static final String NETX_ABOUT_FILE = JAVA_HOME_DIR + File.separator + "lib"
+            + File.separator + "about.jnlp";
+
+    
+    
+    /**
+     * Returns whether the JNLP runtime environment has been
+     * initialized.  Once initialized, some properties such as the
+     * base directory cannot be changed.  Before 
+     */
+    public static boolean isInitialized() {
+        return initialized;
+    }
+
+    /**
+     * Initialize the JNLP runtime environment by installing the
+     * security manager and security policy, initializing the JNLP
+     * standard services, etc.<p>
+     *
+     * This method cannot be called more than once.  Once
+     * initialized, methods that alter the runtime can only be
+     * called by the exit class.<p>
+     *
+     * @param isApplication is true if a webstart application is being initialized
+     *
+     * @throws IllegalStateException if the runtime was previously initialized
+     */
+    public static void initialize(boolean isApplication) throws IllegalStateException {
+        checkInitialized();
+     
+        isWebstartApplication = isApplication;
+
+        if (headless == false)
+            checkHeadless();
+
+        if (!headless && windowIcon == null)
+            loadWindowIcon();
+
+        if (!headless && indicator == null)
+            indicator = new DefaultDownloadIndicator();
+
+        if (handler == null)
+            handler = new DefaultLaunchHandler();
+
+        if (baseDir == null)
+            baseDir = getDefaultBaseDir();
+
+        if (baseDir == null)
+            throw new IllegalStateException(JNLPRuntime.getMessage("BNoBase"));
+
+        ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start
+	
+        policy = new JNLPPolicy();
+        security = new JNLPSecurityManager(); // side effect: create JWindow
+
+        if (securityEnabled) {
+            Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy
+            System.setSecurityManager(security);
+        }
+
+        initialized = true;
+    }
+
+    /**
+     * Returns true if a webstart application has been initialized, and false
+     * for a plugin applet.
+     */
+    public static boolean isWebstartApplication() {
+        return isWebstartApplication;
+    }
+
+    /**
+     * Returns the window icon.
+     */
+    public static Image getWindowIcon() {
+        return windowIcon;
+    }
+
+    /**
+     * Sets the window icon that is displayed in Java applications
+     * and applets instead of the default Java icon.
+     *
+     * @throws IllegalStateException if caller is not the exit class
+     */
+    public static void setWindowIcon(Image image) {
+        checkExitClass();
+        windowIcon = image;
+    }
+
+    /**
+     * Returns whether the JNLP client will use any AWT/Swing
+     * components.
+     */
+    public static boolean isHeadless() {
+        return headless;
+    }
+
+	/**
+	 * Returns whether we are verifying code signing.
+	 */
+	public static boolean isVerifying() {
+		return verify;
+	}
+    /**
+     * Sets whether the JNLP client will use any AWT/Swing
+     * components.  In headless mode, client features that use the
+     * AWT are disabled such that the client can be used in
+     * headless mode (<code>java.awt.headless=true</code>).
+     *
+     * @throws IllegalStateException if the runtime was previously initialized
+     */
+    public static void setHeadless(boolean enabled) {
+        checkInitialized();
+        headless = enabled;
+    }
+
+   /**
+	* Sets whether we will verify code signing.
+	* @throws IllegalStateException if the runtime was previously initialized
+	*/
+    public static void setVerify(boolean enabled) {
+		checkInitialized();
+		verify = enabled;
+    }
+
+    /**
+     * Return the base directory containing the cache, persistence
+     * store, etc.
+     */
+    public static File getBaseDir() {
+        return baseDir;
+    }
+
+    /**
+     * Sets the base directory containing the cache, persistence
+     * store, etc.
+     *
+     * @throws IllegalStateException if caller is not the exit class
+     */
+    public static void setBaseDir(File baseDirectory) {
+        checkInitialized();
+        baseDir = baseDirectory;
+    }
+
+    /**
+     * Returns whether the secure runtime environment is enabled.
+     */
+    public static boolean isSecurityEnabled() {
+        return securityEnabled;
+    }
+
+    /**
+     * Sets whether to enable the secure runtime environment.
+     * Disabling security can increase performance for some
+     * applications, and can be used to use netx with other code
+     * that uses its own security manager or policy.
+     *
+     * Disabling security is not recommended and should only be
+     * used if the JNLP files opened are trusted.  This method can
+     * only be called before initalizing the runtime.<p>
+     *
+     * @param enabled whether security should be enabled
+     * @throws IllegalStateException if the runtime is already initialized
+     */
+    public static void setSecurityEnabled(boolean enabled) {
+        checkInitialized();
+        securityEnabled = enabled;
+    }
+
+    /**
+     * Returns the system default base dir for or if not set,
+     * prompts the user for the location.
+     *
+     * @return the base dir, or null if the user canceled the dialog
+     * @throws IOException if there was an io exception
+     */
+    public static File getDefaultBaseDir() {
+        PropertiesFile props = JNLPRuntime.getProperties();
+
+        String baseStr = props.getProperty("basedir");
+        if (baseStr != null)
+            return new File(baseStr);
+
+        String homeDir = HOME_DIR;
+        File baseDir = new File(NETX_DIR);
+        if (homeDir == null || (!baseDir.isDirectory() && !baseDir.mkdir()))
+            return null;
+
+        props.setProperty("basedir", baseDir.toString());
+        props.store();
+
+        return baseDir;
+    }
+
+    /**
+     * Set a class that can exit the JVM; if not set then any class
+     * can exit the JVM.
+     *
+     * @throws IllegalStateException if caller is not the exit class
+     */
+    public static void setExitClass(Class exitClass) {
+        checkExitClass();
+        security.setExitClass(exitClass);
+    }
+    
+    /**
+     * Disables applets from calling exit.
+     * 
+     * Once disabled, exit cannot be re-enabled for the duration of the JVM instance
+     */
+    public static void disableExit() {
+    	security.disableExit();
+    }
+
+    /**
+     * Return the current Application, or null if none can be
+     * determined.
+     */
+    public static ApplicationInstance getApplication() {
+        return security.getApplication();
+    }
+
+    /**
+     * Return a PropertiesFile object backed by the runtime's
+     * properties file.
+     */
+    public static PropertiesFile getProperties() {
+        File netxrc = new File(NETXRC_FILE);
+        return new PropertiesFile(netxrc);
+    }
+
+    /**
+     * Return whether debug statements for the JNLP client code
+     * should be printed.
+     */
+    public static boolean isDebug() {
+        return debug;
+    }
+
+    /**
+     * Sets whether debug statements for the JNLP client code
+     * should be printed to the standard output.
+     *
+     * @throws IllegalStateException if caller is not the exit class
+     */
+    public static void setDebug(boolean enabled) {
+        checkExitClass();
+        debug = enabled;
+    }
+
+    /**
+     * Sets the default update policy.
+     *
+     * @throws IllegalStateException if caller is not the exit class
+     */
+    public static void setDefaultUpdatePolicy(UpdatePolicy policy) {
+        checkExitClass();
+        updatePolicy = policy;
+    }
+
+    /**
+     * Returns the default update policy.
+     */
+    public static UpdatePolicy getDefaultUpdatePolicy() {
+        return updatePolicy;
+    }
+
+    /**
+     * Sets the default launch handler.
+     */
+    public static void setDefaultLaunchHandler(LaunchHandler handler) {
+        checkExitClass();
+        JNLPRuntime.handler = handler;
+    }
+
+    /**
+     * Returns the default launch handler.
+     */
+    public static LaunchHandler getDefaultLaunchHandler() {
+        return handler;
+    }
+
+    /**
+     * Sets the default download indicator.
+     *
+     * @throws IllegalStateException if caller is not the exit class
+     */
+    public static void setDefaultDownloadIndicator(DownloadIndicator indicator) {
+        checkExitClass();
+        JNLPRuntime.indicator = indicator;
+    }
+
+    /**
+     * Returns the default download indicator.
+     */
+    public static DownloadIndicator getDefaultDownloadIndicator() {
+        return indicator;
+    }
+
+    /**
+     * Returns the localized resource string identified by the
+     * specified key.  If the message is empty, a null is
+     * returned.
+     */
+    public static String getMessage(String key) {
+        try {
+            String result = resources.getString(key);
+            if (result.length() == 0)
+                return null;
+            else
+                return result;
+        }
+        catch (Exception ex) {
+            if (!key.equals("RNoResource"))
+                return getMessage("RNoResource", new Object[] {key});
+            else
+                return "Missing resource: "+key;
+        }
+    }
+
+    /**
+     * Returns the localized resource string using the specified
+     * arguments.
+     *
+     * @param args the formatting arguments to the resource string
+     */
+    public static String getMessage(String key, Object args[]) {
+        return MessageFormat.format(getMessage(key), args);
+    }
+
+    /**
+     * Returns true if the current runtime will fork
+     */
+    public static boolean getForksAllowed() {
+        return forksAllowed;
+    }
+    
+    public static void setForksAllowed(boolean value) {
+        checkInitialized();
+        forksAllowed = value;
+    }
+    
+    /**
+     * Throws an exception if called when the runtime is
+     * already initialized.
+     */
+    private static void checkInitialized() {
+        if (initialized)
+            throw new IllegalStateException("JNLPRuntime already initialized.");
+    }
+
+    /**
+     * Throws an exception if called with security enabled but
+     * a caller is not the exit class and the runtime has been
+     * initialized.
+     */
+    private static void checkExitClass() {
+        if (securityEnabled && initialized)
+            if (!security.isExitClass())
+                throw new IllegalStateException("Caller is not the exit class");
+    }
+
+    /**
+     * Check whether the VM is in headless mode.
+     */
+    private static void checkHeadless() {
+        //if (GraphicsEnvironment.isHeadless()) // jdk1.4+ only
+        //    headless = true;
+        try {
+            if ("true".equalsIgnoreCase(System.getProperty("java.awt.headless")))
+                headless = true;
+        }
+        catch (SecurityException ex) {
+        }
+    }
+
+    /**
+     * Load the resources.
+     */
+    private static void loadResources() {
+        try {
+            resources = ResourceBundle.getBundle("net.sourceforge.jnlp.resources.Messages");
+        }
+        catch (Exception ex) {
+            throw new IllegalStateException("Missing resource bundle in netx.jar:net/sourceforge/jnlp/resource/Messages.properties");
+        }
+    }
+
+    /**
+     * Load the window icon.
+     */
+    private static void loadWindowIcon() {
+        if (windowIcon != null)
+            return;
+
+        try {
+            windowIcon = new javax.swing.ImageIcon((new sun.misc.Launcher())
+            		.getClassLoader().getResource("net/sourceforge/jnlp/resources/netx-icon.png")).getImage();
+        }
+        catch (Exception ex) {
+            if (JNLPRuntime.isDebug())
+                ex.printStackTrace();
+        }
+    }
+
+
+    public static void setInitialArgments(List<String> args) {
+        checkInitialized();
+        SecurityManager securityManager = System.getSecurityManager();
+        if (securityManager != null)
+            securityManager.checkPermission(new AllPermission());
+        initialArguments = args;
+    }
+    
+    public static List<String> getInitialArguments() {
+        return initialArguments;
+    }
+    
+}
+
+
--- a/rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,490 +1,490 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.runtime;
-
-import java.awt.Frame;
-import java.awt.Window;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.lang.ref.WeakReference;
-import java.net.SocketPermission;
-import java.security.AccessControlException;
-import java.security.AccessController;
-import java.security.Permission;
-import java.security.PrivilegedAction;
-
-import javax.swing.JWindow;
-
-import net.sourceforge.jnlp.JNLPFile;
-import net.sourceforge.jnlp.security.SecurityWarningDialog;
-import net.sourceforge.jnlp.services.ServiceUtil;
-import net.sourceforge.jnlp.util.WeakList;
-import sun.security.util.SecurityConstants;
-
-/**
- * Security manager for JNLP environment.  This security manager
- * cannot be replaced as it always denies attempts to replace the
- * security manager or policy.<p>
- *
- * The JNLP security manager tracks windows created by an
- * application, allowing those windows to be disposed when the
- * application exits but the JVM does not.  If security is not
- * enabled then the first application to call System.exit will
- * halt the JVM.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.17 $
- */
-class JNLPSecurityManager extends SecurityManager {
-
-    // todo: some apps like JDiskReport can close the VM even when
-    // an exit class is set - fix!
-
-    // todo: create an event dispatch thread for each application,
-    // so that the context classloader doesn't have to be switched
-    // to the foreground application (the currently the approach
-    // since some apps need their classloader as event dispatch
-    // thread's context classloader).
-
-    // todo: use a custom Permission object to identify the current
-    // application in an AccessControlContext by setting a side
-    // effect in its implies method.  Use a custom
-    // AllPermissions-like permission to do this for apps granted
-    // all permissions (but investigate whether this will nuke
-    // the all-permission optimizations in the JRE).
-
-    // todo: does not exit app if close button pressed on JFrame
-    // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an
-    // WindowListener to catch WindowClosing event, then if exit is
-    // called immediately afterwards from AWT thread.
-
-    // todo: deny all permissions to applications that should have
-    // already been 'shut down' by closing their resources and
-    // interrupt the threads if operating in a shared-VM (exit class
-    // set).  Deny will probably will slow checks down a lot though.
-
-    // todo: weak remember last getProperty application and
-    // re-install properties if another application calls, or find
-    // another way for different apps to have different properties
-    // in java.lang.Sytem with the same names.
-
-    private static String R(String key) { return JNLPRuntime.getMessage(key); }
-
-    /** only class that can exit the JVM, if set */
-    private Object exitClass = null;
-
-    /** this exception prevents exiting the JVM */
-    private SecurityException closeAppEx = // making here prevents huge stack traces
-        new SecurityException(JNLPRuntime.getMessage("RShutdown"));
-
-    /** weak list of windows created */
-    private WeakList weakWindows = new WeakList();
-
-    /** weak list of applications corresponding to window list */
-    private WeakList weakApplications = new WeakList();
-
-    /** weak reference to most app who's windows was most recently activated */
-    private WeakReference activeApplication = null;
-
-    /** listener installs the app's classloader on the event dispatch thread */
-    private ContextUpdater contextListener = new ContextUpdater();
-    
-    /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */
-    private boolean exitAllowed = true;
-
-    private class ContextUpdater extends WindowAdapter implements PrivilegedAction {
-        private ApplicationInstance app = null;
-
-        public void windowActivated(WindowEvent e) {
-            app = getApplication(e.getWindow());
-            AccessController.doPrivileged(this);
-            app = null;
-        }
-
-        public Object run() {
-            if (app != null) {
-                Thread.currentThread().setContextClassLoader(app.getClassLoader());
-                activeApplication = new WeakReference(app);
-            }
-            else
-                Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
-
-            return null;
-        }
-
-        public void windowDeactivated(WindowEvent e) {
-            activeApplication = null;
-        }
-        
-        public void windowClosing(WindowEvent e) {
-        	System.err.println("Disposing window");
-        	e.getWindow().dispose();
-        }
-    };
-
-
-    /**
-     * Creates a JNLP SecurityManager.
-     */
-    JNLPSecurityManager() {
-        // this has the side-effect of creating the Swing shared Frame
-        // owner.  Since no application is running at this time, it is
-        // not added to any window list when checkTopLevelWindow is
-        // called for it (and not disposed).
-
-        if (!JNLPRuntime.isHeadless())
-            new JWindow().getOwner();
-    }
-
-    /**
-     * Returns whether the exit class is present on the stack, or
-     * true if no exit class is set.
-     */
-    public boolean isExitClass() {
-        return isExitClass(getClassContext());
-    }
-
-    /**
-     * Returns whether the exit class is present on the stack, or
-     * true if no exit class is set.
-     */
-    private boolean isExitClass(Class stack[]) {
-        if (exitClass == null)
-            return true;
-
-        for (int i=0; i < stack.length; i++)
-            if (stack[i] == exitClass)
-                return true;
-
-        return false;
-    }
-
-    /**
-     * Set the exit class, which is the only class that can exit the
-     * JVM; if not set then any class can exit the JVM.
-     *
-     * @param exitClass the exit class
-     * @throws IllegalStateException if the exit class is already set
-     */
-    public void setExitClass(Class exitClass) throws IllegalStateException {
-        if (this.exitClass != null)
-            throw new IllegalStateException(R("RExitTaken"));
-
-        this.exitClass = exitClass;
-    }
-
-    /**
-     * Return the current Application, or null if none can be
-     * determined.
-     */
-    protected ApplicationInstance getApplication() {
-        return getApplication(getClassContext(), 0);
-    }
-
-    /**
-     * Return the application the opened the specified window (only
-     * call from event dispatch thread).
-     */
-    protected ApplicationInstance getApplication(Window window) {
-        for (int i = weakWindows.size(); i-->0;) {
-            Window w = (Window) weakWindows.get(i);
-            if (w == null) {
-                weakWindows.remove(i);
-                weakApplications.remove(i);
-            }
-
-            if (w == window)
-                return (ApplicationInstance) weakApplications.get(i);
-        }
-
-        return null;
-    }
-
-    /**
-     * Return the current Application, or null.
-     */
-    protected ApplicationInstance getApplication(Class stack[], int maxDepth) {
-    	if (maxDepth <= 0)
-    		maxDepth = stack.length;
-
-    	// this needs to be tightened up
-    	for (int i=0; i < stack.length && i < maxDepth; i++) {
-    		if (stack[i].getClassLoader() instanceof JNLPClassLoader) {
-    			JNLPClassLoader loader = (JNLPClassLoader) stack[i].getClassLoader();
-
-    			if (loader != null && loader.getApplication() != null) {
-    				return loader.getApplication();
-    			}
-    		} 
-    	}
-
-    	return null;
-    }
-
-    /**
-     * Returns the application's thread group if the application can
-     * be determined; otherwise returns super.getThreadGroup()
-     */
-    public ThreadGroup getThreadGroup() {
-        ApplicationInstance app = getApplication();
-        if (app == null)
-            return super.getThreadGroup();
-
-        return app.getThreadGroup();
-    }
-
-    /**
-     * Throws a SecurityException if the permission is denied,
-     * otherwise return normally.  This method always denies
-     * permission to change the security manager or policy.
-     */
-    public void checkPermission(Permission perm) {
-        String name = perm.getName();
-
-        // Enable this manually -- it'll produce too much output for -verbose
-        // otherwise.
-	//	if (true)
-	//  	  System.out.println("Checking permission: " + perm.toString());
-
-        if (!JNLPRuntime.isWebstartApplication() && 
-	      ("setPolicy".equals(name) || "setSecurityManager".equals(name)))
-            throw new SecurityException(R("RCantReplaceSM"));
-
-        try {
-            // deny all permissions to stopped applications
-        	// The call to getApplication() below might not work if an 
-        	// application hasn't been fully initialized yet.
-//            if (JNLPRuntime.isDebug()) {
-//                if (!"getClassLoader".equals(name)) {
-//                    ApplicationInstance app = getApplication();
-//                    if (app != null && !app.isRunning())
-//                        throw new SecurityException(R("RDenyStopped"));
-//                }
-//            }
-        	
-			try {
-				super.checkPermission(perm);
-			} catch (SecurityException se) {
-
-				//This section is a special case for dealing with SocketPermissions.
-				if (JNLPRuntime.isDebug())
-					System.err.println("Requesting permission: " + perm.toString());
-
-				//Change this SocketPermission's action to connect and accept
-				//(and resolve). This is to avoid asking for connect permission 
-				//on every address resolve.
-				Permission tmpPerm;
-				if (perm instanceof SocketPermission) {
-					tmpPerm = new SocketPermission(perm.getName(), 
-							SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION);
-					
-					// before proceeding, check if we are trying to connect to same origin
-					ApplicationInstance app = getApplication();
-					JNLPFile file = app.getJNLPFile();
-
-					String srcHost =  file.getSourceLocation().getAuthority();
-					String destHost = name;
-					
-					// host = abc.xyz.com or abc.xyz.com:<port> 
-					if (destHost.indexOf(':') >= 0)
-						destHost = destHost.substring(0, destHost.indexOf(':'));
-					
-					// host = abc.xyz.com
-					String[] hostComponents = destHost.split("\\.");
-					
-					int length = hostComponents.length;
-					if (length >= 2) {
-						
-						// address is in xxx.xxx.xxx format
-						destHost = hostComponents[length -2] + "." + hostComponents[length -1];
-					
-						// host = xyz.com i.e. origin
-						boolean isDestHostName = false;
-
-						// make sure that it is not an ip address
-						try {
-							Integer.parseInt(hostComponents[length -1]);
-						} catch (NumberFormatException e) {
-							isDestHostName = true;
-						}
-
-						if (isDestHostName) {
-							// okay, destination is hostname. Now figure out if it is a subset of origin
-							if (srcHost.endsWith(destHost)) {
-								addPermission(tmpPerm);
-								return;
-							}
-						}
-					}
-
-				} else
-					tmpPerm = perm;
-				
-				//askPermission will only prompt the user on SocketPermission 
-				//meaning we're denying all other SecurityExceptions that may arise.
-				if (askPermission(tmpPerm)) {
-					addPermission(tmpPerm);
-					//return quietly.
-				} else {
-					throw se;
-				}
-			}
-        }
-        catch (SecurityException ex) {
-            if (JNLPRuntime.isDebug()) {
-                System.out.println("Denying permission: "+perm);
-            }
-            throw ex;
-        }
-    }
-    
-    /**
-     * Asks the user whether or not to grant permission.
-     * @param perm the permission to be granted
-     * @return true if the permission was granted, false otherwise.
-     */
-    private boolean askPermission(Permission perm)	{
-    	
-    	ApplicationInstance app = getApplication();
-    	if (app != null && !app.isSigned()) {
-        	if (perm instanceof SocketPermission 
-        			&& ServiceUtil.checkAccess(SecurityWarningDialog.AccessType.NETWORK, perm.getName())) {
-        		return true;
-        	}
-    	}
-
-    	return false;
-    }
-
-    /**
-     * Adds a permission to the JNLPClassLoader.
-     * @param perm the permission to add to the JNLPClassLoader
-     */
-    private void addPermission(Permission perm)	{
-    	if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) {
-
-    		JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader();
-    		cl.addPermission(perm);
-        	if (JNLPRuntime.isDebug()) {
-        		if (cl.getPermissions(null).implies(perm))
-        			System.err.println("Added permission: " + perm.toString());
-        		else
-        			System.err.println("Unable to add permission: " + perm.toString());
-        	}
-    	} else {
-        	if (JNLPRuntime.isDebug())
-        		System.err.println("Unable to add permission: " + perm + ", classloader not JNLP.");
-    	}
-    }
-    
-    /**
-     * Checks whether the window can be displayed without an applet
-     * warning banner, and adds the window to the list of windows to
-     * be disposed when the calling application exits.
-     */
-    public boolean checkTopLevelWindow(Object window) {
-        ApplicationInstance app = getApplication();
-
-        // remember window -> application mapping for focus, close on exit 
-        if (app != null && window instanceof Window) {
-            Window w = (Window) window;
-
-            if (JNLPRuntime.isDebug())
-                System.err.println("SM: app: "+app.getTitle()+" is adding a window: "+window);
-
-            weakWindows.add(window); // for mapping window -> app
-            weakApplications.add(app);
-
-            w.addWindowListener(contextListener); // for dynamic context classloader
-
-            app.addWindow(w);
-        }
-
-        // change coffee cup to netx for default icon
-        if (window instanceof Window)
-            for (Window w = (Window)window; w != null; w = w.getOwner())
-                if (window instanceof Frame)
-                    ((Frame)window).setIconImage(JNLPRuntime.getWindowIcon());
-
-        // todo: set awt.appletWarning to custom message
-        // todo: logo on with glass pane on JFrame/JWindow?
-        
-        return super.checkTopLevelWindow(window);
-    }
-
-    /**
-     * Checks whether the caller can exit the system.  This method
-     * identifies whether the caller is a real call to Runtime.exec
-     * and has special behavior when returning from this method
-     * would exit the JVM and an exit class is set: if the caller is
-     * not the exit class then the calling application will be
-     * stopped and its resources destroyed (when possible), and an
-     * exception will be thrown to prevent the JVM from shutting
-     * down.<p>
-     *
-     * Calls not from Runtime.exit or with no exit class set will
-     * behave normally, and the exit class can always exit the JVM.
-     */
-    public void checkExit(int status) {
-
-    	// applets are not allowed to exit, but the plugin main class (primordial loader) is
-        Class stack[] = getClassContext();
-        if (!exitAllowed) {
-        	for (int i=0; i < stack.length; i++)
-        		if (stack[i].getClassLoader() != null)
-        			throw new AccessControlException("Applets may not call System.exit()");
-        }
-
-    	super.checkExit(status);
-        
-        boolean realCall = (stack[1] == Runtime.class);
-
-        if (isExitClass(stack)) // either exitClass called or no exitClass set
-            return; // to Runtime.exit or fake call to see if app has permission
-
-        // not called from Runtime.exit()
-        if (!realCall) {
-            // apps that can't exit should think they can exit normally
-            super.checkExit(status);
-            return;
-        }
-
-        // but when they really call, stop only the app instead of the JVM
-        ApplicationInstance app = getApplication(stack, 0);
-        if (app == null) {
-            // should check caller to make sure it is JFrame.close or
-            // other known System.exit call
-            if (activeApplication != null)
-                app = (ApplicationInstance) activeApplication.get();
-
-            if (app == null)
-                throw new SecurityException(R("RExitNoApp"));
-        }
-
-        app.destroy();
-
-        throw closeAppEx;
-    }
-
-    protected void disableExit() {
-    	exitAllowed = false;
-    }
-    
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.runtime;
+
+import java.awt.Frame;
+import java.awt.Window;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.lang.ref.WeakReference;
+import java.net.SocketPermission;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
+
+import javax.swing.JWindow;
+
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.security.SecurityWarningDialog;
+import net.sourceforge.jnlp.services.ServiceUtil;
+import net.sourceforge.jnlp.util.WeakList;
+import sun.security.util.SecurityConstants;
+
+/**
+ * Security manager for JNLP environment.  This security manager
+ * cannot be replaced as it always denies attempts to replace the
+ * security manager or policy.<p>
+ *
+ * The JNLP security manager tracks windows created by an
+ * application, allowing those windows to be disposed when the
+ * application exits but the JVM does not.  If security is not
+ * enabled then the first application to call System.exit will
+ * halt the JVM.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.17 $
+ */
+class JNLPSecurityManager extends SecurityManager {
+
+    // todo: some apps like JDiskReport can close the VM even when
+    // an exit class is set - fix!
+
+    // todo: create an event dispatch thread for each application,
+    // so that the context classloader doesn't have to be switched
+    // to the foreground application (the currently the approach
+    // since some apps need their classloader as event dispatch
+    // thread's context classloader).
+
+    // todo: use a custom Permission object to identify the current
+    // application in an AccessControlContext by setting a side
+    // effect in its implies method.  Use a custom
+    // AllPermissions-like permission to do this for apps granted
+    // all permissions (but investigate whether this will nuke
+    // the all-permission optimizations in the JRE).
+
+    // todo: does not exit app if close button pressed on JFrame
+    // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an
+    // WindowListener to catch WindowClosing event, then if exit is
+    // called immediately afterwards from AWT thread.
+
+    // todo: deny all permissions to applications that should have
+    // already been 'shut down' by closing their resources and
+    // interrupt the threads if operating in a shared-VM (exit class
+    // set).  Deny will probably will slow checks down a lot though.
+
+    // todo: weak remember last getProperty application and
+    // re-install properties if another application calls, or find
+    // another way for different apps to have different properties
+    // in java.lang.Sytem with the same names.
+
+    private static String R(String key) { return JNLPRuntime.getMessage(key); }
+
+    /** only class that can exit the JVM, if set */
+    private Object exitClass = null;
+
+    /** this exception prevents exiting the JVM */
+    private SecurityException closeAppEx = // making here prevents huge stack traces
+        new SecurityException(JNLPRuntime.getMessage("RShutdown"));
+
+    /** weak list of windows created */
+    private WeakList weakWindows = new WeakList();
+
+    /** weak list of applications corresponding to window list */
+    private WeakList weakApplications = new WeakList();
+
+    /** weak reference to most app who's windows was most recently activated */
+    private WeakReference activeApplication = null;
+
+    /** listener installs the app's classloader on the event dispatch thread */
+    private ContextUpdater contextListener = new ContextUpdater();
+    
+    /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */
+    private boolean exitAllowed = true;
+
+    private class ContextUpdater extends WindowAdapter implements PrivilegedAction {
+        private ApplicationInstance app = null;
+
+        public void windowActivated(WindowEvent e) {
+            app = getApplication(e.getWindow());
+            AccessController.doPrivileged(this);
+            app = null;
+        }
+
+        public Object run() {
+            if (app != null) {
+                Thread.currentThread().setContextClassLoader(app.getClassLoader());
+                activeApplication = new WeakReference(app);
+            }
+            else
+                Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+
+            return null;
+        }
+
+        public void windowDeactivated(WindowEvent e) {
+            activeApplication = null;
+        }
+        
+        public void windowClosing(WindowEvent e) {
+        	System.err.println("Disposing window");
+        	e.getWindow().dispose();
+        }
+    };
+
+
+    /**
+     * Creates a JNLP SecurityManager.
+     */
+    JNLPSecurityManager() {
+        // this has the side-effect of creating the Swing shared Frame
+        // owner.  Since no application is running at this time, it is
+        // not added to any window list when checkTopLevelWindow is
+        // called for it (and not disposed).
+
+        if (!JNLPRuntime.isHeadless())
+            new JWindow().getOwner();
+    }
+
+    /**
+     * Returns whether the exit class is present on the stack, or
+     * true if no exit class is set.
+     */
+    public boolean isExitClass() {
+        return isExitClass(getClassContext());
+    }
+
+    /**
+     * Returns whether the exit class is present on the stack, or
+     * true if no exit class is set.
+     */
+    private boolean isExitClass(Class stack[]) {
+        if (exitClass == null)
+            return true;
+
+        for (int i=0; i < stack.length; i++)
+            if (stack[i] == exitClass)
+                return true;
+
+        return false;
+    }
+
+    /**
+     * Set the exit class, which is the only class that can exit the
+     * JVM; if not set then any class can exit the JVM.
+     *
+     * @param exitClass the exit class
+     * @throws IllegalStateException if the exit class is already set
+     */
+    public void setExitClass(Class exitClass) throws IllegalStateException {
+        if (this.exitClass != null)
+            throw new IllegalStateException(R("RExitTaken"));
+
+        this.exitClass = exitClass;
+    }
+
+    /**
+     * Return the current Application, or null if none can be
+     * determined.
+     */
+    protected ApplicationInstance getApplication() {
+        return getApplication(getClassContext(), 0);
+    }
+
+    /**
+     * Return the application the opened the specified window (only
+     * call from event dispatch thread).
+     */
+    protected ApplicationInstance getApplication(Window window) {
+        for (int i = weakWindows.size(); i-->0;) {
+            Window w = (Window) weakWindows.get(i);
+            if (w == null) {
+                weakWindows.remove(i);
+                weakApplications.remove(i);
+            }
+
+            if (w == window)
+                return (ApplicationInstance) weakApplications.get(i);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the current Application, or null.
+     */
+    protected ApplicationInstance getApplication(Class stack[], int maxDepth) {
+    	if (maxDepth <= 0)
+    		maxDepth = stack.length;
+
+    	// this needs to be tightened up
+    	for (int i=0; i < stack.length && i < maxDepth; i++) {
+    		if (stack[i].getClassLoader() instanceof JNLPClassLoader) {
+    			JNLPClassLoader loader = (JNLPClassLoader) stack[i].getClassLoader();
+
+    			if (loader != null && loader.getApplication() != null) {
+    				return loader.getApplication();
+    			}
+    		} 
+    	}
+
+    	return null;
+    }
+
+    /**
+     * Returns the application's thread group if the application can
+     * be determined; otherwise returns super.getThreadGroup()
+     */
+    public ThreadGroup getThreadGroup() {
+        ApplicationInstance app = getApplication();
+        if (app == null)
+            return super.getThreadGroup();
+
+        return app.getThreadGroup();
+    }
+
+    /**
+     * Throws a SecurityException if the permission is denied,
+     * otherwise return normally.  This method always denies
+     * permission to change the security manager or policy.
+     */
+    public void checkPermission(Permission perm) {
+        String name = perm.getName();
+
+        // Enable this manually -- it'll produce too much output for -verbose
+        // otherwise.
+	//	if (true)
+	//  	  System.out.println("Checking permission: " + perm.toString());
+
+        if (!JNLPRuntime.isWebstartApplication() && 
+	      ("setPolicy".equals(name) || "setSecurityManager".equals(name)))
+            throw new SecurityException(R("RCantReplaceSM"));
+
+        try {
+            // deny all permissions to stopped applications
+        	// The call to getApplication() below might not work if an 
+        	// application hasn't been fully initialized yet.
+//            if (JNLPRuntime.isDebug()) {
+//                if (!"getClassLoader".equals(name)) {
+//                    ApplicationInstance app = getApplication();
+//                    if (app != null && !app.isRunning())
+//                        throw new SecurityException(R("RDenyStopped"));
+//                }
+//            }
+        	
+			try {
+				super.checkPermission(perm);
+			} catch (SecurityException se) {
+
+				//This section is a special case for dealing with SocketPermissions.
+				if (JNLPRuntime.isDebug())
+					System.err.println("Requesting permission: " + perm.toString());
+
+				//Change this SocketPermission's action to connect and accept
+				//(and resolve). This is to avoid asking for connect permission 
+				//on every address resolve.
+				Permission tmpPerm;
+				if (perm instanceof SocketPermission) {
+					tmpPerm = new SocketPermission(perm.getName(), 
+							SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION);
+					
+					// before proceeding, check if we are trying to connect to same origin
+					ApplicationInstance app = getApplication();
+					JNLPFile file = app.getJNLPFile();
+
+					String srcHost =  file.getSourceLocation().getAuthority();
+					String destHost = name;
+					
+					// host = abc.xyz.com or abc.xyz.com:<port> 
+					if (destHost.indexOf(':') >= 0)
+						destHost = destHost.substring(0, destHost.indexOf(':'));
+					
+					// host = abc.xyz.com
+					String[] hostComponents = destHost.split("\\.");
+					
+					int length = hostComponents.length;
+					if (length >= 2) {
+						
+						// address is in xxx.xxx.xxx format
+						destHost = hostComponents[length -2] + "." + hostComponents[length -1];
+					
+						// host = xyz.com i.e. origin
+						boolean isDestHostName = false;
+
+						// make sure that it is not an ip address
+						try {
+							Integer.parseInt(hostComponents[length -1]);
+						} catch (NumberFormatException e) {
+							isDestHostName = true;
+						}
+
+						if (isDestHostName) {
+							// okay, destination is hostname. Now figure out if it is a subset of origin
+							if (srcHost.endsWith(destHost)) {
+								addPermission(tmpPerm);
+								return;
+							}
+						}
+					}
+
+				} else
+					tmpPerm = perm;
+				
+				//askPermission will only prompt the user on SocketPermission 
+				//meaning we're denying all other SecurityExceptions that may arise.
+				if (askPermission(tmpPerm)) {
+					addPermission(tmpPerm);
+					//return quietly.
+				} else {
+					throw se;
+				}
+			}
+        }
+        catch (SecurityException ex) {
+            if (JNLPRuntime.isDebug()) {
+                System.out.println("Denying permission: "+perm);
+            }
+            throw ex;
+        }
+    }
+    
+    /**
+     * Asks the user whether or not to grant permission.
+     * @param perm the permission to be granted
+     * @return true if the permission was granted, false otherwise.
+     */
+    private boolean askPermission(Permission perm)	{
+    	
+    	ApplicationInstance app = getApplication();
+    	if (app != null && !app.isSigned()) {
+        	if (perm instanceof SocketPermission 
+        			&& ServiceUtil.checkAccess(SecurityWarningDialog.AccessType.NETWORK, perm.getName())) {
+        		return true;
+        	}
+    	}
+
+    	return false;
+    }
+
+    /**
+     * Adds a permission to the JNLPClassLoader.
+     * @param perm the permission to add to the JNLPClassLoader
+     */
+    private void addPermission(Permission perm)	{
+    	if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) {
+
+    		JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader();
+    		cl.addPermission(perm);
+        	if (JNLPRuntime.isDebug()) {
+        		if (cl.getPermissions(null).implies(perm))
+        			System.err.println("Added permission: " + perm.toString());
+        		else
+        			System.err.println("Unable to add permission: " + perm.toString());
+        	}
+    	} else {
+        	if (JNLPRuntime.isDebug())
+        		System.err.println("Unable to add permission: " + perm + ", classloader not JNLP.");
+    	}
+    }
+    
+    /**
+     * Checks whether the window can be displayed without an applet
+     * warning banner, and adds the window to the list of windows to
+     * be disposed when the calling application exits.
+     */
+    public boolean checkTopLevelWindow(Object window) {
+        ApplicationInstance app = getApplication();
+
+        // remember window -> application mapping for focus, close on exit 
+        if (app != null && window instanceof Window) {
+            Window w = (Window) window;
+
+            if (JNLPRuntime.isDebug())
+                System.err.println("SM: app: "+app.getTitle()+" is adding a window: "+window);
+
+            weakWindows.add(window); // for mapping window -> app
+            weakApplications.add(app);
+
+            w.addWindowListener(contextListener); // for dynamic context classloader
+
+            app.addWindow(w);
+        }
+
+        // change coffee cup to netx for default icon
+        if (window instanceof Window)
+            for (Window w = (Window)window; w != null; w = w.getOwner())
+                if (window instanceof Frame)
+                    ((Frame)window).setIconImage(JNLPRuntime.getWindowIcon());
+
+        // todo: set awt.appletWarning to custom message
+        // todo: logo on with glass pane on JFrame/JWindow?
+        
+        return super.checkTopLevelWindow(window);
+    }
+
+    /**
+     * Checks whether the caller can exit the system.  This method
+     * identifies whether the caller is a real call to Runtime.exec
+     * and has special behavior when returning from this method
+     * would exit the JVM and an exit class is set: if the caller is
+     * not the exit class then the calling application will be
+     * stopped and its resources destroyed (when possible), and an
+     * exception will be thrown to prevent the JVM from shutting
+     * down.<p>
+     *
+     * Calls not from Runtime.exit or with no exit class set will
+     * behave normally, and the exit class can always exit the JVM.
+     */
+    public void checkExit(int status) {
+
+    	// applets are not allowed to exit, but the plugin main class (primordial loader) is
+        Class stack[] = getClassContext();
+        if (!exitAllowed) {
+        	for (int i=0; i < stack.length; i++)
+        		if (stack[i].getClassLoader() != null)
+        			throw new AccessControlException("Applets may not call System.exit()");
+        }
+
+    	super.checkExit(status);
+        
+        boolean realCall = (stack[1] == Runtime.class);
+
+        if (isExitClass(stack)) // either exitClass called or no exitClass set
+            return; // to Runtime.exit or fake call to see if app has permission
+
+        // not called from Runtime.exit()
+        if (!realCall) {
+            // apps that can't exit should think they can exit normally
+            super.checkExit(status);
+            return;
+        }
+
+        // but when they really call, stop only the app instead of the JVM
+        ApplicationInstance app = getApplication(stack, 0);
+        if (app == null) {
+            // should check caller to make sure it is JFrame.close or
+            // other known System.exit call
+            if (activeApplication != null)
+                app = (ApplicationInstance) activeApplication.get();
+
+            if (app == null)
+                throw new SecurityException(R("RExitNoApp"));
+        }
+
+        app.destroy();
+
+        throw closeAppEx;
+    }
+
+    protected void disableExit() {
+    	exitAllowed = false;
+    }
+    
+}
+
+
--- a/rt/net/sourceforge/jnlp/services/XBasicService.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/services/XBasicService.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,234 +1,234 @@
-// Copyright (C) 2001 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.services;
-
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import javax.jnlp.BasicService;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-
-import net.sourceforge.jnlp.InformationDesc;
-import net.sourceforge.jnlp.JARDesc;
-import net.sourceforge.jnlp.JNLPFile;
-import net.sourceforge.jnlp.Launcher;
-import net.sourceforge.jnlp.runtime.ApplicationInstance;
-import net.sourceforge.jnlp.runtime.JNLPRuntime;
-import net.sourceforge.jnlp.util.PropertiesFile;
-
-/**
- * The BasicService JNLP service.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.10 $ 
- */
-class XBasicService implements BasicService {
-
-    /** command used to exec the native browser */
-    private String command = null;
-
-    /** whether the command was loaded / prompted for */
-    private boolean initialized = false;
-
-
-    protected XBasicService() {
-    }
-
-    /**
-     * Returns the codebase of the application, applet, or
-     * installer.  If the codebase was not specified in the JNLP
-     * element then the main JAR's location is returned.  If no main
-     * JAR was specified then the location of the JAR containing the
-     * main class is returned.
-     */
-    public URL getCodeBase() {
-        ApplicationInstance app = JNLPRuntime.getApplication();
-
-        if (app != null) {
-            JNLPFile file = app.getJNLPFile();
-
-            // return the codebase.
-            if (file.getCodeBase() != null)
-                return file.getCodeBase();
-
-            // else return the main JAR's URL.
-            JARDesc mainJar = file.getResources().getMainJAR();
-            if (mainJar != null)
-                return mainJar.getLocation();
-
-            // else find JAR where main class was defined.
-            //
-            // JNLPFile file = app.getJNLPFile();
-            // String mainClass = file.getLaunchInfo().getMainClass()+".class";
-            // URL jarUrl = app.getClassLoader().getResource(mainClass);
-            // go through list of JARDesc to find one matching jarUrl
-        }
-
-        return null;
-    }
-
-    /**
-     * Return true if the Environment is Offline
-     */
-    public boolean isOffline() {
-        
-        URL url = findFirstURLFromJNLPFile();
-        
-        try {
-            url.openConnection().getInputStream().close();
-            return false;
-        } catch (IOException exception) {
-            return true;
-        }
-    }
-
-    /**
-     * Return the first URL from the jnlp file
-     * Or a default URL if no url found in JNLP file
-     */
-    private URL findFirstURLFromJNLPFile() {
-        
-        ApplicationInstance app = JNLPRuntime.getApplication();
-        
-        if (app != null) {
-            JNLPFile jnlpFile = app.getJNLPFile();
-            
-            URL sourceURL = jnlpFile.getSourceLocation();
-            if (sourceURL != null) {
-                return sourceURL;
-            }
-            
-            URL codeBaseURL = jnlpFile.getCodeBase();
-            if (codeBaseURL != null) {
-                return codeBaseURL;
-            }
-    
-            InformationDesc informationDesc = jnlpFile.getInformation();
-            URL homePage = informationDesc.getHomepage();
-            if (homePage != null) {
-                return homePage;
-            }
-            
-            JARDesc[] jarDescs = jnlpFile.getResources().getJARs();
-            for (JARDesc jarDesc: jarDescs) {
-                return jarDesc.getLocation();
-            }
-        }
-        
-        // this section is only reached if the jnlp file has no jars.
-        // that doesnt seem very likely.
-        URL arbitraryURL;
-        try {
-            arbitraryURL = new URL("http://icedtea.classpath.org");
-        } catch (MalformedURLException malformedURL) {
-            throw new RuntimeException(malformedURL);
-        }
-        
-        return arbitraryURL;
-    }
-
-    /**
-     * Return true if a Web Browser is Supported
-     */
-    public boolean isWebBrowserSupported() {
-        initialize();
-
-        return command != null;
-    }
-
-    /**
-     * Show a document.
-     *
-     * @return whether the document was opened 
-     */
-    public boolean showDocument(URL url)  {
-        initialize();
-
-        if (url.toString().endsWith(".jnlp")) {
-            try {
-                new Launcher().launchExternal(url);
-                return true;
-            }
-            catch (Exception ex) {
-                return false;
-            }
-        }
-
-        if (command != null) {
-            try {
-                // this is bogus because the command may require options;
-                // should use a StreamTokenizer or similar to get tokens
-                // outside of quotes.
-                Runtime.getRuntime().exec(command + url.toString());
-                //Runtime.getRuntime().exec(new String[]{command,url.toString()});
-
-                return true;
-            }
-            catch(IOException ex){
-                if (JNLPRuntime.isDebug())
-                    ex.printStackTrace();
-            }
-        }
-
-        return false;
-    }
-
-    private void initialize() {
-        if (initialized)
-            return;
-        initialized = true;
-
-        if(isWindows()) {
-            command = "rundll32 url.dll,FileProtocolHandler ";
-        }
-        else {
-            PropertiesFile props = JNLPRuntime.getProperties();
-            command = props.getProperty("browser.command");
-
-            if(command == null) { // prompt & store
-                command = promptForCommand(null);
-
-                if(command != null) {
-                    props.setProperty("browser.command", command);
-                    props.store();
-                }
-            }
-        }
-    }
-
-    private boolean isWindows() {
-        String os = System.getProperty("os.name");
-        if(os != null && os.startsWith("Windows"))
-            return true;
-        else
-            return false;
-    }
-
-    private String promptForCommand(String cmd) {
-        return JOptionPane.showInputDialog(new JPanel(),
-                                           "Browser Location:",
-                                           "Specify Browser Location",
-                                           JOptionPane.PLAIN_MESSAGE 
-                                          );
-    }
-
-}
-
-
+// Copyright (C) 2001 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.services;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.jnlp.BasicService;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+
+import net.sourceforge.jnlp.InformationDesc;
+import net.sourceforge.jnlp.JARDesc;
+import net.sourceforge.jnlp.JNLPFile;
+import net.sourceforge.jnlp.Launcher;
+import net.sourceforge.jnlp.runtime.ApplicationInstance;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.util.PropertiesFile;
+
+/**
+ * The BasicService JNLP service.
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.10 $ 
+ */
+class XBasicService implements BasicService {
+
+    /** command used to exec the native browser */
+    private String command = null;
+
+    /** whether the command was loaded / prompted for */
+    private boolean initialized = false;
+
+
+    protected XBasicService() {
+    }
+
+    /**
+     * Returns the codebase of the application, applet, or
+     * installer.  If the codebase was not specified in the JNLP
+     * element then the main JAR's location is returned.  If no main
+     * JAR was specified then the location of the JAR containing the
+     * main class is returned.
+     */
+    public URL getCodeBase() {
+        ApplicationInstance app = JNLPRuntime.getApplication();
+
+        if (app != null) {
+            JNLPFile file = app.getJNLPFile();
+
+            // return the codebase.
+            if (file.getCodeBase() != null)
+                return file.getCodeBase();
+
+            // else return the main JAR's URL.
+            JARDesc mainJar = file.getResources().getMainJAR();
+            if (mainJar != null)
+                return mainJar.getLocation();
+
+            // else find JAR where main class was defined.
+            //
+            // JNLPFile file = app.getJNLPFile();
+            // String mainClass = file.getLaunchInfo().getMainClass()+".class";
+            // URL jarUrl = app.getClassLoader().getResource(mainClass);
+            // go through list of JARDesc to find one matching jarUrl
+        }
+
+        return null;
+    }
+
+    /**
+     * Return true if the Environment is Offline
+     */
+    public boolean isOffline() {
+        
+        URL url = findFirstURLFromJNLPFile();
+        
+        try {
+            url.openConnection().getInputStream().close();
+            return false;
+        } catch (IOException exception) {
+            return true;
+        }
+    }
+
+    /**
+     * Return the first URL from the jnlp file
+     * Or a default URL if no url found in JNLP file
+     */
+    private URL findFirstURLFromJNLPFile() {
+        
+        ApplicationInstance app = JNLPRuntime.getApplication();
+        
+        if (app != null) {
+            JNLPFile jnlpFile = app.getJNLPFile();
+            
+            URL sourceURL = jnlpFile.getSourceLocation();
+            if (sourceURL != null) {
+                return sourceURL;
+            }
+            
+            URL codeBaseURL = jnlpFile.getCodeBase();
+            if (codeBaseURL != null) {
+                return codeBaseURL;
+            }
+    
+            InformationDesc informationDesc = jnlpFile.getInformation();
+            URL homePage = informationDesc.getHomepage();
+            if (homePage != null) {
+                return homePage;
+            }
+            
+            JARDesc[] jarDescs = jnlpFile.getResources().getJARs();
+            for (JARDesc jarDesc: jarDescs) {
+                return jarDesc.getLocation();
+            }
+        }
+        
+        // this section is only reached if the jnlp file has no jars.
+        // that doesnt seem very likely.
+        URL arbitraryURL;
+        try {
+            arbitraryURL = new URL("http://icedtea.classpath.org");
+        } catch (MalformedURLException malformedURL) {
+            throw new RuntimeException(malformedURL);
+        }
+        
+        return arbitraryURL;
+    }
+
+    /**
+     * Return true if a Web Browser is Supported
+     */
+    public boolean isWebBrowserSupported() {
+        initialize();
+
+        return command != null;
+    }
+
+    /**
+     * Show a document.
+     *
+     * @return whether the document was opened 
+     */
+    public boolean showDocument(URL url)  {
+        initialize();
+
+        if (url.toString().endsWith(".jnlp")) {
+            try {
+                new Launcher().launchExternal(url);
+                return true;
+            }
+            catch (Exception ex) {
+                return false;
+            }
+        }
+
+        if (command != null) {
+            try {
+                // this is bogus because the command may require options;
+                // should use a StreamTokenizer or similar to get tokens
+                // outside of quotes.
+                Runtime.getRuntime().exec(command + url.toString());
+                //Runtime.getRuntime().exec(new String[]{command,url.toString()});
+
+                return true;
+            }
+            catch(IOException ex){
+                if (JNLPRuntime.isDebug())
+                    ex.printStackTrace();
+            }
+        }
+
+        return false;
+    }
+
+    private void initialize() {
+        if (initialized)
+            return;
+        initialized = true;
+
+        if(isWindows()) {
+            command = "rundll32 url.dll,FileProtocolHandler ";
+        }
+        else {
+            PropertiesFile props = JNLPRuntime.getProperties();
+            command = props.getProperty("browser.command");
+
+            if(command == null) { // prompt & store
+                command = promptForCommand(null);
+
+                if(command != null) {
+                    props.setProperty("browser.command", command);
+                    props.store();
+                }
+            }
+        }
+    }
+
+    private boolean isWindows() {
+        String os = System.getProperty("os.name");
+        if(os != null && os.startsWith("Windows"))
+            return true;
+        else
+            return false;
+    }
+
+    private String promptForCommand(String cmd) {
+        return JOptionPane.showInputDialog(new JPanel(),
+                                           "Browser Location:",
+                                           "Specify Browser Location",
+                                           JOptionPane.PLAIN_MESSAGE 
+                                          );
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/util/PropertiesFile.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/util/PropertiesFile.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,148 +1,148 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.util;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-
-import net.sourceforge.jnlp.*;
-
-/**
- * A properties object backed by a specified file without throwing
- * exceptions.  The properties are automatically loaded from the
- * file when the first property is requested, but the save method
- * must be called before changes are saved to the file.<p>
- *
- * This class does not report IO exceptions.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.4 $ 
- */
-public class PropertiesFile extends Properties {
-
-    /** the file to save to */
-    File file;
-
-    /** the header string */
-    String header = "netx file";
-
-    /** lazy loaded on getProperty */
-    boolean loaded = false;
-
-
-    /** 
-     * Create a properties object backed by the specified file. 
-     *
-     * @param file the file to save and load to
-     */
-    public PropertiesFile(File file) {
-        this.file = file;
-    }
-
-    /** 
-     * Create a properties object backed by the specified file. 
-     *
-     * @param file the file to save and load to
-     * @param header the file header
-     */
-    public PropertiesFile(File file, String header) {
-        this.file = file;
-        this.header = header;
-    }
-
-    /**
-     * Returns the value of the specified key, or null if the key
-     * does not exist.
-     */
-    public String getProperty(String key) {
-        if (!loaded)
-            load();
-
-        return super.getProperty(key);
-    }
-
-    /**
-     * Returns the value of the specified key, or the default value
-     * if the key does not exist.
-     */
-    public String getProperty(String key, String defaultValue) {
-        if (!loaded)
-            load();
-
-        return super.getProperty(key, defaultValue);
-    }
-
-    /**
-     * Sets the value for the specified key.
-     *
-     * @return the previous value
-     */
-    public Object setProperty(String key, String value) {
-        if (!loaded)
-            load();
-
-        return super.setProperty(key, value);
-    }
-
-    /**
-     * Returns the file backing this properties object.
-     */
-    public File getStoreFile() {
-        return file;
-    }
-
-    /**
-     * Ensures that the file backing these properties has been
-     * loaded; call this method before calling any method defined by
-     * a superclass.
-     */
-    public void load() {
-        loaded = true;
-
-        try {
-            if (!file.exists())
-                return;
-
-            InputStream s = new FileInputStream(file);
-            load(s);
-        }
-        catch (IOException ex) {
-            // eat
-        }
-    }
-
-    /**
-     * Saves the properties to the file.
-     */
-    public void store() {
-        if (!loaded)
-            return; // nothing could have changed so save unnecessary load/save
-
-        try {
-            OutputStream s = new FileOutputStream(file);
-            store(s, header);
-        }
-        catch (IOException ex) {
-            // eat
-        }
-    }
-
-}
-
-
+// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.util;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import net.sourceforge.jnlp.*;
+
+/**
+ * A properties object backed by a specified file without throwing
+ * exceptions.  The properties are automatically loaded from the
+ * file when the first property is requested, but the save method
+ * must be called before changes are saved to the file.<p>
+ *
+ * This class does not report IO exceptions.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.4 $ 
+ */
+public class PropertiesFile extends Properties {
+
+    /** the file to save to */
+    File file;
+
+    /** the header string */
+    String header = "netx file";
+
+    /** lazy loaded on getProperty */
+    boolean loaded = false;
+
+
+    /** 
+     * Create a properties object backed by the specified file. 
+     *
+     * @param file the file to save and load to
+     */
+    public PropertiesFile(File file) {
+        this.file = file;
+    }
+
+    /** 
+     * Create a properties object backed by the specified file. 
+     *
+     * @param file the file to save and load to
+     * @param header the file header
+     */
+    public PropertiesFile(File file, String header) {
+        this.file = file;
+        this.header = header;
+    }
+
+    /**
+     * Returns the value of the specified key, or null if the key
+     * does not exist.
+     */
+    public String getProperty(String key) {
+        if (!loaded)
+            load();
+
+        return super.getProperty(key);
+    }
+
+    /**
+     * Returns the value of the specified key, or the default value
+     * if the key does not exist.
+     */
+    public String getProperty(String key, String defaultValue) {
+        if (!loaded)
+            load();
+
+        return super.getProperty(key, defaultValue);
+    }
+
+    /**
+     * Sets the value for the specified key.
+     *
+     * @return the previous value
+     */
+    public Object setProperty(String key, String value) {
+        if (!loaded)
+            load();
+
+        return super.setProperty(key, value);
+    }
+
+    /**
+     * Returns the file backing this properties object.
+     */
+    public File getStoreFile() {
+        return file;
+    }
+
+    /**
+     * Ensures that the file backing these properties has been
+     * loaded; call this method before calling any method defined by
+     * a superclass.
+     */
+    public void load() {
+        loaded = true;
+
+        try {
+            if (!file.exists())
+                return;
+
+            InputStream s = new FileInputStream(file);
+            load(s);
+        }
+        catch (IOException ex) {
+            // eat
+        }
+    }
+
+    /**
+     * Saves the properties to the file.
+     */
+    public void store() {
+        if (!loaded)
+            return; // nothing could have changed so save unnecessary load/save
+
+        try {
+            OutputStream s = new FileOutputStream(file);
+            store(s, header);
+        }
+        catch (IOException ex) {
+            // eat
+        }
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/util/Reflect.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/util/Reflect.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,148 +1,148 @@
-// Copyright (C) 2003 Jon A. Maxwell (JAM)
-// 
-// This program is free software; you can redistribute it and/or
-// modify it under the terms of the GNU General Public License
-// as published by the Free Software Foundation; either version 2
-// of the License, or (at your option) any later version.
-// 
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// 
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-package net.sourceforge.jnlp.util;
-
-import java.util.*;
-import java.lang.reflect.*;
-
-
-/**
- * Provides simply, convenient methods to invoke methods by
- * name.  This class is used to consolidate reflection needed to
- * access methods specific to Sun's JVM or to remain backward
- * compatible while supporting method in newer JVMs.<p>
- *
- * Most methods of this class invoke the first method on the
- * specified object that matches the name and number of
- * parameters.  The type of the parameters are not considered, so
- * do not attempt to use this class to invoke overloaded
- * methods.<p>
- *
- * Instances of this class are not synchronized.<p>
- *
- * @author <a href="mailto:jon.maxwell@acm.org">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.1 $ 
- */
-public class Reflect {
-
-    // todo: check non-null parameter types, try to send to proper
-    // method if overloaded ones exist on the target object
-
-    // todo: optimize slightly using hashtable of Methods
-
-    private boolean accessible;
-
-    private static Object zero[] = new Object[0];
-
-    
-    /**
-     * Create a new Reflect instance.
-     */
-    public Reflect() {
-        //
-    }
-
-    /**
-     * Create a new Reflect instance.
-     *
-     * @param accessible whether to bypass access permissions
-     */
-    public Reflect(boolean accessible) {
-        this.accessible = accessible;
-    }
-
-    /**
-     * Invoke a zero-parameter static method by name.
-     */
-    public Object invokeStatic(String className, String method) {
-        return invokeStatic(className, method, zero);
-    }
-
-    /**
-     * Invoke the static method using the specified parameters.
-     */
-    public Object invokeStatic(String className, String method, Object args[]) {
-        try {
-            Class c = Class.forName(className, true, Reflect.class.getClassLoader());
-
-            Method m = getMethod(c, method, args);
-            if (m.isAccessible() != accessible)
-                m.setAccessible(accessible);
-
-            return m.invoke(null, args);
-        }
-        catch (Exception ex) { // eat
-            return null;
-        }
-    }
-
-    /**
-     * Invoke a zero-parameter method by name on the specified
-     * object.
-     */
-    public Object invoke(Object object, String method) {
-        return invoke(object, method, zero);
-    }
-
-    /**
-     * Invoke a method by name with the specified parameters.
-     *
-     * @return the result of the method, or null on exception.
-     */
-    public Object invoke(Object object, String method, Object args[]) {
-        try {
-            Method m = getMethod(object.getClass(), method, args);
-            if (m.isAccessible() != accessible)
-                m.setAccessible(accessible);
-
-            return m.invoke(object, args);
-        }
-        catch (Exception ex) { // eat
-            ex.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Return the Method matching the specified name and number of
-     * arguments.
-     */
-    public Method getMethod(Class type, String method, Object args[]) {
-        try {
-            for (Class c = type; c != null; c = c.getSuperclass()) {
-                Method methods[] = c.getMethods();
-
-                for (int i=0; i < methods.length; i++) {
-                    if (methods[i].getName().equals(method)) {
-                        Class parameters[] = methods[i].getParameterTypes();
-
-                        if (parameters.length == args.length)
-                            return methods[i];
-                    }
-                }
-            }
-        }
-        catch (Exception ex) { // eat
-            ex.printStackTrace();
-        }
-
-        return null;
-    }
-
-}
-
-
+// Copyright (C) 2003 Jon A. Maxwell (JAM)
+// 
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+// 
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// 
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+package net.sourceforge.jnlp.util;
+
+import java.util.*;
+import java.lang.reflect.*;
+
+
+/**
+ * Provides simply, convenient methods to invoke methods by
+ * name.  This class is used to consolidate reflection needed to
+ * access methods specific to Sun's JVM or to remain backward
+ * compatible while supporting method in newer JVMs.<p>
+ *
+ * Most methods of this class invoke the first method on the
+ * specified object that matches the name and number of
+ * parameters.  The type of the parameters are not considered, so
+ * do not attempt to use this class to invoke overloaded
+ * methods.<p>
+ *
+ * Instances of this class are not synchronized.<p>
+ *
+ * @author <a href="mailto:jon.maxwell@acm.org">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.1 $ 
+ */
+public class Reflect {
+
+    // todo: check non-null parameter types, try to send to proper
+    // method if overloaded ones exist on the target object
+
+    // todo: optimize slightly using hashtable of Methods
+
+    private boolean accessible;
+
+    private static Object zero[] = new Object[0];
+
+    
+    /**
+     * Create a new Reflect instance.
+     */
+    public Reflect() {
+        //
+    }
+
+    /**
+     * Create a new Reflect instance.
+     *
+     * @param accessible whether to bypass access permissions
+     */
+    public Reflect(boolean accessible) {
+        this.accessible = accessible;
+    }
+
+    /**
+     * Invoke a zero-parameter static method by name.
+     */
+    public Object invokeStatic(String className, String method) {
+        return invokeStatic(className, method, zero);
+    }
+
+    /**
+     * Invoke the static method using the specified parameters.
+     */
+    public Object invokeStatic(String className, String method, Object args[]) {
+        try {
+            Class c = Class.forName(className, true, Reflect.class.getClassLoader());
+
+            Method m = getMethod(c, method, args);
+            if (m.isAccessible() != accessible)
+                m.setAccessible(accessible);
+
+            return m.invoke(null, args);
+        }
+        catch (Exception ex) { // eat
+            return null;
+        }
+    }
+
+    /**
+     * Invoke a zero-parameter method by name on the specified
+     * object.
+     */
+    public Object invoke(Object object, String method) {
+        return invoke(object, method, zero);
+    }
+
+    /**
+     * Invoke a method by name with the specified parameters.
+     *
+     * @return the result of the method, or null on exception.
+     */
+    public Object invoke(Object object, String method, Object args[]) {
+        try {
+            Method m = getMethod(object.getClass(), method, args);
+            if (m.isAccessible() != accessible)
+                m.setAccessible(accessible);
+
+            return m.invoke(object, args);
+        }
+        catch (Exception ex) { // eat
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Return the Method matching the specified name and number of
+     * arguments.
+     */
+    public Method getMethod(Class type, String method, Object args[]) {
+        try {
+            for (Class c = type; c != null; c = c.getSuperclass()) {
+                Method methods[] = c.getMethods();
+
+                for (int i=0; i < methods.length; i++) {
+                    if (methods[i].getName().equals(method)) {
+                        Class parameters[] = methods[i].getParameterTypes();
+
+                        if (parameters.length == args.length)
+                            return methods[i];
+                    }
+                }
+            }
+        }
+        catch (Exception ex) { // eat
+            ex.printStackTrace();
+        }
+
+        return null;
+    }
+
+}
+
+
--- a/rt/net/sourceforge/jnlp/util/WeakList.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/rt/net/sourceforge/jnlp/util/WeakList.java	Tue Aug 04 17:39:11 2009 +0100
@@ -1,128 +1,128 @@
-// Copyright (C) 2002-2003 Jon A. Maxwell (JAM)
-// 
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-// 
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-// 
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-
-package net.sourceforge.jnlp.util;
-
-import java.lang.ref.*;
-import java.util.*;
-
-
-/**
- * This list stores objects automatically using weak references.
- * Objects are added and removed from the list as normal, but may
- * turn to null at any point (ie, indexOf(x) followed by get(x)
- * may return null).  The weak references are only removed when
- * the trimToSize method is called so that the indices remain
- * constant otherwise.<p>
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.3 $ 
- */
-public class WeakList extends AbstractList {
-
-    /* list of weak references */
-    private ArrayList refs = new ArrayList();
-
-
-    /** 
-     * Create a weak random-access list.
-     */
-    public WeakList() {
-    }
-
-    /**
-     * Extract the hard reference out of a weak reference.
-     */
-    private Object deref(Object o) {
-        if (o != null && o instanceof WeakReference)
-            return ((WeakReference)o).get();
-        else
-            return null;
-    }
-
-    /**
-     * Returns the object at the specified index, or null if the
-     * object has been collected.
-     */
-    public Object get(int index) {
-        return deref(refs.get(index));
-    }
-
-    /**
-     * Returns the size of the list, including already collected
-     * objects.
-     */
-    public int size() {
-        return refs.size();
-    }
-
-    /**
-     * Sets the object at the specified position and returns the
-     * previous object at that position or null if it was already
-     * collected.
-     */
-    public Object set(int index, Object element) {
-        return deref(refs.set(index, new WeakReference(element)));
-    }
-
-    /**
-     * Inserts the object at the specified position in the list.
-     * Automatically creates a weak reference to the object.
-     */
-    public void add(int index, Object element) {
-        refs.add(index, new WeakReference(element));
-    }
-
-    /**
-     * Removes the object at the specified position and returns it
-     * or returns null if it was already collected.
-     */
-    public Object remove(int index) {
-        return deref(refs.remove(index));
-    }
-
-    /**
-     * Returns a list of hard references to the objects.  The
-     * returned list does not include the collected elements, so its
-     * indices do not necessarily correlate with those of this list.
-     */
-    public List hardList() {
-        List result = new ArrayList();
-
-        for (int i=0; i < size(); i++) {
-            Object tmp = get(i);
-
-            if (tmp != null)
-                result.add(tmp);
-        }
-
-        return result;
-    }
-
-    /**
-     * Compacts the list by removing references to collected
-     * objects.
-     */
-    public void trimToSize() {
-        for (int i=size(); i-->0;)
-            if (get(i)==null)
-                remove(i);
-    }
-
-}
-
-
+// Copyright (C) 2002-2003 Jon A. Maxwell (JAM)
+// 
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+// 
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+// 
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+
+
+package net.sourceforge.jnlp.util;
+
+import java.lang.ref.*;
+import java.util.*;
+
+
+/**
+ * This list stores objects automatically using weak references.
+ * Objects are added and removed from the list as normal, but may
+ * turn to null at any point (ie, indexOf(x) followed by get(x)
+ * may return null).  The weak references are only removed when
+ * the trimToSize method is called so that the indices remain
+ * constant otherwise.<p>
+ *
+ * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
+ * @version $Revision: 1.3 $ 
+ */
+public class WeakList extends AbstractList {
+
+    /* list of weak references */
+    private ArrayList refs = new ArrayList();
+
+
+    /** 
+     * Create a weak random-access list.
+     */
+    public WeakList() {
+    }
+
+    /**
+     * Extract the hard reference out of a weak reference.
+     */
+    private Object deref(Object o) {
+        if (o != null && o instanceof WeakReference)
+            return ((WeakReference)o).get();
+        else
+            return null;
+    }
+
+    /**
+     * Returns the object at the specified index, or null if the
+     * object has been collected.
+     */
+    public Object get(int index) {
+        return deref(refs.get(index));
+    }
+
+    /**
+     * Returns the size of the list, including already collected
+     * objects.
+     */
+    public int size() {
+        return refs.size();
+    }
+
+    /**
+     * Sets the object at the specified position and returns the
+     * previous object at that position or null if it was already
+     * collected.
+     */
+    public Object set(int index, Object element) {
+        return deref(refs.set(index, new WeakReference(element)));
+    }
+
+    /**
+     * Inserts the object at the specified position in the list.
+     * Automatically creates a weak reference to the object.
+     */
+    public void add(int index, Object element) {
+        refs.add(index, new WeakReference(element));
+    }
+
+    /**
+     * Removes the object at the specified position and returns it
+     * or returns null if it was already collected.
+     */
+    public Object remove(int index) {
+        return deref(refs.remove(index));
+    }
+
+    /**
+     * Returns a list of hard references to the objects.  The
+     * returned list does not include the collected elements, so its
+     * indices do not necessarily correlate with those of this list.
+     */
+    public List hardList() {
+        List result = new ArrayList();
+
+        for (int i=0; i < size(); i++) {
+            Object tmp = get(i);
+
+            if (tmp != null)
+                result.add(tmp);
+        }
+
+        return result;
+    }
+
+    /**
+     * Compacts the list by removing references to collected
+     * objects.
+     */
+    public void trimToSize() {
+        for (int i=size(); i-->0;)
+            if (get(i)==null)
+                remove(i);
+    }
+
+}
+
+
--- a/test/jtreg/com/sun/javatest/exec/FileTable.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/test/jtreg/com/sun/javatest/exec/FileTable.java	Tue Aug 04 17:39:11 2009 +0100
@@ -24,76 +24,76 @@
  * CA 95054 USA or visit www.sun.com if you need additional information or
  * have any questions.
  */
-package com.sun.javatest.exec;
-
-import com.sun.javatest.tool.UIFactory;
-import java.awt.Component;
-import javax.swing.Icon;
-import javax.swing.JTable;
-import javax.swing.ListSelectionModel;
-import javax.swing.table.DefaultTableCellRenderer;
-
-public class FileTable extends JTable {
-    
-    
-    
-    public FileTable(FileSystemTableModel model, UIFactory uif) {
-        super(model);
-        this.uif = uif;
-        setCellSelectionEnabled(false);
-        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-        setRowSelectionAllowed(true);
-        setShowGrid(false);
-        getColumnModel().getColumn(0).setCellRenderer(new IconRenderer());
-    }
-    
-    private class IconRenderer extends DefaultTableCellRenderer {
-
-        {
-            up = uif.createIcon("upper");
-            dir = uif.createIcon("folder");
-        }
-        
-        public Component getTableCellRendererComponent(JTable table, Object value,
-                boolean isSelected, boolean hasFocus, int row, int column) {
-            
-            setFont(table.getFont());
-            setIcon(null);
-            setText("");
-            
-            if (isSelected) {
-                super.setForeground(table.getSelectionForeground());
-                super.setBackground(table.getSelectionBackground());
-            } else {
-                super.setForeground(table.getForeground());
-                super.setBackground(table.getBackground());
-            }
-            
-            if (value instanceof FileTableNode) {
-                FileTableNode fn = (FileTableNode) value;
-                if (fn.getMode() != 'f') {
-                    if (fn.getMode() == 'u') {
-                        setIcon(up);
-                        return this;
-                    }
-                    if (fn.getMode() == 'd') {
-                        setIcon(dir);
-                        setText(fn.toString());
-                        return this;
-                    }
-                }
-            }
-            return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
-        }
-
-        private Icon up;
-        private Icon dir;
-        
-    }
-    
-    
-    
-    private UIFactory uif;
-    
-    
-}
+package com.sun.javatest.exec;
+
+import com.sun.javatest.tool.UIFactory;
+import java.awt.Component;
+import javax.swing.Icon;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.table.DefaultTableCellRenderer;
+
+public class FileTable extends JTable {
+    
+    
+    
+    public FileTable(FileSystemTableModel model, UIFactory uif) {
+        super(model);
+        this.uif = uif;
+        setCellSelectionEnabled(false);
+        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        setRowSelectionAllowed(true);
+        setShowGrid(false);
+        getColumnModel().getColumn(0).setCellRenderer(new IconRenderer());
+    }
+    
+    private class IconRenderer extends DefaultTableCellRenderer {
+
+        {
+            up = uif.createIcon("upper");
+            dir = uif.createIcon("folder");
+        }
+        
+        public Component getTableCellRendererComponent(JTable table, Object value,
+                boolean isSelected, boolean hasFocus, int row, int column) {
+            
+            setFont(table.getFont());
+            setIcon(null);
+            setText("");
+            
+            if (isSelected) {
+                super.setForeground(table.getSelectionForeground());
+                super.setBackground(table.getSelectionBackground());
+            } else {
+                super.setForeground(table.getForeground());
+                super.setBackground(table.getBackground());
+            }
+            
+            if (value instanceof FileTableNode) {
+                FileTableNode fn = (FileTableNode) value;
+                if (fn.getMode() != 'f') {
+                    if (fn.getMode() == 'u') {
+                        setIcon(up);
+                        return this;
+                    }
+                    if (fn.getMode() == 'd') {
+                        setIcon(dir);
+                        setText(fn.toString());
+                        return this;
+                    }
+                }
+            }
+            return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+        }
+
+        private Icon up;
+        private Icon dir;
+        
+    }
+    
+    
+    
+    private UIFactory uif;
+    
+    
+}
--- a/test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java	Tue Aug 04 17:39:11 2009 +0100
@@ -24,240 +24,240 @@
  * CA 95054 USA or visit www.sun.com if you need additional information or
  * have any questions.
  */
-package com.sun.javatest.mrep;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Container;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.BorderFactory;
-import javax.swing.Box;
-import javax.swing.DefaultListModel;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JDialog;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.ListSelectionModel;
-import javax.swing.border.Border;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-
-import com.sun.javatest.tool.UIFactory;
-import javax.swing.JOptionPane;
-
-public class ConflictResolutionDialog extends JDialog {
-
-    private String resolveButtonStr = "resolve";
-    private String cancelButtonStr = "cancel";
-    private String useMostRecentCheckBoxStr = "useMost";
-
-    private JCheckBox preferredReportCheckBox;
-    private JCheckBox useMostRecentCheckBox;
-
-    private JButton resolveButton;
-    private JButton cancelButton;
-
-    private DefaultListModel listModel;
-    private JList list;
-
-    private int     selectedIndex;
-    private boolean bPreferredReport;
-    private boolean bUseMostRecent;
-    
-    private UIFactory uif;
-
-    private boolean cancel = false;
-
-
-    public ConflictResolutionDialog(JFrame parent, String testName, String[] reportsList, boolean bPreferredSet, UIFactory uif) {
-        super(parent, true);
-        this.uif = uif;
-        
-        setName("conflict");        
-        setTitle(uif.getI18NString("conflict.name"));
-        setResizable(false);
-
-        Container cp = getContentPane();
-        cp.setLayout(new BorderLayout());
-        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
-        ConflictResolutionActionListener conflictResolutionListener = new ConflictResolutionActionListener();
-
-        JLabel text = uif.createLabel("conflict.text"); 
-        text.setText(text.getText() + " " + testName);
-       
-
-        text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
-        JPanel textPanel = uif.createPanel("conflict.text.panel", new FlowLayout(FlowLayout.CENTER));
-        textPanel.add(text);
-
-        Box vBox = Box.createVerticalBox();
-
-        JLabel chooseText =uif.createLabel("conflict.chooseText");
-        //text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
-        JPanel chooseTextPanel = uif.createPanel("conflict.choosePanel", new FlowLayout(FlowLayout.CENTER));
-        chooseTextPanel.add(chooseText);
-
-        // Build list box
-        listModel=new DefaultListModel();
-        for (int i=0; i< reportsList.length; i++) {
-            listModel.addElement(reportsList[i]);
-        }
-        list = uif.createList("conflict.list", listModel);
-        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
-
-        ReportsListSelectionListener rl = new ReportsListSelectionListener();
-        list.addListSelectionListener(rl);
-        Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK);
-        list.setBorder(brd);
-        JScrollPane scrollPane = uif.createScrollPane(list);
-        Box hBox = Box.createHorizontalBox();
-        //hBox.add(Box.createHorizontalStrut(20));
-        hBox.add(scrollPane);
-        //hBox.add(Box.createHorizontalStrut(20));
-
-        preferredReportCheckBox = uif.createCheckBox("conflict.preffered");
-        preferredReportCheckBox.setMnemonic(0);
-        preferredReportCheckBox.setEnabled(false);
-        JPanel preferredReportPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
-        preferredReportPanel.add(preferredReportCheckBox);
-
-
-        useMostRecentCheckBox = uif.createCheckBox("conflict.most.recent");
-        useMostRecentCheckBox.setMnemonic(1);
-        useMostRecentCheckBox.addActionListener(conflictResolutionListener);
-        useMostRecentCheckBox.setActionCommand(useMostRecentCheckBoxStr);
-        JPanel useRecentPanel = uif.createPanel("conflict.recent", new FlowLayout(FlowLayout.LEFT));
-        useRecentPanel.add(useMostRecentCheckBox);
-
-
-
-        vBox.setBorder(BorderFactory.createEmptyBorder(0,20,0,20));
-        vBox.add(chooseTextPanel);
-        vBox.add(hBox);
-        // if preferred report was already chosen, in previous dialogs, it should not be seen here
-        if (!bPreferredSet) {
-            vBox.add(preferredReportPanel);
-        }
-        vBox.add(useRecentPanel);
-
-
-        // Build control buttons
-        JPanel controlButtonsPanel = uif.createPanel("conflict.control", new FlowLayout(FlowLayout.CENTER));
-        JPanel p2 = new JPanel();
-        p2.setLayout(new GridLayout(1,0,5,5));
-
-        resolveButton = uif.createButton("conflict.resolve");
-        resolveButton.setMnemonic(0);
-        resolveButton.addActionListener(conflictResolutionListener);
-        resolveButton.setActionCommand(resolveButtonStr);
-        resolveButton.setEnabled(false);
-
-        cancelButton = uif.createButton("conflict.cancel");
-        cancelButton.addActionListener(conflictResolutionListener);
-        cancelButton.setActionCommand(cancelButtonStr);
-        
-        p2.add(resolveButton);
-        p2.add(cancelButton);
-
-        controlButtonsPanel.add(p2);
-        controlButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20,0,0,0));
-
-        cp.add(textPanel, BorderLayout.NORTH);
-        cp.add(vBox, BorderLayout.CENTER);
-        cp.add(controlButtonsPanel, BorderLayout.SOUTH);
-
-        pack();
-        setLocationRelativeTo(parent);
-    }
-
-    public int  getSelectedIndex() {
-        return selectedIndex;
-    }
-    public boolean getPreferredReport() {
-        return bPreferredReport;
-    }
-
-    public boolean getUseMostRecent() {
-        return bUseMostRecent;
-    }
-
-    public boolean wasCanceled() {
-        return cancel;
-    }
-    
-    class CancelException extends Exception {
-        
-    }
-    
-    
-    class ConflictResolutionActionListener implements ActionListener {
-
-        public void actionPerformed(ActionEvent e) {
-
-            String cmd = e.getActionCommand();
-            if (cmd.equals(cancelButtonStr)) {
-                
-                if (uif.showYesNoDialog("conflict.areyousure") != JOptionPane.YES_OPTION)
-                    return;
-                
-                ConflictResolutionDialog.this.cancel = true;
-                ConflictResolutionDialog.this.dispose();
-            } else if (cmd.equals(resolveButtonStr)) {
-                bUseMostRecent = useMostRecentCheckBox.isSelected();
-                bPreferredReport = preferredReportCheckBox.isSelected();
-                selectedIndex = list.getSelectedIndex();
-                ConflictResolutionDialog.this.dispose();
-
-            } else if (cmd.equals(useMostRecentCheckBoxStr)) {
-                if ((list.getSelectedValues().length == 0) &&
-                        (!useMostRecentCheckBox.isSelected())) {
-                    resolveButton.setEnabled(false);
-                } else {
-                    resolveButton.setEnabled(true);
-                }
-
-
-                if (useMostRecentCheckBox.isSelected()) {
-                    list.setEnabled(false);
-                    preferredReportCheckBox.setEnabled(false);
-                } else {
-                    list.setEnabled(true);
-                    preferredReportCheckBox.setEnabled(true);
-                }
-            } else
-                ; // ignore events on all other objects
-        }
-    }
-
-
-    class ReportsListSelectionListener implements ListSelectionListener {
-        public void valueChanged(ListSelectionEvent e) {
-            if(e.getValueIsAdjusting()) return;
-
-            if ((list.getSelectedValues().length == 0) &&
-                    (!useMostRecentCheckBox.isSelected())) {
-                resolveButton.setEnabled(false);
-            } else {
-                resolveButton.setEnabled(true);
-            }
-
-
-            if (list.getSelectedValues().length == 0) {
-                preferredReportCheckBox.setEnabled(false);
-            } else {
-                preferredReportCheckBox.setEnabled(true);
-            }
-
-        }
-    }
-
-}
-
+package com.sun.javatest.mrep;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListSelectionModel;
+import javax.swing.border.Border;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import com.sun.javatest.tool.UIFactory;
+import javax.swing.JOptionPane;
+
+public class ConflictResolutionDialog extends JDialog {
+
+    private String resolveButtonStr = "resolve";
+    private String cancelButtonStr = "cancel";
+    private String useMostRecentCheckBoxStr = "useMost";
+
+    private JCheckBox preferredReportCheckBox;
+    private JCheckBox useMostRecentCheckBox;
+
+    private JButton resolveButton;
+    private JButton cancelButton;
+
+    private DefaultListModel listModel;
+    private JList list;
+
+    private int     selectedIndex;
+    private boolean bPreferredReport;
+    private boolean bUseMostRecent;
+    
+    private UIFactory uif;
+
+    private boolean cancel = false;
+
+
+    public ConflictResolutionDialog(JFrame parent, String testName, String[] reportsList, boolean bPreferredSet, UIFactory uif) {
+        super(parent, true);
+        this.uif = uif;
+        
+        setName("conflict");        
+        setTitle(uif.getI18NString("conflict.name"));
+        setResizable(false);
+
+        Container cp = getContentPane();
+        cp.setLayout(new BorderLayout());
+        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+        ConflictResolutionActionListener conflictResolutionListener = new ConflictResolutionActionListener();
+
+        JLabel text = uif.createLabel("conflict.text"); 
+        text.setText(text.getText() + " " + testName);
+       
+
+        text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+        JPanel textPanel = uif.createPanel("conflict.text.panel", new FlowLayout(FlowLayout.CENTER));
+        textPanel.add(text);
+
+        Box vBox = Box.createVerticalBox();
+
+        JLabel chooseText =uif.createLabel("conflict.chooseText");
+        //text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+        JPanel chooseTextPanel = uif.createPanel("conflict.choosePanel", new FlowLayout(FlowLayout.CENTER));
+        chooseTextPanel.add(chooseText);
+
+        // Build list box
+        listModel=new DefaultListModel();
+        for (int i=0; i< reportsList.length; i++) {
+            listModel.addElement(reportsList[i]);
+        }
+        list = uif.createList("conflict.list", listModel);
+        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+        ReportsListSelectionListener rl = new ReportsListSelectionListener();
+        list.addListSelectionListener(rl);
+        Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK);
+        list.setBorder(brd);
+        JScrollPane scrollPane = uif.createScrollPane(list);
+        Box hBox = Box.createHorizontalBox();
+        //hBox.add(Box.createHorizontalStrut(20));
+        hBox.add(scrollPane);
+        //hBox.add(Box.createHorizontalStrut(20));
+
+        preferredReportCheckBox = uif.createCheckBox("conflict.preffered");
+        preferredReportCheckBox.setMnemonic(0);
+        preferredReportCheckBox.setEnabled(false);
+        JPanel preferredReportPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        preferredReportPanel.add(preferredReportCheckBox);
+
+
+        useMostRecentCheckBox = uif.createCheckBox("conflict.most.recent");
+        useMostRecentCheckBox.setMnemonic(1);
+        useMostRecentCheckBox.addActionListener(conflictResolutionListener);
+        useMostRecentCheckBox.setActionCommand(useMostRecentCheckBoxStr);
+        JPanel useRecentPanel = uif.createPanel("conflict.recent", new FlowLayout(FlowLayout.LEFT));
+        useRecentPanel.add(useMostRecentCheckBox);
+
+
+
+        vBox.setBorder(BorderFactory.createEmptyBorder(0,20,0,20));
+        vBox.add(chooseTextPanel);
+        vBox.add(hBox);
+        // if preferred report was already chosen, in previous dialogs, it should not be seen here
+        if (!bPreferredSet) {
+            vBox.add(preferredReportPanel);
+        }
+        vBox.add(useRecentPanel);
+
+
+        // Build control buttons
+        JPanel controlButtonsPanel = uif.createPanel("conflict.control", new FlowLayout(FlowLayout.CENTER));
+        JPanel p2 = new JPanel();
+        p2.setLayout(new GridLayout(1,0,5,5));
+
+        resolveButton = uif.createButton("conflict.resolve");
+        resolveButton.setMnemonic(0);
+        resolveButton.addActionListener(conflictResolutionListener);
+        resolveButton.setActionCommand(resolveButtonStr);
+        resolveButton.setEnabled(false);
+
+        cancelButton = uif.createButton("conflict.cancel");
+        cancelButton.addActionListener(conflictResolutionListener);
+        cancelButton.setActionCommand(cancelButtonStr);
+        
+        p2.add(resolveButton);
+        p2.add(cancelButton);
+
+        controlButtonsPanel.add(p2);
+        controlButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20,0,0,0));
+
+        cp.add(textPanel, BorderLayout.NORTH);
+        cp.add(vBox, BorderLayout.CENTER);
+        cp.add(controlButtonsPanel, BorderLayout.SOUTH);
+
+        pack();
+        setLocationRelativeTo(parent);
+    }
+
+    public int  getSelectedIndex() {
+        return selectedIndex;
+    }
+    public boolean getPreferredReport() {
+        return bPreferredReport;
+    }
+
+    public boolean getUseMostRecent() {
+        return bUseMostRecent;
+    }
+
+    public boolean wasCanceled() {
+        return cancel;
+    }
+    
+    class CancelException extends Exception {
+        
+    }
+    
+    
+    class ConflictResolutionActionListener implements ActionListener {
+
+        public void actionPerformed(ActionEvent e) {
+
+            String cmd = e.getActionCommand();
+            if (cmd.equals(cancelButtonStr)) {
+                
+                if (uif.showYesNoDialog("conflict.areyousure") != JOptionPane.YES_OPTION)
+                    return;
+                
+                ConflictResolutionDialog.this.cancel = true;
+                ConflictResolutionDialog.this.dispose();
+            } else if (cmd.equals(resolveButtonStr)) {
+                bUseMostRecent = useMostRecentCheckBox.isSelected();
+                bPreferredReport = preferredReportCheckBox.isSelected();
+                selectedIndex = list.getSelectedIndex();
+                ConflictResolutionDialog.this.dispose();
+
+            } else if (cmd.equals(useMostRecentCheckBoxStr)) {
+                if ((list.getSelectedValues().length == 0) &&
+                        (!useMostRecentCheckBox.isSelected())) {
+                    resolveButton.setEnabled(false);
+                } else {
+                    resolveButton.setEnabled(true);
+                }
+
+
+                if (useMostRecentCheckBox.isSelected()) {
+                    list.setEnabled(false);
+                    preferredReportCheckBox.setEnabled(false);
+                } else {
+                    list.setEnabled(true);
+                    preferredReportCheckBox.setEnabled(true);
+                }
+            } else
+                ; // ignore events on all other objects
+        }
+    }
+
+
+    class ReportsListSelectionListener implements ListSelectionListener {
+        public void valueChanged(ListSelectionEvent e) {
+            if(e.getValueIsAdjusting()) return;
+
+            if ((list.getSelectedValues().length == 0) &&
+                    (!useMostRecentCheckBox.isSelected())) {
+                resolveButton.setEnabled(false);
+            } else {
+                resolveButton.setEnabled(true);
+            }
+
+
+            if (list.getSelectedValues().length == 0) {
+                preferredReportCheckBox.setEnabled(false);
+            } else {
+                preferredReportCheckBox.setEnabled(true);
+            }
+
+        }
+    }
+
+}
+
--- a/test/jtreg/com/sun/javatest/report/XMLReportMaker.java	Tue Aug 04 11:34:49 2009 -0400
+++ b/test/jtreg/com/sun/javatest/report/XMLReportMaker.java	Tue Aug 04 17:39:11 2009 +0100
@@ -24,586 +24,586 @@
  * CA 95054 USA or visit www.sun.com if you need additional information or
  * have any questions.
  */
-package com.sun.javatest.report;
-
-import com.sun.javatest.Status;
-import java.io.File;
-import java.io.IOException;
-import java.io.Writer;
-import java.text.ParseException;
-import java.util.Date;
-import java.util.Properties;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-import org.xml.sax.Attributes;
-import org.xml.sax.ext.LexicalHandler;
-
-public class XMLReportMaker {
-    
-    private TransformerHandler ser;
-    
-    XMLReportMaker(Writer w) throws IOException {
-        
-        Properties outputProps = new Properties();
-        outputProps.put("indent", "yes");
-        outputProps.put("encoding", XML_CHARSET);
-        SAXTransformerFactory stf = (SAXTransformerFactory )TransformerFactory.newInstance();
-        stf.setAttribute("indent-number", 4);
-        try {
-            ser = stf.newTransformerHandler();
-        } catch (TransformerConfigurationException ex) {
-            ex.printStackTrace();
-        }
-        ser.getTransformer().setOutputProperties(outputProps);
-        ser.setResult(new StreamResult(w));
-    }
-    
-    void sDocument() throws SAXException {
-        ser.startDocument();
-    }
-    
-    void eDocument() throws SAXException, IOException {
-        ser.endDocument();
-    }
-    
-    void sReport() throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.XSI, "String", Scheme.XSI_VAL);
-        atts.addAttribute("", "", Scheme.SCH_LOC, "String", Scheme.SCH_LOC_VAL);
-        atts.addAttribute("", "", Scheme.REPORT_FORMST, "String", "v1");
-        atts.addAttribute("", "", Scheme.REPORT_GENTIME, "String", XMLReport.Utils.dateToISO8601(new Date()));
-        sE(Scheme.REPORT, atts);
-    }
-    
-    void eReport() throws SAXException {
-        eE(Scheme.REPORT);
-    }
-    
-    void sSummary() throws SAXException {
-        sE(Scheme.SUMMARY);
-    }
-    
-    void eSummary() throws SAXException {
-        eE(Scheme.SUMMARY);
-    }
-    
-    
-    void sWorkdirectories() throws SAXException {
-        sE(Scheme.WDS);
-    }
-    
-    void eWorkdirectories() throws SAXException {
-        eE(Scheme.WDS);
-    }
-    
-    void sEnvironment(String name, String descr) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        if (name != null && ! "".equals(name.trim())) {
-            atts.addAttribute("", "", Scheme.ENV_NAME, "String", name);
-        }
-        if (descr != null && ! "".equals(descr.trim())) {
-            atts.addAttribute("", "", Scheme.ENV_DESCR, "String", descr);
-        }
-        sE(Scheme.ENV, atts);
-    }
-    
-    void eEnvironment() throws SAXException {
-        eE(Scheme.ENV);
-    }
-    
-    void conCur(int val) throws SAXException  {
-        sE(Scheme.CONC);
-        String sVal = Integer.toString(val);
-        ser.characters(sVal.toCharArray(), 0, sVal.length());
-        eE(Scheme.CONC);
-    }
-    
-    void timeOut(float val) throws SAXException  {
-        sE(Scheme.TIMO);
-        String sVal = Float.toString(val);
-        ser.characters(sVal.toCharArray(), 0, sVal.length());
-        eE(Scheme.TIMO);
-    }
-    
-    void sWorkdirectory(String jti) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.WD_ID, "String", "1");
-        if (jti != null) {
-            atts.addAttribute("", "", Scheme.WD_JTI, "String", jti);
-        }
-        sE(Scheme.WD, atts);
-    }
-    
-    void eWorkdirectory() throws SAXException {
-        eE(Scheme.WD);
-    }
-    
-    void sTestResults() throws SAXException {
-        sE(Scheme.TRS);
-    }
-    
-    void eTestResults() throws SAXException {
-        eE(Scheme.TRS);
-    }
-    
-    void sTestResult(String url, Status st, int id) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.TR_URL, "String", url);
-        atts.addAttribute("", "", Scheme.TR_STATUS, "String", XMLReport.Utils.statusToString(st));
-        atts.addAttribute("", "", Scheme.TR_WDID, "Integer", Integer.toString(id));
-        sE(Scheme.TR, atts);
-    }
-    
-    void eTestResult() throws SAXException {
-        eE(Scheme.TR);
-    }
-    
-    void sDescriptionData() throws SAXException {
-        sE(Scheme.DESCR_DATA);
-    }
-    
-    void eDescriptionData() throws SAXException {
-        eE(Scheme.DESCR_DATA);
-    }
-    
-    
-    void sKeyWords() throws SAXException {
-        sE(Scheme.KEY_WORDS);
-    }
-    
-    void sKeyWords(String expr) throws SAXException {
-        
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.KEYWORDS_EXPR, "String", expr);
-        sE(Scheme.KEY_WORDS, atts);
-    }
-    
-    void eKeyWords() throws SAXException {
-        eE(Scheme.KEY_WORDS);
-    }
-    
-    void sTestEnvironment() throws SAXException {
-        sE(Scheme.TEST_ENV);
-    }
-    
-    void eTestEnvironment() throws SAXException {
-        eE(Scheme.TEST_ENV);
-    }
-    
-    void sResultProps(String time) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        if (time != null) {
-            try {
-                time = XMLReport.Utils.dateToISO8601(XMLReport.Utils.jtrToDate(time));
-            } catch (ParseException ex) {
-                throw new SAXException(ex);
-            }
-            atts.addAttribute("", "", Scheme.RES_PROP_TIM, "String", time);
-        }
-        sE(Scheme.RES_PROP, atts);
-    }
-    
-    void eResultProps() throws SAXException {
-        eE(Scheme.RES_PROP);
-    }
-    
-    void sSections() throws SAXException {
-        sE(Scheme.SES);
-    }
-    
-    void eSections() throws SAXException {
-        eE(Scheme.SES);
-    }
-    
-    void sStdValues() throws SAXException {
-        sE(Scheme.STD_VALS);
-    }
-    
-    void eStdValues() throws SAXException {
-        eE(Scheme.STD_VALS);
-    }
-    
-    void sPriorStatusList() throws SAXException {
-        sE(Scheme.PRIOS);
-    }
-    
-    void ePriorStatusList() throws SAXException {
-        eE(Scheme.PRIOS);
-    }
-    
-    void sExclList() throws SAXException {
-        sE(Scheme.EXCL_LIST);
-    }
-    
-    void eExclList() throws SAXException {
-        eE(Scheme.EXCL_LIST);
-    }
-    
-    void sTests() throws SAXException {
-        sE(Scheme.TESTS);
-    }
-    
-    void eTests() throws SAXException {
-        eE(Scheme.TESTS);
-    }
-    
-    void sInterview() throws SAXException {
-        sE(Scheme.INT);
-    }
-    
-    void eInterview() throws SAXException {
-        eE(Scheme.INT);
-    }
-    
-    void sQuestion(String value, String text, String summary) throws SAXException {
-        
-        AttributesImpl atts = new AttributesImpl();
-        if (summary != null) {
-            atts.addAttribute("", "", Scheme.QUEST_SUMM, "String", summary);
-        }
-        if (value != null) {
-            atts.addAttribute("", "", Scheme.QUEST_VALUE, "String", value);
-        }
-        if (value != null) {
-            atts.addAttribute("", "", Scheme.QUEST_TEXT, "String", text);
-        }
-        
-        sE(Scheme.QUEST, atts);
-    }
-    
-    void eQuestion() throws SAXException {
-        eE(Scheme.QUEST);
-    }
-    
-    
-    void sListQuestion() throws SAXException {
-        sE(Scheme.LIST_QUEST);
-    }
-    
-    void eListQuestion() throws SAXException {
-        eE(Scheme.LIST_QUEST);
-    }
-    
-    void sChoiceQuestion() throws SAXException {
-        sE(Scheme.CHOICE_QUEST);
-    }
-    
-    void eChoiceQuestion() throws SAXException {
-        eE(Scheme.CHOICE_QUEST);
-    }
-    
-    void sPropertiesQuestion()  throws SAXException {
-        sE(Scheme.PROP_QUEST);
-    }
-    
-    void ePropertiesQuestion()  throws SAXException {
-        eE(Scheme.PROP_QUEST);
-    }
-    
-    void sGroup(String name, String hd1, String hd2) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        if (name != null) {
-            atts.addAttribute("", "", Scheme.GROUP_NAME, "String", name);
-        }
-        if (hd1 != null) {
-            atts.addAttribute("", "", Scheme.GROUP_HD1, "String", hd1);
-        }
-        if (hd2 != null) {
-            atts.addAttribute("", "", Scheme.GROUP_HD2, "String", hd2);
-        }
-        sE(Scheme.GROUP, atts);
-    }
-    
-    void eGroup(String name, String h1, String h2) throws SAXException {
-        eE(Scheme.GROUP);
-    }
-    
-    void makeRow(String key, String val) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        if (key != null) {
-            atts.addAttribute("", "", Scheme.ROW_KEY, "String", key);
-        }
-        if (val != null) {
-            atts.addAttribute("", "", Scheme.ROW_VAL, "String", val);
-        }
-        sE(Scheme.ROW, atts);
-        eE(Scheme.ROW);
-    }
-    
-    void makeChoices(String[] ch, String[] dispCh) throws SAXException {
-        if (ch != null) {
-            for (int i = 0; i < ch.length; i++) {
-                makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null);
-            }
-        }
-    }
-    
-    void makeChoices(String[] ch, String[] dispCh, boolean[] values) throws SAXException {
-        if (ch != null) {
-            for (int i = 0; i < ch.length; i++) {
-                makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null, i < values.length ? values[i] : false);
-            }
-        }
-    }
-    
-    
-    private void makeChoice(String ch, String di) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        if (ch != null) {
-            atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch);
-        }
-        if (di != null) {
-            atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di);
-        }
-        sE(Scheme.CHOICE, atts);
-        eE(Scheme.CHOICE);
-    }
-    
-    private void makeChoice(String ch, String di, boolean val) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch);
-        if (di != null) {
-            atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di);
-        }
-        atts.addAttribute("", "", Scheme.CHOICE_VAL, "String", Boolean.toString(val));
-        sE(Scheme.CHOICE, atts);
-        eE(Scheme.CHOICE);
-    }
-    
-    
-    void makeEntireTestTree() throws SAXException {
-        sE(Scheme.ENTTREE);
-        eE(Scheme.ENTTREE);
-    }
-    
-    void sSection(String title, Status st) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.SE_TIT, "String", title);
-        if (st != null) {
-            atts.addAttribute("", "", Scheme.SE_ST, "String", XMLReport.Utils.statusToString(st));
-        }
-        sE(Scheme.SE, atts);
-    }
-    
-    void eSection() throws SAXException {
-        eE(Scheme.SE);
-    }
-    
-    void sOutput(String title, String content) throws SAXException, IOException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.OU_TIT, "String", title);
-        sE(Scheme.OU, atts);
-        if (content != null && content.length() > 0) {
-            writeCDATA(ser, ser, content);
-        }
-    }
-    
-    void eOutput() throws SAXException {
-        eE(Scheme.OU);
-    }
-    
-    void makeTemplateInfo(String tPath, String name, String descr) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        if (tPath != null) {
-            atts.addAttribute("", "", Scheme.TEM_FILE, "String", tPath);
-        }
-        if (name != null) {
-            atts.addAttribute("", "", Scheme.TEM_NAME, "String", name);
-        }
-        if (descr != null) {
-            atts.addAttribute("", "", Scheme.TEM_DESCRIPTION, "String", descr);
-        }
-        sE(Scheme.TEMPLATE, atts);
-        eE(Scheme.TEMPLATE);
-    }
-    
-    
-    void makeProperty(String key, String val) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.PR_NAME, "String", key);
-        atts.addAttribute("", "", Scheme.PR_VAL, "CDATA", val);
-        sE(Scheme.PR, atts);
-        eE(Scheme.PR);
-    }
-    
-    void makeItem(String val) throws SAXException {
-        AttributesImpl atts = new AttributesImpl();
-        atts.addAttribute("", "", Scheme.IT_VAL, "CDATA", val);
-        sE(Scheme.IT, atts);
-        eE(Scheme.IT);
-    }
-    
-    void makeItems(String[] vals) throws SAXException {
-        if (vals != null) {
-            for (int i = 0 ; i < vals.length; i++) {
-                if (vals[i] != null) {
-                    makeItem(vals[i]);
-                }
-            }
-        }
-    }
-    
-    void makeItems(File[] files) throws SAXException {
-        if (files != null) {
-            for (int i = 0 ; i < files.length; i++) {
-                makeItem(files[i].getPath());
-            }
-        }
-    }
-    
-    
-    private void sE(String name) throws SAXException {
-        ser.startElement("","",name, emptyAttr);
-    }
-    
-    private void sE(String name, Attributes atts) throws SAXException {
-        ser.startElement("","",name, atts);
-    }
-    
-    private void eE(String name) throws SAXException {
-        ser.endElement("","",name);
-    }
-    
-    public static void writeCDATA(LexicalHandler lh, ContentHandler ser, String cdata) throws IOException, SAXException {
-        
-        cdata = convertProhibitedChars(cdata);
-        
-        if (lh != null) {
-            int start = 0;
-            int end;
-            while ( start < cdata.length()) {
-                end = cdata.length(); int xpos = cdata.indexOf("]]>", start);
-                if (xpos != -1) {
-                    end = xpos+1;
-                }
-                lh.startCDATA();
-                String fragment= cdata.substring(start, end);
-                ser.characters(fragment.toCharArray(), 0, fragment.length());
-                lh.endCDATA();
-                start = end;
-            }
-        }
-    }
-    
-    public static String convertProhibitedChars(String cdata) {
-        StringBuffer sb = new StringBuffer();
-        char [] data = cdata.toCharArray();
-        for (int i = 0; i < data.length; i++) {
-            if (prohibited(data[i])) {
-                sb.append("\\u");
-                String rX = Integer.toHexString((int)data[i]);
-                for (int ii = rX.length();  ii < 4; ii++) {
-                    sb.append("0");  //
-                }
-                sb.append(rX);
-            } else {
-                sb.append(data[i]);
-            }
-        }
-        return sb.toString();
-    }
-    
-    // XML 1.0 specification ( http://www.w3.org/TR/2004/REC-xml-20040204/ ) defines legal chars:
-    // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
-    public static boolean prohibited(int c) {
-        if (c == 0x0009 || c == 0x000A || c == 0x000D) return false;
-        if (c >= 0x0020 && c <= 0xD7FF) return false;
-        if (c >= 0xE000 && c <= 0xFFFD) return false;
-        if (c >= 0x10000 && c <= 0x10FFFF) return false; // can't be java char, but any way...
-        return true;
-    }
-    
-    
-    private final AttributesImpl emptyAttr = new AttributesImpl();
-    
-    /**
-     * Elements and attributes names are defined here
-     */
-    private static class Scheme {
-        
-        // ELEMENTS
-        private static final String REPORT = "Report";
-        private static final String SUMMARY = "Summary";
-        private static final String WDS = "WorkDirectories";
-        private static final String WD = "WorkDirectory";
-        private static final String TRS = "TestResults";
-        private static final String TR = "TestResult";
-        private static final String INT = "Interview";
-        private static final String Q = "Question";
-        private static final String DESCR_DATA ="DescriptionData";
-        private static final String KEY_WORDS = "Keywords";
-        private static final String TEST_ENV = "TestEnvironment";
-        private static final String RES_PROP = "ResultProperties";
-        private static final String SES = "Sections";
-        private static final String SE = "Section";
-        private static final String OU = "Output";
-        private static final String PR = "Property";
-        private static final String IT = "Item";
-        private static final String ENV = "Environment";
-        private static final String CONC = "Concurrency";
-        private static final String TIMO = "TimeOut";
-        private static final String PRIOS = "PriorStatusList";
-        private static final String EXCL_LIST = "ExcludeList";
-        private static final String STD_VALS = "StandardValues";
-        private static final String TESTS = "Tests";
-        private static final String ENTTREE = "EntireTestTree";
-        private static final String QUEST = "Question";
-        private static final String LIST_QUEST = "ListQuestion";
-        private static final String CHOICE_QUEST = "ChoiceQuestion";
-        private static final String CHOICE = "Choice";
-        private static final String PROP_QUEST = "PropertiesQuestion";
-        private static final String GROUP = "Group";
-        private static final String ROW = "Row";
-        private static final String TEMPLATE = "Template";
-        
-        // ATTRS
-        private static final String TR_URL = "url";
-        private static final String TR_STATUS = "status";
-        private static final String TR_WDID = "workDirID";
-        private static final String PR_NAME = "name";
-        private static final String PR_VAL = "value";
-        private static final String IT_VAL = "value";
-        private static final String SE_TIT = "title";
-        private static final String SE_ST = "status";
-        private static final String OU_TIT = "title";
-        private static final String RES_PROP_TIM = "endTime";
-        private static final String WD_ID = "id";
-        private static final String WD_JTI = "jti";
-        private static final String KEYWORDS_EXPR = "expression";
-        private static final String REPORT_FORMST = "formatVersion";
-        private static final String REPORT_GENTIME = "generatedTime";
-        private static final String XSI = "xmlns:xsi";
-        private static final String SCH_LOC = "xsi:noNamespaceSchemaLocation";
-        private static final String QUEST_VALUE = "value";
-        private static final String QUEST_TEXT = "text";
-        private static final String QUEST_SUMM = "summary";
-        private static final String CHOICE_CH = "choice";
-        private static final String CHOICE_DCH = "displayChoice";
-        private static final String CHOICE_VAL = "value";
-        private static final String GROUP_NAME = "name";
-        private static final String GROUP_HD1 = "header1";
-        private static final String GROUP_HD2 = "header2";
-        private static final String ROW_KEY = "key";
-        private static final String ROW_VAL = "value";
-        private static final String ENV_NAME = "name";
-        private static final String ENV_DESCR = "description";
-        
-        private static final String TEM_NAME = "name";
-        private static final String TEM_DESCRIPTION = "description";
-        private static final String TEM_FILE = "fileName";
-        
-        // VALUES
-        private static final String XSI_VAL = "http://www.w3.org/2001/XMLSchema-instance";
-        private static final String SCH_LOC_VAL = "Report.xsd";
-        
-    }
-    
-    public static String XML_CHARSET = "UTF-8";
-    
-}
+package com.sun.javatest.report;
+
+import com.sun.javatest.Status;
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Properties;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.Attributes;
+import org.xml.sax.ext.LexicalHandler;
+
+public class XMLReportMaker {
+    
+    private TransformerHandler ser;
+    
+    XMLReportMaker(Writer w) throws IOException {
+        
+        Properties outputProps = new Properties();
+        outputProps.put("indent", "yes");
+        outputProps.put("encoding", XML_CHARSET);
+        SAXTransformerFactory stf = (SAXTransformerFactory )TransformerFactory.newInstance();
+        stf.setAttribute("indent-number", 4);
+        try {
+            ser = stf.newTransformerHandler();
+        } catch (TransformerConfigurationException ex) {
+            ex.printStackTrace();
+        }
+        ser.getTransformer().setOutputProperties(outputProps);
+        ser.setResult(new StreamResult(w));
+    }
+    
+    void sDocument() throws SAXException {
+        ser.startDocument();
+    }
+    
+    void eDocument() throws SAXException, IOException {
+        ser.endDocument();
+    }
+    
+    void sReport() throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.XSI, "String", Scheme.XSI_VAL);
+        atts.addAttribute("", "", Scheme.SCH_LOC, "String", Scheme.SCH_LOC_VAL);
+        atts.addAttribute("", "", Scheme.REPORT_FORMST, "String", "v1");
+        atts.addAttribute("", "", Scheme.REPORT_GENTIME, "String", XMLReport.Utils.dateToISO8601(new Date()));
+        sE(Scheme.REPORT, atts);
+    }
+    
+    void eReport() throws SAXException {
+        eE(Scheme.REPORT);
+    }
+    
+    void sSummary() throws SAXException {
+        sE(Scheme.SUMMARY);
+    }
+    
+    void eSummary() throws SAXException {
+        eE(Scheme.SUMMARY);
+    }
+    
+    
+    void sWorkdirectories() throws SAXException {
+        sE(Scheme.WDS);
+    }
+    
+    void eWorkdirectories() throws SAXException {
+        eE(Scheme.WDS);
+    }
+    
+    void sEnvironment(String name, String descr) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (name != null && ! "".equals(name.trim())) {
+            atts.addAttribute("", "", Scheme.ENV_NAME, "String", name);
+        }
+        if (descr != null && ! "".equals(descr.trim())) {
+            atts.addAttribute("", "", Scheme.ENV_DESCR, "String", descr);
+        }
+        sE(Scheme.ENV, atts);
+    }
+    
+    void eEnvironment() throws SAXException {
+        eE(Scheme.ENV);
+    }
+    
+    void conCur(int val) throws SAXException  {
+        sE(Scheme.CONC);
+        String sVal = Integer.toString(val);
+        ser.characters(sVal.toCharArray(), 0, sVal.length());
+        eE(Scheme.CONC);
+    }
+    
+    void timeOut(float val) throws SAXException  {
+        sE(Scheme.TIMO);
+        String sVal = Float.toString(val);
+        ser.characters(sVal.toCharArray(), 0, sVal.length());
+        eE(Scheme.TIMO);
+    }
+    
+    void sWorkdirectory(String jti) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.WD_ID, "String", "1");
+        if (jti != null) {
+            atts.addAttribute("", "", Scheme.WD_JTI, "String", jti);
+        }
+        sE(Scheme.WD, atts);
+    }
+    
+    void eWorkdirectory() throws SAXException {
+        eE(Scheme.WD);
+    }
+    
+    void sTestResults() throws SAXException {
+        sE(Scheme.TRS);
+    }
+    
+    void eTestResults() throws SAXException {
+        eE(Scheme.TRS);
+    }
+    
+    void sTestResult(String url, Status st, int id) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.TR_URL, "String", url);
+        atts.addAttribute("", "", Scheme.TR_STATUS, "String", XMLReport.Utils.statusToString(st));
+        atts.addAttribute("", "", Scheme.TR_WDID, "Integer", Integer.toString(id));
+        sE(Scheme.TR, atts);
+    }
+    
+    void eTestResult() throws SAXException {
+        eE(Scheme.TR);
+    }
+    
+    void sDescriptionData() throws SAXException {
+        sE(Scheme.DESCR_DATA);
+    }
+    
+    void eDescriptionData() throws SAXException {
+        eE(Scheme.DESCR_DATA);
+    }
+    
+    
+    void sKeyWords() throws SAXException {
+        sE(Scheme.KEY_WORDS);
+    }
+    
+    void sKeyWords(String expr) throws SAXException {
+        
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.KEYWORDS_EXPR, "String", expr);
+        sE(Scheme.KEY_WORDS, atts);
+    }
+    
+    void eKeyWords() throws SAXException {
+        eE(Scheme.KEY_WORDS);
+    }
+    
+    void sTestEnvironment() throws SAXException {
+        sE(Scheme.TEST_ENV);
+    }
+    
+    void eTestEnvironment() throws SAXException {
+        eE(Scheme.TEST_ENV);
+    }
+    
+    void sResultProps(String time) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (time != null) {
+            try {
+                time = XMLReport.Utils.dateToISO8601(XMLReport.Utils.jtrToDate(time));
+            } catch (ParseException ex) {
+                throw new SAXException(ex);
+            }
+            atts.addAttribute("", "", Scheme.RES_PROP_TIM, "String", time);
+        }
+        sE(Scheme.RES_PROP, atts);
+    }
+    
+    void eResultProps() throws SAXException {
+        eE(Scheme.RES_PROP);
+    }
+    
+    void sSections() throws SAXException {
+        sE(Scheme.SES);
+    }
+    
+    void eSections() throws SAXException {
+        eE(Scheme.SES);
+    }
+    
+    void sStdValues() throws SAXException {
+        sE(Scheme.STD_VALS);
+    }
+    
+    void eStdValues() throws SAXException {
+        eE(Scheme.STD_VALS);
+    }
+    
+    void sPriorStatusList() throws SAXException {
+        sE(Scheme.PRIOS);
+    }
+    
+    void ePriorStatusList() throws SAXException {
+        eE(Scheme.PRIOS);
+    }
+    
+    void sExclList() throws SAXException {
+        sE(Scheme.EXCL_LIST);
+    }
+    
+    void eExclList() throws SAXException {
+        eE(Scheme.EXCL_LIST);
+    }
+    
+    void sTests() throws SAXException {
+        sE(Scheme.TESTS);
+    }
+    
+    void eTests() throws SAXException {
+        eE(Scheme.TESTS);
+    }
+    
+    void sInterview() throws SAXException {
+        sE(Scheme.INT);
+    }
+    
+    void eInterview() throws SAXException {
+        eE(Scheme.INT);
+    }
+    
+    void sQuestion(String value, String text, String summary) throws SAXException {
+        
+        AttributesImpl atts = new AttributesImpl();
+        if (summary != null) {
+            atts.addAttribute("", "", Scheme.QUEST_SUMM, "String", summary);
+        }
+        if (value != null) {
+            atts.addAttribute("", "", Scheme.QUEST_VALUE, "String", value);
+        }
+        if (value != null) {
+            atts.addAttribute("", "", Scheme.QUEST_TEXT, "String", text);
+        }
+        
+        sE(Scheme.QUEST, atts);
+    }
+    
+    void eQuestion() throws SAXException {
+        eE(Scheme.QUEST);
+    }
+    
+    
+    void sListQuestion() throws SAXException {
+        sE(Scheme.LIST_QUEST);
+    }
+    
+    void eListQuestion() throws SAXException {
+        eE(Scheme.LIST_QUEST);
+    }
+    
+    void sChoiceQuestion() throws SAXException {
+        sE(Scheme.CHOICE_QUEST);
+    }
+    
+    void eChoiceQuestion() throws SAXException {
+        eE(Scheme.CHOICE_QUEST);
+    }
+    
+    void sPropertiesQuestion()  throws SAXException {
+        sE(Scheme.PROP_QUEST);
+    }
+    
+    void ePropertiesQuestion()  throws SAXException {
+        eE(Scheme.PROP_QUEST);
+    }
+    
+    void sGroup(String name, String hd1, String hd2) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (name != null) {
+            atts.addAttribute("", "", Scheme.GROUP_NAME, "String", name);
+        }
+        if (hd1 != null) {
+            atts.addAttribute("", "", Scheme.GROUP_HD1, "String", hd1);
+        }
+        if (hd2 != null) {
+            atts.addAttribute("", "", Scheme.GROUP_HD2, "String", hd2);
+        }
+        sE(Scheme.GROUP, atts);
+    }
+    
+    void eGroup(String name, String h1, String h2) throws SAXException {
+        eE(Scheme.GROUP);
+    }
+    
+    void makeRow(String key, String val) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (key != null) {
+            atts.addAttribute("", "", Scheme.ROW_KEY, "String", key);
+        }
+        if (val != null) {
+            atts.addAttribute("", "", Scheme.ROW_VAL, "String", val);
+        }
+        sE(Scheme.ROW, atts);
+        eE(Scheme.ROW);
+    }
+    
+    void makeChoices(String[] ch, String[] dispCh) throws SAXException {
+        if (ch != null) {
+            for (int i = 0; i < ch.length; i++) {
+                makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null);
+            }
+        }
+    }
+    
+    void makeChoices(String[] ch, String[] dispCh, boolean[] values) throws SAXException {
+        if (ch != null) {
+            for (int i = 0; i < ch.length; i++) {
+                makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null, i < values.length ? values[i] : false);
+            }
+        }
+    }
+    
+    
+    private void makeChoice(String ch, String di) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (ch != null) {
+            atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch);
+        }
+        if (di != null) {
+            atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di);
+        }
+        sE(Scheme.CHOICE, atts);
+        eE(Scheme.CHOICE);
+    }
+    
+    private void makeChoice(String ch, String di, boolean val) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch);
+        if (di != null) {
+            atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di);
+        }
+        atts.addAttribute("", "", Scheme.CHOICE_VAL, "String", Boolean.toString(val));
+        sE(Scheme.CHOICE, atts);
+        eE(Scheme.CHOICE);
+    }
+    
+    
+    void makeEntireTestTree() throws SAXException {
+        sE(Scheme.ENTTREE);
+        eE(Scheme.ENTTREE);
+    }
+    
+    void sSection(String title, Status st) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.SE_TIT, "String", title);
+        if (st != null) {
+            atts.addAttribute("", "", Scheme.SE_ST, "String", XMLReport.Utils.statusToString(st));
+        }
+        sE(Scheme.SE, atts);
+    }
+    
+    void eSection() throws SAXException {
+        eE(Scheme.SE);
+    }
+    
+    void sOutput(String title, String content) throws SAXException, IOException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.OU_TIT, "String", title);
+        sE(Scheme.OU, atts);
+        if (content != null && content.length() > 0) {
+            writeCDATA(ser, ser, content);
+        }
+    }
+    
+    void eOutput() throws SAXException {
+        eE(Scheme.OU);
+    }
+    
+    void makeTemplateInfo(String tPath, String name, String descr) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (tPath != null) {
+            atts.addAttribute("", "", Scheme.TEM_FILE, "String", tPath);
+        }
+        if (name != null) {
+            atts.addAttribute("", "", Scheme.TEM_NAME, "String", name);
+        }
+        if (descr != null) {
+            atts.addAttribute("", "", Scheme.TEM_DESCRIPTION, "String", descr);
+        }
+        sE(Scheme.TEMPLATE, atts);
+        eE(Scheme.TEMPLATE);
+    }
+    
+    
+    void makeProperty(String key, String val) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.PR_NAME, "String", key);
+        atts.addAttribute("", "", Scheme.PR_VAL, "CDATA", val);
+        sE(Scheme.PR, atts);
+        eE(Scheme.PR);
+    }
+    
+    void makeItem(String val) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute("", "", Scheme.IT_VAL, "CDATA", val);
+        sE(Scheme.IT, atts);
+        eE(Scheme.IT);
+    }
+    
+    void makeItems(String[] vals) throws SAXException {
+        if (vals != null) {
+            for (int i = 0 ; i < vals.length; i++) {
+                if (vals[i] != null) {
+                    makeItem(vals[i]);
+                }
+            }
+        }
+    }
+    
+    void makeItems(File[] files) throws SAXException {
+        if (files != null) {
+            for (int i = 0 ; i < files.length; i++) {
+                makeItem(files[i].getPath());
+            }
+        }
+    }
+    
+    
+    private void sE(String name) throws SAXException {
+        ser.startElement("","",name, emptyAttr);
+    }
+    
+    private void sE(String name, Attributes atts) throws SAXException {
+        ser.startElement("","",name, atts);
+    }
+    
+    private void eE(String name) throws SAXException {
+        ser.endElement("","",name);
+    }
+    
+    public static void writeCDATA(LexicalHandler lh, ContentHandler ser, String cdata) throws IOException, SAXException {
+        
+        cdata = convertProhibitedChars(cdata);
+        
+        if (lh != null) {
+            int start = 0;
+            int end;
+            while ( start < cdata.length()) {
+                end = cdata.length(); int xpos = cdata.indexOf("]]>", start);
+                if (xpos != -1) {
+                    end = xpos+1;
+                }
+                lh.startCDATA();
+                String fragment= cdata.substring(start, end);
+                ser.characters(fragment.toCharArray(), 0, fragment.length());
+                lh.endCDATA();
+                start = end;
+            }
+        }
+    }
+    
+    public static String convertProhibitedChars(String cdata) {
+        StringBuffer sb = new StringBuffer();
+        char [] data = cdata.toCharArray();
+        for (int i = 0; i < data.length; i++) {
+            if (prohibited(data[i])) {
+                sb.append("\\u");
+                String rX = Integer.toHexString((int)data[i]);
+                for (int ii = rX.length();  ii < 4; ii++) {
+                    sb.append("0");  //
+                }
+                sb.append(rX);
+            } else {
+                sb.append(data[i]);
+            }
+        }
+        return sb.toString();
+    }
+    
+    // XML 1.0 specification ( http://www.w3.org/TR/2004/REC-xml-20040204/ ) defines legal chars:
+    // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
+    public static boolean prohibited(int c) {
+        if (c == 0x0009 || c == 0x000A || c == 0x000D) return false;
+        if (c >= 0x0020 && c <= 0xD7FF) return false;
+        if (c >= 0xE000 && c <= 0xFFFD) return false;
+        if (c >= 0x10000 && c <= 0x10FFFF) return false; // can't be java char, but any way...
+        return true;
+    }
+    
+    
+    private final AttributesImpl emptyAttr = new AttributesImpl();
+    
+    /**
+     * Elements and attributes names are defined here
+     */
+    private static class Scheme {
+        
+        // ELEMENTS
+        private static final String REPORT = "Report";
+        private static final String SUMMARY = "Summary";
+        private static final String WDS = "WorkDirectories";
+        private static final String WD = "WorkDirectory";
+        private static final String TRS = "TestResults";
+        private static final String TR = "TestResult";
+        private static final String INT = "Interview";
+        private static final String Q = "Question";
+        private static final String DESCR_DATA ="DescriptionData";
+        private static final String KEY_WORDS = "Keywords";
+        private static final String TEST_ENV = "TestEnvironment";
+        private static final String RES_PROP = "ResultProperties";
+        private static final String SES = "Sections";
+        private static final String SE = "Section";
+        private static final String OU = "Output";
+        private static final String PR = "Property";
+        private static final String IT = "Item";
+        private static final String ENV = "Environment";
+        private static final String CONC = "Concurrency";
+        private static final String TIMO = "TimeOut";
+        private static final String PRIOS = "PriorStatusList";
+        private static final String EXCL_LIST = "ExcludeList";
+        private static final String STD_VALS = "StandardValues";
+        private static final String TESTS = "Tests";
+        private static final String ENTTREE = "EntireTestTree";
+        private static final String QUEST = "Question";
+        private static final String LIST_QUEST = "ListQuestion";
+        private static final String CHOICE_QUEST = "ChoiceQuestion";
+        private static final String CHOICE = "Choice";
+        private static final String PROP_QUEST = "PropertiesQuestion";
+        private static final String GROUP = "Group";
+        private static final String ROW = "Row";
+        private static final String TEMPLATE = "Template";
+        
+        // ATTRS
+        private static final String TR_URL = "url";
+        private static final String TR_STATUS = "status";
+        private static final String TR_WDID = "workDirID";
+        private static final String PR_NAME = "name";
+        private static final String PR_VAL = "value";
+        private static final String IT_VAL = "value";
+        private static final String SE_TIT = "title";
+        private static final String SE_ST = "status";
+        private static final String OU_TIT = "title";
+        private static final String RES_PROP_TIM = "endTime";
+        private static final String WD_ID = "id";
+        private static final String WD_JTI = "jti";
+        private static final String KEYWORDS_EXPR = "expression";
+        private static final String REPORT_FORMST = "formatVersion";
+        private static final String REPORT_GENTIME = "generatedTime";
+        private static final String XSI = "xmlns:xsi";
+        private static final String SCH_LOC = "xsi:noNamespaceSchemaLocation";
+        private static final String QUEST_VALUE = "value";
+        private static final String QUEST_TEXT = "text";
+        private static final String QUEST_SUMM = "summary";
+        private static final String CHOICE_CH = "choice";
+        private static final String CHOICE_DCH = "displayChoice";
+        private static final String CHOICE_VAL = "value";
+        private static final String GROUP_NAME = "name";
+        private static final String GROUP_HD1 = "header1";
+        private static final String GROUP_HD2 = "header2";
+        private static final String ROW_KEY = "key";
+        private static final String ROW_VAL = "value";
+        private static final String ENV_NAME = "name";
+        private static final String ENV_DESCR = "description";
+        
+        private static final String TEM_NAME = "name";
+        private static final String TEM_DESCRIPTION = "description";
+        private static final String TEM_FILE = "fileName";
+        
+        // VALUES
+        private static final String XSI_VAL = "http://www.w3.org/2001/XMLSchema-instance";
+        private static final String SCH_LOC_VAL = "Report.xsd";
+        
+    }
+    
+    public static String XML_CHARSET = "UTF-8";
+    
+}