changeset 486:cfad6b012cbf

Fix PR955: regression: SweetHome3D fails to run
author Danesh Dadachanji <ddadacha@redhat.com>
date Wed, 08 Aug 2012 11:48:06 -0400
parents f9ce023d1bf2
children 2359b7014c64
files ChangeLog NEWS netx/net/sourceforge/jnlp/JNLPFile.java netx/net/sourceforge/jnlp/MissingInformationException.java netx/net/sourceforge/jnlp/MissingTitleException.java netx/net/sourceforge/jnlp/MissingVendorException.java netx/net/sourceforge/jnlp/Parser.java netx/net/sourceforge/jnlp/RequiredElementException.java netx/net/sourceforge/jnlp/resources/Messages.properties tests/netx/unit/net/sourceforge/jnlp/JNLPFileTest.java tests/netx/unit/net/sourceforge/jnlp/ParserTest.java tests/reproducers/simple/InformationTitleVendorParser/testcases/InformationTitleVendorParserTest.java tests/test-extensions/net/sourceforge/jnlp/mock/MockJNLPFile.java
diffstat 13 files changed, 1892 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Aug 21 12:02:00 2012 +0200
+++ b/ChangeLog	Wed Aug 08 11:48:06 2012 -0400
@@ -1,3 +1,38 @@
+2012-08-22  Danesh Dadachanji  <ddadacha@redhat.com>
+
+	Fix PR955: regression: SweetHome3D fails to run
+	* NEWS: Added entry for PR955
+	* netx/net/sourceforge/jnlp/JNLPFile.java: New enum Match that represents
+	the level of precision to use when matching locales.
+	(localMatches): Renamed to localeMatches, added matchLevel paramater
+	and updated conditionals to handle the level of precision specified by it.
+	(getVendor): New method that returns an information's vendor text.
+	(getInformation): Added override methods for getTitle and getVendor
+	that are used by the anonymous class to filter by locale. All three
+	methods now go through all levels of precision to search for the best
+	fitted locale.
+	(getResources), (getResourcesDescs): Updated to check if any level of
+	precision matches when searching for locales.
+	(parse): Added call to checkForTitleVendor.
+	* netx/net/sourceforge/jnlp/Parser.java
+	(checkForTitleVendor): New method to check for availability of localized
+	title and vendor from the information tags. Throws ParseException.
+	(getInfo): Replace loop with foreach loop.
+	(getInformationDesc): Remove check for present title and vendor.
+	(getLocale): Variant returned can now use everything after the eigth
+	element of the locale's string.
+	* netx/net/sourceforge/jnlp/resources/Messages.properties:
+	Update missing title and vendor messages to mention localization.
+	* tests/reproducers/simple/InformationTitleVendorParser/testcases/InformationTitleVendorParserTest.java:
+	Update output string as per new changes to Messages internationalizations.
+	* tests/netx/unit/net/sourceforge/jnlp/JNLPFileTest.java:
+	New unit test that checks the localesMatches method in JNLPFile.
+	* tests/netx/unit/net/sourceforge/jnlp/MockJNLPFile.java:
+	New class used to create a mock JNLPFile object.
+	* tests/netx/unit/net/sourceforge/jnlp/ParserTest.java:
+	New unit test that checks that the return of getTitle and getVendor
+	have localized information.
+
 2012-08-21  Jiri Vanek  <jvanek@redhat.com>
 
 	* tests/test-extensions/net/sourceforge/jnlp/ProcessAssasin.java:
--- a/NEWS	Tue Aug 21 12:02:00 2012 +0200
+++ b/NEWS	Wed Aug 08 11:48:06 2012 -0400
@@ -32,6 +32,7 @@
   - RH838417: Disambiguate signed applet security prompt from certificate warning
   - RH838559: Disambiguate signed applet security prompt from certificate warning
   - RH720836: project can be compiled against GTK+ 2 or 3 librarie
+  - PR955: regression: SweetHome3D fails to run
 
 New in release 1.2 (2011-XX-XX):
 * Security updates:
--- a/netx/net/sourceforge/jnlp/JNLPFile.java	Tue Aug 21 12:02:00 2012 +0200
+++ b/netx/net/sourceforge/jnlp/JNLPFile.java	Wed Aug 08 11:48:06 2012 -0400
@@ -99,18 +99,18 @@
     /** the security descriptor */
     protected SecurityDesc security;
 
-    /** the default OS */
+    /** the default JVM locale */
     protected Locale defaultLocale = null;
 
-    /** the default arch */
+    /** the default OS */
     protected String defaultOS = null;
 
-    /** the default jvm */
+    /** the default arch */
     protected String defaultArch = null;
-    
+
     /** A signed JNLP file is missing from the main jar */
     private boolean missingSignedJNLP = false;
-    
+
     /** JNLP file contains special properties */
     private boolean containsSpecialProperties = false;
 
@@ -118,7 +118,7 @@
      * List of acceptable properties (not-special)
      */
     private String[] generalProperties = SecurityDesc.getJnlpRIAPermissions();
-    
+
     { // initialize defaults if security allows
         try {
             defaultLocale = Locale.getDefault();
@@ -129,6 +129,8 @@
         }
     }
 
+    static enum Match { LANG_COUNTRY_VARIANT, LANG_COUNTRY, LANG, GENERALIZED }
+
     /**
      * Empty stub, allowing child classes to override the constructor
      */
@@ -185,9 +187,9 @@
      * @throws ParseException if the JNLP file was invalid
      */
     public JNLPFile(URL location, Version version, boolean strict, UpdatePolicy policy) throws IOException, ParseException {
-	this(location, version, strict, policy, null);
+        this(location, version, strict, policy, null);
     }
-    
+
     /**
      * Create a JNLPFile from a URL and a version, checking for updates
      * using the specified policy.
@@ -284,7 +286,7 @@
     }
 
     /**
-     * Returns the JNLP file's title.  This method returns the same
+     * Returns the JNLP file's best localized title. This method returns the same
      * value as InformationDesc.getTitle().
      */
     public String getTitle() {
@@ -292,6 +294,14 @@
     }
 
     /**
+     * Returns the JNLP file's best localized vendor. This method returns the same
+     * value as InformationDesc.getVendor().
+     */
+    public String getVendor() {
+        return getInformation().getVendor();
+    }
+
+    /**
      * Returns the JNLP file's network location as specified in the
      * JNLP file.
      */
@@ -349,17 +359,52 @@
      */
     public InformationDesc getInformation(final Locale locale) {
         return new InformationDesc(this, new Locale[] { locale }) {
+            @Override
             protected List<Object> getItems(Object key) {
                 List<Object> result = new ArrayList<Object>();
 
-                for (int i = 0; i < info.size(); i++) {
-                    InformationDesc infoDesc = info.get(i);
+                for (Match precision : Match.values()) {
+                    for (InformationDesc infoDesc : JNLPFile.this.info) {
+                        if (localeMatches(locale, infoDesc.getLocales(), precision)) {
+                            result.addAll(infoDesc.getItems(key));
+                        }
+                    }
 
-                    if (localMatches(locale, infoDesc.getLocales()))
-                        result.addAll(infoDesc.getItems(key));
+                    if (result.size() > 0) {
+                        return result;
+                    }
+                }
+                return result;
+            }
+
+            @Override
+            public String getTitle() {
+                for (Match precision : Match.values()) {
+                    for (InformationDesc infoDesc : JNLPFile.this.info) {
+                        String title = infoDesc.getTitle();
+                        if (localeMatches(locale, infoDesc.getLocales(), precision)
+                                && title != null && !"".equals(title)) {
+                            return title;
+                        }
+                    }
                 }
 
-                return result;
+                return null;
+            }
+
+            @Override
+            public String getVendor() {
+                for (Match precision : Match.values()) {
+                    for (InformationDesc infoDesc : JNLPFile.this.info) {
+                        String vendor = infoDesc.getVendor();
+                        if (localeMatches(locale, infoDesc.getLocales(), precision)
+                                && vendor != null && !"".equals(vendor)) {
+                            return vendor;
+                        }
+                    }
+                }
+
+                return null;
             }
         };
     }
@@ -393,11 +438,17 @@
      */
     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 }) {
+
+            @Override
             public <T> List<T> getResources(Class<T> launchType) {
                 List<T> result = new ArrayList<T>();
 
                 for (ResourcesDesc rescDesc : resources) {
-                    if (localMatches(locale, rescDesc.getLocales())
+                    boolean hasUsableLocale = false;
+                    for (Match match : Match.values()) {
+                        hasUsableLocale |= localeMatches(locale, rescDesc.getLocales(), match);
+                    }
+                    if (hasUsableLocale
                             && stringMatches(os, rescDesc.getOS())
                             && stringMatches(arch, rescDesc.getArch()))
                         result.addAll(rescDesc.getResources(launchType));
@@ -408,6 +459,7 @@
                 return result;
             }
 
+            @Override
             public void addResource(Object resource) {
                 // todo: honor the current locale, os, arch values
                 sharedResources.addResource(resource);
@@ -433,7 +485,11 @@
     public ResourcesDesc[] getResourcesDescs(final Locale locale, final String os, final String arch) {
         List<ResourcesDesc> matchingResources = new ArrayList<ResourcesDesc>();
         for (ResourcesDesc rescDesc: resources) {
-            if (localMatches(locale, rescDesc.getLocales())
+            boolean hasUsableLocale = false;
+            for (Match match : Match.values()) {
+                hasUsableLocale |= localeMatches(locale, rescDesc.getLocales(), match);
+            }
+            if (hasUsableLocale
                     && stringMatches(os, rescDesc.getOS())
                     && stringMatches(arch, rescDesc.getArch())) {
                 matchingResources.add(rescDesc);
@@ -546,28 +602,48 @@
      *
      * @param requested the local
      * @param available the available locales
+     * @param precision the depth with which to match locales. 1 checks only
+     * language, 2 checks language and country, 3 checks language, country and
+     * variant for matches. Passing 0 will always return true.
      * @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;
+    public boolean localeMatches(Locale requested, Locale available[], Match matchLevel) {
 
-        for (int i = 0; i < available.length; i++) {
-            String language = requested.getLanguage(); // "" but never null
-            String country = requested.getCountry();
-            String variant = requested.getVariant();
+        if (matchLevel == Match.GENERALIZED)
+            return available == null || available.length == 0;
+
+        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;
+        for (Locale locale : available) {
+            switch (matchLevel) {
+                case LANG:
+                    if (!language.isEmpty()
+                            && language.equals(locale.getLanguage())
+                            && locale.getCountry().isEmpty()
+                            && locale.getVariant().isEmpty())
+                        return true;
+                    break;
+                case LANG_COUNTRY:
+                    if (!language.isEmpty()
+                            && language.equals(locale.getLanguage())
+                            && !country.isEmpty()
+                            && country.equals(locale.getCountry())
+                            && locale.getVariant().isEmpty())
+                        return true;
+                    break;
+                case LANG_COUNTRY_VARIANT:
+                    if (language.equals(locale.getLanguage())
+                            && country.equals(locale.getCountry())
+                            && variant.equals(locale.getVariant()))
+                        return true;
+                    break;
+                default:
+                    break;
+            }
         }
-
         return false;
     }
 
@@ -612,14 +688,15 @@
             codeBase = parser.getCodeBase();
             sourceLocation = parser.getFileLocation() != null ? parser.getFileLocation() : location;
             info = parser.getInfo(root);
+            parser.checkForInformation();
             update = parser.getUpdate(root);
             resources = parser.getResources(root, false); // false == not a j2se/java resources section
             launchType = parser.getLauncher(root);
             component = parser.getComponent(root);
             security = parser.getSecurity(root);
-            
+
             checkForSpecialProperties();
-            
+
         } catch (ParseException ex) {
             throw ex;
         } catch (Exception ex) {
@@ -729,7 +806,7 @@
     /**
      * Returns a boolean after determining if a signed JNLP warning should be
      * displayed in the 'More Information' panel.
-     * 
+     *
      * @return true if a warning should be displayed; otherwise false
      */
     public boolean requiresSignedJNLPWarning() {
@@ -742,5 +819,4 @@
     public void setSignedJNLPAsMissing() {
         missingSignedJNLP = true;
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/MissingInformationException.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,44 @@
+// Copyright (C) 2012 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 static net.sourceforge.jnlp.runtime.Translator.R;
+
+/**
+ * Thrown when the required information tag is not found
+ * under the current JVM's locale or as a generalized element.
+ */
+public class MissingInformationException extends RequiredElementException {
+
+    private static final long serialVersionUID = 1L;
+    private static final String message = R("PNoInfoElement");
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String)
+     */
+    public MissingInformationException() {
+        super(message);
+    }
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String, Throwable)
+     */
+    public MissingInformationException(Throwable cause) {
+        super(message, cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/MissingTitleException.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,45 @@
+// Copyright (C) 2012 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 static net.sourceforge.jnlp.runtime.Translator.R;
+
+/**
+ * Thrown when a title that is required from the information tag is not found
+ * under the current JVM's locale or as a generalized element.
+ */
+public class MissingTitleException extends RequiredElementException {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String message = R("PMissingElement", R("PMissingTitle"));
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String)
+     */
+    public MissingTitleException() {
+        super(message);
+    }
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String, Throwable)
+     */
+    public MissingTitleException(Throwable cause) {
+        super(message, cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/MissingVendorException.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,44 @@
+// Copyright (C) 2012 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 static net.sourceforge.jnlp.runtime.Translator.R;
+
+/**
+ * Thrown when a vendor that is required from the information tag is not found
+ * under the current JVM's locale or as a generalized element.
+ */
+public class MissingVendorException extends RequiredElementException {
+
+    private static final long serialVersionUID = 1L;
+    private static final String message = R("PMissingElement", R("PMissingVendor"));
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String)
+     */
+    public MissingVendorException() {
+        super(message);
+    }
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String, Throwable)
+     */
+    public MissingVendorException(Throwable cause) {
+        super(message, cause);
+    }
+}
--- a/netx/net/sourceforge/jnlp/Parser.java	Tue Aug 21 12:02:00 2012 +0200
+++ b/netx/net/sourceforge/jnlp/Parser.java	Wed Aug 08 11:48:06 2012 -0400
@@ -1,5 +1,5 @@
 // Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-// Copyright (C) 2009 Red Hat, Inc.
+// Copyright (C) 2012 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
@@ -28,6 +28,7 @@
 //import gd.xml.tiny.*;
 import net.sourceforge.jnlp.UpdateDesc.Check;
 import net.sourceforge.jnlp.UpdateDesc.Policy;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
 import net.sourceforge.nanoxml.*;
 
 /**
@@ -425,6 +426,35 @@
     //
 
     /**
+     * Make sure a title and vendor are present and nonempty and localized as
+     * best matching as possible for the JVM's current locale. Fallback to a
+     * generalized title and vendor otherwise. If none is found, throw an exception.
+     *
+     * Additionally prints homepage, description, title and vendor to stdout
+     * if in Debug mode.
+     * @throws RequiredElementException
+     */
+    void checkForInformation() throws RequiredElementException {
+        if (JNLPRuntime.isDebug()) {
+            System.out.println("Homepage: " + file.getInformation().getHomepage());
+            System.out.println("Description: " + file.getInformation().getDescription());
+        }
+
+        String title = file.getTitle();
+        String vendor = file.getVendor();
+
+        if (title == null || title.trim().isEmpty())
+            throw new MissingTitleException();
+        else if (JNLPRuntime.isDebug())
+            System.out.println("Acceptable title tag found, contains: " + title);
+
+        if (vendor == null || vendor.trim().isEmpty())
+            throw new MissingVendorException();
+        else if (JNLPRuntime.isDebug())
+            System.out.println("Acceptable vendor tag found, contains: " + vendor);
+    }
+
+    /**
      * Returns all of the information elements under the specified
      * node.
      *
@@ -438,11 +468,12 @@
 
         // ensure that there are at least one information section present
         if (info.length == 0)
-            throw new ParseException(R("PNoInfoElement"));
+            throw new MissingInformationException();
 
         // create objects from the info sections
-        for (int i = 0; i < info.length; i++)
-            result.add(getInformationDesc(info[i]));
+        for (Node infoNode : info) {
+            result.add(getInformationDesc(infoNode));
+        }
 
         return result;
     }
@@ -504,11 +535,6 @@
             child = child.getNextSibling();
         }
 
-        if (info.getTitle() == null || info.getTitle().trim().isEmpty())
-            throw new ParseException(R("PNoTitleElement"));
-        if (info.getVendor() == null || info.getVendor().trim().isEmpty())
-            throw new ParseException(R("PNoVendorElement"));
-
         return info;
     }
 
@@ -896,7 +922,7 @@
 
         String language = localeStr.substring(0, 2);
         String country = (localeStr.length() < 5) ? "" : localeStr.substring(3, 5);
-        String variant = (localeStr.length() < 7) ? "" : localeStr.substring(6, 8);
+        String variant = (localeStr.length() > 7) ? localeStr.substring(6) : "";
 
         // null is not allowed n locale but "" is
         return new Locale(language, country, variant);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/RequiredElementException.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,41 @@
+// Copyright (C) 2012 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;
+
+/**
+ * Thrown when a field that is required from the information tag is not found
+ * under the current JVM's locale or as a generalized element.
+ */
+public class RequiredElementException extends ParseException {
+
+    private static final long serialVersionUID = 1L;
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String)
+     */
+    public RequiredElementException(String message) {
+        super(message);
+    }
+
+    /* (non-Javadoc)
+     * @see net.sourceforge.jnlp.ParseException(String, Throwable)
+     */
+    public RequiredElementException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties	Tue Aug 21 12:02:00 2012 +0200
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties	Wed Aug 08 11:48:06 2012 -0400
@@ -106,9 +106,10 @@
 PInnerJ2SE=j2se element cannot be specified within a j2se element.
 PTwoMains=Duplicate main JAR defined in a resources element (there can be only one)
 PNativeHasMain=Cannot specify main attribute on native JARs.
-PNoInfoElement=No information section defined
-PNoTitleElement=The title section has not been defined in the JNLP file.
-PNoVendorElement=The vendor section has not been defined in the JNLP file.
+PNoInfoElement=No information section defined.
+PMissingTitle=title
+PMissingVendor=vendor
+PMissingElement=The {0} section has not been defined for your locale nor does a default value exist in the JNLP file.
 PTwoDescriptions=Duplicate description of kind {0}
 PSharing=Element "sharing-allowed" is illegal in a standard JNLP file
 PTwoSecurity=Only one security element allowed per JNLPFile.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/net/sourceforge/jnlp/JNLPFileTest.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,106 @@
+/* JNLPFileTest.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+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, version 2.
+
+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.Locale;
+
+import net.sourceforge.jnlp.JNLPFile.Match;
+import net.sourceforge.jnlp.mock.MockJNLPFile;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class JNLPFileTest {
+    Locale jvmLocale = new Locale("en", "CA", "utf8");
+    MockJNLPFile file = new MockJNLPFile(jvmLocale);
+
+    @Test
+    public void testCompareAll() {
+        Locale[] correctAvailable = { new Locale("en", "CA", "utf8") };
+        Assert.assertTrue("Entire locale should match but did not.",
+                file.localeMatches(jvmLocale, correctAvailable, Match.LANG_COUNTRY_VARIANT));
+
+        Locale[] mismatchedAvailable = { new Locale("en", "CA", "utf16") };
+        Assert.assertFalse("Should not match variant but did.",
+                file.localeMatches(jvmLocale, mismatchedAvailable, Match.LANG_COUNTRY_VARIANT));
+    }
+
+    @Test
+    public void testLangAndCountry() {
+        Locale[] correctAvailable = { new Locale("en", "CA") };
+        Assert.assertTrue("Should match language and country, ignoring variant but did not.",
+                file.localeMatches(jvmLocale, correctAvailable, Match.LANG_COUNTRY));
+
+        Locale[] mismatchedAvailable = { new Locale("en", "EN") };
+        Assert.assertFalse("Should not match country but did.",
+                file.localeMatches(jvmLocale, mismatchedAvailable, Match.LANG_COUNTRY));
+
+        Locale[] extraMismatched = { new Locale("en", "CA", "utf16") };
+        Assert.assertFalse("Should not match because of extra variant but did.",
+                file.localeMatches(jvmLocale, extraMismatched, Match.LANG_COUNTRY));
+    }
+
+    @Test
+    public void testLangOnly() {
+        Locale[] correctAvailable = { new Locale("en") };
+        Assert.assertTrue("Should match only language but did not.",
+                file.localeMatches(jvmLocale, correctAvailable, Match.LANG));
+
+        Locale[] mismatchedAvailable = { new Locale("fr", "CA", "utf8") };
+        Assert.assertFalse("Should not match language but did.",
+                file.localeMatches(jvmLocale, mismatchedAvailable, Match.LANG));
+
+        Locale[] extraMismatched = { new Locale("en", "EN") };
+        Assert.assertFalse("Should not match because of extra country but did.",
+                file.localeMatches(jvmLocale, extraMismatched, Match.LANG));
+    }
+
+    @Test
+    public void testNoLocalAvailable() {
+        Assert.assertTrue("Null locales should match but did not.",
+                file.localeMatches(jvmLocale, null, Match.GENERALIZED));
+
+        Locale[] emptyAvailable = {};
+        Assert.assertTrue("Empty locales list should match but did not.",
+                file.localeMatches(jvmLocale, emptyAvailable, Match.GENERALIZED));
+
+        Locale[] mismatchAvailable = { new Locale("fr", "FR", "utf16") };
+        Assert.assertFalse("Locales list should not match generalized case but did.",
+                file.localeMatches(jvmLocale, mismatchAvailable, Match.GENERALIZED));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/netx/unit/net/sourceforge/jnlp/ParserTest.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,1358 @@
+/* ParserTest.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+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, version 2.
+
+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.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import net.sourceforge.jnlp.mock.MockJNLPFile;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/** Test various corner cases of the parser */
+public class ParserTest {
+
+    private static final String LANG = "en";
+    private static final String COUNTRY = "CA";
+    private static final String VARIANT = "utf8";
+    private static final Locale LANG_LOCALE = new Locale(LANG);
+    private static final Locale LANG_COUNTRY_LOCALE = new Locale(LANG, COUNTRY);
+    private static final Locale ALL_LOCALE = new Locale(LANG, COUNTRY, VARIANT);
+
+    @Test(expected = MissingInformationException.class)
+    public void testMissingInfoFullLocale() throws ParseException {
+        String data = "<jnlp></jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        parser.getInfo(root);
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testEmptyLocalizedInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test
+    public void testOneFullyLocalizedInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>English_CA_utf8_T</title>\n"
+                + "    <vendor>English_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+
+        Assert.assertEquals("Title should be `English_CA_utf8_T' but wasn't",
+                "English_CA_utf8_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_utf8_V' but wasn't",
+                "English_CA_utf8_V", file.getVendor());
+    }
+
+    @Test
+    public void testOneLangCountryLocalizedInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_CA_T' but wasn't",
+                "English_CA_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test
+    public void testOneLangLocalizedInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testGeneralizedInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `Generalized_T' but wasn't",
+                "Generalized_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoDifferentLocalizedInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <title>French_T</title>\n"
+                + "    <vendor>French_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoLocalizedWithSameLangInfoFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_CA_T' but wasn't",
+                "English_CA_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoSameLangOneMissingTitleFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoSameLangWithGeneralizedTitleFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `Generalized_T' but wasn't",
+                "Generalized_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testMissingTitleFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testMissingVendorFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testMissingLocalizedTitleFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testMissingLocalizedVendorFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly two info descs should be found",infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testEmptyLocalizedTitleFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor>English_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testEmptyLocalizedVendorFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>English_CA_utf8_T</title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test
+    public void testFallbackEmptyLocalizedTitleVendorFullLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>French_CA_utf8_T</title>\n"
+                + "    <vendor>French_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(ALL_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+
+        Assert.assertTrue("Exactly five info descs should be found", infoDescs.size() == 5);
+
+        file.setInfo(infoDescs);
+
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingInformationException.class)
+    public void testMissingInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp></jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        parser.getInfo(root);
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testEmptyLocalizedInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testOneFullyLocalizedInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>English_CA_utf8_T</title>\n"
+                + "    <vendor>English_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test
+    public void testOneLangCountryLocalizedInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_CA_T' but wasn't",
+                "English_CA_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test
+    public void testOneLangLocalizedInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testGeneralizedInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `Generalized_T' but wasn't",
+                "Generalized_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoDifferentLocalizedInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <title>French_T</title>\n"
+                + "    <vendor>French_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoLocalizedWithSameLangInfoLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_CA_T' but wasn't",
+                "English_CA_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoSameLangOneMissingTitleLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoSameLangWithGeneralizedTitleLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `Generalized_T' but wasn't",
+                "Generalized_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_CA_V' but wasn't",
+                "English_CA_V", file.getVendor());
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testMissingTitleLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testMissingVendorLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testMissingLocalizedTitleLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testMissingLocalizedVendorLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly two info descs should be found",infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testEmptyLocalizedTitleLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor>English_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testEmptyLocalizedVendorLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_utf8_T</title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test
+    public void testFallbackEmptyLocalizedTitleVendorLangCountryLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>French_CA_utf8_T</title>\n"
+                + "    <vendor>French_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_COUNTRY_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+
+        Assert.assertTrue("Exactly five info descs should be found", infoDescs.size() == 5);
+
+        file.setInfo(infoDescs);
+
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingInformationException.class)
+    public void testMissingInfoLangLocale() throws ParseException {
+        String data = "<jnlp></jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        parser.getInfo(root);
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testEmptyLocalizedInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testOneFullyLocalizedInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>English_CA_utf8_T</title>\n"
+                + "    <vendor>English_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testOneLangCountryLocalizedInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test
+    public void testOneLangLocalizedInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testGeneralizedInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `Generalized_T' but wasn't",
+                "Generalized_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoDifferentLocalizedInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <title>French_T</title>\n"
+                + "    <vendor>French_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoLocalizedWithSameLangInfoLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `English_V' but wasn't",
+                "English_V", file.getVendor());
+    }
+
+    @Test
+    public void testTwoSameLangOneMissingTitleLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testMissingTitleLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <vendor>English_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testMissingVendorLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly three info descs should be found", infoDescs.size() == 3);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testMissingLocalizedTitleLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <vendor>English_CA_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly two info descs should be found", infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testMissingLocalizedVendorLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "  </information>\n"
+                + "  <information locale='fr'>\n"
+                + "    <title>English_CA_T</title>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly two info descs should be found",infoDescs.size() == 2);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingTitleException.class)
+    public void testEmptyLocalizedTitleLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor>English_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test(expected = MissingVendorException.class)
+    public void testEmptyLocalizedVendorLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_CA_utf8_T</title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = new ArrayList<InformationDesc>();
+        infoDescs.addAll(parser.getInfo(root));
+
+        Assert.assertTrue("Exactly one info desc should be found", infoDescs.size() == 1);
+
+        file.setInfo(infoDescs);
+        parser.checkForInformation();
+    }
+
+    @Test
+    public void testFallbackEmptyLocalizedTitleVendorLangLocale() throws ParseException {
+        String data = "<jnlp>\n"
+                + "  <information>\n"
+                + "    <title>Generalized_T</title>\n"
+                + "    <vendor>Generalized_V</vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "'>\n"
+                + "    <title>English_T</title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='" + LANG + "_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title></title>\n"
+                + "    <vendor></vendor>\n"
+                + "  </information>\n"
+                + "  <information locale='fr_" + COUNTRY +  "." + VARIANT + "'>\n"
+                + "    <title>French_CA_utf8_T</title>\n"
+                + "    <vendor>French_CA_utf8_V</vendor>\n"
+                + "  </information>\n"
+                + "</jnlp>\n";
+
+        Node root = Parser.getRootNode(new ByteArrayInputStream(data.getBytes()));
+        Assert.assertEquals("Root name is not jnlp", "jnlp", root.getNodeName());
+
+        MockJNLPFile file = new MockJNLPFile(LANG_LOCALE);
+        Parser parser = new Parser(file, null, root, false, false);
+        List<InformationDesc> infoDescs = parser.getInfo(root);
+
+        Assert.assertTrue("Exactly five info descs should be found", infoDescs.size() == 5);
+
+        file.setInfo(infoDescs);
+
+        Assert.assertEquals("Title should be `English_T' but wasn't",
+                "English_T", file.getTitle());
+        Assert.assertEquals("Vendor should be `Generalized_V' but wasn't",
+                "Generalized_V", file.getVendor());
+
+        parser.checkForInformation();
+    }
+}
--- a/tests/reproducers/simple/InformationTitleVendorParser/testcases/InformationTitleVendorParserTest.java	Tue Aug 21 12:02:00 2012 +0200
+++ b/tests/reproducers/simple/InformationTitleVendorParser/testcases/InformationTitleVendorParserTest.java	Wed Aug 08 11:48:06 2012 -0400
@@ -36,6 +36,9 @@
  */
 
 
+import java.util.Arrays;
+import java.util.List;
+
 import net.sourceforge.jnlp.ServerAccess;
 import org.junit.Assert;
 import org.junit.Test;
@@ -44,34 +47,33 @@
 
     private static ServerAccess server = new ServerAccess();
 
-    public void runTest(String jnlpName, String exceptionMessage) throws Exception {
-        ServerAccess.ProcessResult pr=server.executeJavawsHeadless(null,"/" + jnlpName + ".jnlp");
+    public void runTest(String jnlpName, String exception) throws Exception {
+        List<String> verbosed = Arrays.asList(new String[] { "-verbose" });
+        ServerAccess.ProcessResult pr=server.executeJavawsHeadless(verbosed, "/" + jnlpName + ".jnlp");
         String s1 = "Good simple javaws exapmle";
         Assert.assertFalse("test" + jnlpName + " stdout should not contain " + s1 + " but did.", pr.stdout.contains(s1));
-        // Looking for "Could not read or parse the JNLP file. (${DESCRIPTION})"
-        String s2 = "(?s).*Could not read or parse the JNLP file.{0,5}" + exceptionMessage + "(?s).*";
-        Assert.assertTrue("testForTitle stderr should match " + s2 + " but did not.", pr.stderr.matches(s2));
+        Assert.assertTrue("testForTitle stderr should contain " + exception + " but did not.", pr.stderr.contains(exception));
         Assert.assertFalse(pr.wasTerminated);
         Assert.assertEquals((Integer)0, pr.returnValue);
     }
 
     @Test
     public void testInformationeParser() throws Exception {
-        runTest("InformationParser", "No information section defined");
+        runTest("InformationParser", "net.sourceforge.jnlp.MissingInformationException");
     }
 
     @Test
     public void testTitleParser() throws Exception {
-        runTest("TitleParser", "The title section has not been defined in the JNLP file.");
+        runTest("TitleParser", "net.sourceforge.jnlp.MissingTitleException");
     }
     @Test
     public void testVendorParser() throws Exception {
-        runTest("VendorParser", "The vendor section has not been defined in the JNLP file.");
+        runTest("VendorParser", "net.sourceforge.jnlp.MissingVendorException");
     }
 
     @Test
     public void testTitleVendorParser() throws Exception {
         // Note that the title message missing causes an immediate exception, regardless of Vendor.
-        runTest("TitleVendorParser", "The title section has not been defined in the JNLP file.");
+        runTest("TitleVendorParser", "net.sourceforge.jnlp.MissingTitleException");
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extensions/net/sourceforge/jnlp/mock/MockJNLPFile.java	Wed Aug 08 11:48:06 2012 -0400
@@ -0,0 +1,55 @@
+/* MockJNLPFile.java
+   Copyright (C) 2012 Red Hat, Inc.
+
+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, version 2.
+
+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.mock;
+
+import java.util.List;
+import java.util.Locale;
+
+import net.sourceforge.jnlp.InformationDesc;
+import net.sourceforge.jnlp.JNLPFile;
+
+public class MockJNLPFile extends JNLPFile {
+
+    public MockJNLPFile(Locale locale) {
+        defaultLocale = locale;
+    }
+
+    public void setInfo(List<InformationDesc> infoList) {
+        info = infoList;
+    }
+}