changeset 576:741a105054af

Remove redundant HTML-tag scanner from ITW. Do not reconstruct tags.
author Adam Domurad <adomurad@redhat.com>
date Tue, 04 Dec 2012 10:43:59 -0500
parents aff6fb36a9ab
children 21756f8bedfc
files ChangeLog Makefile.am netx/net/sourceforge/jnlp/NetxPanel.java netx/net/sourceforge/jnlp/PluginBridge.java netx/net/sourceforge/jnlp/PluginParameterException.java netx/net/sourceforge/jnlp/PluginParameters.java netx/net/sourceforge/jnlp/resources/Messages.properties netx/net/sourceforge/jnlp/resources/Messages_cs_CZ.properties plugin/icedteanp/IcedTeaNPPlugin.cc plugin/icedteanp/IcedTeaNPPlugin.h plugin/icedteanp/java/sun/applet/PluginAppletViewer.java plugin/icedteanp/java/sun/applet/PluginParameterParser.java tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc tests/cpp-unit-tests/PluginParametersTest.cc tests/netx/unit/net/sourceforge/jnlp/PluginBridgeTest.java tests/netx/unit/net/sourceforge/jnlp/PluginParametersTest.java tests/netx/unit/sun/applet/PluginParameterParserTest.java
diffstat 17 files changed, 850 insertions(+), 793 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Dec 03 18:08:38 2012 +0100
+++ b/ChangeLog	Tue Dec 04 10:43:59 2012 -0500
@@ -1,3 +1,38 @@
+2012-12-04  Adam Domurad  <adomurad@redhat.com>
+
+	Remove the applet/embed/object tag parser from ITW. Send the applet
+	parameters directly from the C++.
+	* Makefile.am: Allow unit-testing for classes in plugin.jar.
+	* netx/net/sourceforge/jnlp/NetxPanel.java: Use PluginParameters for 
+	attribute lookup
+	* netx/net/sourceforge/jnlp/PluginBridge.java: Use PluginParameters 
+	for attribute lookup
+	* netx/net/sourceforge/jnlp/resources/Messages.properties: Add message
+	for missing code/object attributes.
+	* netx/net/sourceforge/jnlp/resources/Messages_cs_CZ.properties: Same.
+	* plugin/icedteanp/IcedTeaNPPlugin.cc: Send escaped parameter
+	name/values instead of applet tag. Remove some dead code.
+	* plugin/icedteanp/IcedTeaNPPlugin.h: Rename applet_tag ->
+	parameters_string.
+	* plugin/icedteanp/java/sun/applet/PluginAppletViewer.java: Extract
+	parsing code into its own class.
+	* tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc: Use CHECK_EQUALS
+	instead of CHECK.
+	* tests/netx/unit/net/sourceforge/jnlp/PluginBridgeTest.java: Update 
+	unit tests due to constructor changes.
+	* netx/net/sourceforge/jnlp/PluginParameterException.java: New, thrown
+	when code/object attributes are missing.
+	* netx/net/sourceforge/jnlp/PluginParameters.java: New, Hashtable
+	wrapper that handles plugin attribute/parameter lookups.
+	* plugin/icedteanp/java/sun/applet/PluginParameterParser.java: New,
+	creates PluginParameters from escaped name/values.
+	* tests/cpp-unit-tests/PluginParametersTest.cc: New, C++ Unit tests for
+	plugin parameter related functions
+	* tests/netx/unit/net/sourceforge/jnlp/PluginParametersTest.java: New, 
+	unit tests for PluginParameters class.
+	* tests/netx/unit/sun/applet/PluginParameterParserTest.java: New, unit
+	tests for PluginParameterParser class.
+
 2012-11-03  Jiri Vanek <jvanek@redhat.com>
 
 	Fixed logging bottleneck
--- a/Makefile.am	Mon Dec 03 18:08:38 2012 +0100
+++ b/Makefile.am	Tue Dec 04 10:43:59 2012 -0500
@@ -969,7 +969,7 @@
 	mkdir -p $(NETX_UNIT_TEST_DIR) && \
 	$(BOOT_DIR)/bin/javac $(IT_JAVACFLAGS) \
 	 -d $(NETX_UNIT_TEST_DIR) \
-	 -classpath $(JUNIT_JAR):$(NETX_DIR)/lib/classes.jar:$(TEST_EXTENSIONS_DIR) \
+	 -classpath $(JUNIT_JAR):$(DESTDIR)$(datadir)/$(PACKAGE_NAME)/plugin.jar:$(NETX_DIR)/lib/classes.jar:$(TEST_EXTENSIONS_DIR) \
 	 @netx-unit-tests-source-files.txt && \
 	mkdir -p stamps && \
 	touch $@
@@ -999,7 +999,7 @@
 	done ; \
 	cd $(NETX_UNIT_TEST_DIR) ; \
 	class_names=`cat $(UNIT_CLASS_NAMES)` ; \
-	CLASSPATH=$(NETX_DIR)/lib/classes.jar:$(JUNIT_JAR):$(JUNIT_RUNNER_JAR):$(TEST_EXTENSIONS_DIR):. \
+	CLASSPATH=$(NETX_DIR)/lib/classes.jar:$(DESTDIR)$(datadir)/$(PACKAGE_NAME)/plugin.jar:$(JUNIT_JAR):$(JUNIT_RUNNER_JAR):$(TEST_EXTENSIONS_DIR):. \
 	  $(BOOT_DIR)/bin/java -Xbootclasspath:$(RUNTIME) CommandLine $$class_names 
 if WITH_XSLTPROC
 	$(XSLTPROC) --stringparam logs logs_unit.html $(TESTS_SRCDIR)/$(REPORT_STYLES_DIRNAME)/jreport.xsl $(NETX_UNIT_TEST_DIR)/tests-output.xml > $(TESTS_DIR)/index_unit.html
--- a/netx/net/sourceforge/jnlp/NetxPanel.java	Mon Dec 03 18:08:38 2012 +0100
+++ b/netx/net/sourceforge/jnlp/NetxPanel.java	Tue Dec 04 10:43:59 2012 -0500
@@ -27,7 +27,6 @@
 
 import java.net.URL;
 import java.util.HashMap;
-import java.util.Hashtable;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -45,12 +44,12 @@
  * @author      Francis Kung <fkung@redhat.com>
  */
 public class NetxPanel extends AppletViewerPanel implements SplashController {
+    private final PluginParameters parameters;
     private PluginBridge bridge = null;
     private boolean exitOnFailure = true;
     private AppletInstance appInst = null;
     private SplashController splashController;
     private boolean appletAlive;
-    private final String uKey;
 
     // We use this so that we can create exactly one thread group
     // for all panels with the same uKey.
@@ -68,51 +67,24 @@
     private static final ConcurrentMap<String, Boolean> appContextCreated =
         new ConcurrentHashMap<String, Boolean>();
 
-    public NetxPanel(URL documentURL, Hashtable<String, String> atts) {
-        super(documentURL, atts);
+    public NetxPanel(URL documentURL, PluginParameters params) {
+        super(documentURL, params.getUnderlyingHashtable());
 
-        /* According to http://download.oracle.com/javase/6/docs/technotes/guides/deployment/deployment-guide/applet-compatibility.html, 
-         * classloaders are shared iff these properties match:
-         * codebase, cache_archive, java_archive, archive
-         * 
-         * To achieve this, we create the uniquekey based on those 4 values,
-         * always in the same order. The initial "<NAME>=" parts ensure a 
-         * bad tag cannot trick the loader into getting shared with another.
-         */
-
-        // Firefox sometimes skips the codebase if it is default  -- ".", 
-        // so set it that way if absent
-        String codebaseAttr =      atts.get("codebase") != null ?
-                                   atts.get("codebase") : ".";
+        this.parameters = params;
 
-        String cache_archiveAttr = atts.get("cache_archive") != null ? 
-                                   atts.get("cache_archive") : "";
-
-        String java_archiveAttr =  atts.get("java_archive") != null ? 
-                                   atts.get("java_archive") : "";
-
-        String archiveAttr =       atts.get("archive") != null ? 
-                                   atts.get("archive") : "";
-
-        this.uKey = "codebase=" + codebaseAttr +
-                    "cache_archive=" + cache_archiveAttr + 
-                    "java_archive=" + java_archiveAttr + 
-                    "archive=" +  archiveAttr;
-
-        // when this was being done (incorrectly) in Launcher, the call was
-        // new AppThreadGroup(mainGroup, file.getTitle());
+        String uniqueKey = params.getUniqueKey();
         synchronized(TGMapMutex) {
-            if (!uKeyToTG.containsKey(this.uKey)) {
+            if (!uKeyToTG.containsKey(uniqueKey)) {
                 ThreadGroup tg = new ThreadGroup(Launcher.mainGroup, this.documentURL.toString());
-                uKeyToTG.put(this.uKey, tg);
+                uKeyToTG.put(uniqueKey, tg);
             }
         }
     }
 
     // overloaded constructor, called when initialized via plugin
-    public NetxPanel(URL documentURL, Hashtable<String, String> atts,
+    public NetxPanel(URL documentURL, PluginParameters params,
                      boolean exitOnFailure) {
-        this(documentURL, atts);
+        this(documentURL, params);
         this.exitOnFailure = exitOnFailure;
         this.appletAlive = true;
     }
@@ -129,6 +101,7 @@
 
     //Overriding to use Netx classloader. You might need to relax visibility
     //in sun.applet.AppletPanel for runLoader().
+    @Override
     protected void runLoader() {
 
         try {
@@ -138,7 +111,7 @@
                                 getCode(),
                                 getWidth(),
                                 getHeight(),
-                                atts, uKey);
+                                parameters);
 
             doInit = true;
             dispatchAppletEvent(APPLET_LOADING, null);
@@ -188,6 +161,7 @@
      * the applet
      */
     // Reminder: Relax visibility in sun.applet.AppletPanel
+    @Override
     protected synchronized void createAppletThread() {
         // initialize JNLPRuntime in the main threadgroup
         synchronized (JNLPRuntime.initMutex) {
@@ -208,8 +182,7 @@
     }
 
     public void updateSizeInAtts(int height, int width) {
-        this.atts.put("height", Integer.toString(height));
-        this.atts.put("width", Integer.toString(width));
+        parameters.updateSize(width, height);
     }
 
     public ClassLoader getAppletClassLoader() {
@@ -222,7 +195,7 @@
 
     public ThreadGroup getThreadGroup() {
         synchronized(TGMapMutex) {
-            return uKeyToTG.get(uKey);
+            return uKeyToTG.get(parameters.getUniqueKey());
         }
     }
 
@@ -232,7 +205,7 @@
         }
         // only create a new context if one hasn't already been created for the
         // applets with this unique key.
-        if (null == appContextCreated.putIfAbsent(uKey, Boolean.TRUE)) {
+        if (null == appContextCreated.putIfAbsent(parameters.getUniqueKey(), Boolean.TRUE)) {
             SunToolkit.createNewAppContext();
         }
     }
--- a/netx/net/sourceforge/jnlp/PluginBridge.java	Mon Dec 03 18:08:38 2012 +0100
+++ b/netx/net/sourceforge/jnlp/PluginBridge.java	Tue Dec 04 10:43:59 2012 -0500
@@ -45,26 +45,23 @@
  */
 public class PluginBridge extends JNLPFile {
 
-    private String name;
+    private PluginParameters params;
     private Set<String> jars = new HashSet<String>();
     //Folders can be added to the code-base through the archive tag
     private List<String> codeBaseFolders = new ArrayList<String>();
     private String[] cacheJars = new String[0];
     private String[] cacheExJars = new String[0];
-    private Map<String, String> atts;
     private boolean usePack;
     private boolean useVersion;
-    private boolean codeBaseLookup;
     private boolean useJNLPHref;
 
     /**
      * Creates a new PluginBridge using a default JNLPCreator.
      */
     public PluginBridge(URL codebase, URL documentBase, String jar, String main,
-                        int width, int height, Map<String, String> atts,
-                        String uKey)
+                        int width, int height, PluginParameters params)
             throws Exception {
-        this(codebase, documentBase, jar, main, width, height, atts, uKey, new JNLPCreator());
+        this(codebase, documentBase, jar, main, width, height, params, new JNLPCreator());
     }
 
     /**
@@ -86,25 +83,24 @@
     }
 
     public PluginBridge(URL codebase, URL documentBase, String archive, String main,
-                        int width, int height, Map<String, String> atts,
-                        String uKey, JNLPCreator jnlpCreator)
+                        int width, int height, PluginParameters params, JNLPCreator jnlpCreator)
             throws Exception {
         specVersion = new Version("1.0");
         fileVersion = new Version("1.1");
         this.codeBase = codebase;
         this.sourceLocation = documentBase;
-        this.atts = atts;
+        this.params = params;
 
-        if (atts.containsKey("jnlp_href")) {
+        if (params.getJNLPHref() != null) {
             useJNLPHref = true;
             try {
                 // Use codeBase as the context for the URL. If jnlp_href's
                 // value is a complete URL, it will replace codeBase's context.
-                URL jnlp = new URL(codeBase, atts.get("jnlp_href"));
+                URL jnlp = new URL(codeBase, params.getJNLPHref());
                 JNLPFile jnlpFile = null;
 
-                if (atts.containsKey("jnlp_embedded")) {
-                    InputStream jnlpInputStream = new ByteArrayInputStream(decodeBase64String(atts.get("jnlp_embedded")));
+                if (params.getJNLPEmbedded() != null) {
+                    InputStream jnlpInputStream = new ByteArrayInputStream(decodeBase64String(params.getJNLPEmbedded()));
                     jnlpFile = new JNLPFile(jnlpInputStream, codeBase, false);
                 } else {
                     jnlpFile = jnlpCreator.create(jnlp, null, false, JNLPRuntime.getDefaultUpdatePolicy(), codeBase);
@@ -118,7 +114,7 @@
 
                 // Change the parameter name to lowercase to follow conventions.
                 for (Map.Entry<String, String> entry : jnlpParams.entrySet()) {
-                    this.atts.put(entry.getKey().toLowerCase(), entry.getValue());
+                    this.params.put(entry.getKey().toLowerCase(), entry.getValue());
                 }
                 JARDesc[] jarDescs = jnlpFile.getResources().getJARs();
                 for (JARDesc jarDesc : jarDescs) {
@@ -128,7 +124,7 @@
             } catch (MalformedURLException e) {
                 // Don't fail because we cannot get the jnlp file. Parameters are optional not required.
                 // it is the site developer who should ensure that file exist.
-                System.err.println("Unable to get JNLP file at: " + atts.get("jnlp_href")
+                System.err.println("Unable to get JNLP file at: " + params.getJNLPHref()
                         + " with context of URL as: " + codeBase.toExternalForm());
             }
         } else {
@@ -138,14 +134,14 @@
         }
 
         // also, see if cache_archive is specified
-        String cacheArchive = atts.get("cache_archive");
-        if (cacheArchive != null && cacheArchive.length() > 0) {
+        String cacheArchive = params.getCacheArchive();
+        if (!cacheArchive.isEmpty()) {
 
             String[] versions = new String[0];
 
             // are there accompanying versions?
-            String cacheVersion = atts.get("cache_version");
-            if (cacheVersion != null) {
+            String cacheVersion = params.getCacheVersion();
+            if (!cacheVersion.isEmpty()) {
                 versions = cacheVersion.split(",");
             }
 
@@ -162,8 +158,8 @@
             }
         }
 
-        String cacheArchiveEx = atts.get("cache_archive_ex");
-        if (cacheArchiveEx != null && cacheArchiveEx.length() > 0) {
+        String cacheArchiveEx = params.getCacheArchiveEx();
+        if (!cacheArchiveEx.isEmpty()) {
             cacheExJars = cacheArchiveEx.split(",");
         }
 
@@ -178,19 +174,13 @@
             }
         }
 
-        name = atts.get("name");
-        if (name == null)
-            name = "Applet";
-        else
-            name = name + " applet";
-
         if (main.endsWith(".class"))
             main = main.substring(0, main.length() - 6);
 
         // the class name should be of the form foo.bar.Baz not foo/bar/Baz
         String mainClass = main.replace('/', '.');
-        launchType = new AppletDesc(name, mainClass, documentBase, width,
-                                    height, atts);
+        launchType = new AppletDesc(params.getAppletTitle(), mainClass, documentBase, width,
+                                    height, params.getUnmodifiableMap());
 
         if (main.endsWith(".class")) //single class file only
             security = new SecurityDesc(this, SecurityDesc.SANDBOX_PERMISSIONS,
@@ -198,11 +188,11 @@
         else
             security = null;
 
-        this.uniqueKey = uKey;
+        this.uniqueKey = params.getUniqueKey();
         usePack = false;
         useVersion = false;
-        String jargs = atts.get("java_arguments");
-        if (jargs != null) {
+        String jargs = params.getJavaArguments();
+        if (!jargs.isEmpty()) {
             for (String s : jargs.split(" ")) {
                 String[] parts = s.trim().split("=");
                 if (parts.length == 2 && Boolean.valueOf(parts[1])) {
@@ -214,12 +204,10 @@
                 }
             }
         }
-        String cbl = atts.get("codebase_lookup");
-        codeBaseLookup = cbl == null || (Boolean.valueOf(cbl));
     }
 
     public boolean codeBaseLookup() {
-    	return codeBaseLookup;
+    	return params.useCodebaseLookup();
     }
 
     public boolean useJNLPHref() {
@@ -235,7 +223,7 @@
     }
 
     public String getTitle() {
-        return name;
+        return params.getAppletTitle();
     }
 
     public ResourcesDesc getResources(final Locale locale, final String os,
@@ -258,9 +246,7 @@
                         }
 
                         boolean cacheable = true;
-
-                        String cacheOption = atts.get("cache_option");
-                        if (cacheOption != null && cacheOption.equalsIgnoreCase("no"))
+                        if (params.getCacheOption().equalsIgnoreCase("no"))
                             cacheable = false;
 
                         for (String cacheJar : cacheJars) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/PluginParameterException.java	Tue Dec 04 10:43:59 2012 -0500
@@ -0,0 +1,43 @@
+/* Copyright (C) 2012 Red Hat
+
+This file is part of IcedTea.
+
+IcedTea 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, or (at your option)
+any later version.
+
+IcedTea 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 IcedTea; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sourceforge.jnlp;
+
+public class PluginParameterException extends RuntimeException {
+    public PluginParameterException(String detail) {
+        super(detail);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/PluginParameters.java	Tue Dec 04 10:43:59 2012 -0500
@@ -0,0 +1,238 @@
+/* PluginAppletAttributes -- Provides parsing for applet attributes
+   Copyright (C) 2012  Red Hat
+
+This file is part of IcedTea.
+
+IcedTea 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, or (at your option)
+any later version.
+
+IcedTea 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 IcedTea; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+package net.sourceforge.jnlp;
+
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Map;
+
+import static net.sourceforge.jnlp.runtime.Translator.R;
+
+/**
+ * Represents plugin applet parameters, backed by a Hashtable.
+ */
+
+public class PluginParameters {
+    private final Hashtable<String, String> parameters;
+
+    public PluginParameters(Map<String, String> params) {
+        this.parameters = createParameterTable(params);
+
+        if (this.parameters.get("code") == null
+                && this.parameters.get("object") == null) {
+            throw new PluginParameterException(R("BNoCodeOrObjectApplet"));
+        }
+    }
+
+    // Note, lower-case key expected
+    public String get(String key) {
+        return this.parameters.get(key);
+    }
+
+    public void put(String key, String value) {
+        parameters.put(key.toLowerCase(), value);
+    }
+
+    public Map<String, String> getUnmodifiableMap() {
+        return Collections.unmodifiableMap(parameters);
+    }
+
+    /**
+     * Used for compatibility with Hashtable-expecting classes.
+     * 
+     * @return the underlying hashtable.
+     */
+    Hashtable<String, String> getUnderlyingHashtable() {
+        return parameters;
+    }
+
+    public String getDefaulted(String key, String defaultStr) {
+        String value = get(key);
+        return (value != null) ? value : defaultStr;
+    }
+
+    public String getAppletTitle() {
+        String name = get("name");
+        if (name == null) {
+            return "Applet";
+        } else {
+            return name + " applet";
+        }
+    }
+
+    public String getCodebase() {
+        return getDefaulted("codebase", ".");
+    }
+
+    public boolean useCodebaseLookup() {
+        return Boolean.valueOf(getDefaulted("codebase_lookup", "true"));
+    }
+
+    public String getArchive() {
+        return getDefaulted("archive", "");
+    }
+
+    public String getJavaArchive() {
+        return getDefaulted("java_archive", "");
+    }
+
+    public String getJavaArguments() {
+        return getDefaulted("java_arguments", "");
+    }
+
+    public String getCacheArchive() {
+        return getDefaulted("cache_archive", "");
+    }
+
+    public String getCacheArchiveEx() {
+        return getDefaulted("cache_archive_ex", "");
+    }
+
+    public String getCacheOption() {
+        return getDefaulted("cache_option", "");
+    }
+
+    public String getCacheVersion() {
+        return getDefaulted("cache_version", "");
+    }
+
+    public String getCode() {
+        return getDefaulted("code", "");
+    }
+
+    public String getJNLPHref() {
+        return get("jnlp_href");
+    }
+
+    public String getJNLPEmbedded() {
+        return get("jnlp_embedded");
+    }
+
+    public String getJarFiles() {
+        return getDefaulted("archive", "");
+    }
+
+    public int getWidth() {
+        String widthStr = getDefaulted("width", "0");
+        return Integer.valueOf(widthStr);
+    }
+
+    public int getHeight() {
+        String heightStr = getDefaulted("height", "0");
+        return Integer.valueOf(heightStr);
+    }
+
+    public void updateSize(int width, int height) {
+        parameters.put("width", Integer.toString(width));
+        parameters.put("height", Integer.toString(height));
+    }
+
+    public String getUniqueKey() {
+        /* According to http://download.oracle.com/javase/6/docs/technotes/guides/deployment/deployment-guide/applet-compatibility.html, 
+        * classloaders are shared iff these properties match:
+        * codebase, cache_archive, java_archive, archive
+        * 
+        * To achieve this, we create the uniquekey based on those 4 values,
+        * always in the same order. The initial "<NAME>=" parts ensure a 
+        * bad tag cannot trick the loader into getting shared with another.
+        */
+        return "codebase=" + getCodebase() + "cache_archive=" + getCacheArchive() +
+                "java_archive=" + getJavaArchive() + "archive=" + getArchive();
+    }
+
+    /**
+     * Replace an attribute with its 'java_'-prefixed version.      
+     * Note that java_* aliases override older names:
+     * http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/using_tags.html#in-nav
+     */
+    static void ensureJavaPrefixTakesPrecedence(Map<String, String> params,
+            String attribute) {
+        String javaPrefixAttribute = params.get("java_" + attribute);
+        if (javaPrefixAttribute != null) {
+            params.put(attribute, javaPrefixAttribute);
+        }
+    }
+
+    /**
+     * Creates the underlying hash table with the proper overrides. Ensure all
+     * keys are lowercase consistently.
+     * 
+     * @param params
+     *            the properties, before parameter aliasing rules.
+     * @return the resulting parameter table
+     */
+    static Hashtable<String, String> createParameterTable(
+            Map<String, String> rawParams) {
+        Hashtable<String, String> params = new Hashtable<String, String>();
+
+        for (Map.Entry<String, String> entry : rawParams.entrySet()) {
+            String key = entry.getKey().toLowerCase();
+            String value = entry.getValue();
+            params.put(key, value);
+        }
+
+        String codeTag = params.get("code");
+        String classID = params.get("classid");
+
+        // If there is a classid and no code tag present, transform it to code tag
+        if (codeTag == null && classID != null && !classID.startsWith("clsid:")) {
+            codeTag = classID;
+            params.put("code", codeTag);
+        }
+
+        // remove java: from code tag
+        if (codeTag != null && codeTag.startsWith("java:")) {
+            codeTag = codeTag.substring("java:".length());
+            params.put("code", codeTag);
+        }
+
+        // java_* aliases override older names:
+        // http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/using_tags.html#in-nav
+        ensureJavaPrefixTakesPrecedence(params, "code");
+        ensureJavaPrefixTakesPrecedence(params, "codebase");
+        ensureJavaPrefixTakesPrecedence(params, "archive");
+        ensureJavaPrefixTakesPrecedence(params, "object");
+        ensureJavaPrefixTakesPrecedence(params, "type");
+
+        return params;
+    }
+
+    public String toString() {
+        return parameters.toString();
+    }
+}
\ No newline at end of file
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties	Mon Dec 03 18:08:38 2012 +0100
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties	Tue Dec 04 10:43:59 2012 -0500
@@ -141,6 +141,7 @@
 BBadProp=Incorrect property format {0} (should be key=value)
 BBadParam=Incorrect parameter format {0} (should be name=value)
 BNoDir=Directory {0} does not exist.
+BNoCodeOrObjectApplet=Applet tag must specify a 'code' or 'object' attribute.
 RNoResource=Missing Resource: {0}
 RShutdown=This exception to prevent shutdown of JVM, but the process has been terminated.
 RExitTaken=Exit class already set and caller is not exit class.
--- a/netx/net/sourceforge/jnlp/resources/Messages_cs_CZ.properties	Mon Dec 03 18:08:38 2012 +0100
+++ b/netx/net/sourceforge/jnlp/resources/Messages_cs_CZ.properties	Tue Dec 04 10:43:59 2012 -0500
@@ -139,6 +139,7 @@
 BBadProp=Neplatn\u00fd form\u00e1t vlastnosti {0} (platn\u00fd form\u00e1t: kl\u00ed\u010d=hodnota)
 BBadParam=Neplatn\u00fd form\u00e1t parametru {0} (platn\u00fd form\u00e1t: n\u00e1zev=hodnota)
 BNoDir=Adres\u00e1\u0159 {0} neexistuje.
+BNoCodeOrObjectApplet=Zna\u010dka applet mus\u00ed m\u00edt defnov\u00e1n k\u00f3d nebo objekt - atributy 'code' nebo 'object' chyb\u00ed.
 RNoResource=Chyb\u011bj\u00edc\u00ed zdroj: {0}
 RShutdown=Tato v\u00fdjimka zabra\u0148uje ukon\u010den\u00ed prost\u0159ed\u00ed JVM, av\u0161ak proces byl ukon\u010den.
 RExitTaken=T\u0159\u00edda exit class m\u016f\u017ee b\u00fdt nastavena pouze jednou a pouze ta pak m\u016f\u017ee ukon\u010dit prost\u0159ed\u00ed JVM.
--- a/plugin/icedteanp/IcedTeaNPPlugin.cc	Mon Dec 03 18:08:38 2012 +0100
+++ b/plugin/icedteanp/IcedTeaNPPlugin.cc	Tue Dec 04 10:43:59 2012 -0500
@@ -229,8 +229,7 @@
                                           GIOCondition condition,
                                           gpointer plugin_data);
 static NPError plugin_start_appletviewer (ITNPPluginData* data);
-static gchar* plugin_create_applet_tag (int16_t argc, char* argn[],
-                                        char* argv[]);
+std::string plugin_parameters_string (int argc, char* argn[], char* argv[]);
 static void plugin_stop_appletviewer ();
 // Uninitialize ITNPPluginData structure
 static void plugin_data_destroy (NPP instance);
@@ -347,7 +346,6 @@
 
   gchar* documentbase = NULL;
   gchar* read_message = NULL;
-  gchar* applet_tag = NULL;
   gchar* cookie_info = NULL;
 
   NPObject* npPluginObj = NULL;
@@ -395,11 +393,10 @@
   documentbase = plugin_get_documentbase (instance);
   if (documentbase && argc != 0)
     {
-      // Send applet tag message to appletviewer.
-      applet_tag = plugin_create_applet_tag (argc, argn, argv);
-
-      data->applet_tag = (gchar*) malloc(strlen(applet_tag)*sizeof(gchar) + strlen(documentbase)*sizeof(gchar) + 32);
-      g_sprintf(data->applet_tag, "tag %s %s", documentbase, applet_tag);
+      // Send parameters to appletviewer.
+      std::string params_string = plugin_parameters_string(argc, argn, argv);
+
+      data->parameters_string =  g_strdup_printf("tag %s %s", documentbase, params_string.c_str());
 
       data->is_applet_instance = true;
     }
@@ -424,33 +421,7 @@
 
   instance->pdata = data;
 
-  goto cleanup_done;
-
- cleanup_appletviewer_mutex:
-  g_mutex_free (data->appletviewer_mutex);
-  data->appletviewer_mutex = NULL;
-
-  // cleanup_instance_string:
-  g_free (data->instance_id);
-  data->instance_id = NULL;
-
-  // cleanup applet tag:
-  g_free (data->applet_tag);
-  data->applet_tag = NULL;
-
-  // cleanup_data:
-  // Eliminate back-pointer to plugin instance.
-  data->owner = NULL;
-  (*browser_functions.memfree) (data);
-  data = NULL;
-
-  // Initialization failed so return a NULL pointer for the browser
-  // data.
-  instance->pdata = NULL;
-
  cleanup_done:
-  g_free (applet_tag);
-  applet_tag = NULL;
   g_free (read_message);
   read_message = NULL;
   g_free (documentbase);
@@ -834,7 +805,7 @@
       // Now we have everything. Send this data to the Java side
       plugin_send_initialization_message(
     		  data->instance_id, (gulong) data->window_handle,
-    		  data->window_width, data->window_height, data->applet_tag);
+    		  data->window_width, data->window_height, data->parameters_string);
 
       g_mutex_unlock (data->appletviewer_mutex);
 
@@ -1694,159 +1665,67 @@
   return error;
 }
 
+
 /*
- * Replaces certain characters (\r, \n, etc) with HTML escape equivalents.
- *
- * Return string is allocated on the heap. Caller assumes responsibility 
- * for freeing the memory via free()
+ * Escape characters for passing to Java.
+ * "\n" for new line, "\\" for "\", "\:" for ";"
  */
-static char*
-encode_string(char* to_encode)
-{
-
-  // Do nothing for an empty string
-  if (to_encode == '\0')
-      return to_encode;
-
-  // worst case scenario -> all characters are newlines or
-  // returns, each of which translates to 5 substitutions
-  char* encoded = (char*) calloc(((strlen(to_encode)*5)+1), sizeof(char));
-
-  strcpy(encoded, "");
-
-  for (int i=0; i < strlen(to_encode); i++)
+std::string
+escape_parameter_string(const char* to_encode) {
+  std::string encoded;
+
+  if (to_encode == NULL)
   {
-      if (to_encode[i] == '\r')
-          encoded = strcat(encoded, "&#13;");
-      else if (to_encode[i] == '\n')
-          encoded = strcat(encoded, "&#10;");
-      else if (to_encode[i] == '>')
-          encoded = strcat(encoded, "&gt;");
-      else if (to_encode[i] == '<')
-          encoded = strcat(encoded, "&lt;");
-      else if (to_encode[i] == '&')
-          encoded = strcat(encoded, "&amp;");
-      else if (to_encode[i] == '"')
-          encoded = strcat(encoded, "&quot;");
+      return encoded;
+  }
+
+  size_t length = strlen(to_encode);
+  for (int i = 0; i < length; i++)
+  {
+      if (to_encode[i] == '\n')
+          encoded += "\\n";
+      else if (to_encode[i] == '\\')
+    	  encoded += "\\\\";
+      else if (to_encode[i] == ';')
+    	  encoded += "\\:";
       else
-      {
-           char* orig_char = (char*) calloc(2, sizeof(char));
-           orig_char[0] = to_encode[i];
-           orig_char[1] = '\0';
- 
-           strcat(encoded, orig_char);
- 
-           free(orig_char);
-           orig_char = NULL;
-      }
+          encoded += to_encode[i];
   }
 
   return encoded;
 }
 
-// Build up the applet tag string that we'll send to the applet
-// viewer.
-static gchar*
-plugin_create_applet_tag (int16_t argc, char* argn[], char* argv[])
+/*
+ * Build a string containing an encoded list of parameters to send to the applet viewer.
+ * The parameters are separated as 'key1;value1;key2;value2;'. As well, they are
+ * separated and escaped as:
+ * "\n" for new line, "\\" for "\", "\:" for ";"
+ */
+std::string
+plugin_parameters_string (int argc, char* argn[], char* argv[])
 {
-  PLUGIN_DEBUG ("plugin_create_applet_tag\n");
-
-  gchar* applet_tag = g_strdup ("<EMBED ");
-  gchar* parameters = g_strdup ("");
-
-  for (int16_t i = 0; i < argc; i++)
-    {
-      gchar* argn_escaped = encode_string(argn[i]);
-      gchar* argv_escaped = encode_string(argv[i]);
-
-      if (!g_ascii_strcasecmp (argn_escaped, "code"))
-        {
-          gchar* code = g_strdup_printf ("CODE=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, code, NULL);
-          g_free (code);
-          code = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "java_code"))
-    {
-          gchar* java_code = g_strdup_printf ("JAVA_CODE=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, java_code, NULL);
-          g_free (java_code);
-          java_code = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "codebase"))
-    {
-          gchar* codebase = g_strdup_printf ("CODEBASE=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, codebase, NULL);
-          g_free (codebase);
-          codebase = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "java_codebase"))
-    {
-          gchar* java_codebase = g_strdup_printf ("JAVA_CODEBASE=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, java_codebase, NULL);
-          g_free (java_codebase);
-          java_codebase = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "classid"))
-    {
-          gchar* classid = g_strdup_printf ("CLASSID=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, classid, NULL);
-          g_free (classid);
-          classid = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "archive"))
+  PLUGIN_DEBUG ("plugin_parameters_string\n");
+
+  std::string parameters;
+
+  for (int i = 0; i < argc; i++)
+  {
+    if (argv[i] != NULL)
     {
-          gchar* archive = g_strdup_printf ("ARCHIVE=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, archive, NULL);
-          g_free (archive);
-          archive = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "java_archive"))
-    {
-          gchar* java_archive = g_strdup_printf ("JAVA_ARCHIVE=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, java_archive, NULL);
-          g_free (java_archive);
-          java_archive = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "width"))
-    {
-          gchar* width = g_strdup_printf ("width=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, width, NULL);
-          g_free (width);
-          width = NULL;
-    }
-      else if (!g_ascii_strcasecmp (argn_escaped, "height"))
-    {
-          gchar* height = g_strdup_printf ("height=\"%s\" ", argv_escaped);
-          applet_tag = g_strconcat (applet_tag, height, NULL);
-          g_free (height);
-          height = NULL;
+        std::string name_escaped = escape_parameter_string(argn[i]);
+        std::string value_escaped = escape_parameter_string(argv[i]);
+
+        //Encode parameters and send as 'key1;value1;key2;value2;' etc
+        parameters += name_escaped;
+        parameters += ';';
+        parameters += value_escaped;
+        parameters += ';';
     }
-      else
-        {
-
-          if (argv_escaped != '\0')
-            {
-              parameters = g_strconcat (parameters, "<PARAM NAME=\"", argn_escaped,
-                                        "\" VALUE=\"", argv_escaped, "\">", NULL);
-            }
-        }
-
-      free(argn_escaped);
-      free(argv_escaped);
-
-      argn_escaped = NULL;
-      argv_escaped = NULL;
-    }
-
-  applet_tag = g_strconcat (applet_tag, ">", parameters, "</EMBED>", NULL);
-
-  g_free (parameters);
-  parameters = NULL;
-
-  PLUGIN_DEBUG ("plugin_create_applet_tag return\n");
-
-  return applet_tag;
+  }
+
+  PLUGIN_DEBUG ("plugin_parameters_string return\n");
+
+  return parameters;
 }
 
 // plugin_send_message_to_appletviewer must be called while holding
@@ -2057,8 +1936,8 @@
   tofree->instance_id = NULL;
 
   // cleanup applet tag
-  g_free (tofree->applet_tag);
-  tofree->applet_tag = NULL;
+  g_free (tofree->parameters_string);
+  tofree->parameters_string = NULL;
 
   g_free(tofree->source);
   tofree->source = NULL;
@@ -2537,7 +2416,7 @@
         // a 0 handle
         if (!data->window_handle)
         {
-            plugin_send_initialization_message(data->instance_id, 0, 0, 0, data->applet_tag);
+            plugin_send_initialization_message(data->instance_id, 0, 0, 0, data->parameters_string);
         }
 
         java_result = java_request.getAppletObjectInstance(id_str);
--- a/plugin/icedteanp/IcedTeaNPPlugin.h	Mon Dec 03 18:08:38 2012 +0100
+++ b/plugin/icedteanp/IcedTeaNPPlugin.h	Tue Dec 04 10:43:59 2012 -0500
@@ -66,8 +66,8 @@
 {
   // A unique identifier for this plugin window.
   gchar* instance_id;
-  // The applet tag sent to Java side
-  gchar* applet_tag;
+  // The parameter list string sent to Java side
+  gchar* parameters_string;
   // Mutex to protect appletviewer_alive.
   GMutex* appletviewer_mutex;
   // Back-pointer to the plugin instance to which this data belongs.
--- a/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java	Mon Dec 03 18:08:38 2012 +0100
+++ b/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java	Tue Dec 04 10:43:59 2012 -0500
@@ -70,7 +70,6 @@
 import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Insets;
-import java.awt.Toolkit;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.awt.event.WindowListener;
@@ -87,6 +86,7 @@
 import java.net.SocketPermission;
 import java.net.URI;
 import java.net.URL;
+import java.net.URLConnection;
 import java.security.AccessController;
 import java.security.AllPermission;
 import java.security.PrivilegedAction;
@@ -107,6 +107,7 @@
 import javax.swing.SwingUtilities;
 
 import net.sourceforge.jnlp.NetxPanel;
+import net.sourceforge.jnlp.PluginParameters;
 import net.sourceforge.jnlp.runtime.JNLPClassLoader;
 import sun.awt.AppContext;
 import sun.awt.SunToolkit;
@@ -129,14 +130,14 @@
 
     public AppletPanel createPanel(PluginStreamHandler streamhandler,
                                    final int identifier,
-                                   final long handle, int x, int y,
+                                   final long handle,
                                    final URL doc,
-                                   final Hashtable<String, String> atts) {
+                                   final PluginParameters params) {
         final NetxPanel panel = AccessController.doPrivileged(new PrivilegedAction<NetxPanel>() {
             public NetxPanel run() {
-                NetxPanel panel = new NetxPanel(doc, atts, false);
+                NetxPanel panel = new NetxPanel(doc, params, false);
                 NetxPanel.debug("Using NetX panel");
-                PluginDebug.debug(atts.toString());
+                PluginDebug.debug(params.toString());
                 return panel;
             }
         });
@@ -146,14 +147,13 @@
         // isn't the case, the awt eventqueue thread's context classloader
         // won't be set to a JNLPClassLoader, and when an applet class needs
         // to be loaded from the awt eventqueue, it won't be found.
-        final int width = Integer.parseInt(atts.get("width"));
-        final int height = Integer.parseInt(atts.get("height"));
         Thread panelInit = new Thread(panel.getThreadGroup(), new Runnable() {
             @Override public void run() {
                 panel.createNewAppContext();
                 // create the frame.
-                PluginDebug.debug("X and Y are: " + width + " " + height);
-                panel.setAppletViewerFrame(PluginAppletViewer.framePanel(identifier,handle, width, height, panel));
+                PluginDebug.debug("X and Y are: " + params.getWidth() + " " + params.getHeight());
+                panel.setAppletViewerFrame(PluginAppletViewer.framePanel(identifier, handle,
+                        params.getWidth(), params.getHeight(), panel));
 
                 panel.init();
                 // Start the applet
@@ -194,7 +194,7 @@
         try {
             SwingUtilities.invokeAndWait(new Runnable() {
                 public void run() {
-                    panel.getParent().setSize(width, height);
+                    panel.getParent().setSize(params.getWidth(), params.getHeight());
                 }
             });
         } catch (InvocationTargetException ite) {
@@ -599,18 +599,28 @@
                 int spaceLocation = message.indexOf(' ', "tag".length() + 1);
                 String documentBase =
                         UrlUtil.decode(message.substring("tag".length() + 1, spaceLocation));
-                String tag = message.substring(spaceLocation + 1);
+                String paramString = message.substring(spaceLocation + 1);
 
                 PluginDebug.debug("Handle = ", handle, "\n",
                                     "Width = ", width, "\n",
                                     "Height = ", height, "\n",
                                     "DocumentBase = ", documentBase, "\n",
-                                    "Tag = ", tag);
+                                    "Params = ", paramString);
 
-                PluginAppletViewer.parse
-                                        (identifier, handle, width, height,
-                                                new StringReader(tag),
-                                                new URL(documentBase));
+                PluginAppletPanelFactory factory = new PluginAppletPanelFactory();
+                AppletMessageHandler amh = new AppletMessageHandler("appletviewer");
+                URL url = new URL(documentBase);
+                URLConnection conn = url.openConnection();
+                /* The original URL may have been redirected - this
+                 * sets it to whatever URL/codebase we ended up getting
+                 */
+                url = conn.getURL();
+
+                PluginParameters params = new PluginParameterParser().parse(width, height, paramString);
+
+                // Let user know we are starting up
+                streamhandler.write("instance " + identifier + " status " + amh.getMessage("status.start"));
+                factory.createPanel(streamhandler, identifier, handle, url, params);
 
                 long maxTimeToSleep = APPLET_TIMEOUT;
                 appletsLock.lock();
@@ -1546,24 +1556,6 @@
     }
 
     /**
-     * Decodes the string (converts html escapes into proper characters)
-     *
-     * @param toDecode The string to decode
-     * @return The decoded string
-     */
-    public static String decodeString(String toDecode) {
-
-        toDecode = toDecode.replace("&gt;", ">");
-        toDecode = toDecode.replace("&lt;", "<");
-        toDecode = toDecode.replace("&amp;", "&");
-        toDecode = toDecode.replace("&#10;", "\n");
-        toDecode = toDecode.replace("&#13;", "\r");
-        toDecode = toDecode.replace("&quot;", "\"");
-
-        return toDecode;
-    }
-
-    /**
      * System parameters.
      */
     static Hashtable<String, String> systemParam = new Hashtable<String, String>();
@@ -1580,76 +1572,14 @@
     }
 
     /**
-     * Print the HTML tag.
-     */
-    public static void printTag(PrintStream out, Hashtable<String, String> atts) {
-        out.print("<applet");
-
-        String v = atts.get("codebase");
-        if (v != null) {
-            out.print(" codebase=\"" + v + "\"");
-        }
-
-        v = atts.get("code");
-        if (v == null) {
-            v = "applet.class";
-        }
-        out.print(" code=\"" + v + "\"");
-        v = atts.get("width");
-        if (v == null) {
-            v = "150";
-        }
-        out.print(" width=" + v);
-
-        v = atts.get("height");
-        if (v == null) {
-            v = "100";
-        }
-        out.print(" height=" + v);
-
-        v = atts.get("name");
-        if (v != null) {
-            out.print(" name=\"" + v + "\"");
-        }
-        out.println(">");
-
-        // A very slow sorting algorithm
-        int len = atts.size();
-        String params[] = new String[len];
-        len = 0;
-        for (Enumeration<String> e = atts.keys(); e.hasMoreElements();) {
-            String param = e.nextElement();
-            int i = 0;
-            for (; i < len; i++) {
-                if (params[i].compareTo(param) >= 0) {
-                    break;
-                }
-            }
-            System.arraycopy(params, i, params, i + 1, len - i);
-            params[i] = param;
-            len++;
-        }
-
-        for (int i = 0; i < len; i++) {
-            String param = params[i];
-            if (systemParam.get(param) == null) {
-                out.println("<param name=" + param +
-                        " value=\"" + atts.get(param) + "\">");
-            }
-        }
-        out.println("</applet>");
-    }
-
-    /**
      * Make sure the atrributes are uptodate.
      */
     public void updateAtts() {
         Dimension d = panel.getSize();
         Insets in = panel.getInsets();
-        panel.atts.put("width",
-                       Integer.valueOf(d.width - (in.left + in.right)).toString());
-        panel.atts.put("height",
-                       Integer.valueOf(d.height - (in.top + in.bottom)).toString());
+        int width = d.width - (in.left + in.right);
+        int height = d.height - (in.top + in.bottom);
+        panel.updateSizeInAtts(height, width);
     }
 
     /**
@@ -1796,412 +1726,6 @@
         return appletPanels.size();
     }
 
-    /**
-     * Scan spaces.
-     */
-    public static void skipSpace(int[] c, Reader in) throws IOException {
-        while ((c[0] >= 0) &&
-                ((c[0] == ' ') || (c[0] == '\t') || (c[0] == '\n') || (c[0] == '\r'))) {
-            c[0] = in.read();
-        }
-    }
-
-    /**
-     * Scan identifier
-     */
-    public static String scanIdentifier(int[] c, Reader in) throws IOException {
-        StringBuilder buf = new StringBuilder();
-
-        if (c[0] == '!') {
-            // Technically, we should be scanning for '!--' but we are reading
-            // from a stream, and there is no way to peek ahead. That said,
-            // a ! at this point can only mean comment here afaik, so we
-            // should be okay
-            skipComment(c, in);
-            return "";
-        }
-
-        while (true) {
-            if (((c[0] >= 'a') && (c[0] <= 'z')) ||
-                    ((c[0] >= 'A') && (c[0] <= 'Z')) ||
-                    ((c[0] >= '0') && (c[0] <= '9')) || (c[0] == '_')) {
-                buf.append((char) c[0]);
-                c[0] = in.read();
-            } else {
-                return buf.toString();
-            }
-        }
-    }
-
-    public static void skipComment(int[] c, Reader in) throws IOException {
-        StringBuilder buf = new StringBuilder();
-        boolean commentHeaderPassed = false;
-        c[0] = in.read();
-        buf.append((char) c[0]);
-
-        while (true) {
-            if (c[0] == '-' && (c[0] = in.read()) == '-') {
-                buf.append((char) c[0]);
-                if (commentHeaderPassed) {
-                    // -- encountered ... is > next?
-                    if ((c[0] = in.read()) == '>') {
-                        buf.append((char) c[0]);
-
-                        PluginDebug.debug("Comment skipped: ", buf.toString());
-
-                        // comment skipped.
-                        return;
-                    }
-                } else {
-                    // first -- is part of <!-- ... , just mark that we have passed it
-                    commentHeaderPassed = true;
-                }
-
-            } else if (commentHeaderPassed == false) {
-                buf.append((char) c[0]);
-                PluginDebug.debug("Warning: Attempted to skip comment, but this tag does not appear to be a comment: ", buf.toString());
-                return;
-            }
-
-            c[0] = in.read();
-            buf.append((char) c[0]);
-        }
-    }
-
-    /**
-     * Scan tag
-     */
-    public static Hashtable<String, String> scanTag(int[] c, Reader in) throws IOException {
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        skipSpace(c, in);
-        while (c[0] >= 0 && c[0] != '>') {
-            String att = decodeString(scanIdentifier(c, in));
-            String val = "";
-            skipSpace(c, in);
-            if (c[0] == '=') {
-                int quote = -1;
-                c[0] = in.read();
-                skipSpace(c, in);
-                if ((c[0] == '\'') || (c[0] == '\"')) {
-                    quote = c[0];
-                    c[0] = in.read();
-                }
-                StringBuilder buf = new StringBuilder();
-                while ((c[0] > 0) &&
-                        (((quote < 0) && (c[0] != ' ') && (c[0] != '\t') &&
-                                (c[0] != '\n') && (c[0] != '\r') && (c[0] != '>'))
-                         || ((quote >= 0) && (c[0] != quote)))) {
-                    buf.append((char) c[0]);
-                    c[0] = in.read();
-                }
-                if (c[0] == quote) {
-                    c[0] = in.read();
-                }
-                skipSpace(c, in);
-                val = decodeString(buf.toString());
-            }
-
-            PluginDebug.debug("PUT ", att, " = '", val, "'");
-            atts.put(att.toLowerCase(java.util.Locale.ENGLISH), val);
-
-            while (true) {
-                if ((c[0] == '>') || (c[0] < 0) ||
-                        ((c[0] >= 'a') && (c[0] <= 'z')) ||
-                        ((c[0] >= 'A') && (c[0] <= 'Z')) ||
-                        ((c[0] >= '0') && (c[0] <= '9')) || (c[0] == '_'))
-                    break;
-                c[0] = in.read();
-            }
-            //skipSpace(in);
-        }
-        return atts;
-    }
-
-    // private static final == inline
-    private static final boolean isInt(Object o) {
-        boolean isInt = false;
-        try {
-            Integer.parseInt((String) o);
-            isInt = true;
-        } catch (Exception e) {
-            // don't care
-        }
-
-        return isInt;
-    }
-
-    /* values used for placement of AppletViewer's frames */
-    private static int x = 0;
-    private static int y = 0;
-    private static final int XDELTA = 30;
-    private static final int YDELTA = XDELTA;
-
-    static String encoding = null;
-
-    /**
-     * Scan an html file for <applet> tags
-     */
-    public static void parse(int identifier, long handle, String width, String height, Reader in, URL url, String enc)
-            throws IOException {
-        encoding = enc;
-        parse(identifier, handle, width, height, in, url, System.out, new PluginAppletPanelFactory());
-    }
-
-    public static void parse(int identifier, long handle, String width, String height, Reader in, URL url)
-            throws PrivilegedActionException {
-
-        final int fIdentifier = identifier;
-        final long fHandle = handle;
-        final String fWidth = width;
-        final String fHeight = height;
-        final Reader fIn = in;
-        final URL fUrl = url;
-        AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
-            public Void run() throws IOException {
-                parse(fIdentifier, fHandle, fWidth, fHeight, fIn, fUrl,
-                        System.out, new PluginAppletPanelFactory());
-                return null;
-            }
-        });
-    }
-
-    @SuppressWarnings("unused")
-    public static void parse(int identifier, long handle, String width,
-                 String height, Reader in, URL url,
-                              PrintStream statusMsgStream,
-                              PluginAppletPanelFactory factory)
-            throws IOException {
-        boolean isObjectTag = false;
-        boolean objectTagAlreadyParsed = false;
-
-        // The current character
-        // FIXME: This is an evil hack to force pass-by-reference.. the
-        // parsing code needs to be rewritten from scratch to prevent such
-        //a need
-        int[] c = new int[1];
-
-        // warning messages
-        String requiresNameWarning = amh.getMessage("parse.warning.requiresname");
-        String paramOutsideWarning = amh.getMessage("parse.warning.paramoutside");
-        String appletRequiresCodeWarning = amh.getMessage("parse.warning.applet.requirescode");
-        String appletRequiresHeightWarning = amh.getMessage("parse.warning.applet.requiresheight");
-        String appletRequiresWidthWarning = amh.getMessage("parse.warning.applet.requireswidth");
-        String objectRequiresCodeWarning = amh.getMessage("parse.warning.object.requirescode");
-        String objectRequiresHeightWarning = amh.getMessage("parse.warning.object.requiresheight");
-        String objectRequiresWidthWarning = amh.getMessage("parse.warning.object.requireswidth");
-        String embedRequiresCodeWarning = amh.getMessage("parse.warning.embed.requirescode");
-        String embedRequiresHeightWarning = amh.getMessage("parse.warning.embed.requiresheight");
-        String embedRequiresWidthWarning = amh.getMessage("parse.warning.embed.requireswidth");
-        String appNotLongerSupportedWarning = amh.getMessage("parse.warning.appnotLongersupported");
-
-        java.net.URLConnection conn = url.openConnection();
-        /* The original URL may have been redirected - this
-         * sets it to whatever URL/codebase we ended up getting
-         */
-        url = conn.getURL();
-
-        int ydisp = 1;
-        Hashtable<String, String> atts = null;
-
-        while (true) {
-            c[0] = in.read();
-            if (c[0] == -1)
-                break;
-
-            if (c[0] == '<') {
-                c[0] = in.read();
-                if (c[0] == '/') {
-                    c[0] = in.read();
-                    String nm = scanIdentifier(c, in);
-                    if (nm.equalsIgnoreCase("applet") ||
-                             nm.equalsIgnoreCase("object") ||
-                             nm.equalsIgnoreCase("embed")) {
-
-                        // We can't test for a code tag until </OBJECT>
-                        // because it is a parameter, not an attribute.
-                        if (isObjectTag) {
-                            if (atts.get("code") == null && atts.get("object") == null) {
-                                statusMsgStream.println(objectRequiresCodeWarning);
-                                atts = null;
-                            }
-                        }
-
-                        if (atts != null) {
-                            // XXX 5/18 In general this code just simply
-                            // shouldn't be part of parsing.  It's presence
-                            // causes things to be a little too much of a
-                            // hack.
-
-                            // Let user know we are starting up
-                            streamhandler.write("instance " + identifier + " status " + amh.getMessage("status.start"));
-                            factory.createPanel(streamhandler, identifier, handle, x, y, url, atts);
-
-                            x += XDELTA;
-                            y += YDELTA;
-                            // make sure we don't go too far!
-                            Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
-                            if ((x > d.width - 300) || (y > d.height - 300)) {
-                                x = 0;
-                                y = 2 * ydisp * YDELTA;
-                                ydisp++;
-                            }
-                        }
-                        atts = null;
-                        isObjectTag = false;
-                    }
-                } else {
-                    String nm = scanIdentifier(c, in);
-                    if (nm.equalsIgnoreCase("param")) {
-                        Hashtable<String, String> t = scanTag(c, in);
-                        String att = t.get("name");
-
-                        if (att == null) {
-                            statusMsgStream.println(requiresNameWarning);
-                        } else {
-                            String val = t.get("value");
-                            if (val == null) {
-                                statusMsgStream.println(requiresNameWarning);
-                            } else {
-                                PluginDebug.debug("PUT ", att, " = ", val);
-                                atts.put(att.toLowerCase(), val);
-                            }
-                        }
-                    } else if (nm.equalsIgnoreCase("applet")) {
-                        atts = scanTag(c, in);
-
-                        // If there is a classid and no code tag present, transform it to code tag
-                        if (atts.get("code") == null && atts.get("classid") != null
-                                && !(atts.get("classid")).startsWith("clsid:")) {
-                            atts.put("code", atts.get("classid"));
-                        }
-
-                        // remove java: from code tag
-                        if (atts.get("code") != null && (atts.get("code")).startsWith("java:")) {
-                            atts.put("code", (atts.get("code")).substring(5));
-                        }
-
-                        if (atts.get("code") == null && atts.get("object") == null) {
-                            statusMsgStream.println(appletRequiresCodeWarning);
-                            atts = null;
-                        }
-
-                        if (atts.get("width") == null || !isInt(atts.get("width"))) {
-                            atts.put("width", width);
-                        }
-
-                        if (atts.get("height") == null || !isInt(atts.get("height"))) {
-                            atts.put("height", height);
-                        }
-                    } else if (nm.equalsIgnoreCase("object")) {
-                        isObjectTag = true;
-
-                        // Once code is set, additional nested objects are ignored
-                        if (!objectTagAlreadyParsed) {
-                            objectTagAlreadyParsed = true;
-                            atts = scanTag(c, in);
-                        }
-
-                        // If there is a classid and no code tag present, transform it to code tag
-                        if (atts.get("code") == null && atts.get("classid") != null
-                                && !(atts.get("classid")).startsWith("clsid:")) {
-                            atts.put("code", atts.get("classid"));
-                        }
-
-                        // remove java: from code tag
-                        if (atts.get("code") != null && (atts.get("code")).startsWith("java:")) {
-                            atts.put("code", (atts.get("code")).substring(5));
-                        }
-
-                        // java_* aliases override older names:
-                        // http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/using_tags.html#in-ie
-                        if (atts.get("java_code") != null) {
-                            atts.put("code", (atts.get("java_code")));
-                        }
-
-                        if (atts.containsKey("code")) {
-                            objectTagAlreadyParsed = true;
-                        }
-
-                        if (atts.get("java_codebase") != null) {
-                            atts.put("codebase", (atts.get("java_codebase")));
-                        }
-
-                        if (atts.get("java_archive") != null) {
-                            atts.put("archive", (atts.get("java_archive")));
-                        }
-
-                        if (atts.get("java_object") != null) {
-                            atts.put("object", (atts.get("java_object")));
-                        }
-
-                        if (atts.get("java_type") != null) {
-                            atts.put("type", (atts.get("java_type")));
-                        }
-
-                        if (atts.get("width") == null || !isInt(atts.get("width"))) {
-                            atts.put("width", width);
-                        }
-
-                        if (atts.get("height") == null || !isInt(atts.get("height"))) {
-                            atts.put("height", height);
-                        }
-                    } else if (nm.equalsIgnoreCase("embed")) {
-                        atts = scanTag(c, in);
-
-                        // If there is a classid and no code tag present, transform it to code tag
-                        if (atts.get("code") == null && atts.get("classid") != null
-                                && !(atts.get("classid")).startsWith("clsid:")) {
-                            atts.put("code", atts.get("classid"));
-                        }
-
-                        // remove java: from code tag
-                        if (atts.get("code") != null && (atts.get("code")).startsWith("java:")) {
-                            atts.put("code", (atts.get("code")).substring(5));
-                        }
-
-                        // java_* aliases override older names:
-                        // http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/using_tags.html#in-nav
-                        if (atts.get("java_code") != null) {
-                            atts.put("code", (atts.get("java_code")));
-                        }
-
-                        if (atts.get("java_codebase") != null) {
-                            atts.put("codebase", (atts.get("java_codebase")));
-                        }
-
-                        if (atts.get("java_archive") != null) {
-                            atts.put("archive", (atts.get("java_archive")));
-                        }
-
-                        if (atts.get("java_object") != null) {
-                            atts.put("object", (atts.get("java_object")));
-                        }
-
-                        if (atts.get("java_type") != null) {
-                            atts.put("type", (atts.get("java_type")));
-                        }
-
-                        if (atts.get("code") == null && atts.get("object") == null) {
-                            statusMsgStream.println(embedRequiresCodeWarning);
-                            atts = null;
-                        }
-
-                        if (atts.get("width") == null || !isInt(atts.get("width"))) {
-                            atts.put("width", width);
-                        }
-
-                        if (atts.get("height") == null || !isInt(atts.get("height"))) {
-                            atts.put("height", height);
-                        }
-
-                    }
-                }
-            }
-        }
-        in.close();
-    }
-
-    private static AppletMessageHandler amh = new AppletMessageHandler("appletviewer");
 
     private static void checkConnect(URL url) {
         SecurityManager security = System.getSecurityManager();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugin/icedteanp/java/sun/applet/PluginParameterParser.java	Tue Dec 04 10:43:59 2012 -0500
@@ -0,0 +1,90 @@
+package sun.applet;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sourceforge.jnlp.PluginParameters;
+
+class PluginParameterParser {
+    static private final char DELIMITER_ESCAPE = ':';
+    static private final String KEY_VALUE_DELIMITER = ";";
+
+    /**
+     * Unescape characters passed from C++.
+     * Specifically, "\n" -> new line, "\\" -> "\", "\:" -> ";"
+     *
+     * @param str The string to unescape
+     * @return The unescaped string
+     */
+    static String unescapeString(String str) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < str.length(); i++) {
+            char chr = str.charAt(i);
+            if (chr != '\\') {
+                sb.append(chr);
+            } else {
+                i++; // Skip ahead one
+                chr = str.charAt(i);
+                if (chr == 'n') {
+                    sb.append('\n');
+                } else if (chr == '\\') {
+                    sb.append('\\');
+                } else if (chr == DELIMITER_ESCAPE) {
+                    sb.append(KEY_VALUE_DELIMITER);
+                }
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Parse semi-colon delimited key-value pairs.
+     * @param keyvalString the escaped, semicolon-delimited, string
+     * @return a map of the keys to the values
+     */
+    static Map<String, String> parseEscapedKeyValuePairs(String keyvalString) {
+        // Split on ';', ensuring empty strings at end are kept
+        String[] strs = keyvalString.split(KEY_VALUE_DELIMITER, -1 /* Keep empty strings */);
+        System.out.println("Split array: " + Arrays.toString(strs));
+
+        Map<String, String> attributes = new HashMap<String, String>();
+
+        /* Note that we will typically have one empty string at end */
+        for (int i = 0; i < strs.length - 1; i += 2) {
+            String key = unescapeString(strs[i]).toLowerCase();
+            String value = unescapeString(strs[i + 1]);
+            attributes.put(key, value);
+        }
+
+        return attributes;
+    }
+
+    static boolean isInt(String s) {
+        return s.matches("^-?\\d+$");
+    }
+
+    /**
+     * Parsers parameters given a string containing 
+     * parameters in quotes.
+     * 
+     * @param width default applet width
+     * @param height default applet height
+     * @param parameterString the parameters 
+     * @return the attributes in a hash table
+     */
+    public PluginParameters parse(String width,
+            String height, String parameterString) {
+        Map<String, String> params = parseEscapedKeyValuePairs(parameterString);
+
+        if (params.get("width") == null || !isInt(params.get("width"))) {
+            params.put("width", width);
+        }
+
+        if (params.get("height") == null || !isInt(params.get("height"))) {
+            params.put("height", height);
+        }
+
+        return new PluginParameters(params);
+    }
+}
--- a/tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc	Mon Dec 03 18:08:38 2012 +0100
+++ b/tests/cpp-unit-tests/IcedTeaPluginUtilsTest.cc	Tue Dec 04 10:43:59 2012 -0500
@@ -48,7 +48,7 @@
     STRINGZ_TO_NPVARIANT("test", var);
 
     std::string cppstr = IcedTeaPluginUtilities::NPVariantAsString(var);
-    CHECK(cppstr == "test");
+    CHECK_EQUAL("test", cppstr);
 
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cpp-unit-tests/PluginParametersTest.cc	Tue Dec 04 10:43:59 2012 -0500
@@ -0,0 +1,93 @@
+/* Copyright (C) 2012 Red Hat
+
+ This file is part of IcedTea.
+
+ IcedTea 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, or (at your option)
+ any later version.
+
+ IcedTea 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 IcedTea; see the file COPYING.  If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version. */
+
+/******************************************************************************
+ * Unit tests for functions related to sending applet parameters              *
+ * (key value pairs).                                                         *
+ ******************************************************************************/
+
+#include <UnitTest++.h>
+
+#include "IcedTeaNPPlugin.h"
+
+
+/* Not normally exposed */
+std::string escape_parameter_string(const char* to_encode);
+
+TEST(escape_parameter_string) {
+	CHECK_EQUAL("\\n", escape_parameter_string("\n"));
+	CHECK_EQUAL("\\\\", escape_parameter_string("\\"));
+	CHECK_EQUAL("\\:", escape_parameter_string(";"));
+
+	CHECK_EQUAL(std::string("test") + "\\n" + "\\\\" + "\\:",
+			escape_parameter_string("test\n\\;"));
+}
+
+/* Not normally exposed */
+std::string plugin_parameters_string(int argc, char* argn[], char* argv[]);
+
+TEST(plugin_parameters_string) {
+
+	/* test empty */{
+		const char* argn[] = { "" };
+		const char* argv[] = { "" };
+		CHECK_EQUAL("",
+				plugin_parameters_string(0, (char**)argn, (char**)argv));
+	}
+
+	/* test simple key & value */{
+		const char* argn[] = { "key" };
+		const char* argv[] = { "value" };
+		CHECK_EQUAL("key;value;",
+				plugin_parameters_string(1, (char**)argn, (char**)argv));
+	}
+
+	/* test key & value characters that require escaping */{
+		const char* argn[] = { "key\\" };
+		const char* argv[] = { "value;" };
+		CHECK_EQUAL("key\\\\;value\\:;",
+				plugin_parameters_string(1, (char**)argn, (char**)argv));
+	}
+
+	/* multiple key & value pairs that require escaping*/{
+		const char* argn[] = { "key1\\", "key2\\" };
+		const char* argv[] = { "value1;", "value2;" };
+		CHECK_EQUAL("key1\\\\;value1\\:;key2\\\\;value2\\:;",
+				plugin_parameters_string(2, (char**)argn, (char**)argv));
+	}
+}
+
+
--- a/tests/netx/unit/net/sourceforge/jnlp/PluginBridgeTest.java	Mon Dec 03 18:08:38 2012 +0100
+++ b/tests/netx/unit/net/sourceforge/jnlp/PluginBridgeTest.java	Tue Dec 04 10:43:59 2012 -0500
@@ -29,6 +29,7 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Hashtable;
 import java.util.List;
 import junit.framework.Assert;
@@ -64,14 +65,20 @@
         }
     }
 
+    static private PluginParameters createValidParamObject() {
+        Map<String, String> params = new HashMap<String, String>();
+        params.put("code", ""); // Avoids an exception being thrown
+        return new PluginParameters(params);
+    }
+
     @Test
     public void testAbsoluteJNLPHref() throws MalformedURLException, Exception {
         URL codeBase = new URL("http://undesired.absolute.codebase.com");
         String absoluteLocation = "http://absolute.href.com/test.jnlp";
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        atts.put("jnlp_href", absoluteLocation);
+        PluginParameters params = createValidParamObject();
+        params.put("jnlp_href", absoluteLocation);
         MockJNLPCreator mockCreator = new MockJNLPCreator();
-        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, atts, "", mockCreator);
+        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, params, mockCreator);
         assertEquals(absoluteLocation, mockCreator.getJNLPHref().toExternalForm());
     }
 
@@ -79,12 +86,12 @@
     public void testRelativeJNLPHref() throws MalformedURLException, Exception {
         URL codeBase = new URL("http://desired.absolute.codebase.com/");
         String relativeLocation = "sub/dir/test.jnlp";
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        atts.put("jnlp_href", relativeLocation);
+        PluginParameters params = createValidParamObject();
+        params.put("jnlp_href", relativeLocation);
         MockJNLPCreator mockCreator = new MockJNLPCreator();
-        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, atts, "", mockCreator);
+        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, params, mockCreator);
         assertEquals(codeBase.toExternalForm() + relativeLocation,
-                     mockCreator.getJNLPHref().toExternalForm());
+                mockCreator.getJNLPHref().toExternalForm());
     }
 
     @Test
@@ -92,12 +99,12 @@
         String desiredDomain = "http://desired.absolute.codebase.com";
         URL codeBase = new URL(desiredDomain + "/undesired/sub/dir");
         String relativeLocation = "/app/test/test.jnlp";
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        atts.put("jnlp_href", relativeLocation);
+        PluginParameters params = createValidParamObject();
+        params.put("jnlp_href", relativeLocation);
         MockJNLPCreator mockCreator = new MockJNLPCreator();
-        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, atts, "", mockCreator);
+        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, params, mockCreator);
         assertEquals(desiredDomain + relativeLocation,
-                     mockCreator.getJNLPHref().toExternalForm());
+                mockCreator.getJNLPHref().toExternalForm());
     }
 
     @Test
@@ -166,12 +173,12 @@
                 "ICAgICAgLz4NCiAgICAgICAgICAgIDwvam5scD4=";
 
         MockJNLPCreator mockCreator = new MockJNLPCreator();
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        atts.put("jnlp_href", relativeLocation);
-        atts.put("jnlp_embedded", jnlpFileEncoded);
+        PluginParameters params = createValidParamObject();
+        params.put("jnlp_href", relativeLocation);
+        params.put("jnlp_embedded", jnlpFileEncoded);
 
         String jnlpCodebase = "http://www.redhat.com";
-        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, atts, "", mockCreator);
+        PluginBridge pb = new PluginBridge(codeBase, null, "", "", 0, 0, params, mockCreator);
         JARDesc[] jars = pb.getResources().getJARs();
 
         //Check if there are two jars cached
@@ -239,11 +246,11 @@
                 "ICAgICAgICAgICAgPC9qbmxwPg==";
 
         MockJNLPCreator mockCreator = new MockJNLPCreator();
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        atts.put("jnlp_href", relativeLocation);
-        atts.put("jnlp_embedded", jnlpFileEncoded);
+        PluginParameters params = createValidParamObject();
+        params.put("jnlp_href", relativeLocation);
+        params.put("jnlp_embedded", jnlpFileEncoded);
 
-        PluginBridge pb = new PluginBridge(overwrittenCodebase, null, "", "", 0, 0, atts, "", mockCreator);
+        PluginBridge pb = new PluginBridge(overwrittenCodebase, null, "", "", 0, 0, params, mockCreator);
         JARDesc[] jars = pb.getResources().getJARs();
 
         //Check if there are two jars cached
@@ -268,12 +275,12 @@
         String jnlpFileEncoded = "thisContextIsInvalid";
 
         MockJNLPCreator mockCreator = new MockJNLPCreator();
-        Hashtable<String, String> atts = new Hashtable<String, String>();
-        atts.put("jnlp_href", relativeLocation);
-        atts.put("jnlp_embedded", jnlpFileEncoded);
+        PluginParameters params = createValidParamObject();
+        params.put("jnlp_href", relativeLocation);
+        params.put("jnlp_embedded", jnlpFileEncoded);
 
         try {
-            new PluginBridge(overwrittenCodebase, null, "", "", 0, 0, atts, "", mockCreator);
+            new PluginBridge(overwrittenCodebase, null, "", "", 0, 0, params, mockCreator);
         } catch (Exception e) {
             return;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/net/sourceforge/jnlp/PluginParametersTest.java	Tue Dec 04 10:43:59 2012 -0500
@@ -0,0 +1,114 @@
+package net.sourceforge.jnlp;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class PluginParametersTest {
+
+    @Test
+    public void testAttributeParseJavaPrefix() {
+        // java_* aliases override older names:
+        // http://java.sun.com/j2se/1.4.2/docs/guide/plugin/developer_guide/using_tags.html#in-nav
+
+        Map<String, String> rawParams;
+        Hashtable<String, String> params;
+
+        rawParams = new HashMap<String, String>();
+        rawParams.put("code", "codeValue");
+        rawParams.put("java_code", "java_codeValue");
+        params = PluginParameters.createParameterTable(rawParams);
+
+        assertEquals("java_codeValue", params.get("code"));
+
+        rawParams = new HashMap<String, String>();
+        rawParams.put("codebase", "codebaseValue");
+        rawParams.put("java_codebase", "java_codebaseValue");
+        params = PluginParameters.createParameterTable(rawParams);
+
+        assertEquals("java_codebaseValue", params.get("codebase"));
+
+        rawParams = new HashMap<String, String>();
+        rawParams.put("archive", "archiveValue");
+        rawParams.put("java_archive", "java_archiveValue");
+        params = PluginParameters.createParameterTable(rawParams);
+
+        assertEquals("java_archiveValue", params.get("archive"));
+
+        rawParams = new HashMap<String, String>();
+        rawParams.put("object", "objectValue");
+        rawParams.put("java_object", "java_objectValue");
+        params = PluginParameters.createParameterTable(rawParams);
+
+        assertEquals("java_objectValue", params.get("object"));
+
+        rawParams = new HashMap<String, String>();
+        rawParams.put("type", "typeValue");
+        rawParams.put("java_type", "java_typeValue");
+        params = PluginParameters.createParameterTable(rawParams);
+
+        assertEquals("java_typeValue", params.get("type"));
+    }
+
+    @Test
+    public void testEnsureJavaPrefixTakesPrecedence() {
+        Map<String, String> params;
+        params = new HashMap<String, String>();
+        params.put("test", "testValue");
+        params.put("java_test", "java_testValue");
+        PluginParameters.ensureJavaPrefixTakesPrecedence(params, "test");
+        assertEquals("java_testValue", params.get("test"));
+
+        params = new HashMap<String, String>();
+        params.put("test", "testValue");
+        PluginParameters.ensureJavaPrefixTakesPrecedence(params, "test");
+        assertEquals("testValue", params.get("test"));
+
+        params = new HashMap<String, String>();
+        params.put("java_test", "java_testValue");
+        PluginParameters.ensureJavaPrefixTakesPrecedence(params, "test");
+        assertEquals("java_testValue", params.get("test"));
+    }
+
+    @Test
+    public void testAttributeParseCodeAttribute() {
+        Map<String, String> rawParams;
+        Hashtable<String, String> params;
+
+        // Simple test of object tag being set
+        rawParams = new HashMap<String, String>();
+        rawParams.put("object", "objectValue");
+        params = PluginParameters.createParameterTable(rawParams);
+        assertEquals("objectValue", params.get("object"));
+
+        // Classid tag gets used as code tag
+        rawParams = new HashMap<String, String>();
+        rawParams.put("classid", "classidValue");
+        params = PluginParameters.createParameterTable(rawParams);
+        assertEquals("classidValue", params.get("code"));
+
+        // Java: gets stripped from code tag
+        rawParams = new HashMap<String, String>();
+        rawParams.put("code", "java:codeValue");
+        params = PluginParameters.createParameterTable(rawParams);
+        assertEquals("codeValue", params.get("code"));
+
+        // Classid tag gets used as code tag, and java: is stripped
+        rawParams = new HashMap<String, String>();
+        rawParams.put("classid", "java:classidValue");
+        params = PluginParameters.createParameterTable(rawParams);
+        assertEquals("classidValue", params.get("code"));
+
+        // Classid tag gets used as code tag, and clsid: is stripped
+        rawParams = new HashMap<String, String>();
+        rawParams.put("classid", "clsid:classidValue");
+        params = PluginParameters.createParameterTable(rawParams);
+        assertEquals(null, params.get("code"));
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/sun/applet/PluginParameterParserTest.java	Tue Dec 04 10:43:59 2012 -0500
@@ -0,0 +1,73 @@
+package sun.applet;
+
+import static org.junit.Assert.*;
+
+import java.util.Map;
+
+import net.sourceforge.jnlp.PluginParameters;
+
+import org.junit.Test;
+
+public class PluginParameterParserTest {
+
+    @Test
+    public void testIsInt() {
+        assertFalse(PluginParameterParser.isInt("1.0"));
+        assertFalse(PluginParameterParser.isInt("abc"));
+        assertTrue(PluginParameterParser.isInt("1"));
+    }
+
+    @Test
+    public void testUnescapeString() {
+        assertEquals("", PluginParameterParser.unescapeString(""));
+        assertEquals("\n", PluginParameterParser.unescapeString("\n"));
+        assertEquals("\\", PluginParameterParser.unescapeString("\\\\"));
+        assertEquals(";", PluginParameterParser.unescapeString("\\:"));
+
+        assertEquals("test\n\\;",
+                PluginParameterParser.unescapeString("test" + "\\n" + "\\\\" + "\\:"));
+
+        assertEquals("start\n;end\\;",
+                PluginParameterParser.unescapeString("start\\n\\:end\\\\;"));
+    }
+
+    @Test
+    public void testParseEscapedKeyValuePairs() {
+        Map<String, String> params;
+
+        params = PluginParameterParser.parseEscapedKeyValuePairs("key1;value1;KEY2\\:;value2\\\\;");
+        assertEquals(params.size(), 2);
+        assertEquals(params.get("key1"), "value1");
+        assertEquals(params.get("key2;"), "value2\\"); // ensure key is lowercased
+
+        params = PluginParameterParser.parseEscapedKeyValuePairs("");
+        assertEquals(params.size(), 0);
+
+        params = PluginParameterParser.parseEscapedKeyValuePairs("key;;");
+        assertEquals(params.size(), 1);
+        assertEquals(params.get("key"), "");
+
+        params = PluginParameterParser.parseEscapedKeyValuePairs(";value;");
+        assertEquals(params.size(), 1);
+        assertEquals(params.get(""), "value");
+    }
+
+    @Test
+    public void testAttributeParseWidthHeightAttributes() {
+        final String width = "1", height = "1";
+        final String codeKeyVal = "code;codeValue;";
+
+        PluginParameterParser parser = new PluginParameterParser();
+        PluginParameters params;
+
+        params = parser.parse(width, height, codeKeyVal);
+        assertEquals("1", params.get("width"));
+        assertEquals("1", params.get("height"));
+
+        //Test that width height are defaulted to in case of not-a-number attributes:
+        params = parser.parse(width, height, codeKeyVal + " width;NAN;height;NAN;");
+        assertEquals("1", params.get("width"));
+        assertEquals("1", params.get("height"));
+    }
+
+}