Mercurial > hg > release > icedtea6-1.6
changeset 1650:e315bc774531
Convert DOS line endings to UNIX.
2009-08-04 Andrew John Hughes <ahughes@redhat.com>
* rt/net/sourceforge/jnlp/DefaultLaunchHandler.java,
* rt/net/sourceforge/jnlp/ExtensionDesc.java,
* rt/net/sourceforge/jnlp/JNLPFile.java,
* rt/net/sourceforge/jnlp/LaunchException.java,
* rt/net/sourceforge/jnlp/LaunchHandler.java,
* rt/net/sourceforge/jnlp/Launcher.java,
* rt/net/sourceforge/jnlp/ParseException.java,
* rt/net/sourceforge/jnlp/Parser.java,
* rt/net/sourceforge/jnlp/cache/CacheEntry.java,
* rt/net/sourceforge/jnlp/cache/CacheUtil.java,
* rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java,
* rt/net/sourceforge/jnlp/cache/ResourceTracker.java,
* rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java,
* rt/net/sourceforge/jnlp/runtime/AppletInstance.java,
* rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java,
* rt/net/sourceforge/jnlp/runtime/Boot.java,
* rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java,
* rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java,
* rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java,
* rt/net/sourceforge/jnlp/services/XBasicService.java,
* rt/net/sourceforge/jnlp/util/PropertiesFile.java,
* rt/net/sourceforge/jnlp/util/Reflect.java,
* rt/net/sourceforge/jnlp/util/WeakList.java,
* test/jtreg/com/sun/javatest/exec/FileTable.java,
* test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java,
* test/jtreg/com/sun/javatest/report/XMLReportMaker.java:
Convert DOS line endings to UNIX.
line wrap: on
line diff
--- a/ChangeLog Tue Aug 04 11:34:49 2009 -0400 +++ b/ChangeLog Tue Aug 04 17:39:11 2009 +0100 @@ -1,3 +1,33 @@ +2009-08-04 Andrew John Hughes <ahughes@redhat.com> + + * rt/net/sourceforge/jnlp/DefaultLaunchHandler.java, + * rt/net/sourceforge/jnlp/ExtensionDesc.java, + * rt/net/sourceforge/jnlp/JNLPFile.java, + * rt/net/sourceforge/jnlp/LaunchException.java, + * rt/net/sourceforge/jnlp/LaunchHandler.java, + * rt/net/sourceforge/jnlp/Launcher.java, + * rt/net/sourceforge/jnlp/ParseException.java, + * rt/net/sourceforge/jnlp/Parser.java, + * rt/net/sourceforge/jnlp/cache/CacheEntry.java, + * rt/net/sourceforge/jnlp/cache/CacheUtil.java, + * rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java, + * rt/net/sourceforge/jnlp/cache/ResourceTracker.java, + * rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java, + * rt/net/sourceforge/jnlp/runtime/AppletInstance.java, + * rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java, + * rt/net/sourceforge/jnlp/runtime/Boot.java, + * rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java, + * rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java, + * rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java, + * rt/net/sourceforge/jnlp/services/XBasicService.java, + * rt/net/sourceforge/jnlp/util/PropertiesFile.java, + * rt/net/sourceforge/jnlp/util/Reflect.java, + * rt/net/sourceforge/jnlp/util/WeakList.java, + * test/jtreg/com/sun/javatest/exec/FileTable.java, + * test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java, + * test/jtreg/com/sun/javatest/report/XMLReportMaker.java: + Convert DOS line endings to UNIX. + 2009-08-04 Omair Majid <omajid@redhat.com> * rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java
--- a/rt/net/sourceforge/jnlp/DefaultLaunchHandler.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/DefaultLaunchHandler.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,114 +1,114 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -package net.sourceforge.jnlp; - -import net.sourceforge.jnlp.runtime.*; -import net.sourceforge.jnlp.util.*; - -import java.awt.*; -import java.util.*; -import javax.swing.*; - - -/** - * This default implementation shows prints the exception to - * stdout and if not in headless mode displays the exception in a - * dialog. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.1 $ - */ -public class DefaultLaunchHandler implements LaunchHandler { - - /** - * Called when the application could not be launched due to a - * fatal error, such as the inability to find the main class - * or non-parseable XML. - */ - public void launchError(LaunchException exception) { - printMessage(exception); - } - - /** - * Called when launching the application can not be launched - * due to an error that is not fatal. For example a JNLP file - * that is not strictly correct yet does not necessarily - * prohibit the system from attempting to launch the - * application. - * - * @return true if the launch should continue, false to abort - */ - public boolean launchWarning(LaunchException warning) { - printMessage(warning); - return true; - } - - /** - * Called when a security validation error occurs while - * launching the application. - * - * @return true to allow the application to continue, false to stop it. - */ - public boolean validationError(LaunchException security) { - printMessage(security); - return true; - } - - /** - * Called when an application, applet, or installer has been - * launched successfully (the main method or applet start method - * returned normally). - * - * @param application the launched application instance - */ - public void launchCompleted(ApplicationInstance application) { - // - } - - /** - * Print a message to stdout. - */ - protected void printMessage(LaunchException ex) { - StringBuffer result = new StringBuffer(); - result.append("netx: "); - result.append(ex.getCategory()); - if (ex.getSummary() != null) { - result.append(": "); - result.append(ex.getSummary()); - } - - if (JNLPRuntime.isDebug()) { - if (ex.getCause() != null) - ex.getCause().printStackTrace(); - else - ex.printStackTrace(); - } - - Throwable causes[] = ex.getCauses(); - - for (int i=0; i < causes.length; i++) { - result.append(" ("); - result.append(causes[i].getClass().getName()); - result.append(" "); - result.append(causes[i].getMessage()); - result.append(")"); - } - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp; + +import net.sourceforge.jnlp.runtime.*; +import net.sourceforge.jnlp.util.*; + +import java.awt.*; +import java.util.*; +import javax.swing.*; + + +/** + * This default implementation shows prints the exception to + * stdout and if not in headless mode displays the exception in a + * dialog. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.1 $ + */ +public class DefaultLaunchHandler implements LaunchHandler { + + /** + * Called when the application could not be launched due to a + * fatal error, such as the inability to find the main class + * or non-parseable XML. + */ + public void launchError(LaunchException exception) { + printMessage(exception); + } + + /** + * Called when launching the application can not be launched + * due to an error that is not fatal. For example a JNLP file + * that is not strictly correct yet does not necessarily + * prohibit the system from attempting to launch the + * application. + * + * @return true if the launch should continue, false to abort + */ + public boolean launchWarning(LaunchException warning) { + printMessage(warning); + return true; + } + + /** + * Called when a security validation error occurs while + * launching the application. + * + * @return true to allow the application to continue, false to stop it. + */ + public boolean validationError(LaunchException security) { + printMessage(security); + return true; + } + + /** + * Called when an application, applet, or installer has been + * launched successfully (the main method or applet start method + * returned normally). + * + * @param application the launched application instance + */ + public void launchCompleted(ApplicationInstance application) { + // + } + + /** + * Print a message to stdout. + */ + protected void printMessage(LaunchException ex) { + StringBuffer result = new StringBuffer(); + result.append("netx: "); + result.append(ex.getCategory()); + if (ex.getSummary() != null) { + result.append(": "); + result.append(ex.getSummary()); + } + + if (JNLPRuntime.isDebug()) { + if (ex.getCause() != null) + ex.getCause().printStackTrace(); + else + ex.printStackTrace(); + } + + Throwable causes[] = ex.getCauses(); + + for (int i=0; i < causes.length; i++) { + result.append(" ("); + result.append(causes[i].getClass().getName()); + result.append(" "); + result.append(causes[i].getMessage()); + result.append(")"); + } + } + +} + +
--- a/rt/net/sourceforge/jnlp/ExtensionDesc.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/ExtensionDesc.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,145 +1,145 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp; - -import java.io.*; -import java.net.*; -import java.util.*; - -import net.sourceforge.jnlp.runtime.JNLPRuntime; - - -/** - * The extension element. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.8 $ - */ -public class ExtensionDesc { - - /** the extension name */ - private String name; - - /** the required extension version */ - private Version version; - - /** the location of the extension JNLP file */ - private URL location; - - /** the JNLPFile the extension refers to */ - private JNLPFile file; - - /** map from ext-part to local part */ - private Map extToPart = new HashMap(); - - /** eager ext parts */ - private List eagerExtParts = new ArrayList(); - - - /** - * Create an extention descriptor. - * - * @param name the extension name - * @param version the required version of the extention JNLPFile - * @param location the location of the extention JNLP file - */ - public ExtensionDesc(String name, Version version, URL location) { - this.name = name; - this.version = version; - this.location = location; - } - - /** - * Adds an extension part to be downloaded when the specified - * part of the main JNLP file is loaded. The extension part - * will be downloaded before the application is launched if the - * lazy value is false or the part is empty or null. - * - * @param extPart the part name in the extension file - * @param part the part name in the main file - * @param lazy whether to load the part before launching - */ - protected void addPart(String extPart, String part, boolean lazy) { - extToPart.put(extPart, part); - - if (!lazy || part == null || part.length() == 0) - eagerExtParts.add(extPart); - } - - /** - * Returns the parts in the extension JNLP file mapped to the - * part of the main file. - */ - public String[] getExtensionParts(String thisPart) { - - return null; - } - - /** - * Returns the name of the extension. - */ - public String getName() { - return name; - } - - /** - * Returns the required version of the extension JNLP file. - */ - public Version getVersion() { - return version; - } - - /** - * Returns the location of the extension JNLP file. - */ - public URL getLocation() { - return location; - } - - /** - * Resolves the extension by creating a JNLPFile from the file - * specified by the extension's location property. - * - * @throws IOException if the extension JNLPFile could not be resolved. - * @throws ParseException if the extension JNLPFile could not be - * parsed or was not a component or installer descriptor. - */ - public void resolve() throws ParseException, IOException { - if (file == null) { - file = new JNLPFile(location); - - if (JNLPRuntime.isDebug()) - System.out.println("Resolve: "+file.getInformation().getTitle()); - - // check for it being an extension descriptor - if (!file.isComponent() && !file.isInstaller()) - throw new ParseException(JNLPRuntime.getMessage("JInvalidExtensionDescriptor", new Object[] {name, location} )); - } - - } - - /** - * Returns a JNLPFile for the extension, or null if the JNLP - * file has not been resolved. - */ - public JNLPFile getJNLPFile() { - return file; - } -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp; + +import java.io.*; +import java.net.*; +import java.util.*; + +import net.sourceforge.jnlp.runtime.JNLPRuntime; + + +/** + * The extension element. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.8 $ + */ +public class ExtensionDesc { + + /** the extension name */ + private String name; + + /** the required extension version */ + private Version version; + + /** the location of the extension JNLP file */ + private URL location; + + /** the JNLPFile the extension refers to */ + private JNLPFile file; + + /** map from ext-part to local part */ + private Map extToPart = new HashMap(); + + /** eager ext parts */ + private List eagerExtParts = new ArrayList(); + + + /** + * Create an extention descriptor. + * + * @param name the extension name + * @param version the required version of the extention JNLPFile + * @param location the location of the extention JNLP file + */ + public ExtensionDesc(String name, Version version, URL location) { + this.name = name; + this.version = version; + this.location = location; + } + + /** + * Adds an extension part to be downloaded when the specified + * part of the main JNLP file is loaded. The extension part + * will be downloaded before the application is launched if the + * lazy value is false or the part is empty or null. + * + * @param extPart the part name in the extension file + * @param part the part name in the main file + * @param lazy whether to load the part before launching + */ + protected void addPart(String extPart, String part, boolean lazy) { + extToPart.put(extPart, part); + + if (!lazy || part == null || part.length() == 0) + eagerExtParts.add(extPart); + } + + /** + * Returns the parts in the extension JNLP file mapped to the + * part of the main file. + */ + public String[] getExtensionParts(String thisPart) { + + return null; + } + + /** + * Returns the name of the extension. + */ + public String getName() { + return name; + } + + /** + * Returns the required version of the extension JNLP file. + */ + public Version getVersion() { + return version; + } + + /** + * Returns the location of the extension JNLP file. + */ + public URL getLocation() { + return location; + } + + /** + * Resolves the extension by creating a JNLPFile from the file + * specified by the extension's location property. + * + * @throws IOException if the extension JNLPFile could not be resolved. + * @throws ParseException if the extension JNLPFile could not be + * parsed or was not a component or installer descriptor. + */ + public void resolve() throws ParseException, IOException { + if (file == null) { + file = new JNLPFile(location); + + if (JNLPRuntime.isDebug()) + System.out.println("Resolve: "+file.getInformation().getTitle()); + + // check for it being an extension descriptor + if (!file.isComponent() && !file.isInstaller()) + throw new ParseException(JNLPRuntime.getMessage("JInvalidExtensionDescriptor", new Object[] {name, location} )); + } + + } + + /** + * Returns a JNLPFile for the extension, or null if the JNLP + * file has not been resolved. + */ + public JNLPFile getJNLPFile() { + return file; + } +} + +
--- a/rt/net/sourceforge/jnlp/JNLPFile.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/JNLPFile.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,578 +1,578 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -import net.sourceforge.jnlp.cache.ResourceTracker; -import net.sourceforge.jnlp.cache.UpdatePolicy; -import net.sourceforge.jnlp.runtime.JNLPRuntime; - -/** - * Provides methods to access the information in a Java Network - * Launching Protocol (JNLP) file. The Java Network Launching - * Protocol specifies in an XML file the information needed to - * load, cache, and run Java code over the network and in a secure - * environment.<p> - * - * This class represents the overall information about a JNLP file - * from the jnlp element. Other information is accessed through - * objects that represent the elements of a JNLP file - * (information, resources, application-desc, etc). References to - * these objects are obtained by calling the getInformation, - * getResources, getSecurity, etc methods.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.21 $ - */ -public class JNLPFile { - - // todo: save the update policy, then if file was not updated - // then do not check resources for being updated. - // - // todo: make getLaunchInfo return a superclass that all the - // launch types implement (can get codebase from it). - // - // todo: currently does not filter resources by jvm version. - // - - private static String R(String key) { return JNLPRuntime.getMessage(key); } - - /** the location this JNLP file was created from */ - protected URL sourceLocation = null; - - /** the network location of this JNLP file */ - protected URL fileLocation; - - /** the URL used to resolve relative URLs in the file */ - protected URL codeBase; - - /** file version */ - protected Version fileVersion; - - /** spec version */ - protected Version specVersion; - - /** information */ - protected List info; - - /** resources */ - protected List resources; - - /** additional resources not in JNLP file (from command line) */ - protected ResourcesDesc sharedResources = new ResourcesDesc(this, null, null, null); - - /** the application description */ - protected Object launchType; - - /** the security descriptor */ - protected SecurityDesc security; - - /** the default OS */ - protected Locale defaultLocale = null; - - /** the default arch */ - protected String defaultOS = null; - - /** the default jvm */ - protected String defaultArch = null; - - { // initialize defaults if security allows - try { - defaultLocale = Locale.getDefault(); - defaultOS = System.getProperty("os.name"); - defaultArch = System.getProperty("os.arch"); - } - catch (SecurityException ex) { - // null values will still work, and app can set defaults later - } - } - - /** - * Empty stub, allowing child classes to override the constructor - */ - protected JNLPFile() { - } - - /** - * Create a JNLPFile from a URL. - * - * @param location the location of the JNLP file - * @throws IOException if an IO exception occurred - * @throws ParseException if the JNLP file was invalid - */ - public JNLPFile(URL location) throws IOException, ParseException { - this(location, false); // not strict - } - - /** - * Create a JNLPFile from a URL checking for updates using the - * default policy. - * - * @param location the location of the JNLP file - * @param strict whether to enforce the spec when - * @throws IOException if an IO exception occurred - * @throws ParseException if the JNLP file was invalid - */ - public JNLPFile(URL location, boolean strict) throws IOException, ParseException { - this(location, (Version) null, strict); - } - - /** - * Create a JNLPFile from a URL and a Version checking for updates using - * the default policy. - * - * @param location the location of the JNLP file - * @param version the version of the JNLP file - * @param strict whether to enforce the spec when - * @throws IOException if an IO exception occurred - * @throws ParseException if the JNLP file was invalid - */ - public JNLPFile(URL location, Version version, boolean strict) throws IOException, ParseException { - this(location, version, strict, JNLPRuntime.getDefaultUpdatePolicy()); - } - - /** - * Create a JNLPFile from a URL and a version, checking for updates - * using the specified policy. - * - * @param location the location of the JNLP file - * @param version the version of the JNLP file - * @param strict whether to enforce the spec when - * @param policy the update policy - * @throws IOException if an IO exception occurred - * @throws ParseException if the JNLP file was invalid - */ - public JNLPFile(URL location, Version version, boolean strict, UpdatePolicy policy) throws IOException, ParseException { - Node root = Parser.getRootNode(openURL(location, version, policy)); - parse(root, strict, location); - - this.fileLocation = location; - } - - /** - * Create a JNLPFile from an input stream. - * - * @throws IOException if an IO exception occurred - * @throws ParseException if the JNLP file was invalid - */ - public JNLPFile(InputStream input, boolean strict) throws ParseException { - parse(Parser.getRootNode(input), strict, null); - } - - /** - * Create a JNLPFile from a character stream. - * - * @param input the stream - * @param strict whether to enforce the spec when - * @throws IOException if an IO exception occurred - * @throws ParseException if the JNLP file was invalid - */ - private JNLPFile(Reader input, boolean strict) throws ParseException { - // todo: now that we are using NanoXML we can use a Reader - //parse(Parser.getRootNode(input), strict, null); - } - - - /** - * Open the jnlp file URL from the cache if there, otherwise - * download to the cache. Called from constructor. - */ - private static InputStream openURL(URL location, Version version, UpdatePolicy policy) throws IOException { - if (location == null || policy == null) - throw new IllegalArgumentException(R("NullParameter")); - - try { - ResourceTracker tracker = new ResourceTracker(false); // no prefetch - tracker.addResource(location, version , policy); - - return tracker.getInputStream(location); - } - catch (Exception ex) { - throw new IOException(ex.getMessage()); - } - } - - /** - * Returns the JNLP specification versions supported. - */ - public static Version getSupportedVersions() { - return Parser.getSupportedVersions(); - } - - /** - * Returns the JNLP file's title. This method returns the same - * value as InformationDesc.getTitle(). - */ - public String getTitle() { - return getInformation().getTitle(); - } - - /** - * Returns the JNLP file's network location as specified in the - * JNLP file. - */ - public URL getSourceLocation() { - return sourceLocation; - } - - /** - * Returns the location of the file parsed to create the JNLP - * file, or null if it was not created from a URL. - */ - public URL getFileLocation() { - return fileLocation; - } - - /** - * Returns the JNLP file's version. - */ - public Version getFileVersion() { - return fileVersion; - } - - /** - * Returns the specification version required by the file. - */ - public Version getSpecVersion() { - return specVersion; - } - - /** - * Returns the codebase URL for the JNLP file. - */ - public URL getCodeBase() { - return codeBase; - } - - /** - * Returns the information section of the JNLP file as viewed - * through the default locale. - */ - public InformationDesc getInformation() { - return getInformation(defaultLocale); - } - - /** - * Returns the information section of the JNLP file as viewed - * through the specified locale. - */ - public InformationDesc getInformation(final Locale locale) { - return new InformationDesc(this, new Locale[] {locale}) { - protected List getItems(Object key) { - List result = new ArrayList(); - - for (int i=0; i < info.size(); i++) { - InformationDesc infoDesc = (InformationDesc) info.get(i); - - if (localMatches(locale, infoDesc.getLocales())) - if (localMatches(locale, infoDesc.getLocales())) - result.addAll(infoDesc.getItems(key) ); - } - - return result; - } - }; - } - - /** - * Returns the security section of the JNLP file. - */ - public SecurityDesc getSecurity() { - return security; - } - - /** - * Returns the resources section of the JNLP file as viewed - * through the default locale and the os.name and os.arch - * properties. - */ - public ResourcesDesc getResources() { - return getResources(defaultLocale, defaultOS, defaultArch); - } - - /** - * Returns the information section of the JNLP file for the - * specified locale, os, and arch. - */ - public ResourcesDesc getResources(final Locale locale, final String os, final String arch) { - return new ResourcesDesc(this, new Locale[] {locale}, new String[] {os}, new String[] {arch}) { - public List getResources(Class launchType) { - List result = new ArrayList(); - - for (int i=0; i < resources.size(); i++) { - ResourcesDesc rescDesc = (ResourcesDesc) resources.get(i); - - if (localMatches(locale, rescDesc.getLocales()) - && stringMatches(os, rescDesc.getOS()) - && stringMatches(arch, rescDesc.getArch())) - result.addAll(rescDesc.getResources(launchType) ); - } - - result.addAll(sharedResources.getResources(launchType)); - - return result; - } - - public void addResource(Object resource) { - // todo: honor the current locale, os, arch values - sharedResources.addResource(resource); - } - }; - } - - /** - * Returns an object of one of the following types: AppletDesc, - * ApplicationDesc, InstallerDesc, and ComponentDesc. - */ - public Object getLaunchInfo() { - return launchType; - } - - /** - * Returns the launch information for an applet. - * - * @throws UnsupportedOperationException if there is no applet information - */ - public AppletDesc getApplet() { - if (!isApplet()) - throw new UnsupportedOperationException(R("JNotApplet")); - - return (AppletDesc) launchType; - } - - /** - * Returns the launch information for an application. - * - * @throws UnsupportedOperationException if there is no application information - */ - public ApplicationDesc getApplication() { - if (!isApplication()) - throw new UnsupportedOperationException(R("JNotApplication")); - - return (ApplicationDesc) launchType; - } - - /** - * Returns the launch information for a component. - * - * @throws UnsupportedOperationException if there is no component information - */ - public ComponentDesc getComponent() { - if (!isComponent()) - throw new UnsupportedOperationException(R("JNotComponent")); - - return (ComponentDesc) launchType; - } - - /** - * Returns the launch information for an installer. - * - * @throws UnsupportedOperationException if there is no installer information - */ - public InstallerDesc getInstaller() { - if (!isInstaller()) - throw new UnsupportedOperationException(R("NotInstaller")); - - return (InstallerDesc) launchType; - } - - /** - * Returns whether the lauch descriptor describes an Applet. - */ - public boolean isApplet() { - return launchType instanceof AppletDesc; - } - - /** - * Returns whether the lauch descriptor describes an Application. - */ - public boolean isApplication() { - return launchType instanceof ApplicationDesc; - } - - /** - * Returns whether the lauch descriptor describes a Component. - */ - public boolean isComponent() { - return launchType instanceof ComponentDesc; - } - - /** - * Returns whether the lauch descriptor describes an Installer. - */ - public boolean isInstaller() { - return launchType instanceof InstallerDesc; - } - - /** - * Sets the default view of the JNLP file returned by - * getInformation, getResources, etc. If unset, the defaults - * are the properties os.name, os.arch, and the locale returned - * by Locale.getDefault(). - */ - public void setDefaults(String os, String arch, Locale locale) { - defaultOS = os; - defaultArch = arch; - defaultLocale = locale; - } - - - /** - * Returns whether a locale is matched by one of more other - * locales. Only the non-empty language, country, and variant - * codes are compared; for example, a requested locale of - * Locale("","","") would always return true. - * - * @param requested the local - * @param available the available locales - * @return true if requested matches any of available, or if - * available is empty or null. - */ - private boolean localMatches(Locale requested, Locale available[]) { - if (available == null || available.length == 0) - return true; - - for (int i=0; i < available.length; i++) { - String language = requested.getLanguage(); // "" but never null - String country = requested.getCountry(); - String variant = requested.getVariant(); - - if (!"".equals(language) && !language.equalsIgnoreCase(available[i].getLanguage())) - continue; - if (!"".equals(country) && !country.equalsIgnoreCase(available[i].getCountry())) - continue; - if (!"".equals(variant) && !variant.equalsIgnoreCase(available[i].getVariant())) - continue; - - return true; - } - - return false; - } - - /** - * Returns whether the string is a prefix for any of the strings - * in the specified array. - * - * @param prefixStr the prefix string - * @param available the strings to test - * @return true if prefixStr is a prefix of any strings in - * available, or if available is empty or null. - */ - private boolean stringMatches(String prefixStr, String available[]) { - if (available == null || available.length == 0) - return true; - - for (int i=0; i < available.length; i++) - if (available[i] != null && available[i].startsWith(prefixStr)) - return true; - - return false; - } - - /** - * Initialize the JNLPFile fields. Private because it's called - * from the constructor. - * - * @param root the root node - * @param strict whether to enforce the spec when - * @param location the file location or null - */ - private void parse(Node root, boolean strict, URL location) throws ParseException { - try { - //if (location != null) - // location = new URL(location, "."); // remove filename - - Parser parser = new Parser(this, location, root, strict, true); // true == allow extensions - - // JNLP tag information - specVersion = parser.getSpecVersion(); - fileVersion = parser.getFileVersion(); - codeBase = parser.getCodeBase(); - sourceLocation = parser.getFileLocation(); - - info = parser.getInfo(root); - resources = parser.getResources(root, false); // false == not a j2se/java resources section - launchType = parser.getLauncher(root); - security = parser.getSecurity(root); - } - catch (ParseException ex) { - throw ex; - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - throw new RuntimeException(ex.toString()); - } - } - - /** - * - * @return true if the JNLP file specifies things that can only be - * applied on a new vm (eg: different max heap memory) - */ - public boolean needsNewVM() { - - if (getNewVMArgs().size() == 0) { - return false; - } else { - return true; - } - } - - /** - * @return a list of args to pass to the new - * JVM based on this JNLP file - */ - public List<String> getNewVMArgs() { - - List<String> newVMArgs = new LinkedList<String>(); - - JREDesc[] jres = getResources().getJREs(); - for (int jreIndex = 0; jreIndex < jres.length; jreIndex++) { - String initialHeapSize = jres[jreIndex].getInitialHeapSize(); - if (initialHeapSize != null) { - newVMArgs.add("-Xms" + initialHeapSize); - } - - String maxHeapSize = jres[jreIndex].getMaximumHeapSize(); - if (maxHeapSize != null) { - newVMArgs.add("-Xmx" + maxHeapSize); - } - - String vmArgsFromJre = jres[jreIndex].getVMArgs(); - if (vmArgsFromJre != null) { - String[] args = vmArgsFromJre.split(" "); - newVMArgs.addAll(Arrays.asList(args)); - } - } - - return newVMArgs; - } - -} +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import net.sourceforge.jnlp.cache.ResourceTracker; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.runtime.JNLPRuntime; + +/** + * Provides methods to access the information in a Java Network + * Launching Protocol (JNLP) file. The Java Network Launching + * Protocol specifies in an XML file the information needed to + * load, cache, and run Java code over the network and in a secure + * environment.<p> + * + * This class represents the overall information about a JNLP file + * from the jnlp element. Other information is accessed through + * objects that represent the elements of a JNLP file + * (information, resources, application-desc, etc). References to + * these objects are obtained by calling the getInformation, + * getResources, getSecurity, etc methods.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.21 $ + */ +public class JNLPFile { + + // todo: save the update policy, then if file was not updated + // then do not check resources for being updated. + // + // todo: make getLaunchInfo return a superclass that all the + // launch types implement (can get codebase from it). + // + // todo: currently does not filter resources by jvm version. + // + + private static String R(String key) { return JNLPRuntime.getMessage(key); } + + /** the location this JNLP file was created from */ + protected URL sourceLocation = null; + + /** the network location of this JNLP file */ + protected URL fileLocation; + + /** the URL used to resolve relative URLs in the file */ + protected URL codeBase; + + /** file version */ + protected Version fileVersion; + + /** spec version */ + protected Version specVersion; + + /** information */ + protected List info; + + /** resources */ + protected List resources; + + /** additional resources not in JNLP file (from command line) */ + protected ResourcesDesc sharedResources = new ResourcesDesc(this, null, null, null); + + /** the application description */ + protected Object launchType; + + /** the security descriptor */ + protected SecurityDesc security; + + /** the default OS */ + protected Locale defaultLocale = null; + + /** the default arch */ + protected String defaultOS = null; + + /** the default jvm */ + protected String defaultArch = null; + + { // initialize defaults if security allows + try { + defaultLocale = Locale.getDefault(); + defaultOS = System.getProperty("os.name"); + defaultArch = System.getProperty("os.arch"); + } + catch (SecurityException ex) { + // null values will still work, and app can set defaults later + } + } + + /** + * Empty stub, allowing child classes to override the constructor + */ + protected JNLPFile() { + } + + /** + * Create a JNLPFile from a URL. + * + * @param location the location of the JNLP file + * @throws IOException if an IO exception occurred + * @throws ParseException if the JNLP file was invalid + */ + public JNLPFile(URL location) throws IOException, ParseException { + this(location, false); // not strict + } + + /** + * Create a JNLPFile from a URL checking for updates using the + * default policy. + * + * @param location the location of the JNLP file + * @param strict whether to enforce the spec when + * @throws IOException if an IO exception occurred + * @throws ParseException if the JNLP file was invalid + */ + public JNLPFile(URL location, boolean strict) throws IOException, ParseException { + this(location, (Version) null, strict); + } + + /** + * Create a JNLPFile from a URL and a Version checking for updates using + * the default policy. + * + * @param location the location of the JNLP file + * @param version the version of the JNLP file + * @param strict whether to enforce the spec when + * @throws IOException if an IO exception occurred + * @throws ParseException if the JNLP file was invalid + */ + public JNLPFile(URL location, Version version, boolean strict) throws IOException, ParseException { + this(location, version, strict, JNLPRuntime.getDefaultUpdatePolicy()); + } + + /** + * Create a JNLPFile from a URL and a version, checking for updates + * using the specified policy. + * + * @param location the location of the JNLP file + * @param version the version of the JNLP file + * @param strict whether to enforce the spec when + * @param policy the update policy + * @throws IOException if an IO exception occurred + * @throws ParseException if the JNLP file was invalid + */ + public JNLPFile(URL location, Version version, boolean strict, UpdatePolicy policy) throws IOException, ParseException { + Node root = Parser.getRootNode(openURL(location, version, policy)); + parse(root, strict, location); + + this.fileLocation = location; + } + + /** + * Create a JNLPFile from an input stream. + * + * @throws IOException if an IO exception occurred + * @throws ParseException if the JNLP file was invalid + */ + public JNLPFile(InputStream input, boolean strict) throws ParseException { + parse(Parser.getRootNode(input), strict, null); + } + + /** + * Create a JNLPFile from a character stream. + * + * @param input the stream + * @param strict whether to enforce the spec when + * @throws IOException if an IO exception occurred + * @throws ParseException if the JNLP file was invalid + */ + private JNLPFile(Reader input, boolean strict) throws ParseException { + // todo: now that we are using NanoXML we can use a Reader + //parse(Parser.getRootNode(input), strict, null); + } + + + /** + * Open the jnlp file URL from the cache if there, otherwise + * download to the cache. Called from constructor. + */ + private static InputStream openURL(URL location, Version version, UpdatePolicy policy) throws IOException { + if (location == null || policy == null) + throw new IllegalArgumentException(R("NullParameter")); + + try { + ResourceTracker tracker = new ResourceTracker(false); // no prefetch + tracker.addResource(location, version , policy); + + return tracker.getInputStream(location); + } + catch (Exception ex) { + throw new IOException(ex.getMessage()); + } + } + + /** + * Returns the JNLP specification versions supported. + */ + public static Version getSupportedVersions() { + return Parser.getSupportedVersions(); + } + + /** + * Returns the JNLP file's title. This method returns the same + * value as InformationDesc.getTitle(). + */ + public String getTitle() { + return getInformation().getTitle(); + } + + /** + * Returns the JNLP file's network location as specified in the + * JNLP file. + */ + public URL getSourceLocation() { + return sourceLocation; + } + + /** + * Returns the location of the file parsed to create the JNLP + * file, or null if it was not created from a URL. + */ + public URL getFileLocation() { + return fileLocation; + } + + /** + * Returns the JNLP file's version. + */ + public Version getFileVersion() { + return fileVersion; + } + + /** + * Returns the specification version required by the file. + */ + public Version getSpecVersion() { + return specVersion; + } + + /** + * Returns the codebase URL for the JNLP file. + */ + public URL getCodeBase() { + return codeBase; + } + + /** + * Returns the information section of the JNLP file as viewed + * through the default locale. + */ + public InformationDesc getInformation() { + return getInformation(defaultLocale); + } + + /** + * Returns the information section of the JNLP file as viewed + * through the specified locale. + */ + public InformationDesc getInformation(final Locale locale) { + return new InformationDesc(this, new Locale[] {locale}) { + protected List getItems(Object key) { + List result = new ArrayList(); + + for (int i=0; i < info.size(); i++) { + InformationDesc infoDesc = (InformationDesc) info.get(i); + + if (localMatches(locale, infoDesc.getLocales())) + if (localMatches(locale, infoDesc.getLocales())) + result.addAll(infoDesc.getItems(key) ); + } + + return result; + } + }; + } + + /** + * Returns the security section of the JNLP file. + */ + public SecurityDesc getSecurity() { + return security; + } + + /** + * Returns the resources section of the JNLP file as viewed + * through the default locale and the os.name and os.arch + * properties. + */ + public ResourcesDesc getResources() { + return getResources(defaultLocale, defaultOS, defaultArch); + } + + /** + * Returns the information section of the JNLP file for the + * specified locale, os, and arch. + */ + public ResourcesDesc getResources(final Locale locale, final String os, final String arch) { + return new ResourcesDesc(this, new Locale[] {locale}, new String[] {os}, new String[] {arch}) { + public List getResources(Class launchType) { + List result = new ArrayList(); + + for (int i=0; i < resources.size(); i++) { + ResourcesDesc rescDesc = (ResourcesDesc) resources.get(i); + + if (localMatches(locale, rescDesc.getLocales()) + && stringMatches(os, rescDesc.getOS()) + && stringMatches(arch, rescDesc.getArch())) + result.addAll(rescDesc.getResources(launchType) ); + } + + result.addAll(sharedResources.getResources(launchType)); + + return result; + } + + public void addResource(Object resource) { + // todo: honor the current locale, os, arch values + sharedResources.addResource(resource); + } + }; + } + + /** + * Returns an object of one of the following types: AppletDesc, + * ApplicationDesc, InstallerDesc, and ComponentDesc. + */ + public Object getLaunchInfo() { + return launchType; + } + + /** + * Returns the launch information for an applet. + * + * @throws UnsupportedOperationException if there is no applet information + */ + public AppletDesc getApplet() { + if (!isApplet()) + throw new UnsupportedOperationException(R("JNotApplet")); + + return (AppletDesc) launchType; + } + + /** + * Returns the launch information for an application. + * + * @throws UnsupportedOperationException if there is no application information + */ + public ApplicationDesc getApplication() { + if (!isApplication()) + throw new UnsupportedOperationException(R("JNotApplication")); + + return (ApplicationDesc) launchType; + } + + /** + * Returns the launch information for a component. + * + * @throws UnsupportedOperationException if there is no component information + */ + public ComponentDesc getComponent() { + if (!isComponent()) + throw new UnsupportedOperationException(R("JNotComponent")); + + return (ComponentDesc) launchType; + } + + /** + * Returns the launch information for an installer. + * + * @throws UnsupportedOperationException if there is no installer information + */ + public InstallerDesc getInstaller() { + if (!isInstaller()) + throw new UnsupportedOperationException(R("NotInstaller")); + + return (InstallerDesc) launchType; + } + + /** + * Returns whether the lauch descriptor describes an Applet. + */ + public boolean isApplet() { + return launchType instanceof AppletDesc; + } + + /** + * Returns whether the lauch descriptor describes an Application. + */ + public boolean isApplication() { + return launchType instanceof ApplicationDesc; + } + + /** + * Returns whether the lauch descriptor describes a Component. + */ + public boolean isComponent() { + return launchType instanceof ComponentDesc; + } + + /** + * Returns whether the lauch descriptor describes an Installer. + */ + public boolean isInstaller() { + return launchType instanceof InstallerDesc; + } + + /** + * Sets the default view of the JNLP file returned by + * getInformation, getResources, etc. If unset, the defaults + * are the properties os.name, os.arch, and the locale returned + * by Locale.getDefault(). + */ + public void setDefaults(String os, String arch, Locale locale) { + defaultOS = os; + defaultArch = arch; + defaultLocale = locale; + } + + + /** + * Returns whether a locale is matched by one of more other + * locales. Only the non-empty language, country, and variant + * codes are compared; for example, a requested locale of + * Locale("","","") would always return true. + * + * @param requested the local + * @param available the available locales + * @return true if requested matches any of available, or if + * available is empty or null. + */ + private boolean localMatches(Locale requested, Locale available[]) { + if (available == null || available.length == 0) + return true; + + for (int i=0; i < available.length; i++) { + String language = requested.getLanguage(); // "" but never null + String country = requested.getCountry(); + String variant = requested.getVariant(); + + if (!"".equals(language) && !language.equalsIgnoreCase(available[i].getLanguage())) + continue; + if (!"".equals(country) && !country.equalsIgnoreCase(available[i].getCountry())) + continue; + if (!"".equals(variant) && !variant.equalsIgnoreCase(available[i].getVariant())) + continue; + + return true; + } + + return false; + } + + /** + * Returns whether the string is a prefix for any of the strings + * in the specified array. + * + * @param prefixStr the prefix string + * @param available the strings to test + * @return true if prefixStr is a prefix of any strings in + * available, or if available is empty or null. + */ + private boolean stringMatches(String prefixStr, String available[]) { + if (available == null || available.length == 0) + return true; + + for (int i=0; i < available.length; i++) + if (available[i] != null && available[i].startsWith(prefixStr)) + return true; + + return false; + } + + /** + * Initialize the JNLPFile fields. Private because it's called + * from the constructor. + * + * @param root the root node + * @param strict whether to enforce the spec when + * @param location the file location or null + */ + private void parse(Node root, boolean strict, URL location) throws ParseException { + try { + //if (location != null) + // location = new URL(location, "."); // remove filename + + Parser parser = new Parser(this, location, root, strict, true); // true == allow extensions + + // JNLP tag information + specVersion = parser.getSpecVersion(); + fileVersion = parser.getFileVersion(); + codeBase = parser.getCodeBase(); + sourceLocation = parser.getFileLocation(); + + info = parser.getInfo(root); + resources = parser.getResources(root, false); // false == not a j2se/java resources section + launchType = parser.getLauncher(root); + security = parser.getSecurity(root); + } + catch (ParseException ex) { + throw ex; + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + throw new RuntimeException(ex.toString()); + } + } + + /** + * + * @return true if the JNLP file specifies things that can only be + * applied on a new vm (eg: different max heap memory) + */ + public boolean needsNewVM() { + + if (getNewVMArgs().size() == 0) { + return false; + } else { + return true; + } + } + + /** + * @return a list of args to pass to the new + * JVM based on this JNLP file + */ + public List<String> getNewVMArgs() { + + List<String> newVMArgs = new LinkedList<String>(); + + JREDesc[] jres = getResources().getJREs(); + for (int jreIndex = 0; jreIndex < jres.length; jreIndex++) { + String initialHeapSize = jres[jreIndex].getInitialHeapSize(); + if (initialHeapSize != null) { + newVMArgs.add("-Xms" + initialHeapSize); + } + + String maxHeapSize = jres[jreIndex].getMaximumHeapSize(); + if (maxHeapSize != null) { + newVMArgs.add("-Xmx" + maxHeapSize); + } + + String vmArgsFromJre = jres[jreIndex].getVMArgs(); + if (vmArgsFromJre != null) { + String[] args = vmArgsFromJre.split(" "); + newVMArgs.addAll(Arrays.asList(args)); + } + } + + return newVMArgs; + } + +}
--- a/rt/net/sourceforge/jnlp/LaunchException.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/LaunchException.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,190 +1,190 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -package net.sourceforge.jnlp; - -import java.io.*; -import java.util.*; - -import net.sourceforge.jnlp.util.*; - -/** - * Thrown when a JNLP application, applet, or installer could not - * be created. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.9 $ - */ -public class LaunchException extends Exception { - - /** the original exception */ - private Throwable cause = null; - - /** the file being launched */ - private JNLPFile file; - - /** the category of the exception */ - private String category; - - /** summary */ - private String summary; - - /** description of the action that was taking place */ - private String description; - - /** severity of the warning/error */ - private String severity; - - - /** - * Creates a LaunchException without detail message. - */ - public LaunchException(JNLPFile file, Exception cause, String severity, String category, String summary, String description) { - super(severity + ": " + category + ": "+ summary); - - this.file = file; - this.category = category; - this.summary = summary; - this.description = description; - this.severity = severity; - - // replace with setCause when no longer 1.3 compatible - this.cause = cause; - } - - /** - * Creates a LaunchException with a cause. - */ - public LaunchException(Throwable cause) { - this(cause.getMessage()); - - // replace with setCause when no longer 1.3 compatible - this.cause = cause; - } - - /** - * Creates a LaunchException with a cause and detail message - */ - public LaunchException(String message, Throwable cause) { - this(message+": "+cause.getMessage()); - - // replace with setCause when no longer 1.3 compatible - this.cause = cause; - } - - /** - * Constructs a LaunchException with the specified detail - * message. - * - * @param message the detail message - */ - public LaunchException(String message) { - super(message); - } - - /** - * Returns the JNLPFile being launched. - */ - public JNLPFile getFile() { - return file; - } - - /** - * Returns the category string, a short description of the - * exception suitable for displaying in a window title. - */ - public String getCategory() { - return category; - } - - /** - * Returns a one-sentence summary of the problem. - */ - public String getSummary() { - return summary; - } - - /** - * Return a description of the exception and the action being - * performed when the exception occurred. - */ - public String getDescription() { - return description; - } - - /** - * Returns a short description of the severity of the problem. - */ - public String getSeverity() { - return severity; - } - - /** - * Return the cause of the launch exception or null if there - * is no cause exception. - */ - public Throwable getCause() { - return cause; - } - - /** - * Returns the causes for this exception. This method is - * useful on JRE 1.3 since getCause is not a standard method, - * and will be removed once netx no longer supports 1.3. - */ - public Throwable[] getCauses() { - ArrayList result = new ArrayList(); - - Reflect r = new Reflect(); - Throwable cause = this.cause; - - while (cause != null) { - result.add(cause); - cause = (Throwable) r.invoke(cause, "getCause"); - } - - return (Throwable[]) result.toArray(new Throwable[0]); - } - - /** - * Print the stack trace and the cause exception (1.3 - * compatible) - */ - public void printStackTrace(PrintStream stream) { - super.printStackTrace(stream); - - if (cause != null) { - stream.println("Caused by: "); - cause.printStackTrace(stream); - } - } - - /** - * Print the stack trace and the cause exception (1.3 - * compatible) - */ - public void printStackTrace(PrintWriter stream) { - super.printStackTrace(stream); - - if (cause != null) { - stream.println("Caused by: "); - cause.printStackTrace(stream); - } - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp; + +import java.io.*; +import java.util.*; + +import net.sourceforge.jnlp.util.*; + +/** + * Thrown when a JNLP application, applet, or installer could not + * be created. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.9 $ + */ +public class LaunchException extends Exception { + + /** the original exception */ + private Throwable cause = null; + + /** the file being launched */ + private JNLPFile file; + + /** the category of the exception */ + private String category; + + /** summary */ + private String summary; + + /** description of the action that was taking place */ + private String description; + + /** severity of the warning/error */ + private String severity; + + + /** + * Creates a LaunchException without detail message. + */ + public LaunchException(JNLPFile file, Exception cause, String severity, String category, String summary, String description) { + super(severity + ": " + category + ": "+ summary); + + this.file = file; + this.category = category; + this.summary = summary; + this.description = description; + this.severity = severity; + + // replace with setCause when no longer 1.3 compatible + this.cause = cause; + } + + /** + * Creates a LaunchException with a cause. + */ + public LaunchException(Throwable cause) { + this(cause.getMessage()); + + // replace with setCause when no longer 1.3 compatible + this.cause = cause; + } + + /** + * Creates a LaunchException with a cause and detail message + */ + public LaunchException(String message, Throwable cause) { + this(message+": "+cause.getMessage()); + + // replace with setCause when no longer 1.3 compatible + this.cause = cause; + } + + /** + * Constructs a LaunchException with the specified detail + * message. + * + * @param message the detail message + */ + public LaunchException(String message) { + super(message); + } + + /** + * Returns the JNLPFile being launched. + */ + public JNLPFile getFile() { + return file; + } + + /** + * Returns the category string, a short description of the + * exception suitable for displaying in a window title. + */ + public String getCategory() { + return category; + } + + /** + * Returns a one-sentence summary of the problem. + */ + public String getSummary() { + return summary; + } + + /** + * Return a description of the exception and the action being + * performed when the exception occurred. + */ + public String getDescription() { + return description; + } + + /** + * Returns a short description of the severity of the problem. + */ + public String getSeverity() { + return severity; + } + + /** + * Return the cause of the launch exception or null if there + * is no cause exception. + */ + public Throwable getCause() { + return cause; + } + + /** + * Returns the causes for this exception. This method is + * useful on JRE 1.3 since getCause is not a standard method, + * and will be removed once netx no longer supports 1.3. + */ + public Throwable[] getCauses() { + ArrayList result = new ArrayList(); + + Reflect r = new Reflect(); + Throwable cause = this.cause; + + while (cause != null) { + result.add(cause); + cause = (Throwable) r.invoke(cause, "getCause"); + } + + return (Throwable[]) result.toArray(new Throwable[0]); + } + + /** + * Print the stack trace and the cause exception (1.3 + * compatible) + */ + public void printStackTrace(PrintStream stream) { + super.printStackTrace(stream); + + if (cause != null) { + stream.println("Caused by: "); + cause.printStackTrace(stream); + } + } + + /** + * Print the stack trace and the cause exception (1.3 + * compatible) + */ + public void printStackTrace(PrintWriter stream) { + super.printStackTrace(stream); + + if (cause != null) { + stream.println("Caused by: "); + cause.printStackTrace(stream); + } + } + +} + +
--- a/rt/net/sourceforge/jnlp/LaunchHandler.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/LaunchHandler.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,68 +1,68 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -package net.sourceforge.jnlp; - -import net.sourceforge.jnlp.runtime.*; - -/** - * This optional interface is used to handle conditions that occur - * while launching JNLP applications, applets, and installers. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.9 $ - */ -public interface LaunchHandler { - - /** - * Called when the application could not be launched due to a - * fatal error, such as the inability to find the main class or - * non-parseable XML. - */ - public void launchError(LaunchException exception); - - /** - * Called when launching the application can not be launched due - * to an error that is not fatal. For example a JNLP file that - * is not strictly correct yet does not necessarily prohibit the - * system from attempting to launch the application. - * - * @return true if the launch should continue, false to abort - */ - public boolean launchWarning(LaunchException warning); - - /** - * Called when a security validation error occurs while - * launching the application. - * - * @return true to allow the application to continue, false to stop it. - */ - public boolean validationError(LaunchException security); - // this method will probably be replaced when real security - // controller is in place. - - /** - * Called when an application, applet, or installer has been - * launched successfully (the main method or applet start method - * returned normally). - * - * @param application the launched application instance - */ - public void launchCompleted(ApplicationInstance application); - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp; + +import net.sourceforge.jnlp.runtime.*; + +/** + * This optional interface is used to handle conditions that occur + * while launching JNLP applications, applets, and installers. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.9 $ + */ +public interface LaunchHandler { + + /** + * Called when the application could not be launched due to a + * fatal error, such as the inability to find the main class or + * non-parseable XML. + */ + public void launchError(LaunchException exception); + + /** + * Called when launching the application can not be launched due + * to an error that is not fatal. For example a JNLP file that + * is not strictly correct yet does not necessarily prohibit the + * system from attempting to launch the application. + * + * @return true if the launch should continue, false to abort + */ + public boolean launchWarning(LaunchException warning); + + /** + * Called when a security validation error occurs while + * launching the application. + * + * @return true to allow the application to continue, false to stop it. + */ + public boolean validationError(LaunchException security); + // this method will probably be replaced when real security + // controller is in place. + + /** + * Called when an application, applet, or installer has been + * launched successfully (the main method or applet start method + * returned normally). + * + * @param application the launched application instance + */ + public void launchCompleted(ApplicationInstance application); + +} + +
--- a/rt/net/sourceforge/jnlp/Launcher.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/Launcher.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,775 +1,775 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp; - -import java.applet.Applet; -import java.awt.Container; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.LinkedList; -import java.util.List; -import java.util.jar.JarFile; - -import net.sourceforge.jnlp.cache.CacheUtil; -import net.sourceforge.jnlp.cache.ResourceTracker; -import net.sourceforge.jnlp.cache.UpdatePolicy; -import net.sourceforge.jnlp.runtime.AppThreadGroup; -import net.sourceforge.jnlp.runtime.AppletInstance; -import net.sourceforge.jnlp.runtime.ApplicationInstance; -import net.sourceforge.jnlp.runtime.JNLPClassLoader; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.services.InstanceExistsException; -import net.sourceforge.jnlp.services.ServiceUtil; -import net.sourceforge.jnlp.util.Reflect; - -/** - * Launches JNLPFiles either in the foreground or background.<p> - * - * An optional LaunchHandler can be specified that is notified of - * warning and error condition while launching and that indicates - * whether a launch may proceed after a warning has occurred. If - * specified, the LaunchHandler is notified regardless of whether - * the file is launched in the foreground or background.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.22 $ - */ -public class Launcher { - - // defines class Launcher.BgRunner, Launcher.TgThread - - /** shortcut for resources */ - private static String R(String key) { return JNLPRuntime.getMessage(key); } - - /** shared thread group */ - private static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup")); - - /** the handler */ - private LaunchHandler handler = null; - - /** the update policy */ - private UpdatePolicy updatePolicy = JNLPRuntime.getDefaultUpdatePolicy(); - - /** whether to create an AppContext (if possible) */ - private boolean context = true; - - /** If the application should call System.exit on fatal errors */ - private boolean exitOnFailure = true; - - /** - * Create a launcher with the runtime's default update policy - * and launch handler. - */ - public Launcher() { - this(null, null); - - if (handler == null) - handler = JNLPRuntime.getDefaultLaunchHandler(); - } - - /** - * Create a launcher with the runtime's default update policy - * and launch handler. - * - * @param exitOnError Exit if there is an error (usually default, but false when being used from the plugin) - */ - public Launcher(boolean exitOnFailure) { - this(null, null); - - if (handler == null) - handler = JNLPRuntime.getDefaultLaunchHandler(); - - this.exitOnFailure = exitOnFailure; - } - - /** - * Create a launcher with the specified handler and the - * runtime's default update policy. - * - * @param handler the handler to use or null for no handler. - */ - public Launcher(LaunchHandler handler) { - this(handler, null); - } - - /** - * Create a launcher with an optional handler using the - * specified update policy and launch handler. - * - * @param handler the handler to use or null for no handler. - * @param policy the update policy to use or null for default policy. - */ - public Launcher(LaunchHandler handler, UpdatePolicy policy) { - if (policy == null) - policy = JNLPRuntime.getDefaultUpdatePolicy(); - - this.handler = handler; - this.updatePolicy = policy; - } - - /** - * Sets the update policy used by launched applications. - */ - public void setUpdatePolicy(UpdatePolicy policy) { - if (policy == null) - throw new IllegalArgumentException(R("LNullUpdatePolicy")); - - this.updatePolicy = policy; - } - - /** - * Returns the update policy used when launching applications. - */ - public UpdatePolicy getUpdatePolicy() { - return updatePolicy; - } - - /** - * Sets whether to launch the application in a new AppContext - * (a separate event queue, look and feel, etc). If the - * sun.awt.SunToolkit class is not present then this method - * has no effect. The default value is true. - */ - public void setCreateAppContext(boolean context) { - this.context = context; - } - - /** - * Returns whether applications are launched in their own - * AppContext. - */ - public boolean isCreateAppContext() { - return this.context; - } - - /** - * Launches a JNLP file by calling the launch method for the - * appropriate file type. The application will be started in - * a new window. - * - * @param file the JNLP file to launch - * @return the application instance - * @throws LaunchException if an error occurred while launching (also sent to handler) - */ - public ApplicationInstance launch(JNLPFile file) throws LaunchException { - return launch(file, null); - } - - /** - * Launches a JNLP file inside the given container if it is an applet. Specifying a - * container has no effect for Applcations and Installers. - * - * @param file the JNLP file to launch - * @param cont the container in which to place the application, if it is an applet - * @return the application instance - * @throws LaunchException if an error occurred while launching (also sent to handler) - */ - public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchException { - TgThread tg; - - if (file instanceof PluginBridge && cont != null) - tg = new TgThread(file, cont, true); - else if (cont == null) - tg = new TgThread(file); - else - tg = new TgThread(file, cont); - - tg.start(); - - try { - tg.join(); - } - catch (InterruptedException ex) { - //By default, null is thrown here, and the message dialog is shown. - throw launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCSystem"), R("LThreadInterrupted"), R("LThreadInterruptedInfo"))); - } - - if (tg.getException() != null) - throw tg.getException(); // passed to handler when first created - - if (handler != null) - handler.launchCompleted(tg.getApplication()); - - return tg.getApplication(); - } - - /** - * Launches a JNLP file by calling the launch method for the - * appropriate file type. - * - * @param location the URL of the JNLP file to launch - * @throws LaunchException if there was an exception - * @return the application instance - */ - public ApplicationInstance launch(URL location) throws LaunchException { - return launch(toFile(location)); - } - - /** - * Launches a JNLP file by calling the launch method for the - * appropriate file type in a different thread. - * - * @param file the JNLP file to launch - */ - public void launchBackground(JNLPFile file) { - BgRunner runner = new BgRunner(file, null); - new Thread(runner).start(); - } - - /** - * Launches the JNLP file at the specified location in the - * background by calling the launch method for its file type. - * - * @param location the location of the JNLP file - */ - public void launchBackground(URL location) { - BgRunner runner = new BgRunner(null, location); - new Thread(runner).start(); - } - - /** - * Launches the JNLP file in a new JVM instance. The launched - * application's output is sent to the system out and it's - * standard input channel is closed. - * - * @param vmArgs the arguments to pass to the new JVM. Can be empty but - * must not be null. - * @param file the JNLP file to launch - * @param javawsArgs the arguments to pass to the javaws command. Can be - * an empty list but must not be null. - * @throws LaunchException if there was an exception - */ - public void launchExternal(List<String> vmArgs, JNLPFile file, List<String> javawsArgs) throws LaunchException { - List<String> updatedArgs = new LinkedList<String>(javawsArgs); - - if (file.getFileLocation() != null) - updatedArgs.add(file.getFileLocation().toString()); - else if (file.getSourceLocation() != null) - updatedArgs.add(file.getFileLocation().toString()); - else - launchError(new LaunchException(file, null, R("LSFatal"), R("LCExternalLaunch"), R("LNullLocation"), R("LNullLocationInfo"))); - - launchExternal(vmArgs, updatedArgs); - - } - - /** - * Launches the JNLP file in a new JVM instance. The launched - * application's output is sent to the system out and it's - * standard input channel is closed. - * - * @param url the URL of the JNLP file to launch - * @throws LaunchException if there was an exception - */ - public void launchExternal(URL url) throws LaunchException { - List<String> javawsArgs = new LinkedList<String>(); - javawsArgs.add(url.toString()); - launchExternal(new LinkedList<String>(), javawsArgs); - } - - /** - * Launches the JNLP file at the specified location in a new JVM - * instance. The launched application's output is sent to the - * system out and it's standard input channel is closed. - * @param vmArgs the arguments to pass to the jvm - * @param javawsArgs the arguments to pass to javaws (aka Netx) - * @throws LaunchException if there was an exception - */ - public void launchExternal(List<String> vmArgs, List<String> javawsArgs) throws LaunchException { - try { - - List<String> commands = new LinkedList<String>(); - - String pathToWebstartBinary = System.getProperty("java.home") + - File.separatorChar + - "bin" + - File.separatorChar + - "javaws"; - commands.add(pathToWebstartBinary); - // use -Jargument format to pass arguments to the JVM through the launcher - for (String arg: vmArgs) { - commands.add("-J" + arg); - } - commands.addAll(javawsArgs); - - String[] command = commands.toArray(new String[] {}); - - Process p = Runtime.getRuntime().exec(command); - new StreamEater(p.getErrorStream()).start(); - new StreamEater(p.getInputStream()).start(); - p.getOutputStream().close(); - - } - catch (NullPointerException ex) { - throw launchError(new LaunchException(null, null, R("LSFatal"), R("LCExternalLaunch"), R("LNetxJarMissing"), R("LNetxJarMissingInfo"))); - } - catch (Exception ex) { - throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCExternalLaunch"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); - } - } - - /** - * Returns the JNLPFile for the URL, with error handling. - */ - private JNLPFile toFile(URL location) throws LaunchException { - try { - JNLPFile file = null; - - try { - file = new JNLPFile(location, (Version) null, true, updatePolicy); // strict - } - catch (ParseException ex) { - file = new JNLPFile(location, (Version) null, false, updatePolicy); - - // only here if strict failed but lax did not fail - LaunchException lex = - launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCFileFormat"), R("LNotToSpec"), R("LNotToSpecInfo"))); - - if (lex != null) - throw lex; - } - - return file; - } - catch (Exception ex) { - if (ex instanceof LaunchException) - throw (LaunchException) ex; // already sent to handler when first thrown - else // IO and Parse - throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCReadError"), R("LCantRead"), R("LCantReadInfo"))); - } - } - - /** - * Launches a JNLP application. This method should be called - * from a thread in the application's thread group. - */ - protected ApplicationInstance launchApplication(JNLPFile file) throws LaunchException { - if (!file.isApplication()) - throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplication"), R("LNotApplicationInfo"))); - - try { - - try { - ServiceUtil.checkExistingSingleInstance(file); - } catch (InstanceExistsException e) { - return null; - } - - if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) { - List<String> netxArguments = new LinkedList<String>(); - netxArguments.add("-Xnofork"); - netxArguments.addAll(JNLPRuntime.getInitialArguments()); - launchExternal(file.getNewVMArgs(), netxArguments); - return null; - } - - final int preferredWidth = 500; - final int preferredHeight = 400; - JNLPSplashScreen splashScreen = null; - URL splashImageURL = file.getInformation().getIconLocation( - IconDesc.SPLASH, preferredWidth, preferredHeight); - if (splashImageURL != null) { - ResourceTracker resourceTracker = new ResourceTracker(true); - resourceTracker.addResource(splashImageURL, file.getFileVersion(), updatePolicy); - splashScreen = new JNLPSplashScreen(resourceTracker, null, null); - splashScreen.setSplashImageURL(splashImageURL); - if (splashScreen.isSplashScreenValid()) { - splashScreen.setVisible(true); - } - } - - - ApplicationInstance app = createApplication(file); - app.initialize(); - - String mainName = file.getApplication().getMainClass(); - - // When the application-desc field is empty, we should take a - // look at the main jar for the main class. - if (mainName == null) { - JARDesc mainJarDesc = file.getResources().getMainJAR(); - File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null); - if (f != null) { - JarFile mainJar = new JarFile(f); - mainName = mainJar.getManifest(). - getMainAttributes().getValue("Main-Class"); - } - } - - if (mainName == null) - throw launchError(new LaunchException(file, null, - R("LSFatal"), R("LCClient"), R("LCantDetermineMainClass") , - R("LCantDetermineMainClassInfo"))); - - Class mainClass = app.getClassLoader().loadClass(mainName); - - Method main = mainClass.getDeclaredMethod("main", new Class[] {String[].class} ); - String args[] = file.getApplication().getArguments(); - - setContextClassLoaderForAllThreads(app.getClassLoader()); - - if (splashScreen != null) { - if (splashScreen.isSplashScreenValid()) { - splashScreen.setVisible(false); - } - splashScreen.dispose(); - } - - main.invoke(null, new Object[] { args } ); - - return app; - } - catch (LaunchException lex) { - throw launchError(lex); - } - catch (Exception ex) { - throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); - } - } - - /** - * Set the classloader as the context classloader for all threads. This is - * required to make some applications work. For example, an application that - * provides a custom Swing LnF may ask the swing thread to load resources - * from their JNLP, which would only work if the Swing thread knows about - * the JNLPClassLoader. - * - * @param classLoader the classloader to set as the context classloader - */ - private void setContextClassLoaderForAllThreads(ClassLoader classLoader) { - ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); - ThreadGroup root; - - root = Thread.currentThread().getThreadGroup(); - while (root.getParent() != null) { - root = root.getParent(); - } - - /* be prepared for change in thread size */ - int threadCountGuess = threadBean.getThreadCount(); - Thread[] threads; - do { - threadCountGuess = threadCountGuess * 2; - threads = new Thread[threadCountGuess]; - root.enumerate(threads, true); - } while (threads[threadCountGuess-1] != null); - - - for (Thread thread: threads) { - if (thread != null) { - if (JNLPRuntime.isDebug()) { - System.err.println("Setting " + classLoader + " as the classloader for thread " + thread.getName()); - } - thread.setContextClassLoader(classLoader); - } - } - - } - - /** - * Launches a JNLP applet. This method should be called from a - * thread in the application's thread group.<p> - * - * The enableCodeBase parameter adds the applet's codebase to - * the locations searched for resources and classes. This can - * slow down the applet loading but allows browser-style applets - * that don't use JAR files exclusively to be run from a applet - * JNLP file. If the applet JNLP file does not specify any - * resources then the code base will be enabled regardless of - * the specified value.<p> - * - * @param file the JNLP file - * @param enableCodeBase whether to add the codebase URL to the classloader - */ - protected ApplicationInstance launchApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { - if (!file.isApplet()) - throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); - - try { - AppletInstance applet = createApplet(file, enableCodeBase, cont); - applet.initialize(); - - applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance - return applet; - } - catch (LaunchException lex) { - throw launchError(lex); - } - catch (Exception ex) { - throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); - } - } - - /** - * Gets an ApplicationInstance, but does not launch the applet. - */ - protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { - if (!file.isApplet()) - throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); - - try { - AppletInstance applet = createApplet(file, enableCodeBase, cont); - applet.initialize(); - return applet; - } - catch (LaunchException lex) { - throw launchError(lex); - } - catch (Exception ex) { - throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); - } - } - - /** - * Launches a JNLP installer. This method should be called from - * a thread in the application's thread group. - */ - protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException { - throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo"))); - } - - /** - * Create an AppletInstance. - * - * @param enableCodeBase whether to add the code base URL to the classloader - */ - protected AppletInstance createApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { - try { - JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); - - if (enableCodeBase || file.getResources().getJARs().length == 0) - loader.enableCodeBase(); - - AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup(); - - String appletName = file.getApplet().getMainClass(); - - //Classloader chokes if there's '/' in the path to the main class. - //Must replace with '.' instead. - appletName = appletName.replace('/', '.'); - Class appletClass = loader.loadClass(appletName); - Applet applet = (Applet) appletClass.newInstance(); - - AppletInstance appletInstance; - if (cont == null) - appletInstance = new AppletInstance(file, group, loader, applet); - else - appletInstance = new AppletInstance(file, group, loader, applet, cont); - - group.setApplication(appletInstance); - loader.setApplication(appletInstance); - - return appletInstance; - } - catch (Exception ex) { - throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo"))); - } - } - - /** - * Creates an Applet object from a JNLPFile. This is mainly to be used with - * gcjwebplugin. - * @param file the PluginBridge to be used. - * @param enableCodeBase whether to add the code base URL to the classloader. - */ - protected Applet createAppletObject(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { - try { - JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); - - if (enableCodeBase || file.getResources().getJARs().length == 0) - loader.enableCodeBase(); - - String appletName = file.getApplet().getMainClass(); - - //Classloader chokes if there's '/' in the path to the main class. - //Must replace with '.' instead. - appletName = appletName.replace('/', '.'); - Class appletClass = loader.loadClass(appletName); - Applet applet = (Applet) appletClass.newInstance(); - - return applet; - } - catch (Exception ex) { - throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo"))); - } - } - - /** - * Creates an Application. - */ - protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException { - try { - JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); - AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup(); - - ApplicationInstance app = new ApplicationInstance(file, group, loader); - group.setApplication(app); - loader.setApplication(app); - - return app; - } - catch (Exception ex) { - throw new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplication"), R("LInitApplicationInfo")); - } - } - - /** - * Create a thread group for the JNLP file. - */ - protected AppThreadGroup createThreadGroup(JNLPFile file) { - return new AppThreadGroup(mainGroup, file.getTitle()); - } - - /** - * Send n launch error to the handler, if set, and also to the - * caller. - */ - private LaunchException launchError(LaunchException ex) { - if (handler != null) - handler.launchError(ex); - - return ex; - } - - /** - * Send a launch error to the handler, if set, and to the - * caller only if the handler indicated that the launch should - * continue despite the warning. - * - * @return an exception to throw if the launch should be aborted, or null otherwise - */ - private LaunchException launchWarning(LaunchException ex) { - if (handler != null) - if (!handler.launchWarning(ex)) - // no need to destroy the app b/c it hasn't started - return ex; // chose to abort - - return null; // chose to continue, or no handler - } - - - - /** - * This runnable is used to call the appropriate launch method - * for the application, applet, or installer in its thread group. - */ - private class TgThread extends Thread { // ThreadGroupThread - private JNLPFile file; - private ApplicationInstance application; - private LaunchException exception; - private Container cont; - private boolean isPlugin = false; - - TgThread(JNLPFile file) { - this(file, null); - } - - TgThread(JNLPFile file, Container cont) { - super(createThreadGroup(file), file.getTitle()); - - this.file = file; - this.cont = cont; - } - - TgThread(JNLPFile file, Container cont, boolean isPlugin) { - super(createThreadGroup(file), file.getTitle()); - this.file = file; - this.cont = cont; - this.isPlugin = isPlugin; - } - - public void run() { - try { - // Do not create new AppContext if we're using NetX and gcjwebplugin. - if (context && !isPlugin) - new Reflect().invokeStatic("sun.awt.SunToolkit", "createNewAppContext"); - - if (isPlugin) { - // Do not display download indicators if we're using gcjwebplugin. - JNLPRuntime.setDefaultDownloadIndicator(null); - application = getApplet(file, true, cont); - } else { - if (file.isApplication()) - application = launchApplication(file); - else if (file.isApplet()) - application = launchApplet(file, true, cont); // enable applet code base - else if (file.isInstaller()) - application = launchInstaller(file); - else - throw launchError(new LaunchException(file, null, - R("LSFatal"), R("LCClient"), R("LNotLaunchable"), - R("LNotLaunchableInfo"))); - } - } - catch (LaunchException ex) { - ex.printStackTrace(); - exception = ex; - // Exit if we can't launch the application. - if (exitOnFailure) - System.exit(0); - } - } - - public LaunchException getException() { - return exception; - } - - public ApplicationInstance getApplication() { - return application; - } - - }; - - - /** - * This runnable is used by the <code>launchBackground</code> - * methods to launch a JNLP file from a separate thread. - */ - private class BgRunner implements Runnable { - private JNLPFile file; - private URL location; - - BgRunner(JNLPFile file, URL location) { - this.file = file; - this.location = location; - } - - public void run() { - try { - if (file != null) - launch(file); - if (location != null) - launch(location); - } - catch (LaunchException ex) { - // launch method communicates error conditions to the - // handler if it exists, otherwise we don't care because - // there's nothing that can be done about the exception. - } - } - }; - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp; + +import java.applet.Applet; +import java.awt.Container; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; +import java.util.jar.JarFile; + +import net.sourceforge.jnlp.cache.CacheUtil; +import net.sourceforge.jnlp.cache.ResourceTracker; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.runtime.AppThreadGroup; +import net.sourceforge.jnlp.runtime.AppletInstance; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.JNLPClassLoader; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.services.InstanceExistsException; +import net.sourceforge.jnlp.services.ServiceUtil; +import net.sourceforge.jnlp.util.Reflect; + +/** + * Launches JNLPFiles either in the foreground or background.<p> + * + * An optional LaunchHandler can be specified that is notified of + * warning and error condition while launching and that indicates + * whether a launch may proceed after a warning has occurred. If + * specified, the LaunchHandler is notified regardless of whether + * the file is launched in the foreground or background.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.22 $ + */ +public class Launcher { + + // defines class Launcher.BgRunner, Launcher.TgThread + + /** shortcut for resources */ + private static String R(String key) { return JNLPRuntime.getMessage(key); } + + /** shared thread group */ + private static final ThreadGroup mainGroup = new ThreadGroup(R("LAllThreadGroup")); + + /** the handler */ + private LaunchHandler handler = null; + + /** the update policy */ + private UpdatePolicy updatePolicy = JNLPRuntime.getDefaultUpdatePolicy(); + + /** whether to create an AppContext (if possible) */ + private boolean context = true; + + /** If the application should call System.exit on fatal errors */ + private boolean exitOnFailure = true; + + /** + * Create a launcher with the runtime's default update policy + * and launch handler. + */ + public Launcher() { + this(null, null); + + if (handler == null) + handler = JNLPRuntime.getDefaultLaunchHandler(); + } + + /** + * Create a launcher with the runtime's default update policy + * and launch handler. + * + * @param exitOnError Exit if there is an error (usually default, but false when being used from the plugin) + */ + public Launcher(boolean exitOnFailure) { + this(null, null); + + if (handler == null) + handler = JNLPRuntime.getDefaultLaunchHandler(); + + this.exitOnFailure = exitOnFailure; + } + + /** + * Create a launcher with the specified handler and the + * runtime's default update policy. + * + * @param handler the handler to use or null for no handler. + */ + public Launcher(LaunchHandler handler) { + this(handler, null); + } + + /** + * Create a launcher with an optional handler using the + * specified update policy and launch handler. + * + * @param handler the handler to use or null for no handler. + * @param policy the update policy to use or null for default policy. + */ + public Launcher(LaunchHandler handler, UpdatePolicy policy) { + if (policy == null) + policy = JNLPRuntime.getDefaultUpdatePolicy(); + + this.handler = handler; + this.updatePolicy = policy; + } + + /** + * Sets the update policy used by launched applications. + */ + public void setUpdatePolicy(UpdatePolicy policy) { + if (policy == null) + throw new IllegalArgumentException(R("LNullUpdatePolicy")); + + this.updatePolicy = policy; + } + + /** + * Returns the update policy used when launching applications. + */ + public UpdatePolicy getUpdatePolicy() { + return updatePolicy; + } + + /** + * Sets whether to launch the application in a new AppContext + * (a separate event queue, look and feel, etc). If the + * sun.awt.SunToolkit class is not present then this method + * has no effect. The default value is true. + */ + public void setCreateAppContext(boolean context) { + this.context = context; + } + + /** + * Returns whether applications are launched in their own + * AppContext. + */ + public boolean isCreateAppContext() { + return this.context; + } + + /** + * Launches a JNLP file by calling the launch method for the + * appropriate file type. The application will be started in + * a new window. + * + * @param file the JNLP file to launch + * @return the application instance + * @throws LaunchException if an error occurred while launching (also sent to handler) + */ + public ApplicationInstance launch(JNLPFile file) throws LaunchException { + return launch(file, null); + } + + /** + * Launches a JNLP file inside the given container if it is an applet. Specifying a + * container has no effect for Applcations and Installers. + * + * @param file the JNLP file to launch + * @param cont the container in which to place the application, if it is an applet + * @return the application instance + * @throws LaunchException if an error occurred while launching (also sent to handler) + */ + public ApplicationInstance launch(JNLPFile file, Container cont) throws LaunchException { + TgThread tg; + + if (file instanceof PluginBridge && cont != null) + tg = new TgThread(file, cont, true); + else if (cont == null) + tg = new TgThread(file); + else + tg = new TgThread(file, cont); + + tg.start(); + + try { + tg.join(); + } + catch (InterruptedException ex) { + //By default, null is thrown here, and the message dialog is shown. + throw launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCSystem"), R("LThreadInterrupted"), R("LThreadInterruptedInfo"))); + } + + if (tg.getException() != null) + throw tg.getException(); // passed to handler when first created + + if (handler != null) + handler.launchCompleted(tg.getApplication()); + + return tg.getApplication(); + } + + /** + * Launches a JNLP file by calling the launch method for the + * appropriate file type. + * + * @param location the URL of the JNLP file to launch + * @throws LaunchException if there was an exception + * @return the application instance + */ + public ApplicationInstance launch(URL location) throws LaunchException { + return launch(toFile(location)); + } + + /** + * Launches a JNLP file by calling the launch method for the + * appropriate file type in a different thread. + * + * @param file the JNLP file to launch + */ + public void launchBackground(JNLPFile file) { + BgRunner runner = new BgRunner(file, null); + new Thread(runner).start(); + } + + /** + * Launches the JNLP file at the specified location in the + * background by calling the launch method for its file type. + * + * @param location the location of the JNLP file + */ + public void launchBackground(URL location) { + BgRunner runner = new BgRunner(null, location); + new Thread(runner).start(); + } + + /** + * Launches the JNLP file in a new JVM instance. The launched + * application's output is sent to the system out and it's + * standard input channel is closed. + * + * @param vmArgs the arguments to pass to the new JVM. Can be empty but + * must not be null. + * @param file the JNLP file to launch + * @param javawsArgs the arguments to pass to the javaws command. Can be + * an empty list but must not be null. + * @throws LaunchException if there was an exception + */ + public void launchExternal(List<String> vmArgs, JNLPFile file, List<String> javawsArgs) throws LaunchException { + List<String> updatedArgs = new LinkedList<String>(javawsArgs); + + if (file.getFileLocation() != null) + updatedArgs.add(file.getFileLocation().toString()); + else if (file.getSourceLocation() != null) + updatedArgs.add(file.getFileLocation().toString()); + else + launchError(new LaunchException(file, null, R("LSFatal"), R("LCExternalLaunch"), R("LNullLocation"), R("LNullLocationInfo"))); + + launchExternal(vmArgs, updatedArgs); + + } + + /** + * Launches the JNLP file in a new JVM instance. The launched + * application's output is sent to the system out and it's + * standard input channel is closed. + * + * @param url the URL of the JNLP file to launch + * @throws LaunchException if there was an exception + */ + public void launchExternal(URL url) throws LaunchException { + List<String> javawsArgs = new LinkedList<String>(); + javawsArgs.add(url.toString()); + launchExternal(new LinkedList<String>(), javawsArgs); + } + + /** + * Launches the JNLP file at the specified location in a new JVM + * instance. The launched application's output is sent to the + * system out and it's standard input channel is closed. + * @param vmArgs the arguments to pass to the jvm + * @param javawsArgs the arguments to pass to javaws (aka Netx) + * @throws LaunchException if there was an exception + */ + public void launchExternal(List<String> vmArgs, List<String> javawsArgs) throws LaunchException { + try { + + List<String> commands = new LinkedList<String>(); + + String pathToWebstartBinary = System.getProperty("java.home") + + File.separatorChar + + "bin" + + File.separatorChar + + "javaws"; + commands.add(pathToWebstartBinary); + // use -Jargument format to pass arguments to the JVM through the launcher + for (String arg: vmArgs) { + commands.add("-J" + arg); + } + commands.addAll(javawsArgs); + + String[] command = commands.toArray(new String[] {}); + + Process p = Runtime.getRuntime().exec(command); + new StreamEater(p.getErrorStream()).start(); + new StreamEater(p.getInputStream()).start(); + p.getOutputStream().close(); + + } + catch (NullPointerException ex) { + throw launchError(new LaunchException(null, null, R("LSFatal"), R("LCExternalLaunch"), R("LNetxJarMissing"), R("LNetxJarMissingInfo"))); + } + catch (Exception ex) { + throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCExternalLaunch"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); + } + } + + /** + * Returns the JNLPFile for the URL, with error handling. + */ + private JNLPFile toFile(URL location) throws LaunchException { + try { + JNLPFile file = null; + + try { + file = new JNLPFile(location, (Version) null, true, updatePolicy); // strict + } + catch (ParseException ex) { + file = new JNLPFile(location, (Version) null, false, updatePolicy); + + // only here if strict failed but lax did not fail + LaunchException lex = + launchWarning(new LaunchException(file, ex, R("LSMinor"), R("LCFileFormat"), R("LNotToSpec"), R("LNotToSpecInfo"))); + + if (lex != null) + throw lex; + } + + return file; + } + catch (Exception ex) { + if (ex instanceof LaunchException) + throw (LaunchException) ex; // already sent to handler when first thrown + else // IO and Parse + throw launchError(new LaunchException(null, ex, R("LSFatal"), R("LCReadError"), R("LCantRead"), R("LCantReadInfo"))); + } + } + + /** + * Launches a JNLP application. This method should be called + * from a thread in the application's thread group. + */ + protected ApplicationInstance launchApplication(JNLPFile file) throws LaunchException { + if (!file.isApplication()) + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplication"), R("LNotApplicationInfo"))); + + try { + + try { + ServiceUtil.checkExistingSingleInstance(file); + } catch (InstanceExistsException e) { + return null; + } + + if (JNLPRuntime.getForksAllowed() && file.needsNewVM()) { + List<String> netxArguments = new LinkedList<String>(); + netxArguments.add("-Xnofork"); + netxArguments.addAll(JNLPRuntime.getInitialArguments()); + launchExternal(file.getNewVMArgs(), netxArguments); + return null; + } + + final int preferredWidth = 500; + final int preferredHeight = 400; + JNLPSplashScreen splashScreen = null; + URL splashImageURL = file.getInformation().getIconLocation( + IconDesc.SPLASH, preferredWidth, preferredHeight); + if (splashImageURL != null) { + ResourceTracker resourceTracker = new ResourceTracker(true); + resourceTracker.addResource(splashImageURL, file.getFileVersion(), updatePolicy); + splashScreen = new JNLPSplashScreen(resourceTracker, null, null); + splashScreen.setSplashImageURL(splashImageURL); + if (splashScreen.isSplashScreenValid()) { + splashScreen.setVisible(true); + } + } + + + ApplicationInstance app = createApplication(file); + app.initialize(); + + String mainName = file.getApplication().getMainClass(); + + // When the application-desc field is empty, we should take a + // look at the main jar for the main class. + if (mainName == null) { + JARDesc mainJarDesc = file.getResources().getMainJAR(); + File f = CacheUtil.getCacheFile(mainJarDesc.getLocation(), null); + if (f != null) { + JarFile mainJar = new JarFile(f); + mainName = mainJar.getManifest(). + getMainAttributes().getValue("Main-Class"); + } + } + + if (mainName == null) + throw launchError(new LaunchException(file, null, + R("LSFatal"), R("LCClient"), R("LCantDetermineMainClass") , + R("LCantDetermineMainClassInfo"))); + + Class mainClass = app.getClassLoader().loadClass(mainName); + + Method main = mainClass.getDeclaredMethod("main", new Class[] {String[].class} ); + String args[] = file.getApplication().getArguments(); + + setContextClassLoaderForAllThreads(app.getClassLoader()); + + if (splashScreen != null) { + if (splashScreen.isSplashScreenValid()) { + splashScreen.setVisible(false); + } + splashScreen.dispose(); + } + + main.invoke(null, new Object[] { args } ); + + return app; + } + catch (LaunchException lex) { + throw launchError(lex); + } + catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); + } + } + + /** + * Set the classloader as the context classloader for all threads. This is + * required to make some applications work. For example, an application that + * provides a custom Swing LnF may ask the swing thread to load resources + * from their JNLP, which would only work if the Swing thread knows about + * the JNLPClassLoader. + * + * @param classLoader the classloader to set as the context classloader + */ + private void setContextClassLoaderForAllThreads(ClassLoader classLoader) { + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + ThreadGroup root; + + root = Thread.currentThread().getThreadGroup(); + while (root.getParent() != null) { + root = root.getParent(); + } + + /* be prepared for change in thread size */ + int threadCountGuess = threadBean.getThreadCount(); + Thread[] threads; + do { + threadCountGuess = threadCountGuess * 2; + threads = new Thread[threadCountGuess]; + root.enumerate(threads, true); + } while (threads[threadCountGuess-1] != null); + + + for (Thread thread: threads) { + if (thread != null) { + if (JNLPRuntime.isDebug()) { + System.err.println("Setting " + classLoader + " as the classloader for thread " + thread.getName()); + } + thread.setContextClassLoader(classLoader); + } + } + + } + + /** + * Launches a JNLP applet. This method should be called from a + * thread in the application's thread group.<p> + * + * The enableCodeBase parameter adds the applet's codebase to + * the locations searched for resources and classes. This can + * slow down the applet loading but allows browser-style applets + * that don't use JAR files exclusively to be run from a applet + * JNLP file. If the applet JNLP file does not specify any + * resources then the code base will be enabled regardless of + * the specified value.<p> + * + * @param file the JNLP file + * @param enableCodeBase whether to add the codebase URL to the classloader + */ + protected ApplicationInstance launchApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { + if (!file.isApplet()) + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); + + try { + AppletInstance applet = createApplet(file, enableCodeBase, cont); + applet.initialize(); + + applet.getAppletEnvironment().startApplet(); // this should be a direct call to applet instance + return applet; + } + catch (LaunchException lex) { + throw launchError(lex); + } + catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); + } + } + + /** + * Gets an ApplicationInstance, but does not launch the applet. + */ + protected ApplicationInstance getApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { + if (!file.isApplet()) + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LNotApplet"), R("LNotAppletInfo"))); + + try { + AppletInstance applet = createApplet(file, enableCodeBase, cont); + applet.initialize(); + return applet; + } + catch (LaunchException lex) { + throw launchError(lex); + } + catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCLaunching"), R("LCouldNotLaunch"), R("LCouldNotLaunchInfo"))); + } + } + + /** + * Launches a JNLP installer. This method should be called from + * a thread in the application's thread group. + */ + protected ApplicationInstance launchInstaller(JNLPFile file) throws LaunchException { + throw launchError(new LaunchException(file, null, R("LSFatal"), R("LCNotSupported"), R("LNoInstallers"), R("LNoInstallersInfo"))); + } + + /** + * Create an AppletInstance. + * + * @param enableCodeBase whether to add the code base URL to the classloader + */ + protected AppletInstance createApplet(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { + try { + JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); + + if (enableCodeBase || file.getResources().getJARs().length == 0) + loader.enableCodeBase(); + + AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup(); + + String appletName = file.getApplet().getMainClass(); + + //Classloader chokes if there's '/' in the path to the main class. + //Must replace with '.' instead. + appletName = appletName.replace('/', '.'); + Class appletClass = loader.loadClass(appletName); + Applet applet = (Applet) appletClass.newInstance(); + + AppletInstance appletInstance; + if (cont == null) + appletInstance = new AppletInstance(file, group, loader, applet); + else + appletInstance = new AppletInstance(file, group, loader, applet, cont); + + group.setApplication(appletInstance); + loader.setApplication(appletInstance); + + return appletInstance; + } + catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo"))); + } + } + + /** + * Creates an Applet object from a JNLPFile. This is mainly to be used with + * gcjwebplugin. + * @param file the PluginBridge to be used. + * @param enableCodeBase whether to add the code base URL to the classloader. + */ + protected Applet createAppletObject(JNLPFile file, boolean enableCodeBase, Container cont) throws LaunchException { + try { + JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); + + if (enableCodeBase || file.getResources().getJARs().length == 0) + loader.enableCodeBase(); + + String appletName = file.getApplet().getMainClass(); + + //Classloader chokes if there's '/' in the path to the main class. + //Must replace with '.' instead. + appletName = appletName.replace('/', '.'); + Class appletClass = loader.loadClass(appletName); + Applet applet = (Applet) appletClass.newInstance(); + + return applet; + } + catch (Exception ex) { + throw launchError(new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplet"), R("LInitAppletInfo"))); + } + } + + /** + * Creates an Application. + */ + protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException { + try { + JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy); + AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup(); + + ApplicationInstance app = new ApplicationInstance(file, group, loader); + group.setApplication(app); + loader.setApplication(app); + + return app; + } + catch (Exception ex) { + throw new LaunchException(file, ex, R("LSFatal"), R("LCInit"), R("LInitApplication"), R("LInitApplicationInfo")); + } + } + + /** + * Create a thread group for the JNLP file. + */ + protected AppThreadGroup createThreadGroup(JNLPFile file) { + return new AppThreadGroup(mainGroup, file.getTitle()); + } + + /** + * Send n launch error to the handler, if set, and also to the + * caller. + */ + private LaunchException launchError(LaunchException ex) { + if (handler != null) + handler.launchError(ex); + + return ex; + } + + /** + * Send a launch error to the handler, if set, and to the + * caller only if the handler indicated that the launch should + * continue despite the warning. + * + * @return an exception to throw if the launch should be aborted, or null otherwise + */ + private LaunchException launchWarning(LaunchException ex) { + if (handler != null) + if (!handler.launchWarning(ex)) + // no need to destroy the app b/c it hasn't started + return ex; // chose to abort + + return null; // chose to continue, or no handler + } + + + + /** + * This runnable is used to call the appropriate launch method + * for the application, applet, or installer in its thread group. + */ + private class TgThread extends Thread { // ThreadGroupThread + private JNLPFile file; + private ApplicationInstance application; + private LaunchException exception; + private Container cont; + private boolean isPlugin = false; + + TgThread(JNLPFile file) { + this(file, null); + } + + TgThread(JNLPFile file, Container cont) { + super(createThreadGroup(file), file.getTitle()); + + this.file = file; + this.cont = cont; + } + + TgThread(JNLPFile file, Container cont, boolean isPlugin) { + super(createThreadGroup(file), file.getTitle()); + this.file = file; + this.cont = cont; + this.isPlugin = isPlugin; + } + + public void run() { + try { + // Do not create new AppContext if we're using NetX and gcjwebplugin. + if (context && !isPlugin) + new Reflect().invokeStatic("sun.awt.SunToolkit", "createNewAppContext"); + + if (isPlugin) { + // Do not display download indicators if we're using gcjwebplugin. + JNLPRuntime.setDefaultDownloadIndicator(null); + application = getApplet(file, true, cont); + } else { + if (file.isApplication()) + application = launchApplication(file); + else if (file.isApplet()) + application = launchApplet(file, true, cont); // enable applet code base + else if (file.isInstaller()) + application = launchInstaller(file); + else + throw launchError(new LaunchException(file, null, + R("LSFatal"), R("LCClient"), R("LNotLaunchable"), + R("LNotLaunchableInfo"))); + } + } + catch (LaunchException ex) { + ex.printStackTrace(); + exception = ex; + // Exit if we can't launch the application. + if (exitOnFailure) + System.exit(0); + } + } + + public LaunchException getException() { + return exception; + } + + public ApplicationInstance getApplication() { + return application; + } + + }; + + + /** + * This runnable is used by the <code>launchBackground</code> + * methods to launch a JNLP file from a separate thread. + */ + private class BgRunner implements Runnable { + private JNLPFile file; + private URL location; + + BgRunner(JNLPFile file, URL location) { + this.file = file; + this.location = location; + } + + public void run() { + try { + if (file != null) + launch(file); + if (location != null) + launch(location); + } + catch (LaunchException ex) { + // launch method communicates error conditions to the + // handler if it exists, otherwise we don't care because + // there's nothing that can be done about the exception. + } + } + }; + +} + +
--- a/rt/net/sourceforge/jnlp/ParseException.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/ParseException.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,94 +1,94 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp; - -import java.io.*; -import java.net.*; -import java.util.*; - -/** - * Thrown to indicate that an error has occurred while parsing a - * JNLP file. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.7 $ - */ -public class ParseException extends Exception { - - // todo: add meaningful information, such as the invalid - // element, parse position, etc. - - /** the original exception */ - private Throwable cause = null; - - - /** - * Create a parse exception with the specified message. - */ - public ParseException(String message) { - super(message); - } - - /** - * Create a parse exception with the specified message and - * cause. - */ - public ParseException(String message, Throwable cause) { - super(message); - - // replace with setCause when no longer 1.3 compatible - this.cause = cause; - } - - /** - * Return the cause of the launch exception or null if there - * is no cause exception. - */ - public Throwable getCause() { - return cause; - } - - /** - * Print the stack trace and the cause exception (1.3 - * compatible) - */ - public void printStackTrace(PrintStream stream) { - super.printStackTrace(stream); - - if (cause != null) { - stream.println("Caused by: "); - cause.printStackTrace(stream); - } - } - - /** - * Print the stack trace and the cause exception (1.3 - * compatible) - */ - public void printStackTrace(PrintWriter stream) { - super.printStackTrace(stream); - - if (cause != null) { - stream.println("Caused by: "); - cause.printStackTrace(stream); - } - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * Thrown to indicate that an error has occurred while parsing a + * JNLP file. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.7 $ + */ +public class ParseException extends Exception { + + // todo: add meaningful information, such as the invalid + // element, parse position, etc. + + /** the original exception */ + private Throwable cause = null; + + + /** + * Create a parse exception with the specified message. + */ + public ParseException(String message) { + super(message); + } + + /** + * Create a parse exception with the specified message and + * cause. + */ + public ParseException(String message, Throwable cause) { + super(message); + + // replace with setCause when no longer 1.3 compatible + this.cause = cause; + } + + /** + * Return the cause of the launch exception or null if there + * is no cause exception. + */ + public Throwable getCause() { + return cause; + } + + /** + * Print the stack trace and the cause exception (1.3 + * compatible) + */ + public void printStackTrace(PrintStream stream) { + super.printStackTrace(stream); + + if (cause != null) { + stream.println("Caused by: "); + cause.printStackTrace(stream); + } + } + + /** + * Print the stack trace and the cause exception (1.3 + * compatible) + */ + public void printStackTrace(PrintWriter stream) { + super.printStackTrace(stream); + + if (cause != null) { + stream.println("Caused by: "); + cause.printStackTrace(stream); + } + } + +} + +
--- a/rt/net/sourceforge/jnlp/Parser.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/Parser.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,1206 +1,1206 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// Copyright (C) 2009 Red Hat, Inc. -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp; - -import java.io.*; -import java.net.*; -import java.util.*; -//import javax.xml.parsers.*; // commented to use right Node -//import org.w3c.dom.*; // class for using Tiny XML | NanoXML -//import org.xml.sax.*; -//import gd.xml.tiny.*; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.nanoxml.*; - - -/** - * Contains methods to parse an XML document into a JNLPFile. - * Implements JNLP specification version 1.0. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.13 $ - */ -class Parser { - - private static String R(String key) { return JNLPRuntime.getMessage(key); } - private static String R(String key, Object p1) { return R(key, p1, null); } - private static String R(String key, Object p1, Object p2) { return R(key, p1, p2, null); } - private static String R(String key, Object p1, Object p2, Object p3) { return JNLPRuntime.getMessage(key, new Object[] { p1, p2, p3 }); } - - - // defines netx.jnlp.Node class if using Tiny XML or Nano XML - - // Currently uses the Nano XML parse. Search for "SAX" or - // "TINY" or "NANO" and uncomment those blocks and comment the - // active ones (if any) to switch XML parsers. Also - // (un)comment appropriate Node class at end of this file and - // do a clean build. - - /** - * Ensure consistent error handling. - */ - /* SAX - static ErrorHandler errorHandler = new ErrorHandler() { - public void error(SAXParseException exception) throws SAXParseException { - //throw exception; - } - public void fatalError(SAXParseException exception) throws SAXParseException { - //throw exception; - } - public void warning(SAXParseException exception) { - System.err.println("XML parse warning:"); - exception.printStackTrace(); - } - }; - */ - - - /** the supported JNLP file versions */ - private static Version supportedVersions = new Version("1.0 1.5 6.0"); - - // fix: some descriptors need to use the jnlp file at a later - // date and having file ref lets us pass it to their - // constructors - // - /** the file reference */ - private JNLPFile file; // do not use (uninitialized) - - /** the root node */ - private Node root; - - /** the specification version */ - private Version spec; - - /** the base URL that all hrefs are relative to */ - private URL base; - - /** the codebase URL */ - private URL codebase; - - /** the file URL */ - private URL fileLocation; - - /** whether to throw errors on non-fatal errors. */ - private boolean strict; // if strict==true parses a file with no error then strict==false should also - - /** whether to allow extensions to the JNLP specification */ - private boolean allowExtensions; // true if extensions to JNLP spec are ok - - - /** - * Create a parser for the JNLP file. If the location - * parameters is not null it is used as the default codebase - * (does not override value of jnlp element's href - * attribute).<p> - * - * The root node may be normalized as a side effect of this - * constructor. - * - * @param file the (uninitialized) file reference - * @param base if codebase is not specified, a default base for relative URLs - * @param root the root node - * @param strict whether to enforce strict compliance with the JNLP spec - * @param allowExtensions whether to allow extensions to the JNLP spec - * @throws ParseException if the JNLP file is invalid - */ - public Parser(JNLPFile file, URL base, Node root, boolean strict, boolean allowExtensions) throws ParseException { - this.file = file; - this.root = root; - this.strict = strict; - this.allowExtensions = allowExtensions; - - // ensure it's a JNLP node - if (root == null || !root.getNodeName().equals("jnlp")) - throw new ParseException(R("PInvalidRoot")); - - // JNLP tag information - this.spec = getVersion(root, "spec", "1.0+"); - this.codebase = addSlash(getURL(root, "codebase", base)); - this.base = (codebase!=null) ? codebase : base; // if codebase not specified use default codebase - fileLocation = getURL(root, "href", this.base); - - // ensure version is supported - if (!supportedVersions.matchesAny(spec)) - throw new ParseException(R("PSpecUnsupported", supportedVersions)); - - // normalize the text nodes - root.normalize(); - } - - /** - * Return the JNLP specification versions supported. - */ - public static Version getSupportedVersions() { - return supportedVersions; - } - - /** - * Returns the file version. - */ - public Version getFileVersion() { - return getVersion(root, "version", null); - } - - /** - * Returns the file location. - */ - public URL getFileLocation() { - return fileLocation; - } - - /** - * Returns the codebase. - */ - public URL getCodeBase() { - return codebase; - } - - /** - * Returns the specification version. - */ - public Version getSpecVersion() { - return spec; - } - - // - // This section loads the resources elements - // - - /** - * Returns all of the ResourcesDesc elements under the specified - * node (jnlp or j2se). - * - * @param parent the parent node (either jnlp or j2se) - * @param j2se true if the resources are located under a j2se or java node - * @throws ParseException if the JNLP file is invalid - */ - public List getResources(Node parent, boolean j2se) throws ParseException { - List result = new ArrayList(); - Node resources[] = getChildNodes(parent, "resources"); - - // ensure that there are at least one information section present - if (resources.length == 0 && !j2se) - throw new ParseException(R("PNoResources")); - - // create objects from the resources sections - for (int i=0; i < resources.length; i++) - result.add(getResourcesDesc(resources[i], j2se)); - - return result; - } - - /** - * Returns the ResourcesDesc element at the specified node. - * - * @param node the resources node - * @param j2se true if the resources are located under a j2se or java node - * @throws ParseException if the JNLP file is invalid - */ - public ResourcesDesc getResourcesDesc(Node node, boolean j2se) throws ParseException { - boolean mainFlag = false; // if found a main tag - - // create resources - ResourcesDesc resources = - new ResourcesDesc(file, - getLocales(node), - splitString(getAttribute(node, "os", null)), - splitString(getAttribute(node, "arch", null))); - - // step through the elements - Node child = node.getFirstChild(); - while (child != null) { - String name = child.getNodeName(); - - // check for nativelib but no trusted environment - if ("nativelib".equals(name)) - if (!isTrustedEnvironment()) - throw new ParseException(R("PUntrustedNative")); - - if ("j2se".equals(name) || "java".equals(name)) { - if (getChildNode(root, "component-desc") != null) - if (strict) - throw new ParseException(R("PExtensionHasJ2SE")); - if (!j2se) - resources.addResource( getJRE(child) ); - else - throw new ParseException(R("PInnerJ2SE")); - } - - if ("jar".equals(name) || "nativelib".equals(name)) { - JARDesc jar = getJAR(child); - - // check for duplicate main entries - if (jar.isMain()) { - if (mainFlag == true) - if (strict) - throw new ParseException(R("PTwoMains")); - mainFlag = true; - } - - resources.addResource(jar); - } - - if ("extension".equals(name)) - resources.addResource( getExtension(child) ); - - if ("property".equals(name)) - resources.addResource( getProperty(child) ); - - if ("package".equals(name)) - resources.addResource( getPackage(child) ); - - child = child.getNextSibling(); - } - - return resources; - } - - /** - * Returns the JRE element at the specified node. - * - * @param node the j2se/java node - * @throws ParseException if the JNLP file is invalid - */ - public JREDesc getJRE(Node node) throws ParseException { - Version version = getVersion(node, "version", null); - URL location = getURL(node, "href", base); - String vmArgs = getAttribute(node, "java-vm-args",null); - try { - checkVMArgs(vmArgs); - } catch (IllegalArgumentException argumentException) { - vmArgs = null; - } - String initialHeap = getAttribute(node, "initial-heap-size", null); - String maxHeap = getAttribute(node, "max-heap-size", null); - List resources = getResources(node, true); - - // require version attribute - getRequiredAttribute(node, "version", null); - - return new JREDesc(version, location, vmArgs, initialHeap, maxHeap, resources); - } - - - - /** - * Returns the JAR element at the specified node. - * - * @param node the jar or nativelib node - * @throws ParseException if the JNLP file is invalid - */ - public JARDesc getJAR(Node node) throws ParseException { - boolean nativeJar = "nativelib".equals(node.getNodeName()); - URL location = getRequiredURL(node, "href", base); - Version version = getVersion(node, "version", null); - String part = getAttribute(node, "part", null); - boolean main = "true".equals(getAttribute(node, "main", "false")); - boolean lazy = "lazy".equals(getAttribute(node, "download", "eager")); - int size = Integer.parseInt(getAttribute(node, "size", "0")); - - if (nativeJar && main) - if (strict) - throw new ParseException(R("PNativeHasMain")); - - return new JARDesc(location, version, part, lazy, main, nativeJar, true); - - } - - /** - * Returns the Extension element at the specified node. - * - * @param node the extension node - * @throws ParseException if the JNLP file is invalid - */ - public ExtensionDesc getExtension(Node node) throws ParseException { - String name = getAttribute(node, "name", null); - Version version = getVersion(node, "version", null); - URL location = getRequiredURL(node, "href", base); - - ExtensionDesc ext = new ExtensionDesc(name, version, location); - - Node dload[] = getChildNodes(node, "ext-download"); - for (int i=0; i < dload.length; i++) { - boolean lazy = "lazy".equals(getAttribute(dload[i], "download", "eager")); - - ext.addPart(getRequiredAttribute(dload[i], "ext-part", null), - getAttribute(dload[i], "part", null), - lazy); - } - - return ext; - } - - /** - * Returns the Property element at the specified node. - * - * @param node the property node - * @throws ParseException if the JNLP file is invalid - */ - public PropertyDesc getProperty(Node node) throws ParseException { - String name = getRequiredAttribute(node, "name", null); - String value = getRequiredAttribute(node, "value", ""); - - return new PropertyDesc(name, value); - } - - /** - * Returns the Package element at the specified node. - * - * @param node the package node - * @throws ParseException if the JNLP file is invalid - */ - public PackageDesc getPackage(Node node) throws ParseException { - String name = getRequiredAttribute(node, "name", null); - String part = getRequiredAttribute(node, "part", ""); - boolean recursive = getAttribute(node, "recursive", "false").equals("true"); - - return new PackageDesc(name, part, recursive); - } - - // - // This section loads the information elements - // - - /** - * Returns all of the information elements under the specified - * node. - * - * @param parent the parent node (jnlp) - * @throws ParseException if the JNLP file is invalid - */ - public List getInfo(Node parent) throws ParseException { - List result = new ArrayList(); - Node info[] = getChildNodes(parent, "information"); - - // ensure that there are at least one information section present - if (info.length == 0) - throw new ParseException(R("PNoInfoElement")); - - // create objects from the info sections - for (int i=0; i < info.length; i++) - result.add(getInformationDesc(info[i])); - - return result; - } - - /** - * Returns the information element at the specified node. - * - * @param node the information node - * @throws ParseException if the JNLP file is invalid - */ - public InformationDesc getInformationDesc(Node node) throws ParseException { - List descriptionsUsed = new ArrayList(); - - // locale - Locale locales[] = getLocales(node); - - // create information - InformationDesc info = new InformationDesc(file, locales); - - // step through the elements - Node child = node.getFirstChild(); - while (child != null) { - String name = child.getNodeName(); - - if ("title".equals(name)) - addInfo(info, child, null, getSpanText(child)); - if ("vendor".equals(name)) - addInfo(info, child, null, getSpanText(child)); - if ("description".equals(name)) { - String kind = getAttribute(child, "kind", "default"); - if (descriptionsUsed.contains(kind)) - if (strict) - throw new ParseException(R("PTwoDescriptions", kind)); - - descriptionsUsed.add(kind); - addInfo(info, child, kind, getSpanText(child)); - } - if ("homepage".equals(name)) - addInfo(info, child, null, getRequiredURL(child, "href", base)); - if ("icon".equals(name)) - addInfo(info, child, getAttribute(child, "kind", "default"), getIcon(child)); - if ("offline-allowed".equals(name)) - addInfo(info, child, null, Boolean.TRUE); - if ("sharing-allowed".equals(name)) { - if (strict && !allowExtensions) - throw new ParseException(R("PSharing")); - addInfo(info, child, null, Boolean.TRUE); - } - if ("association".equals(name)) { - addInfo(info, child, null, getAssociation(child)); - } - if ("shortcut".equals(name)) { - addInfo(info, child, null, getShortcut(child)); - } - if ("related-content".equals(name)) { - addInfo(info, child, null, getRelatedContent(child)); - } - - child = child.getNextSibling(); - } - - return info; - } - - /** - * Adds a key,value pair to the information object. - * - * @param info the information object - * @param node node name to be used as the key - * @param mod key name appended with "-"+mod if not null - * @param value the info object to add (icon or string) - */ - protected void addInfo(InformationDesc info, Node node, String mod, Object value) { - String modStr = (mod == null) ? "" : "-"+mod; - - if (node == null) - return; - - info.addItem(node.getNodeName()+modStr, value); - } - - /** - * Returns the icon element at the specified node. - * - * @param node the icon node - * @throws ParseException if the JNLP file is invalid - */ - public IconDesc getIcon(Node node) throws ParseException { - int width = Integer.parseInt(getAttribute(node, "width", "-1")); - int height = Integer.parseInt(getAttribute(node, "height", "-1")); - int size = Integer.parseInt(getAttribute(node, "size", "-1")); - int depth = Integer.parseInt(getAttribute(node, "depth", "-1")); - URL location = getRequiredURL(node, "href", base); - Object kind = getAttribute(node, "kind", "default"); - - return new IconDesc(location, kind, width, height, depth, size); - } - - // - // This section loads the security descriptor element - // - - /** - * Returns the security descriptor element. If no security - * element was specified in the JNLP file then a SecurityDesc - * with applet permissions is returned. - * - * @param parent the parent node - * @throws ParseException if the JNLP file is invalid - */ - public SecurityDesc getSecurity(Node parent) throws ParseException { - Node nodes[] = getChildNodes(parent, "security"); - - // test for too many security elements - if (nodes.length > 1) - if (strict) - throw new ParseException(R("PTwoSecurity")); - - Object type = SecurityDesc.SANDBOX_PERMISSIONS; - - if (nodes.length == 0) - type = SecurityDesc.SANDBOX_PERMISSIONS; - else if (null != getChildNode(nodes[0], "all-permissions")) - type = SecurityDesc.ALL_PERMISSIONS; - else if (null != getChildNode(nodes[0], "j2ee-application-client-permissions")) - type = SecurityDesc.J2EE_PERMISSIONS; - else if (strict) - throw new ParseException(R("PEmptySecurity")); - - if (base != null) - return new SecurityDesc(file, type, base.getHost()); - else - return new SecurityDesc(file, type, null); - } - - /** - * Returns whether the JNLP file requests a trusted execution - * environment. - */ - protected boolean isTrustedEnvironment() { - Node security = getChildNode(root, "security"); - - if (security != null) - if (getChildNode(security, "all-permissions") != null - || getChildNode(security, "j2ee-application-client-permissions") != null) - return true; - - return false; - } - - // - // This section loads the launch descriptor element - // - - /** - * Returns the launch descriptor element, either AppletDesc, - * ApplicationDesc, ComponentDesc, or InstallerDesc. - * - * @param parent the parent node - * @throws ParseException if the JNLP file is invalid - */ - public Object getLauncher(Node parent) throws ParseException { - // check for other than one application type - if (1 != getChildNodes(parent, "applet-desc").length - + getChildNodes(parent, "application-desc").length - + getChildNodes(parent, "component-desc").length - + getChildNodes(parent, "installer-desc").length) - throw new ParseException(R("PTwoDescriptors")); - - Node child = parent.getFirstChild(); - while (child != null) { - String name = child.getNodeName(); - - if ("applet-desc".equals(name)) - return getApplet(child); - if ("application-desc".equals(name)) - return getApplication(child); - if ("component-desc".equals(name)) - return getComponent(child); - if ("installer-desc".equals(name)) - return getInstaller(child); - - child = child.getNextSibling(); - } - - // not reached - return null; - } - - /** - * Returns the applet descriptor. - * - * @throws ParseException if the JNLP file is invalid - */ - public AppletDesc getApplet(Node node) throws ParseException { - String name = getRequiredAttribute(node, "name", R("PUnknownApplet")); - String main = getRequiredAttribute(node, "main-class", null); - URL docbase = getURL(node, "documentbase", base); - Map paramMap = new HashMap(); - int width = 0; - int height = 0; - - try { - width = Integer.parseInt(getRequiredAttribute(node, "width", "100")); - height = Integer.parseInt(getRequiredAttribute(node, "height", "100")); - } - catch (NumberFormatException nfe) { - if (width <= 0) - throw new ParseException(R("PBadWidth")); - throw new ParseException(R("PBadWidth")); - } - - // read params - Node params[] = getChildNodes(node, "param"); - for (int i=0; i < params.length; i++) { - paramMap.put(getRequiredAttribute(params[i], "name", null), - getRequiredAttribute(params[i], "value", "")); - } - - return new AppletDesc(name, main, docbase, width, height, paramMap); - } - - /** - * Returns the application descriptor. - * - * @throws ParseException if the JNLP file is invalid - */ - public ApplicationDesc getApplication(Node node) throws ParseException { - String main = getAttribute(node, "main-class", null); - List argsList = new ArrayList(); - - // if (main == null) - // only ok if can be found in main jar file (can't check here but make a note) - - // read parameters - Node args[] = getChildNodes(node, "argument"); - for (int i=0; i < args.length; i++) { - //argsList.add( args[i].getNodeValue() ); - - //This approach was not finding the argument text - argsList.add( getSpanText(args[i]) ); - } - - String argStrings[] = - (String[]) argsList.toArray( new String[argsList.size()] ); - - return new ApplicationDesc(main, argStrings); - } - - /** - * Returns the component descriptor. - */ - public ComponentDesc getComponent(Node node) { - return new ComponentDesc(); - } - - /** - * Returns the installer descriptor. - */ - public InstallerDesc getInstaller(Node node) { - String main = getAttribute(node, "main-class", null); - - return new InstallerDesc(main); - } - - /** - * Returns the association descriptor. - */ - public AssociationDesc getAssociation(Node node) throws ParseException { - String[] extensions = getRequiredAttribute(node, "extensions", null).split(" "); - String mimeType = getRequiredAttribute(node, "mime-type", null); - - return new AssociationDesc(mimeType, extensions); - } - - /** - * Returns the shortcut descriptor. - */ - public ShortcutDesc getShortcut(Node node) throws ParseException { - - String online = getAttribute(node, "online", "true"); - boolean shortcutIsOnline = Boolean.valueOf(online); - - boolean showOnDesktop = false; - MenuDesc menu = null; - - // step through the elements - Node child = node.getFirstChild(); - while (child != null) { - String name = child.getNodeName(); - - if ("desktop".equals(name)) { - if (showOnDesktop && strict) { - throw new ParseException(R("PTwoDesktops")); - } - showOnDesktop = true; - } else if ("menu".equals(name)){ - if (menu != null && strict) { - throw new ParseException(R("PTwoMenus")); - } - menu = getMenu(child); - } - - child = child.getNextSibling(); - } - - ShortcutDesc shortcut = new ShortcutDesc(shortcutIsOnline, showOnDesktop); - if (menu != null) { - shortcut.addMenu(menu); - } - return shortcut; - } - - /** - * Returns the menu descriptor. - */ - public MenuDesc getMenu(Node node) { - String subMenu = getAttribute(node, "submenu", null); - - return new MenuDesc(subMenu); - } - - - /** - * Returns the related-content descriptor. - */ - public RelatedContentDesc getRelatedContent(Node node) throws ParseException { - - getRequiredAttribute(node, "href", null); - URL location = getURL(node, "href", base); - - String title = null; - String description = null; - IconDesc icon = null; - - // step through the elements - Node child = node.getFirstChild(); - while (child != null) { - String name = child.getNodeName(); - - if ("title".equals(name)) { - if (title != null && strict) { - throw new ParseException(R("PTwoTitles")); - } - title = getSpanText(child); - } else if ("description".equals(name)) { - if (description != null && strict) { - throw new ParseException(R("PTwoDescriptions")); - } - description = getSpanText(child); - } else if ("icon".equals(name)) { - if (icon != null && strict) { - throw new ParseException(R("PTwoIcons")); - } - icon = getIcon(child); - } - - child = child.getNextSibling(); - } - - RelatedContentDesc relatedContent = new RelatedContentDesc(location); - relatedContent.setDescription(description); - relatedContent.setIconDesc(icon); - relatedContent.setTitle(title); - - return relatedContent; - - } - - // other methods - - /** - * Returns an array of substrings seperated by spaces (spaces - * escaped with backslash do not separate strings). This method - * splits strings as per the spec except that it does replace - * escaped other characters with their own value. - */ - public String[] splitString(String source) { - if (source == null) - return new String[0]; - - List result = new ArrayList(); - StringTokenizer st = new StringTokenizer(source, " "); - StringBuffer part = new StringBuffer(); - while (st.hasMoreTokens()) { - part.setLength(0); - - // tack together tokens joined by backslash - while (true) { - part.append(st.nextToken()); - - if (st.hasMoreTokens() && part.charAt(part.length()-1) == '\\') - part.setCharAt(part.length()-1, ' '); // join with the space - else - break; // bizarre while format gets \ at end of string right (no extra space added at end) - } - - // delete \ quote chars - for (int i = part.length(); i-- > 0;) // sweet syntax for reverse loop - if (part.charAt(i) == '\\') - part.deleteCharAt(i--); // and skip previous char so \\ becomes \ - - result.add( part.toString() ); - } - - return (String[]) result.toArray(new String[result.size()] ); - } - - /** - * Returns the Locale object(s) from a node's locale attribute. - * - * @param node the node with a locale attribute - */ - public Locale[] getLocales(Node node) { - List locales = new ArrayList(); - String localeParts[] = - splitString(getAttribute(node, "locale", "")); - - for (int i=0; i < localeParts.length; i++) { - Locale l = getLocale( localeParts[i] ); - if (l != null) - locales.add(l); - } - - return (Locale[]) locales.toArray(new Locale[locales.size()] ); - } - - /** - * Returns a Locale from a single locale. - * - * @param locale the locale string - */ - public Locale getLocale(String localeStr) { - if (localeStr.length() < 2) - return null; - - String language = localeStr.substring(0, 2); - String country = (localeStr.length()<5) ? "" : localeStr.substring(3, 5); - String variant = (localeStr.length()<7) ? "" : localeStr.substring(6, 8); - - // null is not allowed n locale but "" is - return new Locale(language, country, variant); - } - - - - // XML junk - - /** - * Returns the implied text under a node, for example "text" in - * "<description>text</description>". - * - * @param node the node with text under it - * @throws ParseException if the JNLP file is invalid - */ - public String getSpanText(Node node) throws ParseException { - if (node == null) - return null; - - // NANO - return node.getNodeValue(); - - /* TINY - Node child = node.getFirstChild(); - - if (child == null) { - if (strict) - // not sure if this is an error or whether "" is proper - throw new ParseException("No text specified (node="+node.getNodeName()+")"); - else - return ""; - } - - return child.getNodeValue(); - */ - } - - /** - * Returns the first child node with the specified name. - */ - public static Node getChildNode(Node node, String name) { - Node[] result = getChildNodes(node, name); - if (result.length == 0) - return null; - else - return result[0]; - } - - /** - * Returns all child nodes with the specified name. - */ - public static Node[] getChildNodes(Node node, String name) { - List result = new ArrayList(); - - Node child = node.getFirstChild(); - while (child != null) { - if (child.getNodeName().equals(name)) - result.add(child); - child = child.getNextSibling(); - } - - return (Node[]) result.toArray( new Node[result.size()] ); - } - - - /** - * Returns a URL with a trailing / appended to it if there is no - * trailing slash on the specifed URL. - */ - private URL addSlash(URL source) { - if (source == null) - return null; - - if (!source.toString().endsWith("/")) { - try { - source = new URL(source.toString()+"/"); - } - catch (MalformedURLException ex) { - } - } - - return source; - } - - - /** - * Returns the same result as getURL except that a - * ParseException is thrown if the attribute is null or empty. - * - * @param node the node - * @param name the attribute containing an href - * @param base the base URL - * @throws ParseException if the JNLP file is invalid - */ - public URL getRequiredURL(Node node, String name, URL base) throws ParseException { - // probably should change "" to null so that url is always - // required even if !strict - getRequiredAttribute(node, name, ""); - - return getURL(node, name, base); - } - - - /** - * Returns a URL object from a href string relative to the - * code base. If the href denotes a relative URL, it must - * reference a location that is a subdirectory of the - * codebase.<p> - * - * @param node the node - * @param name the attribute containing an href - * @param base the base URL - * @throws ParseException if the JNLP file is invalid - */ - public URL getURL(Node node, String name, URL base) throws ParseException { - String href = getAttribute(node, name, null); - if (href == null) - return null; // so that code can throw an exception if attribute was required - - try { - if (base == null) - return new URL(href); - else { - try { - return new URL(href); - } - catch (MalformedURLException ex) { - // is relative - } - - URL result = new URL(base, href); - - // check for going above the codebase - if (! result.toString().startsWith( base.toString()) ) - if (strict) - throw new ParseException(R("PUrlNotInCodebase", node.getNodeName(), href, base)); - - return result; - } - - } - catch (MalformedURLException ex) { - if (base == null) - throw new ParseException(R("PBadNonrelativeUrl", node.getNodeName(), href)); - else - throw new ParseException(R("PBadRelativeUrl", node.getNodeName(), href, base)); - } - } - - /** - * Returns a Version from the specified attribute and default - * value. - * - * @param node the node - * @param name the attribute - * @param defaultValue default if no such attribute - * @return a Version, or null if no such attribute and default is null - */ - public Version getVersion(Node node, String name, String defaultValue) { - String version = getAttribute(node, name, defaultValue); - if (version == null) - return null; - else - return new Version(version); - } - - /** - * Check that the VM args are valid and safe - * @param vmArgs a string containing the args - * @throws ParseException if the VM arguments are invalid or dangerous - */ - private void checkVMArgs(String vmArgs) throws IllegalArgumentException { - if (vmArgs == null) { - return; - } - - List<String> validArguments = Arrays.asList(getValidVMArguments()); - List<String> validStartingArguments = Arrays.asList(getValidStartingVMArguments()); - - String[] arguments = vmArgs.split(" "); - boolean argumentIsValid = false; - for (String argument: arguments) { - argumentIsValid = false; - - if (validArguments.contains(argument)) { - argumentIsValid = true; - } else { - for (String validStartingArgument: validStartingArguments) { - if (argument.startsWith(validStartingArgument)) { - argumentIsValid = true; - break; - } - } - } - - if (!argumentIsValid) { - throw new IllegalArgumentException(argument); - } - } - - } - - /** - * Returns an array of valid (ie safe and supported) arguments for the JVM - * - * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html - */ - private String[] getValidVMArguments() { - return new String[] { - "-d32", /* use a 32-bit data model if available */ - "-client", /* to select the client VM */ - "-server", /* to select the server VM */ - "-verbose", /* enable verbose output */ - "-version", /* print product version and exit */ - "-showversion", /* print product version and continue */ - "-help", /* print this help message */ - "-X", /* print help on non-standard options */ - "-ea", /* enable assertions */ - "-enableassertions", /* enable assertions */ - "-da", /* disable assertions */ - "-disableassertions", /* disable assertions */ - "-esa", /* enable system assertions */ - "-enablesystemassertions", /* enable system assertions */ - "-dsa", /* disable system assertione */ - "-disablesystemassertions", /* disable system assertione */ - "-Xmixed", /* mixed mode execution (default) */ - "-Xint", /* interpreted mode execution only */ - "-Xnoclassgc", /* disable class garbage collection */ - "-Xincgc", /* enable incremental garbage collection */ - "-Xbatch", /* disable background compilation */ - "-Xprof", /* output cpu profiling data */ - "-Xdebug", /* enable remote debugging */ - "-Xfuture", /* enable strictest checks, anticipating future default */ - "-Xrs", /* reduce use of OS signals by Java/VM (see documentation) */ - "-XX:+ForceTimeHighResolution", /* use high resolution timer */ - "-XX:-ForceTimeHighResolution", /* use low resolution (default) */ - }; - } - - /** - * Returns an array containing the starts of valid (ie safe and supported) - * arguments for the JVM - * - * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html - */ - private String[] getValidStartingVMArguments() { - return new String[] { - "-ea", /* enable assertions for classes */ - "-enableassertions", /* enable assertions for classes */ - "-da", /* disable assertions for classes */ - "-disableassertions", /* disable assertions for classes */ - "-verbose", /* enable verbose output */ - "-Xms", /* set initial Java heap size */ - "-Xmx", /* set maximum Java heap size */ - "-Xss", /* set java thread stack size */ - "-XX:NewRatio", /* set Ratio of new/old gen sizes */ - "-XX:NewSize", /* set initial size of new generation */ - "-XX:MaxNewSize", /* set max size of new generation */ - "-XX:PermSize", /* set initial size of permanent gen */ - "-XX:MaxPermSize", /* set max size of permanent gen */ - "-XX:MaxHeapFreeRatio", /* heap free percentage (default 70) */ - "-XX:MinHeapFreeRatio", /* heap free percentage (default 40) */ - "-XX:UseSerialGC", /* use serial garbage collection */ - "-XX:ThreadStackSize", /* thread stack size (in KB) */ - "-XX:MaxInlineSize", /* set max num of bytecodes to inline */ - "-XX:ReservedCodeCacheSize", /* Reserved code cache size (bytes) */ - "-XX:MaxDirectMemorySize", - - }; - } - - /** - * Returns the same result as getAttribute except that if strict - * mode is enabled or the default value is null a parse - * exception is thrown instead of returning the default value. - * - * @param node the node - * @param name the attribute - * @param defaultValue default value - * @throws ParseException if the attribute does not exist or is empty - */ - public String getRequiredAttribute(Node node, String name, String defaultValue) throws ParseException { - String result = getAttribute(node, name, null); - - if (result == null || result.length() == 0) - if (strict || defaultValue == null) - throw new ParseException(R("PNeedsAttribute", node.getNodeName(), name)); - - if (result == null) - return defaultValue; - else - return result; - } - - /** - * Retuns an attribute or the specified defaultValue if there is - * no such attribute. - * - * @param node the node - * @param name the attribute - * @param defaultValue default if no such attribute - */ - public String getAttribute(Node node, String name, String defaultValue) { - // SAX - // String result = ((Element) node).getAttribute(name); - String result = node.getAttribute(name); - - if (result == null || result.length()==0) - return defaultValue; - - return result; - } - - /** - * Return the root node from the XML document in the specified - * input stream. - * - * @throws ParseException if the JNLP file is invalid - */ - public static Node getRootNode(InputStream input) throws ParseException { - try { - /* SAX - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setValidating(false); - factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - builder.setErrorHandler(errorHandler); - - Document doc = builder.parse(input); - return doc.getDocumentElement(); - */ - - /* TINY - Node document = new Node(TinyParser.parseXML(input)); - Node jnlpNode = getChildNode(document, "jnlp"); // skip comments - */ - - /* NANO */ - final XMLElement xml = new XMLElement(); - final PipedInputStream pin = new PipedInputStream(); - final PipedOutputStream pout = new PipedOutputStream(pin); - final InputStreamReader isr = new InputStreamReader(input); - - // Clean the jnlp xml file of all comments before passing - // it to the parser. - new Thread( - new Runnable(){ - public void run(){ - (new XMLElement()).sanitizeInput(isr, pout); - try { - pout.close(); - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - } - ).start(); - xml.parseFromReader(new InputStreamReader(pin)); - Node jnlpNode = new Node(xml); - return jnlpNode; - } - catch(Exception ex) { - throw new ParseException(R("PBadXML"), ex); - } - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// Copyright (C) 2009 Red Hat, Inc. +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp; + +import java.io.*; +import java.net.*; +import java.util.*; +//import javax.xml.parsers.*; // commented to use right Node +//import org.w3c.dom.*; // class for using Tiny XML | NanoXML +//import org.xml.sax.*; +//import gd.xml.tiny.*; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.nanoxml.*; + + +/** + * Contains methods to parse an XML document into a JNLPFile. + * Implements JNLP specification version 1.0. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.13 $ + */ +class Parser { + + private static String R(String key) { return JNLPRuntime.getMessage(key); } + private static String R(String key, Object p1) { return R(key, p1, null); } + private static String R(String key, Object p1, Object p2) { return R(key, p1, p2, null); } + private static String R(String key, Object p1, Object p2, Object p3) { return JNLPRuntime.getMessage(key, new Object[] { p1, p2, p3 }); } + + + // defines netx.jnlp.Node class if using Tiny XML or Nano XML + + // Currently uses the Nano XML parse. Search for "SAX" or + // "TINY" or "NANO" and uncomment those blocks and comment the + // active ones (if any) to switch XML parsers. Also + // (un)comment appropriate Node class at end of this file and + // do a clean build. + + /** + * Ensure consistent error handling. + */ + /* SAX + static ErrorHandler errorHandler = new ErrorHandler() { + public void error(SAXParseException exception) throws SAXParseException { + //throw exception; + } + public void fatalError(SAXParseException exception) throws SAXParseException { + //throw exception; + } + public void warning(SAXParseException exception) { + System.err.println("XML parse warning:"); + exception.printStackTrace(); + } + }; + */ + + + /** the supported JNLP file versions */ + private static Version supportedVersions = new Version("1.0 1.5 6.0"); + + // fix: some descriptors need to use the jnlp file at a later + // date and having file ref lets us pass it to their + // constructors + // + /** the file reference */ + private JNLPFile file; // do not use (uninitialized) + + /** the root node */ + private Node root; + + /** the specification version */ + private Version spec; + + /** the base URL that all hrefs are relative to */ + private URL base; + + /** the codebase URL */ + private URL codebase; + + /** the file URL */ + private URL fileLocation; + + /** whether to throw errors on non-fatal errors. */ + private boolean strict; // if strict==true parses a file with no error then strict==false should also + + /** whether to allow extensions to the JNLP specification */ + private boolean allowExtensions; // true if extensions to JNLP spec are ok + + + /** + * Create a parser for the JNLP file. If the location + * parameters is not null it is used as the default codebase + * (does not override value of jnlp element's href + * attribute).<p> + * + * The root node may be normalized as a side effect of this + * constructor. + * + * @param file the (uninitialized) file reference + * @param base if codebase is not specified, a default base for relative URLs + * @param root the root node + * @param strict whether to enforce strict compliance with the JNLP spec + * @param allowExtensions whether to allow extensions to the JNLP spec + * @throws ParseException if the JNLP file is invalid + */ + public Parser(JNLPFile file, URL base, Node root, boolean strict, boolean allowExtensions) throws ParseException { + this.file = file; + this.root = root; + this.strict = strict; + this.allowExtensions = allowExtensions; + + // ensure it's a JNLP node + if (root == null || !root.getNodeName().equals("jnlp")) + throw new ParseException(R("PInvalidRoot")); + + // JNLP tag information + this.spec = getVersion(root, "spec", "1.0+"); + this.codebase = addSlash(getURL(root, "codebase", base)); + this.base = (codebase!=null) ? codebase : base; // if codebase not specified use default codebase + fileLocation = getURL(root, "href", this.base); + + // ensure version is supported + if (!supportedVersions.matchesAny(spec)) + throw new ParseException(R("PSpecUnsupported", supportedVersions)); + + // normalize the text nodes + root.normalize(); + } + + /** + * Return the JNLP specification versions supported. + */ + public static Version getSupportedVersions() { + return supportedVersions; + } + + /** + * Returns the file version. + */ + public Version getFileVersion() { + return getVersion(root, "version", null); + } + + /** + * Returns the file location. + */ + public URL getFileLocation() { + return fileLocation; + } + + /** + * Returns the codebase. + */ + public URL getCodeBase() { + return codebase; + } + + /** + * Returns the specification version. + */ + public Version getSpecVersion() { + return spec; + } + + // + // This section loads the resources elements + // + + /** + * Returns all of the ResourcesDesc elements under the specified + * node (jnlp or j2se). + * + * @param parent the parent node (either jnlp or j2se) + * @param j2se true if the resources are located under a j2se or java node + * @throws ParseException if the JNLP file is invalid + */ + public List getResources(Node parent, boolean j2se) throws ParseException { + List result = new ArrayList(); + Node resources[] = getChildNodes(parent, "resources"); + + // ensure that there are at least one information section present + if (resources.length == 0 && !j2se) + throw new ParseException(R("PNoResources")); + + // create objects from the resources sections + for (int i=0; i < resources.length; i++) + result.add(getResourcesDesc(resources[i], j2se)); + + return result; + } + + /** + * Returns the ResourcesDesc element at the specified node. + * + * @param node the resources node + * @param j2se true if the resources are located under a j2se or java node + * @throws ParseException if the JNLP file is invalid + */ + public ResourcesDesc getResourcesDesc(Node node, boolean j2se) throws ParseException { + boolean mainFlag = false; // if found a main tag + + // create resources + ResourcesDesc resources = + new ResourcesDesc(file, + getLocales(node), + splitString(getAttribute(node, "os", null)), + splitString(getAttribute(node, "arch", null))); + + // step through the elements + Node child = node.getFirstChild(); + while (child != null) { + String name = child.getNodeName(); + + // check for nativelib but no trusted environment + if ("nativelib".equals(name)) + if (!isTrustedEnvironment()) + throw new ParseException(R("PUntrustedNative")); + + if ("j2se".equals(name) || "java".equals(name)) { + if (getChildNode(root, "component-desc") != null) + if (strict) + throw new ParseException(R("PExtensionHasJ2SE")); + if (!j2se) + resources.addResource( getJRE(child) ); + else + throw new ParseException(R("PInnerJ2SE")); + } + + if ("jar".equals(name) || "nativelib".equals(name)) { + JARDesc jar = getJAR(child); + + // check for duplicate main entries + if (jar.isMain()) { + if (mainFlag == true) + if (strict) + throw new ParseException(R("PTwoMains")); + mainFlag = true; + } + + resources.addResource(jar); + } + + if ("extension".equals(name)) + resources.addResource( getExtension(child) ); + + if ("property".equals(name)) + resources.addResource( getProperty(child) ); + + if ("package".equals(name)) + resources.addResource( getPackage(child) ); + + child = child.getNextSibling(); + } + + return resources; + } + + /** + * Returns the JRE element at the specified node. + * + * @param node the j2se/java node + * @throws ParseException if the JNLP file is invalid + */ + public JREDesc getJRE(Node node) throws ParseException { + Version version = getVersion(node, "version", null); + URL location = getURL(node, "href", base); + String vmArgs = getAttribute(node, "java-vm-args",null); + try { + checkVMArgs(vmArgs); + } catch (IllegalArgumentException argumentException) { + vmArgs = null; + } + String initialHeap = getAttribute(node, "initial-heap-size", null); + String maxHeap = getAttribute(node, "max-heap-size", null); + List resources = getResources(node, true); + + // require version attribute + getRequiredAttribute(node, "version", null); + + return new JREDesc(version, location, vmArgs, initialHeap, maxHeap, resources); + } + + + + /** + * Returns the JAR element at the specified node. + * + * @param node the jar or nativelib node + * @throws ParseException if the JNLP file is invalid + */ + public JARDesc getJAR(Node node) throws ParseException { + boolean nativeJar = "nativelib".equals(node.getNodeName()); + URL location = getRequiredURL(node, "href", base); + Version version = getVersion(node, "version", null); + String part = getAttribute(node, "part", null); + boolean main = "true".equals(getAttribute(node, "main", "false")); + boolean lazy = "lazy".equals(getAttribute(node, "download", "eager")); + int size = Integer.parseInt(getAttribute(node, "size", "0")); + + if (nativeJar && main) + if (strict) + throw new ParseException(R("PNativeHasMain")); + + return new JARDesc(location, version, part, lazy, main, nativeJar, true); + + } + + /** + * Returns the Extension element at the specified node. + * + * @param node the extension node + * @throws ParseException if the JNLP file is invalid + */ + public ExtensionDesc getExtension(Node node) throws ParseException { + String name = getAttribute(node, "name", null); + Version version = getVersion(node, "version", null); + URL location = getRequiredURL(node, "href", base); + + ExtensionDesc ext = new ExtensionDesc(name, version, location); + + Node dload[] = getChildNodes(node, "ext-download"); + for (int i=0; i < dload.length; i++) { + boolean lazy = "lazy".equals(getAttribute(dload[i], "download", "eager")); + + ext.addPart(getRequiredAttribute(dload[i], "ext-part", null), + getAttribute(dload[i], "part", null), + lazy); + } + + return ext; + } + + /** + * Returns the Property element at the specified node. + * + * @param node the property node + * @throws ParseException if the JNLP file is invalid + */ + public PropertyDesc getProperty(Node node) throws ParseException { + String name = getRequiredAttribute(node, "name", null); + String value = getRequiredAttribute(node, "value", ""); + + return new PropertyDesc(name, value); + } + + /** + * Returns the Package element at the specified node. + * + * @param node the package node + * @throws ParseException if the JNLP file is invalid + */ + public PackageDesc getPackage(Node node) throws ParseException { + String name = getRequiredAttribute(node, "name", null); + String part = getRequiredAttribute(node, "part", ""); + boolean recursive = getAttribute(node, "recursive", "false").equals("true"); + + return new PackageDesc(name, part, recursive); + } + + // + // This section loads the information elements + // + + /** + * Returns all of the information elements under the specified + * node. + * + * @param parent the parent node (jnlp) + * @throws ParseException if the JNLP file is invalid + */ + public List getInfo(Node parent) throws ParseException { + List result = new ArrayList(); + Node info[] = getChildNodes(parent, "information"); + + // ensure that there are at least one information section present + if (info.length == 0) + throw new ParseException(R("PNoInfoElement")); + + // create objects from the info sections + for (int i=0; i < info.length; i++) + result.add(getInformationDesc(info[i])); + + return result; + } + + /** + * Returns the information element at the specified node. + * + * @param node the information node + * @throws ParseException if the JNLP file is invalid + */ + public InformationDesc getInformationDesc(Node node) throws ParseException { + List descriptionsUsed = new ArrayList(); + + // locale + Locale locales[] = getLocales(node); + + // create information + InformationDesc info = new InformationDesc(file, locales); + + // step through the elements + Node child = node.getFirstChild(); + while (child != null) { + String name = child.getNodeName(); + + if ("title".equals(name)) + addInfo(info, child, null, getSpanText(child)); + if ("vendor".equals(name)) + addInfo(info, child, null, getSpanText(child)); + if ("description".equals(name)) { + String kind = getAttribute(child, "kind", "default"); + if (descriptionsUsed.contains(kind)) + if (strict) + throw new ParseException(R("PTwoDescriptions", kind)); + + descriptionsUsed.add(kind); + addInfo(info, child, kind, getSpanText(child)); + } + if ("homepage".equals(name)) + addInfo(info, child, null, getRequiredURL(child, "href", base)); + if ("icon".equals(name)) + addInfo(info, child, getAttribute(child, "kind", "default"), getIcon(child)); + if ("offline-allowed".equals(name)) + addInfo(info, child, null, Boolean.TRUE); + if ("sharing-allowed".equals(name)) { + if (strict && !allowExtensions) + throw new ParseException(R("PSharing")); + addInfo(info, child, null, Boolean.TRUE); + } + if ("association".equals(name)) { + addInfo(info, child, null, getAssociation(child)); + } + if ("shortcut".equals(name)) { + addInfo(info, child, null, getShortcut(child)); + } + if ("related-content".equals(name)) { + addInfo(info, child, null, getRelatedContent(child)); + } + + child = child.getNextSibling(); + } + + return info; + } + + /** + * Adds a key,value pair to the information object. + * + * @param info the information object + * @param node node name to be used as the key + * @param mod key name appended with "-"+mod if not null + * @param value the info object to add (icon or string) + */ + protected void addInfo(InformationDesc info, Node node, String mod, Object value) { + String modStr = (mod == null) ? "" : "-"+mod; + + if (node == null) + return; + + info.addItem(node.getNodeName()+modStr, value); + } + + /** + * Returns the icon element at the specified node. + * + * @param node the icon node + * @throws ParseException if the JNLP file is invalid + */ + public IconDesc getIcon(Node node) throws ParseException { + int width = Integer.parseInt(getAttribute(node, "width", "-1")); + int height = Integer.parseInt(getAttribute(node, "height", "-1")); + int size = Integer.parseInt(getAttribute(node, "size", "-1")); + int depth = Integer.parseInt(getAttribute(node, "depth", "-1")); + URL location = getRequiredURL(node, "href", base); + Object kind = getAttribute(node, "kind", "default"); + + return new IconDesc(location, kind, width, height, depth, size); + } + + // + // This section loads the security descriptor element + // + + /** + * Returns the security descriptor element. If no security + * element was specified in the JNLP file then a SecurityDesc + * with applet permissions is returned. + * + * @param parent the parent node + * @throws ParseException if the JNLP file is invalid + */ + public SecurityDesc getSecurity(Node parent) throws ParseException { + Node nodes[] = getChildNodes(parent, "security"); + + // test for too many security elements + if (nodes.length > 1) + if (strict) + throw new ParseException(R("PTwoSecurity")); + + Object type = SecurityDesc.SANDBOX_PERMISSIONS; + + if (nodes.length == 0) + type = SecurityDesc.SANDBOX_PERMISSIONS; + else if (null != getChildNode(nodes[0], "all-permissions")) + type = SecurityDesc.ALL_PERMISSIONS; + else if (null != getChildNode(nodes[0], "j2ee-application-client-permissions")) + type = SecurityDesc.J2EE_PERMISSIONS; + else if (strict) + throw new ParseException(R("PEmptySecurity")); + + if (base != null) + return new SecurityDesc(file, type, base.getHost()); + else + return new SecurityDesc(file, type, null); + } + + /** + * Returns whether the JNLP file requests a trusted execution + * environment. + */ + protected boolean isTrustedEnvironment() { + Node security = getChildNode(root, "security"); + + if (security != null) + if (getChildNode(security, "all-permissions") != null + || getChildNode(security, "j2ee-application-client-permissions") != null) + return true; + + return false; + } + + // + // This section loads the launch descriptor element + // + + /** + * Returns the launch descriptor element, either AppletDesc, + * ApplicationDesc, ComponentDesc, or InstallerDesc. + * + * @param parent the parent node + * @throws ParseException if the JNLP file is invalid + */ + public Object getLauncher(Node parent) throws ParseException { + // check for other than one application type + if (1 != getChildNodes(parent, "applet-desc").length + + getChildNodes(parent, "application-desc").length + + getChildNodes(parent, "component-desc").length + + getChildNodes(parent, "installer-desc").length) + throw new ParseException(R("PTwoDescriptors")); + + Node child = parent.getFirstChild(); + while (child != null) { + String name = child.getNodeName(); + + if ("applet-desc".equals(name)) + return getApplet(child); + if ("application-desc".equals(name)) + return getApplication(child); + if ("component-desc".equals(name)) + return getComponent(child); + if ("installer-desc".equals(name)) + return getInstaller(child); + + child = child.getNextSibling(); + } + + // not reached + return null; + } + + /** + * Returns the applet descriptor. + * + * @throws ParseException if the JNLP file is invalid + */ + public AppletDesc getApplet(Node node) throws ParseException { + String name = getRequiredAttribute(node, "name", R("PUnknownApplet")); + String main = getRequiredAttribute(node, "main-class", null); + URL docbase = getURL(node, "documentbase", base); + Map paramMap = new HashMap(); + int width = 0; + int height = 0; + + try { + width = Integer.parseInt(getRequiredAttribute(node, "width", "100")); + height = Integer.parseInt(getRequiredAttribute(node, "height", "100")); + } + catch (NumberFormatException nfe) { + if (width <= 0) + throw new ParseException(R("PBadWidth")); + throw new ParseException(R("PBadWidth")); + } + + // read params + Node params[] = getChildNodes(node, "param"); + for (int i=0; i < params.length; i++) { + paramMap.put(getRequiredAttribute(params[i], "name", null), + getRequiredAttribute(params[i], "value", "")); + } + + return new AppletDesc(name, main, docbase, width, height, paramMap); + } + + /** + * Returns the application descriptor. + * + * @throws ParseException if the JNLP file is invalid + */ + public ApplicationDesc getApplication(Node node) throws ParseException { + String main = getAttribute(node, "main-class", null); + List argsList = new ArrayList(); + + // if (main == null) + // only ok if can be found in main jar file (can't check here but make a note) + + // read parameters + Node args[] = getChildNodes(node, "argument"); + for (int i=0; i < args.length; i++) { + //argsList.add( args[i].getNodeValue() ); + + //This approach was not finding the argument text + argsList.add( getSpanText(args[i]) ); + } + + String argStrings[] = + (String[]) argsList.toArray( new String[argsList.size()] ); + + return new ApplicationDesc(main, argStrings); + } + + /** + * Returns the component descriptor. + */ + public ComponentDesc getComponent(Node node) { + return new ComponentDesc(); + } + + /** + * Returns the installer descriptor. + */ + public InstallerDesc getInstaller(Node node) { + String main = getAttribute(node, "main-class", null); + + return new InstallerDesc(main); + } + + /** + * Returns the association descriptor. + */ + public AssociationDesc getAssociation(Node node) throws ParseException { + String[] extensions = getRequiredAttribute(node, "extensions", null).split(" "); + String mimeType = getRequiredAttribute(node, "mime-type", null); + + return new AssociationDesc(mimeType, extensions); + } + + /** + * Returns the shortcut descriptor. + */ + public ShortcutDesc getShortcut(Node node) throws ParseException { + + String online = getAttribute(node, "online", "true"); + boolean shortcutIsOnline = Boolean.valueOf(online); + + boolean showOnDesktop = false; + MenuDesc menu = null; + + // step through the elements + Node child = node.getFirstChild(); + while (child != null) { + String name = child.getNodeName(); + + if ("desktop".equals(name)) { + if (showOnDesktop && strict) { + throw new ParseException(R("PTwoDesktops")); + } + showOnDesktop = true; + } else if ("menu".equals(name)){ + if (menu != null && strict) { + throw new ParseException(R("PTwoMenus")); + } + menu = getMenu(child); + } + + child = child.getNextSibling(); + } + + ShortcutDesc shortcut = new ShortcutDesc(shortcutIsOnline, showOnDesktop); + if (menu != null) { + shortcut.addMenu(menu); + } + return shortcut; + } + + /** + * Returns the menu descriptor. + */ + public MenuDesc getMenu(Node node) { + String subMenu = getAttribute(node, "submenu", null); + + return new MenuDesc(subMenu); + } + + + /** + * Returns the related-content descriptor. + */ + public RelatedContentDesc getRelatedContent(Node node) throws ParseException { + + getRequiredAttribute(node, "href", null); + URL location = getURL(node, "href", base); + + String title = null; + String description = null; + IconDesc icon = null; + + // step through the elements + Node child = node.getFirstChild(); + while (child != null) { + String name = child.getNodeName(); + + if ("title".equals(name)) { + if (title != null && strict) { + throw new ParseException(R("PTwoTitles")); + } + title = getSpanText(child); + } else if ("description".equals(name)) { + if (description != null && strict) { + throw new ParseException(R("PTwoDescriptions")); + } + description = getSpanText(child); + } else if ("icon".equals(name)) { + if (icon != null && strict) { + throw new ParseException(R("PTwoIcons")); + } + icon = getIcon(child); + } + + child = child.getNextSibling(); + } + + RelatedContentDesc relatedContent = new RelatedContentDesc(location); + relatedContent.setDescription(description); + relatedContent.setIconDesc(icon); + relatedContent.setTitle(title); + + return relatedContent; + + } + + // other methods + + /** + * Returns an array of substrings seperated by spaces (spaces + * escaped with backslash do not separate strings). This method + * splits strings as per the spec except that it does replace + * escaped other characters with their own value. + */ + public String[] splitString(String source) { + if (source == null) + return new String[0]; + + List result = new ArrayList(); + StringTokenizer st = new StringTokenizer(source, " "); + StringBuffer part = new StringBuffer(); + while (st.hasMoreTokens()) { + part.setLength(0); + + // tack together tokens joined by backslash + while (true) { + part.append(st.nextToken()); + + if (st.hasMoreTokens() && part.charAt(part.length()-1) == '\\') + part.setCharAt(part.length()-1, ' '); // join with the space + else + break; // bizarre while format gets \ at end of string right (no extra space added at end) + } + + // delete \ quote chars + for (int i = part.length(); i-- > 0;) // sweet syntax for reverse loop + if (part.charAt(i) == '\\') + part.deleteCharAt(i--); // and skip previous char so \\ becomes \ + + result.add( part.toString() ); + } + + return (String[]) result.toArray(new String[result.size()] ); + } + + /** + * Returns the Locale object(s) from a node's locale attribute. + * + * @param node the node with a locale attribute + */ + public Locale[] getLocales(Node node) { + List locales = new ArrayList(); + String localeParts[] = + splitString(getAttribute(node, "locale", "")); + + for (int i=0; i < localeParts.length; i++) { + Locale l = getLocale( localeParts[i] ); + if (l != null) + locales.add(l); + } + + return (Locale[]) locales.toArray(new Locale[locales.size()] ); + } + + /** + * Returns a Locale from a single locale. + * + * @param locale the locale string + */ + public Locale getLocale(String localeStr) { + if (localeStr.length() < 2) + return null; + + String language = localeStr.substring(0, 2); + String country = (localeStr.length()<5) ? "" : localeStr.substring(3, 5); + String variant = (localeStr.length()<7) ? "" : localeStr.substring(6, 8); + + // null is not allowed n locale but "" is + return new Locale(language, country, variant); + } + + + + // XML junk + + /** + * Returns the implied text under a node, for example "text" in + * "<description>text</description>". + * + * @param node the node with text under it + * @throws ParseException if the JNLP file is invalid + */ + public String getSpanText(Node node) throws ParseException { + if (node == null) + return null; + + // NANO + return node.getNodeValue(); + + /* TINY + Node child = node.getFirstChild(); + + if (child == null) { + if (strict) + // not sure if this is an error or whether "" is proper + throw new ParseException("No text specified (node="+node.getNodeName()+")"); + else + return ""; + } + + return child.getNodeValue(); + */ + } + + /** + * Returns the first child node with the specified name. + */ + public static Node getChildNode(Node node, String name) { + Node[] result = getChildNodes(node, name); + if (result.length == 0) + return null; + else + return result[0]; + } + + /** + * Returns all child nodes with the specified name. + */ + public static Node[] getChildNodes(Node node, String name) { + List result = new ArrayList(); + + Node child = node.getFirstChild(); + while (child != null) { + if (child.getNodeName().equals(name)) + result.add(child); + child = child.getNextSibling(); + } + + return (Node[]) result.toArray( new Node[result.size()] ); + } + + + /** + * Returns a URL with a trailing / appended to it if there is no + * trailing slash on the specifed URL. + */ + private URL addSlash(URL source) { + if (source == null) + return null; + + if (!source.toString().endsWith("/")) { + try { + source = new URL(source.toString()+"/"); + } + catch (MalformedURLException ex) { + } + } + + return source; + } + + + /** + * Returns the same result as getURL except that a + * ParseException is thrown if the attribute is null or empty. + * + * @param node the node + * @param name the attribute containing an href + * @param base the base URL + * @throws ParseException if the JNLP file is invalid + */ + public URL getRequiredURL(Node node, String name, URL base) throws ParseException { + // probably should change "" to null so that url is always + // required even if !strict + getRequiredAttribute(node, name, ""); + + return getURL(node, name, base); + } + + + /** + * Returns a URL object from a href string relative to the + * code base. If the href denotes a relative URL, it must + * reference a location that is a subdirectory of the + * codebase.<p> + * + * @param node the node + * @param name the attribute containing an href + * @param base the base URL + * @throws ParseException if the JNLP file is invalid + */ + public URL getURL(Node node, String name, URL base) throws ParseException { + String href = getAttribute(node, name, null); + if (href == null) + return null; // so that code can throw an exception if attribute was required + + try { + if (base == null) + return new URL(href); + else { + try { + return new URL(href); + } + catch (MalformedURLException ex) { + // is relative + } + + URL result = new URL(base, href); + + // check for going above the codebase + if (! result.toString().startsWith( base.toString()) ) + if (strict) + throw new ParseException(R("PUrlNotInCodebase", node.getNodeName(), href, base)); + + return result; + } + + } + catch (MalformedURLException ex) { + if (base == null) + throw new ParseException(R("PBadNonrelativeUrl", node.getNodeName(), href)); + else + throw new ParseException(R("PBadRelativeUrl", node.getNodeName(), href, base)); + } + } + + /** + * Returns a Version from the specified attribute and default + * value. + * + * @param node the node + * @param name the attribute + * @param defaultValue default if no such attribute + * @return a Version, or null if no such attribute and default is null + */ + public Version getVersion(Node node, String name, String defaultValue) { + String version = getAttribute(node, name, defaultValue); + if (version == null) + return null; + else + return new Version(version); + } + + /** + * Check that the VM args are valid and safe + * @param vmArgs a string containing the args + * @throws ParseException if the VM arguments are invalid or dangerous + */ + private void checkVMArgs(String vmArgs) throws IllegalArgumentException { + if (vmArgs == null) { + return; + } + + List<String> validArguments = Arrays.asList(getValidVMArguments()); + List<String> validStartingArguments = Arrays.asList(getValidStartingVMArguments()); + + String[] arguments = vmArgs.split(" "); + boolean argumentIsValid = false; + for (String argument: arguments) { + argumentIsValid = false; + + if (validArguments.contains(argument)) { + argumentIsValid = true; + } else { + for (String validStartingArgument: validStartingArguments) { + if (argument.startsWith(validStartingArgument)) { + argumentIsValid = true; + break; + } + } + } + + if (!argumentIsValid) { + throw new IllegalArgumentException(argument); + } + } + + } + + /** + * Returns an array of valid (ie safe and supported) arguments for the JVM + * + * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html + */ + private String[] getValidVMArguments() { + return new String[] { + "-d32", /* use a 32-bit data model if available */ + "-client", /* to select the client VM */ + "-server", /* to select the server VM */ + "-verbose", /* enable verbose output */ + "-version", /* print product version and exit */ + "-showversion", /* print product version and continue */ + "-help", /* print this help message */ + "-X", /* print help on non-standard options */ + "-ea", /* enable assertions */ + "-enableassertions", /* enable assertions */ + "-da", /* disable assertions */ + "-disableassertions", /* disable assertions */ + "-esa", /* enable system assertions */ + "-enablesystemassertions", /* enable system assertions */ + "-dsa", /* disable system assertione */ + "-disablesystemassertions", /* disable system assertione */ + "-Xmixed", /* mixed mode execution (default) */ + "-Xint", /* interpreted mode execution only */ + "-Xnoclassgc", /* disable class garbage collection */ + "-Xincgc", /* enable incremental garbage collection */ + "-Xbatch", /* disable background compilation */ + "-Xprof", /* output cpu profiling data */ + "-Xdebug", /* enable remote debugging */ + "-Xfuture", /* enable strictest checks, anticipating future default */ + "-Xrs", /* reduce use of OS signals by Java/VM (see documentation) */ + "-XX:+ForceTimeHighResolution", /* use high resolution timer */ + "-XX:-ForceTimeHighResolution", /* use low resolution (default) */ + }; + } + + /** + * Returns an array containing the starts of valid (ie safe and supported) + * arguments for the JVM + * + * Based on http://java.sun.com/javase/6/docs/technotes/guides/javaws/developersguide/syntax.html + */ + private String[] getValidStartingVMArguments() { + return new String[] { + "-ea", /* enable assertions for classes */ + "-enableassertions", /* enable assertions for classes */ + "-da", /* disable assertions for classes */ + "-disableassertions", /* disable assertions for classes */ + "-verbose", /* enable verbose output */ + "-Xms", /* set initial Java heap size */ + "-Xmx", /* set maximum Java heap size */ + "-Xss", /* set java thread stack size */ + "-XX:NewRatio", /* set Ratio of new/old gen sizes */ + "-XX:NewSize", /* set initial size of new generation */ + "-XX:MaxNewSize", /* set max size of new generation */ + "-XX:PermSize", /* set initial size of permanent gen */ + "-XX:MaxPermSize", /* set max size of permanent gen */ + "-XX:MaxHeapFreeRatio", /* heap free percentage (default 70) */ + "-XX:MinHeapFreeRatio", /* heap free percentage (default 40) */ + "-XX:UseSerialGC", /* use serial garbage collection */ + "-XX:ThreadStackSize", /* thread stack size (in KB) */ + "-XX:MaxInlineSize", /* set max num of bytecodes to inline */ + "-XX:ReservedCodeCacheSize", /* Reserved code cache size (bytes) */ + "-XX:MaxDirectMemorySize", + + }; + } + + /** + * Returns the same result as getAttribute except that if strict + * mode is enabled or the default value is null a parse + * exception is thrown instead of returning the default value. + * + * @param node the node + * @param name the attribute + * @param defaultValue default value + * @throws ParseException if the attribute does not exist or is empty + */ + public String getRequiredAttribute(Node node, String name, String defaultValue) throws ParseException { + String result = getAttribute(node, name, null); + + if (result == null || result.length() == 0) + if (strict || defaultValue == null) + throw new ParseException(R("PNeedsAttribute", node.getNodeName(), name)); + + if (result == null) + return defaultValue; + else + return result; + } + + /** + * Retuns an attribute or the specified defaultValue if there is + * no such attribute. + * + * @param node the node + * @param name the attribute + * @param defaultValue default if no such attribute + */ + public String getAttribute(Node node, String name, String defaultValue) { + // SAX + // String result = ((Element) node).getAttribute(name); + String result = node.getAttribute(name); + + if (result == null || result.length()==0) + return defaultValue; + + return result; + } + + /** + * Return the root node from the XML document in the specified + * input stream. + * + * @throws ParseException if the JNLP file is invalid + */ + public static Node getRootNode(InputStream input) throws ParseException { + try { + /* SAX + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setErrorHandler(errorHandler); + + Document doc = builder.parse(input); + return doc.getDocumentElement(); + */ + + /* TINY + Node document = new Node(TinyParser.parseXML(input)); + Node jnlpNode = getChildNode(document, "jnlp"); // skip comments + */ + + /* NANO */ + final XMLElement xml = new XMLElement(); + final PipedInputStream pin = new PipedInputStream(); + final PipedOutputStream pout = new PipedOutputStream(pin); + final InputStreamReader isr = new InputStreamReader(input); + + // Clean the jnlp xml file of all comments before passing + // it to the parser. + new Thread( + new Runnable(){ + public void run(){ + (new XMLElement()).sanitizeInput(isr, pout); + try { + pout.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } + ).start(); + xml.parseFromReader(new InputStreamReader(pin)); + Node jnlpNode = new Node(xml); + return jnlpNode; + } + catch(Exception ex) { + throw new ParseException(R("PBadXML"), ex); + } + } + +} + +
--- a/rt/net/sourceforge/jnlp/cache/CacheEntry.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/cache/CacheEntry.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,174 +1,174 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.cache; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.lang.reflect.*; -import java.security.*; -import javax.jnlp.*; - -import net.sourceforge.jnlp.*; -import net.sourceforge.jnlp.runtime.*; -import net.sourceforge.jnlp.util.*; - -/** - * Describes an entry in the cache.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.10 $ - */ -public class CacheEntry { - - /** the remote resource location */ - private URL location; - - /** the requested version */ - private Version version; - - /** info about the cached file */ - private PropertiesFile properties; - - - /** - * Create a CacheEntry for the resources specified as a remote - * URL. - * - * @param location the remote resource location - * @param version the version of the resource - */ - public CacheEntry(URL location, Version version) { - this.location = location; - this.version = version; - - File infoFile = CacheUtil.getCacheFile(location, version); - infoFile = new File(infoFile.getPath()+".info"); // replace with something that can't be clobbered - - properties = new PropertiesFile(infoFile, JNLPRuntime.getMessage("CAutoGen")); - } - - /** - * Initialize the cache entry data from a connection to the - * remote resource (does not store data). - */ - void initialize(URLConnection connection) { - long modified = connection.getLastModified(); - long length = connection.getContentLength(); // an int - - properties.setProperty("content-length", Long.toString(length)); - properties.setProperty("last-modified", Long.toString(modified)); - } - - /** - * Returns the remote location this entry caches. - */ - public URL getLocation() { - return location; - } - - /** - * Returns the time in the local system clock that the file was - * most recently checked for an update. - */ - public long getLastUpdated() { - try { - return Long.parseLong(properties.getProperty("last-updated")); - } - catch (Exception ex) { - return 0; - } - } - - /** - * Sets the time in the local system clock that the file was - * most recently checked for an update. - */ - public void setLastUpdated(long updatedTime) { - properties.setProperty("last-updated", Long.toString(updatedTime)); - } - - /** - * Returns whether there is a version of the URL contents in - * the cache and it is up to date. This method may not return - * immediately. - * - * @param connection a connection to the remote URL - * @return whether the cache contains the version - */ - public boolean isCurrent(URLConnection connection) { - boolean cached = isCached(); - - if (!cached) - return false; - - try { - long remoteModified = connection.getLastModified(); - long cachedModified = Long.parseLong(properties.getProperty("last-modified")); - - if (remoteModified > 0 && remoteModified <= cachedModified) - return true; - else - return false; - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - return cached; // if can't connect return whether already in cache - } - } - - /** - * Returns true if the cache has a local copy of the contents - * of the URL matching the specified version string. - * - * @return true if the resource is in the cache - */ - public boolean isCached() { - File localFile = CacheUtil.getCacheFile(location, version); - if (!localFile.exists()) - return false; - - try { - long cachedLength = localFile.length(); - long remoteLength = Long.parseLong(properties.getProperty("content-length", "-1")); - - if (remoteLength >= 0 && cachedLength != remoteLength) - return false; - else - return true; - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - return false; // should throw? - } - } - - /** - * Save the current information for the cache entry. - */ - protected void store() { - properties.store(); - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.cache; + +import java.io.*; +import java.net.*; +import java.util.*; +import java.lang.reflect.*; +import java.security.*; +import javax.jnlp.*; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.runtime.*; +import net.sourceforge.jnlp.util.*; + +/** + * Describes an entry in the cache.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.10 $ + */ +public class CacheEntry { + + /** the remote resource location */ + private URL location; + + /** the requested version */ + private Version version; + + /** info about the cached file */ + private PropertiesFile properties; + + + /** + * Create a CacheEntry for the resources specified as a remote + * URL. + * + * @param location the remote resource location + * @param version the version of the resource + */ + public CacheEntry(URL location, Version version) { + this.location = location; + this.version = version; + + File infoFile = CacheUtil.getCacheFile(location, version); + infoFile = new File(infoFile.getPath()+".info"); // replace with something that can't be clobbered + + properties = new PropertiesFile(infoFile, JNLPRuntime.getMessage("CAutoGen")); + } + + /** + * Initialize the cache entry data from a connection to the + * remote resource (does not store data). + */ + void initialize(URLConnection connection) { + long modified = connection.getLastModified(); + long length = connection.getContentLength(); // an int + + properties.setProperty("content-length", Long.toString(length)); + properties.setProperty("last-modified", Long.toString(modified)); + } + + /** + * Returns the remote location this entry caches. + */ + public URL getLocation() { + return location; + } + + /** + * Returns the time in the local system clock that the file was + * most recently checked for an update. + */ + public long getLastUpdated() { + try { + return Long.parseLong(properties.getProperty("last-updated")); + } + catch (Exception ex) { + return 0; + } + } + + /** + * Sets the time in the local system clock that the file was + * most recently checked for an update. + */ + public void setLastUpdated(long updatedTime) { + properties.setProperty("last-updated", Long.toString(updatedTime)); + } + + /** + * Returns whether there is a version of the URL contents in + * the cache and it is up to date. This method may not return + * immediately. + * + * @param connection a connection to the remote URL + * @return whether the cache contains the version + */ + public boolean isCurrent(URLConnection connection) { + boolean cached = isCached(); + + if (!cached) + return false; + + try { + long remoteModified = connection.getLastModified(); + long cachedModified = Long.parseLong(properties.getProperty("last-modified")); + + if (remoteModified > 0 && remoteModified <= cachedModified) + return true; + else + return false; + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + return cached; // if can't connect return whether already in cache + } + } + + /** + * Returns true if the cache has a local copy of the contents + * of the URL matching the specified version string. + * + * @return true if the resource is in the cache + */ + public boolean isCached() { + File localFile = CacheUtil.getCacheFile(location, version); + if (!localFile.exists()) + return false; + + try { + long cachedLength = localFile.length(); + long remoteLength = Long.parseLong(properties.getProperty("content-length", "-1")); + + if (remoteLength >= 0 && cachedLength != remoteLength) + return false; + else + return true; + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + return false; // should throw? + } + } + + /** + * Save the current information for the cache entry. + */ + protected void store() { + properties.store(); + } + +} + +
--- a/rt/net/sourceforge/jnlp/cache/CacheUtil.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/cache/CacheUtil.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,394 +1,394 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.cache; - -import java.io.*; -import java.net.*; -import java.util.*; -import java.lang.reflect.*; -import java.security.*; -import javax.jnlp.*; - -import net.sourceforge.jnlp.*; -import net.sourceforge.jnlp.runtime.*; - -/** - * Provides static methods to interact with the cache, download - * indicator, and other utility methods.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.17 $ - */ -public class CacheUtil { - - private static String R(String key, Object param) { - return JNLPRuntime.getMessage(key, new Object[] {param}); - } - - /** - * Compares a URL using string compare of its protocol, host, - * port, path, query, and anchor. This method avoids the host - * name lookup that URL.equals does for http: protocol URLs. - * It may not return the same value as the URL.equals method - * (different hostnames that resolve to the same IP address, - * ie sourceforge.net and www.sourceforge.net). - */ - public static boolean urlEquals(URL u1, URL u2) { - if (u1==u2) - return true; - if (u1==null || u2==null) - return false; - - if (!compare(u1.getProtocol(), u2.getProtocol(), true) || - !compare(u1.getHost(), u2.getHost(), true) || - //u1.getDefaultPort() != u2.getDefaultPort() || // only in 1.4 - !compare(u1.getPath(), u2.getPath(), false) || - !compare(u1.getQuery(), u2.getQuery(), false) || - !compare(u1.getRef(), u2.getRef(), false)) - return false; - else - return true; - } - - /** - * Caches a resource and returns a URL for it in the cache; - * blocks until resource is cached. If the resource location is - * not cacheable (points to a local file, etc) then the original - * URL is returned.<p> - * - * @param location location of the resource - * @param version the version, or null - * @return either the location in the cache or the original location - */ - public static URL getCachedResource(URL location, Version version, UpdatePolicy policy) { - ResourceTracker rt = new ResourceTracker(); - rt.addResource(location, version, policy); - try { - File f = rt.getCacheFile(location); - return f.toURL(); - } - catch (MalformedURLException ex) { - return location; - } - } - - /** - * Compare strings that can be null. - */ - private static boolean compare(String s1, String s2, boolean ignore) { - if (s1==s2) - return true; - if (s1==null || s2==null) - return false; - - if (ignore) - return s1.equalsIgnoreCase(s2); - else - return s1.equals(s2); - } - - /** - * Returns the Permission object necessary to access the - * resource, or null if no permission is needed. - */ - public static Permission getReadPermission(URL location, Version version) { - if (CacheUtil.isCacheable(location, version)) { - File file = CacheUtil.getCacheFile(location, version); - - return new FilePermission(file.getPath(), "read"); - } - else { - try { - // this is what URLClassLoader does - return location.openConnection().getPermission(); - } - catch (java.io.IOException ioe) { - // should try to figure out the permission - if (JNLPRuntime.isDebug()) - ioe.printStackTrace(); - } - } - - return null; - } - - /** - * Returns whether there is a version of the URL contents in the - * cache and it is up to date. This method may not return - * immediately. - * - * @param source the source URL - * @param version the versions to check for - * @param connection a connection to the URL, or null - * @return whether the cache contains the version - * @throws IllegalArgumentException if the source is not cacheable - */ - public static boolean isCurrent(URL source, Version version, URLConnection connection) { - - if (!isCacheable(source, version)) - throw new IllegalArgumentException(R("CNotCacheable", source)); - - try { - if (connection == null) - connection = source.openConnection(); - - connection.connect(); - - CacheEntry entry = new CacheEntry(source, version); // could pool this - boolean result = entry.isCurrent(connection); - - if (JNLPRuntime.isDebug()) - System.out.println("isCurrent: "+source+" = "+result); - - return result; - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - return isCached(source, version); // if can't connect return whether already in cache - } - } - - /** - * Returns true if the cache has a local copy of the contents of - * the URL matching the specified version string. - * - * @param source the source URL - * @param version the versions to check for - * @return true if the source is in the cache - * @throws IllegalArgumentException if the source is not cacheable - */ - public static boolean isCached(URL source, Version version) { - if (!isCacheable(source, version)) - throw new IllegalArgumentException(R("CNotCacheable", source)); - - CacheEntry entry = new CacheEntry(source, version); // could pool this - boolean result = entry.isCached(); - - if (JNLPRuntime.isDebug()) - System.out.println("isCached: "+source+" = "+result); - - return result; - } - - /** - * Returns whether the resource can be cached as a local file; - * if not, then URLConnection.openStream can be used to obtain - * the contents. - */ - public static boolean isCacheable(URL source, Version version) { - if (source == null) - return false; - - if (source.getProtocol().equals("file")) - return false; - - if (source.getProtocol().equals("jar")) - return false; - - return true; - } - - /** - * Returns the file for the locally cached contents of the - * source. This method returns the file location only and does - * not download the resource. The latest version of the - * resource that matches the specified version will be returned. - * - * @param source the source URL - * @param version the version id of the local file - * @return the file location in the cache, or null if no versions cached - * @throws IllegalArgumentException if the source is not cacheable - */ - public static File getCacheFile(URL source, Version version) { - // ensure that version is an version id not version string - - if (!isCacheable(source, version)) - throw new IllegalArgumentException(R("CNotCacheable", source)); - - try { - File localFile = urlToPath(source, "cache"); - localFile.getParentFile().mkdirs(); - - return localFile; - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - return null; - } - } - - /** - * Returns a buffered output stream open for writing to the - * cache file. - * - * @param source the remote location - * @param version the file version to write to - */ - public static OutputStream getOutputStream(URL source, Version version) throws IOException { - File localFile = getCacheFile(source, version); - OutputStream out = new FileOutputStream(localFile); - - return new BufferedOutputStream(out); - } - - /** - * Copies from an input stream to an output stream. On - * completion, both streams will be closed. Streams are - * buffered automatically. - */ - public static void streamCopy(InputStream is, OutputStream os) throws IOException { - if (!(is instanceof BufferedInputStream)) - is = new BufferedInputStream(is); - - if (!(os instanceof BufferedOutputStream)) - os = new BufferedOutputStream(os); - - try { - byte b[] = new byte[4096]; - while (true) { - int c = is.read(b, 0, b.length); - if (c == -1) - break; - - os.write(b, 0, c); - } - } - finally { - is.close(); - os.close(); - } - } - - /** - * Converts a URL into a local path string within the runtime's - * base directory. - * - * @param location the url - * @param subdir subdirectory under the base directory - * @return the file - */ - public static File urlToPath(URL location, String subdir) { - StringBuffer path = new StringBuffer(); - - if (subdir != null) { - path.append(subdir); - path.append(File.separatorChar); - } - - path.append(location.getProtocol()); - path.append(File.separatorChar); - path.append(location.getHost()); - path.append(File.separatorChar); - path.append(location.getPath().replace('/', File.separatorChar)); - - return new File(JNLPRuntime.getBaseDir(), fixPath(path.toString())); - } - - /** - * Clean up a string by removing characters that can't appear in - * a local file name. - */ - private static String fixPath(String path) { - char badChars[] = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' }; - - for (int i=0; i < badChars.length; i++) - if (badChars[i] != File.separatorChar) - if (-1 != path.indexOf(badChars[i])) - path = path.replace(badChars[i], 'X'); - - return path; - } - - /** - * Waits until the resources are downloaded, while showing a - * progress indicator. - * - * @param tracker the resource tracker - * @param resources the resources to wait for - * @param title name of the download - */ - public static void waitForResources(ApplicationInstance app, ResourceTracker tracker, URL resources[], String title) { - DownloadIndicator indicator = JNLPRuntime.getDefaultDownloadIndicator(); - DownloadServiceListener listener = null; - - try { - if (indicator == null) { - tracker.waitForResources(resources, 0); - return; - } - - // see if resources can be downloaded very quickly; avoids - // overhead of creating display components for the resources - if (tracker.waitForResources(resources, indicator.getInitialDelay())) - return; - - // only resources not starting out downloaded are displayed - List urlList = new ArrayList(); - for (int i=0; i < resources.length; i++) { - if (!tracker.checkResource(resources[i])) - urlList.add(resources[i]); - } - URL undownloaded[] = (URL[]) urlList.toArray( new URL[urlList.size()] ); - - listener = indicator.getListener(app, title, undownloaded); - - do { - long read = 0; - long total = 0; - - for (int i=0; i < undownloaded.length; i++) { - // add in any -1's; they're insignificant - total += tracker.getTotalSize(undownloaded[i]); - read += tracker.getAmountRead(undownloaded[i]); - } - - int percent = (int)( (100*read)/Math.max(1,total) ); - - for (int i=0; i < undownloaded.length; i++) - listener.progress(undownloaded[i], "version", - tracker.getAmountRead(undownloaded[i]), - tracker.getTotalSize(undownloaded[i]), - percent); - } - while (!tracker.waitForResources(resources, indicator.getUpdateRate())); - - // make sure they read 100% until indicator closes - for (int i=0; i < undownloaded.length; i++) - listener.progress(undownloaded[i], "version", - tracker.getTotalSize(undownloaded[i]), - tracker.getTotalSize(undownloaded[i]), - 100); - - } - catch (InterruptedException ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - finally { - if (listener != null) - indicator.disposeListener(listener); - } - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.cache; + +import java.io.*; +import java.net.*; +import java.util.*; +import java.lang.reflect.*; +import java.security.*; +import javax.jnlp.*; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.runtime.*; + +/** + * Provides static methods to interact with the cache, download + * indicator, and other utility methods.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.17 $ + */ +public class CacheUtil { + + private static String R(String key, Object param) { + return JNLPRuntime.getMessage(key, new Object[] {param}); + } + + /** + * Compares a URL using string compare of its protocol, host, + * port, path, query, and anchor. This method avoids the host + * name lookup that URL.equals does for http: protocol URLs. + * It may not return the same value as the URL.equals method + * (different hostnames that resolve to the same IP address, + * ie sourceforge.net and www.sourceforge.net). + */ + public static boolean urlEquals(URL u1, URL u2) { + if (u1==u2) + return true; + if (u1==null || u2==null) + return false; + + if (!compare(u1.getProtocol(), u2.getProtocol(), true) || + !compare(u1.getHost(), u2.getHost(), true) || + //u1.getDefaultPort() != u2.getDefaultPort() || // only in 1.4 + !compare(u1.getPath(), u2.getPath(), false) || + !compare(u1.getQuery(), u2.getQuery(), false) || + !compare(u1.getRef(), u2.getRef(), false)) + return false; + else + return true; + } + + /** + * Caches a resource and returns a URL for it in the cache; + * blocks until resource is cached. If the resource location is + * not cacheable (points to a local file, etc) then the original + * URL is returned.<p> + * + * @param location location of the resource + * @param version the version, or null + * @return either the location in the cache or the original location + */ + public static URL getCachedResource(URL location, Version version, UpdatePolicy policy) { + ResourceTracker rt = new ResourceTracker(); + rt.addResource(location, version, policy); + try { + File f = rt.getCacheFile(location); + return f.toURL(); + } + catch (MalformedURLException ex) { + return location; + } + } + + /** + * Compare strings that can be null. + */ + private static boolean compare(String s1, String s2, boolean ignore) { + if (s1==s2) + return true; + if (s1==null || s2==null) + return false; + + if (ignore) + return s1.equalsIgnoreCase(s2); + else + return s1.equals(s2); + } + + /** + * Returns the Permission object necessary to access the + * resource, or null if no permission is needed. + */ + public static Permission getReadPermission(URL location, Version version) { + if (CacheUtil.isCacheable(location, version)) { + File file = CacheUtil.getCacheFile(location, version); + + return new FilePermission(file.getPath(), "read"); + } + else { + try { + // this is what URLClassLoader does + return location.openConnection().getPermission(); + } + catch (java.io.IOException ioe) { + // should try to figure out the permission + if (JNLPRuntime.isDebug()) + ioe.printStackTrace(); + } + } + + return null; + } + + /** + * Returns whether there is a version of the URL contents in the + * cache and it is up to date. This method may not return + * immediately. + * + * @param source the source URL + * @param version the versions to check for + * @param connection a connection to the URL, or null + * @return whether the cache contains the version + * @throws IllegalArgumentException if the source is not cacheable + */ + public static boolean isCurrent(URL source, Version version, URLConnection connection) { + + if (!isCacheable(source, version)) + throw new IllegalArgumentException(R("CNotCacheable", source)); + + try { + if (connection == null) + connection = source.openConnection(); + + connection.connect(); + + CacheEntry entry = new CacheEntry(source, version); // could pool this + boolean result = entry.isCurrent(connection); + + if (JNLPRuntime.isDebug()) + System.out.println("isCurrent: "+source+" = "+result); + + return result; + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + return isCached(source, version); // if can't connect return whether already in cache + } + } + + /** + * Returns true if the cache has a local copy of the contents of + * the URL matching the specified version string. + * + * @param source the source URL + * @param version the versions to check for + * @return true if the source is in the cache + * @throws IllegalArgumentException if the source is not cacheable + */ + public static boolean isCached(URL source, Version version) { + if (!isCacheable(source, version)) + throw new IllegalArgumentException(R("CNotCacheable", source)); + + CacheEntry entry = new CacheEntry(source, version); // could pool this + boolean result = entry.isCached(); + + if (JNLPRuntime.isDebug()) + System.out.println("isCached: "+source+" = "+result); + + return result; + } + + /** + * Returns whether the resource can be cached as a local file; + * if not, then URLConnection.openStream can be used to obtain + * the contents. + */ + public static boolean isCacheable(URL source, Version version) { + if (source == null) + return false; + + if (source.getProtocol().equals("file")) + return false; + + if (source.getProtocol().equals("jar")) + return false; + + return true; + } + + /** + * Returns the file for the locally cached contents of the + * source. This method returns the file location only and does + * not download the resource. The latest version of the + * resource that matches the specified version will be returned. + * + * @param source the source URL + * @param version the version id of the local file + * @return the file location in the cache, or null if no versions cached + * @throws IllegalArgumentException if the source is not cacheable + */ + public static File getCacheFile(URL source, Version version) { + // ensure that version is an version id not version string + + if (!isCacheable(source, version)) + throw new IllegalArgumentException(R("CNotCacheable", source)); + + try { + File localFile = urlToPath(source, "cache"); + localFile.getParentFile().mkdirs(); + + return localFile; + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + return null; + } + } + + /** + * Returns a buffered output stream open for writing to the + * cache file. + * + * @param source the remote location + * @param version the file version to write to + */ + public static OutputStream getOutputStream(URL source, Version version) throws IOException { + File localFile = getCacheFile(source, version); + OutputStream out = new FileOutputStream(localFile); + + return new BufferedOutputStream(out); + } + + /** + * Copies from an input stream to an output stream. On + * completion, both streams will be closed. Streams are + * buffered automatically. + */ + public static void streamCopy(InputStream is, OutputStream os) throws IOException { + if (!(is instanceof BufferedInputStream)) + is = new BufferedInputStream(is); + + if (!(os instanceof BufferedOutputStream)) + os = new BufferedOutputStream(os); + + try { + byte b[] = new byte[4096]; + while (true) { + int c = is.read(b, 0, b.length); + if (c == -1) + break; + + os.write(b, 0, c); + } + } + finally { + is.close(); + os.close(); + } + } + + /** + * Converts a URL into a local path string within the runtime's + * base directory. + * + * @param location the url + * @param subdir subdirectory under the base directory + * @return the file + */ + public static File urlToPath(URL location, String subdir) { + StringBuffer path = new StringBuffer(); + + if (subdir != null) { + path.append(subdir); + path.append(File.separatorChar); + } + + path.append(location.getProtocol()); + path.append(File.separatorChar); + path.append(location.getHost()); + path.append(File.separatorChar); + path.append(location.getPath().replace('/', File.separatorChar)); + + return new File(JNLPRuntime.getBaseDir(), fixPath(path.toString())); + } + + /** + * Clean up a string by removing characters that can't appear in + * a local file name. + */ + private static String fixPath(String path) { + char badChars[] = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' }; + + for (int i=0; i < badChars.length; i++) + if (badChars[i] != File.separatorChar) + if (-1 != path.indexOf(badChars[i])) + path = path.replace(badChars[i], 'X'); + + return path; + } + + /** + * Waits until the resources are downloaded, while showing a + * progress indicator. + * + * @param tracker the resource tracker + * @param resources the resources to wait for + * @param title name of the download + */ + public static void waitForResources(ApplicationInstance app, ResourceTracker tracker, URL resources[], String title) { + DownloadIndicator indicator = JNLPRuntime.getDefaultDownloadIndicator(); + DownloadServiceListener listener = null; + + try { + if (indicator == null) { + tracker.waitForResources(resources, 0); + return; + } + + // see if resources can be downloaded very quickly; avoids + // overhead of creating display components for the resources + if (tracker.waitForResources(resources, indicator.getInitialDelay())) + return; + + // only resources not starting out downloaded are displayed + List urlList = new ArrayList(); + for (int i=0; i < resources.length; i++) { + if (!tracker.checkResource(resources[i])) + urlList.add(resources[i]); + } + URL undownloaded[] = (URL[]) urlList.toArray( new URL[urlList.size()] ); + + listener = indicator.getListener(app, title, undownloaded); + + do { + long read = 0; + long total = 0; + + for (int i=0; i < undownloaded.length; i++) { + // add in any -1's; they're insignificant + total += tracker.getTotalSize(undownloaded[i]); + read += tracker.getAmountRead(undownloaded[i]); + } + + int percent = (int)( (100*read)/Math.max(1,total) ); + + for (int i=0; i < undownloaded.length; i++) + listener.progress(undownloaded[i], "version", + tracker.getAmountRead(undownloaded[i]), + tracker.getTotalSize(undownloaded[i]), + percent); + } + while (!tracker.waitForResources(resources, indicator.getUpdateRate())); + + // make sure they read 100% until indicator closes + for (int i=0; i < undownloaded.length; i++) + listener.progress(undownloaded[i], "version", + tracker.getTotalSize(undownloaded[i]), + tracker.getTotalSize(undownloaded[i]), + 100); + + } + catch (InterruptedException ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + finally { + if (listener != null) + indicator.disposeListener(listener); + } + } + +} + +
--- a/rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/cache/DefaultDownloadIndicator.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,321 +1,321 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.cache; - -import java.awt.*; -import java.awt.event.*; -import java.net.*; -import java.util.*; -import java.util.List; -import javax.swing.*; -import javax.swing.Timer; -import javax.jnlp.*; - -import net.sourceforge.jnlp.*; -import net.sourceforge.jnlp.runtime.*; - -/** - * Show the progress of downloads. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.3 $ - */ -public class DefaultDownloadIndicator implements DownloadIndicator { - - // todo: rewrite this to cut down on size/complexity; smarter - // panels (JList, renderer) understand resources instead of - // nested panels and grid-bag mess. - - // todo: fix bug where user closes download box and it - // never(?) reappears. - - // todo: UI for user to cancel/restart download - - // todo: this should be synchronized at some point but conflicts - // aren't very likely. - - private static String downloading = JNLPRuntime.getMessage("CDownloading"); - private static String complete = JNLPRuntime.getMessage("CComplete"); - - /** time to wait after completing but before window closes */ - private static final int CLOSE_DELAY = 750; - - /** the display window */ - private static JFrame frame; - - /** shared constraint */ - static GridBagConstraints vertical; - static GridBagConstraints verticalIndent; - static { - vertical = new GridBagConstraints(); - vertical.gridwidth = GridBagConstraints.REMAINDER; - vertical.weightx = 1.0; - vertical.fill = GridBagConstraints.HORIZONTAL; - vertical.anchor = GridBagConstraints.WEST; - - verticalIndent = (GridBagConstraints) vertical.clone(); - verticalIndent.insets = new Insets(0, 10, 3, 0); - } - - /** - * Return the update rate. - */ - public int getUpdateRate() { - return 150; //ms - } - - /** - * Return the initial delay before obtaining a listener. - */ - public int getInitialDelay() { - return 300; //ms - } - - /** - * Return a download service listener that displays the progress - * in a shared download info window. - * - * @param app the downloading application, or null if N/A - * @param downloadName name identifying the download to the user - * @param resources initial urls to display (not required) - */ - public DownloadServiceListener getListener(ApplicationInstance app, String downloadName, URL resources[]) { - DownloadPanel result = new DownloadPanel(downloadName); - - if (frame == null) { - frame = new JFrame(downloading+"..."); - frame.getContentPane().setLayout(new GridBagLayout()); - } - - if (resources != null) - for (int i=0; i < resources.length; i++) - result.addProgressPanel(resources[i], null); - - frame.getContentPane().add(result, vertical); - frame.pack(); - - if (!frame.isVisible()) { - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration()); - Dimension screen = new Dimension(screenSize.width - insets.left , - screenSize.height - insets.top); - frame.setLocation(screen.width-frame.getWidth(), - screen.height-frame.getHeight()); - } - - frame.show(); - - return result; - } - - /** - * Remove a download service listener that was obtained by - * calling the getDownloadListener method from the shared - * download info window. - */ - public void disposeListener(final DownloadServiceListener listener) { - if (!(listener instanceof DownloadPanel)) - return; - - ActionListener hider = new ActionListener() { - public void actionPerformed(ActionEvent evt) { - if (frame.getContentPane().getComponentCount() == 1) - frame.hide(); - - frame.getContentPane().remove((DownloadPanel) listener); - frame.pack(); - } - }; - - Timer timer = new Timer(CLOSE_DELAY, hider); - timer.setRepeats(false); - timer.start(); - } - - - - /** - * Groups the url progress in a panel. - */ - static class DownloadPanel extends JPanel implements DownloadServiceListener { - - /** the download name */ - private String downloadName; - - /** Downloading part: */ - private JLabel header = new JLabel(); - - /** list of URLs being downloaded */ - private List urls = new ArrayList(); - - /** list of ProgressPanels */ - private List panels = new ArrayList(); - - - /** - * Create a new download panel for with the specified download - * name. - */ - protected DownloadPanel(String downloadName) { - setLayout(new GridBagLayout()); - - this.downloadName = downloadName; - this.add(header, vertical); - header.setFont(header.getFont().deriveFont(Font.BOLD)); - - setOverallPercent(0); - } - - /** - * Add a ProgressPanel for a URL. - */ - protected void addProgressPanel(URL url, String version) { - if (!urls.contains(url)) { - ProgressPanel panel = new ProgressPanel(url, version); - - add(panel, verticalIndent); - frame.pack(); - - urls.add(url); - panels.add(panel); - } - } - - /** - * Update the download progress of a url. - */ - protected void update(final URL url, final String version, final long readSoFar, final long total, final int overallPercent) { - Runnable r = new Runnable() { - public void run() { - if (!urls.contains(url)) - addProgressPanel(url, version); - - setOverallPercent(overallPercent); - - ProgressPanel panel = (ProgressPanel) panels.get(urls.indexOf(url)); - panel.setProgress(readSoFar, total); - panel.repaint(); - } - }; - SwingUtilities.invokeLater(r); - } - - /** - * Sets the overall percent completed. - */ - public void setOverallPercent(int percent) { - // don't get whole string from resource and sub in - // values because it'll be doing a MessageFormat for - // each update. - header.setText(downloading+" "+downloadName+": "+percent+"% "+complete+"."); - } - - /** - * Called when a download failed. - */ - public void downloadFailed(URL url, String version) { - update(url, version, -1, -1, -1); - } - - /** - * Called when a download has progressed. - */ - public void progress(URL url, String version, long readSoFar, long total, int overallPercent) { - update(url, version, readSoFar, total, overallPercent); - } - - /** - * Called when an archive is patched. - */ - public void upgradingArchive(URL url, String version, int patchPercent, int overallPercent) { - update(url, version, patchPercent, 100, overallPercent); - } - - /** - * Called when a download is being validated. - */ - public void validating(URL url, String version, long entry, long total, int overallPercent) { - update(url, version, entry, total, overallPercent); - } - - }; - - - - /** - * A progress bar with the URL next to it. - */ - static class ProgressPanel extends JPanel { - private JPanel bar = new JPanel(); - - private long total; - private long readSoFar; - - ProgressPanel(URL url, String version) { - JLabel location = new JLabel(" "+url.getHost()+"/"+url.getFile()); - - bar.setMinimumSize(new Dimension(80,15)); - bar.setPreferredSize(new Dimension(80,15)); - bar.setOpaque(false); - - setLayout(new GridBagLayout()); - - GridBagConstraints gbc = new GridBagConstraints(); - gbc.weightx = 0.0; - gbc.fill = GridBagConstraints.NONE; - gbc.gridwidth = GridBagConstraints.RELATIVE; - add(bar, gbc); - - gbc.insets = new Insets(0, 3, 0, 0); - gbc.weightx = 1.0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.anchor = GridBagConstraints.WEST; - add(location, gbc); - } - - public void setProgress(long readSoFar, long total) { - this.readSoFar = readSoFar; - this.total = total; - } - - public void paintComponent(Graphics g) { - super.paintComponent(g); - - int x = bar.getX(); - int y = bar.getY(); - int h = bar.getHeight(); - int w = bar.getWidth(); - - if (readSoFar <= 0 || total <= 0) { - // make barber pole - } - else { - double progress = (double)readSoFar / (double)total; - int divide = (int)(w * progress); - - g.setColor(Color.white); - g.fillRect(x, y, w, h); - g.setColor(Color.blue); - g.fillRect(x+1, y+1, divide-1, h-1); - } - } - }; - -} - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.cache; + +import java.awt.*; +import java.awt.event.*; +import java.net.*; +import java.util.*; +import java.util.List; +import javax.swing.*; +import javax.swing.Timer; +import javax.jnlp.*; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.runtime.*; + +/** + * Show the progress of downloads. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.3 $ + */ +public class DefaultDownloadIndicator implements DownloadIndicator { + + // todo: rewrite this to cut down on size/complexity; smarter + // panels (JList, renderer) understand resources instead of + // nested panels and grid-bag mess. + + // todo: fix bug where user closes download box and it + // never(?) reappears. + + // todo: UI for user to cancel/restart download + + // todo: this should be synchronized at some point but conflicts + // aren't very likely. + + private static String downloading = JNLPRuntime.getMessage("CDownloading"); + private static String complete = JNLPRuntime.getMessage("CComplete"); + + /** time to wait after completing but before window closes */ + private static final int CLOSE_DELAY = 750; + + /** the display window */ + private static JFrame frame; + + /** shared constraint */ + static GridBagConstraints vertical; + static GridBagConstraints verticalIndent; + static { + vertical = new GridBagConstraints(); + vertical.gridwidth = GridBagConstraints.REMAINDER; + vertical.weightx = 1.0; + vertical.fill = GridBagConstraints.HORIZONTAL; + vertical.anchor = GridBagConstraints.WEST; + + verticalIndent = (GridBagConstraints) vertical.clone(); + verticalIndent.insets = new Insets(0, 10, 3, 0); + } + + /** + * Return the update rate. + */ + public int getUpdateRate() { + return 150; //ms + } + + /** + * Return the initial delay before obtaining a listener. + */ + public int getInitialDelay() { + return 300; //ms + } + + /** + * Return a download service listener that displays the progress + * in a shared download info window. + * + * @param app the downloading application, or null if N/A + * @param downloadName name identifying the download to the user + * @param resources initial urls to display (not required) + */ + public DownloadServiceListener getListener(ApplicationInstance app, String downloadName, URL resources[]) { + DownloadPanel result = new DownloadPanel(downloadName); + + if (frame == null) { + frame = new JFrame(downloading+"..."); + frame.getContentPane().setLayout(new GridBagLayout()); + } + + if (resources != null) + for (int i=0; i < resources.length; i++) + result.addProgressPanel(resources[i], null); + + frame.getContentPane().add(result, vertical); + frame.pack(); + + if (!frame.isVisible()) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(frame.getGraphicsConfiguration()); + Dimension screen = new Dimension(screenSize.width - insets.left , + screenSize.height - insets.top); + frame.setLocation(screen.width-frame.getWidth(), + screen.height-frame.getHeight()); + } + + frame.show(); + + return result; + } + + /** + * Remove a download service listener that was obtained by + * calling the getDownloadListener method from the shared + * download info window. + */ + public void disposeListener(final DownloadServiceListener listener) { + if (!(listener instanceof DownloadPanel)) + return; + + ActionListener hider = new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (frame.getContentPane().getComponentCount() == 1) + frame.hide(); + + frame.getContentPane().remove((DownloadPanel) listener); + frame.pack(); + } + }; + + Timer timer = new Timer(CLOSE_DELAY, hider); + timer.setRepeats(false); + timer.start(); + } + + + + /** + * Groups the url progress in a panel. + */ + static class DownloadPanel extends JPanel implements DownloadServiceListener { + + /** the download name */ + private String downloadName; + + /** Downloading part: */ + private JLabel header = new JLabel(); + + /** list of URLs being downloaded */ + private List urls = new ArrayList(); + + /** list of ProgressPanels */ + private List panels = new ArrayList(); + + + /** + * Create a new download panel for with the specified download + * name. + */ + protected DownloadPanel(String downloadName) { + setLayout(new GridBagLayout()); + + this.downloadName = downloadName; + this.add(header, vertical); + header.setFont(header.getFont().deriveFont(Font.BOLD)); + + setOverallPercent(0); + } + + /** + * Add a ProgressPanel for a URL. + */ + protected void addProgressPanel(URL url, String version) { + if (!urls.contains(url)) { + ProgressPanel panel = new ProgressPanel(url, version); + + add(panel, verticalIndent); + frame.pack(); + + urls.add(url); + panels.add(panel); + } + } + + /** + * Update the download progress of a url. + */ + protected void update(final URL url, final String version, final long readSoFar, final long total, final int overallPercent) { + Runnable r = new Runnable() { + public void run() { + if (!urls.contains(url)) + addProgressPanel(url, version); + + setOverallPercent(overallPercent); + + ProgressPanel panel = (ProgressPanel) panels.get(urls.indexOf(url)); + panel.setProgress(readSoFar, total); + panel.repaint(); + } + }; + SwingUtilities.invokeLater(r); + } + + /** + * Sets the overall percent completed. + */ + public void setOverallPercent(int percent) { + // don't get whole string from resource and sub in + // values because it'll be doing a MessageFormat for + // each update. + header.setText(downloading+" "+downloadName+": "+percent+"% "+complete+"."); + } + + /** + * Called when a download failed. + */ + public void downloadFailed(URL url, String version) { + update(url, version, -1, -1, -1); + } + + /** + * Called when a download has progressed. + */ + public void progress(URL url, String version, long readSoFar, long total, int overallPercent) { + update(url, version, readSoFar, total, overallPercent); + } + + /** + * Called when an archive is patched. + */ + public void upgradingArchive(URL url, String version, int patchPercent, int overallPercent) { + update(url, version, patchPercent, 100, overallPercent); + } + + /** + * Called when a download is being validated. + */ + public void validating(URL url, String version, long entry, long total, int overallPercent) { + update(url, version, entry, total, overallPercent); + } + + }; + + + + /** + * A progress bar with the URL next to it. + */ + static class ProgressPanel extends JPanel { + private JPanel bar = new JPanel(); + + private long total; + private long readSoFar; + + ProgressPanel(URL url, String version) { + JLabel location = new JLabel(" "+url.getHost()+"/"+url.getFile()); + + bar.setMinimumSize(new Dimension(80,15)); + bar.setPreferredSize(new Dimension(80,15)); + bar.setOpaque(false); + + setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = GridBagConstraints.RELATIVE; + add(bar, gbc); + + gbc.insets = new Insets(0, 3, 0, 0); + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.anchor = GridBagConstraints.WEST; + add(location, gbc); + } + + public void setProgress(long readSoFar, long total) { + this.readSoFar = readSoFar; + this.total = total; + } + + public void paintComponent(Graphics g) { + super.paintComponent(g); + + int x = bar.getX(); + int y = bar.getY(); + int h = bar.getHeight(); + int w = bar.getWidth(); + + if (readSoFar <= 0 || total <= 0) { + // make barber pole + } + else { + double progress = (double)readSoFar / (double)total; + int divide = (int)(w * progress); + + g.setColor(Color.white); + g.fillRect(x, y, w, h); + g.setColor(Color.blue); + g.fillRect(x+1, y+1, divide-1, h-1); + } + } + }; + +} +
--- a/rt/net/sourceforge/jnlp/cache/ResourceTracker.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/cache/ResourceTracker.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,1051 +1,1051 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.cache; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.List; -import java.util.jar.JarOutputStream; -import java.util.jar.Pack200; -import java.util.jar.Pack200.Unpacker; -import java.util.zip.GZIPInputStream; - -import net.sourceforge.jnlp.Version; -import net.sourceforge.jnlp.event.DownloadEvent; -import net.sourceforge.jnlp.event.DownloadListener; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.util.WeakList; - -/** - * This class tracks the downloading of various resources of a - * JNLP file to local files in the cache. It can be used to - * download icons, jnlp and extension files, jars, and jardiff - * files using the version based protocol or any file using the - * basic download protocol (jardiff and version not implemented - * yet).<p> - * - * The resource tracker can be configured to prefetch resources, - * which are downloaded in the order added to the media - * tracker.<p> - * - * Multiple threads are used to download and cache resources that - * are actively being waited for (blocking a caller) or those that - * have been started downloading by calling the startDownload - * method. Resources that are prefetched are downloaded one at a - * time and only if no other trackers have requested downloads. - * This allows the tracker to start downloading many items without - * using many system resources, but still quickly download items - * as needed.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.22 $ - */ -public class ResourceTracker { - - // todo: use event listener arrays instead of lists - - // todo: see if there is a way to set the socket options just - // for use by the tracker so checks for updates don't hang for - // a long time. - - // todo: ability to restart/retry a hung download - - // todo: move resource downloading/processing code into Resource - // class, threading stays in ResourceTracker - - // todo: get status method? and some way to convey error status - // to the caller. - - // todo: might make a tracker be able to download more than one - // version of a resource, but probably not very useful. - - - // defines - // ResourceTracker.Downloader (download threads) - - // separately locks on (in order of aquire order, ie, sync on prefetch never syncs on lock): - // lock, prefetch, this.resources, each resource, listeners - - /** notified on initialization or download of a resource */ - private static Object lock = new Integer(0); // used to lock static structures - - // shortcuts - private static final int UNINITIALIZED = Resource.UNINITIALIZED; - private static final int CONNECT = Resource.CONNECT; - private static final int CONNECTING = Resource.CONNECTING; - private static final int CONNECTED = Resource.CONNECTED; - private static final int DOWNLOAD = Resource.DOWNLOAD; - private static final int DOWNLOADING = Resource.DOWNLOADING; - private static final int DOWNLOADED = Resource.DOWNLOADED; - private static final int ERROR = Resource.ERROR; - private static final int STARTED = Resource.STARTED; - - /** max threads */ - private static final int maxThreads = 5; - - /** running threads */ - private static int threads = 0; - - /** weak list of resource trackers with resources to prefetch */ - private static WeakList prefetchTrackers = new WeakList(); - - /** resources requested to be downloaded */ - private static ArrayList queue = new ArrayList(); - - /** resource trackers threads are working for (used for load balancing across multi-tracker downloads) */ - private static ArrayList active = new ArrayList(); // - - /** the resources known about by this resource tracker */ - private List resources = new ArrayList(); - - /** download listeners for this tracker */ - private List listeners = new ArrayList(); - - /** whether to download parts before requested */ - private boolean prefetch; - - - /** - * Creates a resource tracker that does not prefetch resources. - */ - public ResourceTracker() { - this(false); - } - - /** - * Creates a resource tracker. - * - * @param prefetch whether to download resources before requested. - */ - public ResourceTracker(boolean prefetch) { - this.prefetch = prefetch; - - if (prefetch) { - synchronized (prefetchTrackers) { - prefetchTrackers.add(this); - prefetchTrackers.trimToSize(); - } - } - } - - /** - * Add a resource identified by the specified location and - * version. The tracker only downloads one version of a given - * resource per instance (ie cannot download both versions 1 and - * 2 of a resource in the same tracker). - * - * @param location the location of the resource - * @param version the resource version - * @param updatePolicy whether to check for updates if already in cache - */ - public void addResource(URL location, Version version, UpdatePolicy updatePolicy) { - if (location == null) - throw new IllegalArgumentException("location==null"); - - Resource resource = Resource.getResource(location, version, updatePolicy); - boolean downloaded = false; - - synchronized (resources) { - if (resources.contains(resource)) - return; - resource.addTracker(this); - resources.add(resource); - } - - // checkCache may take a while (loads properties file). this - // should really be synchronized on resources, but the worst - // case should be that the resource will be updated once even - // if unnecessary. - downloaded = checkCache(resource, updatePolicy); - - synchronized (lock) { - if (!downloaded) - if (prefetch && threads == 0) // existing threads do pre-fetch when queue empty - startThread(); - } - } - - /** - * Removes a resource from the tracker. This method is useful - * to allow memory to be reclaimed, but calling this method is - * not required as resources are reclaimed when the tracker is - * collected. - * - * @throws IllegalArgumentException if the resource is not being tracked - */ - public void removeResource(URL location) { - synchronized (resources) { - Resource resource = getResource(location); - - if (resource != null) { - resources.remove(resource); - resource.removeTracker(this); - } - - // should remove from queue? probably doesn't matter - } - } - - /** - * Check the cache for a resource, and initialize the resource - * as already downloaded if found. <p> - * - * @param updatePolicy whether to check for updates if already in cache - * @return whether the resource are already downloaded - */ - private boolean checkCache(Resource resource, UpdatePolicy updatePolicy) { - if (!CacheUtil.isCacheable(resource.location, resource.downloadVersion)) { - // pretend that they are already downloaded; essentially - // they will just 'pass through' the tracker as if they were - // never added (for example, not affecting the total download size). - synchronized (resource) { - resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED); - } - fireDownloadEvent(resource); - return true; - } - - if (updatePolicy != UpdatePolicy.ALWAYS && updatePolicy != UpdatePolicy.FORCE) { // save loading entry props file - CacheEntry entry = new CacheEntry(resource.location, resource.downloadVersion); - - if (entry.isCached() && !updatePolicy.shouldUpdate(entry)) { - if (JNLPRuntime.isDebug()) - System.out.println("not updating: "+resource.location); - - synchronized (resource) { - resource.localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion); - resource.size = resource.localFile.length(); - resource.transferred = resource.localFile.length(); - resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED); - } - fireDownloadEvent(resource); - return true; - } - } - - if (updatePolicy == UpdatePolicy.FORCE) { // ALWAYS update - // When we are "always" updating, we update for each instance. Reset resource status. - resource.changeStatus(Integer.MAX_VALUE, 0); - } - - // may or may not be cached, but check update when connection - // is open to possibly save network communication time if it - // has to be downloaded, and allow this call to return quickly - return false; - } - - /** - * Adds the listener to the list of objects interested in - * receivind DownloadEvents.<p> - * - * @param location the resource to add a callback for - * @param runnable the runnable to call when resource is completed - */ - public void addDownloadListener(DownloadListener listener) { - synchronized (listeners) { - if (!listeners.contains(listener)) - listeners.add(listener); - } - } - - /** - * Removes a download listener. - */ - public void removeDownloadListener(DownloadListener listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - - /** - * Fires the download event corresponding to the resource's - * state. This method is typicall called by the Resource itself - * on each tracker that is monitoring the resource. Do not call - * this method with any locks because the listeners may call - * back to this ResourceTracker. - */ - protected void fireDownloadEvent(Resource resource) { - DownloadListener l[] = null; - synchronized (listeners) { - l = (DownloadListener[]) listeners.toArray(new DownloadListener[0]); - } - - int status; - synchronized (resource) { - status = resource.status; - } - - DownloadEvent event = new DownloadEvent(this, resource); - for (int i=0; i < l.length; i++) { - if (0 != ((ERROR|DOWNLOADED) & status)) - l[i].downloadCompleted(event); - else if (0 != (DOWNLOADING & status)) - l[i].downloadStarted(event); - else if (0 != (CONNECTING & status)) - l[i].updateStarted(event); - } - } - - /** - * Returns a URL pointing to the cached location of the - * resource, or the resource itself if it is a non-cacheable - * resource.<p> - * - * If the resource has not downloaded yet, the method will block - * until it has been transferred to the cache.<p> - * - * @param location the resource location - * @return the resource, or null if it could not be downloaded - * @throws IllegalArgumentException if the resource is not being tracked - * @see CacheUtil#isCacheable - */ - public URL getCacheURL(URL location) { - try { - File f = getCacheFile(location); - if (f != null) - return f.toURL(); - } - catch (MalformedURLException ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - - return location; - } - - /** - * Returns a file containing the downloaded resource. If the - * resource is non-cacheable then null is returned unless the - * resource is a local file (the original file is returned).<p> - * - * If the resource has not downloaded yet, the method will block - * until it has been transferred to the cache.<p> - * - * @param location the resource location - * @return a local file containing the resource, or null - * @throws IllegalArgumentException if the resource is not being tracked - * @see CacheUtil#isCacheable - */ - public File getCacheFile(URL location) { - try { - Resource resource = getResource(location); - if (!resource.isSet(DOWNLOADED|ERROR)) - waitForResource(location, 0); - - if (resource.isSet(ERROR)) - return null; - - if (resource.localFile != null) - return resource.localFile; - - if (location.getProtocol().equalsIgnoreCase("file")) { - File file = new File(location.getFile()); - if (file.exists()) - return file; - } - - return null; - } - catch (InterruptedException ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - return null; // need an error exception to throw - } - } - - /** - * Returns an input stream that reads the contents of the - * resource. For non-cacheable resources, an InputStream that - * reads from the source location is returned. Otherwise the - * InputStream reads the cached resource.<p> - * - * This method will block while the resource is downloaded to - * the cache. - * - * @throws IOException if there was an error opening the stream - * @throws IllegalArgumentException if the resource is not being tracked - */ - public InputStream getInputStream(URL location) throws IOException { - try { - Resource resource = getResource(location); - if (!resource.isSet(DOWNLOADED|ERROR)) - waitForResource(location, 0); - - if (resource.localFile != null) - return new FileInputStream(resource.localFile); - - return resource.location.openStream(); - } - catch (InterruptedException ex) { - throw new IOException("wait was interrupted"); - } - } - - /** - * Wait for a group of resources to be downloaded and made - * available locally. - * - * @param urls the resources to wait for - * @param timeout the time in ms to wait before returning, 0 for no timeout - * @return whether the resources downloaded before the timeout - * @throws IllegalArgumentException if the resource is not being tracked - */ - public boolean waitForResources(URL urls[], long timeout) throws InterruptedException { - Resource resources[] = new Resource[ urls.length ]; - - synchronized(resources) { - // keep the lock so getResource doesn't have to aquire it each time - for (int i=0; i < urls.length; i++) - resources[i] = getResource(urls[i]); - } - - if (resources.length > 0) - return wait(resources, timeout); - - return true; - } - - /** - * Wait for a particular resource to be downloaded and made - * available. - * - * @param location the resource to wait for - * @param timeout the timeout, or 0 to wait until completed - * @return whether the resource downloaded before the timeout - * @throws InterruptedException if another thread interrupted the wait - * @throws IllegalArgumentException if the resource is not being tracked - */ - public boolean waitForResource(URL location, long timeout) throws InterruptedException { - return wait(new Resource[] { getResource(location) }, timeout); - } - - /** - * Returns the number of bytes downloaded for a resource. - * - * @param location the resource location - * @return the number of bytes transferred - * @throws IllegalArgumentException if the resource is not being tracked - */ - public long getAmountRead(URL location) { - // not atomic b/c transferred is a long, but so what (each - // byte atomic? so probably won't affect anything...) - return getResource(location).transferred; - } - - /** - * Returns whether a resource is available for use (ie, can be - * accessed with the getCacheFile method). - * - * @throws IllegalArgumentException if the resource is not being tracked - */ - public boolean checkResource(URL location) { - return getResource(location).isSet(DOWNLOADED|ERROR); // isSet atomic - } - - /** - * Starts loading the resource if it is not already being - * downloaded or already cached. Resources started downloading - * using this method may download faster than those prefetched - * by the tracker because the tracker will only prefetch one - * resource at a time to conserve system resources. - * - * @return true if the resource is already downloaded (or an error occurred) - * @throws IllegalArgumentException if the resource is not being tracked - */ - public boolean startResource(URL location) { - Resource resource = getResource(location); - - return startResource(resource); - } - - /** - * Sets the resource status to connect and download, and - * enqueues the resource if not already started. - * - * @return true if the resource is already downloaded (or an error occurred) - * @throws IllegalArgumentException if the resource is not being tracked - */ - private boolean startResource(Resource resource) { - boolean enqueue = false; - - synchronized (resource) { - if (resource.isSet(ERROR)) - return true; - - enqueue = !resource.isSet(STARTED); - - if (!resource.isSet(CONNECTED | CONNECTING)) - resource.changeStatus(0, CONNECT|STARTED); - if (!resource.isSet(DOWNLOADED | DOWNLOADING)) - resource.changeStatus(0, DOWNLOAD|STARTED); - - if (!resource.isSet(DOWNLOAD|CONNECT)) - enqueue = false; - } - - if (enqueue) - queueResource(resource); - - return !enqueue; - } - - /** - * Returns the number of total size in bytes of a resource, or - * -1 it the size is not known. - * - * @param location the resource location - * @return the number of bytes, or -1 - * @throws IllegalArgumentException if the resource is not being tracked - */ - public long getTotalSize(URL location) { - return getResource(location).size; // atomic - } - - /** - * Start a new download thread if there are not too many threads - * already running.<p> - * - * Calls to this method should be synchronized on lock. - */ - protected void startThread() { - if (threads < maxThreads) { - threads++; - - Thread thread = new Thread(new Downloader()); - thread.start(); - } - } - - /** - * A thread is ending, called by the thread itself.<p> - * - * Calls to this method should be synchronized. - */ - private void endThread() { - threads--; - - if (threads < 0) { - // this should never happen but try to recover - threads = 0; - - if (queue.size() > 0) // if any on queue make sure a thread is running - startThread(); // look into whether this could create a loop - - throw new RuntimeException("tracker threads < 0"); - } - - if (threads == 0) { - synchronized (prefetchTrackers) { - queue.trimToSize(); // these only accessed by threads so no sync needed - active.clear(); // no threads so no trackers actively downloading - active.trimToSize(); - prefetchTrackers.trimToSize(); - } - } - } - - /** - * Add a resource to the queue and start a thread to download or - * initialize it. - */ - private void queueResource(Resource resource) { - synchronized (lock) { - if (!resource.isSet(CONNECT|DOWNLOAD)) - throw new IllegalArgumentException("Invalid resource state (resource: "+resource+")"); - - queue.add(resource); - startThread(); - } - } - - /** - * Process the resource by either downloading it or initializing - * it. - */ - private void processResource(Resource resource) { - boolean doConnect = false; - boolean doDownload = false; - - synchronized (resource) { - if (resource.isSet(CONNECTING)) - doConnect = true; - } - if (doConnect) - initializeResource(resource); - - synchronized (resource) { - // return to queue if we just initalized but it still needs - // to download (not cached locally / out of date) - if (resource.isSet(DOWNLOAD)) // would be DOWNLOADING if connected before this method - queueResource(resource); - - if (resource.isSet(DOWNLOADING)) - doDownload = true; - } - if (doDownload) - downloadResource(resource); - } - - /** - * Downloads a resource to a file, uncompressing it if required - * - * @param resource the resource to download - */ - private void downloadResource(Resource resource) { - resource.fireDownloadEvent(); // fire DOWNLOADING - - try { - // create out second in case in does not exist - URLConnection con = getVersionedResourceURL(resource).openConnection(); - con.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip"); - - con.connect(); - - /* - * We dont really know what we are downloading. If we ask for - * foo.jar, the server might send us foo.jar.pack.gz or foo.jar.gz - * instead. So we save the file with the appropriate extension - */ - URL downloadLocation = resource.location; - - String contentEncoding = con.getContentEncoding(); - - if (JNLPRuntime.isDebug()) { - System.err.println("Content encoding for " + resource.location + ": " - + contentEncoding); - } - - if (contentEncoding != null) { - if (contentEncoding.equals("gzip")) { - downloadLocation = new URL(downloadLocation.toString() + ".gz"); - } else if (contentEncoding.equals("pack200-gzip")) { - downloadLocation = new URL(downloadLocation.toString() + ".pack.gz"); - } - } - - InputStream in = new BufferedInputStream(con.getInputStream()); - OutputStream out = CacheUtil.getOutputStream(downloadLocation, resource.downloadVersion); - byte buf[] = new byte[1024]; - int rlen; - - while (-1 != (rlen = in.read(buf))) { - resource.transferred += rlen; - out.write(buf, 0, rlen); - } - - in.close(); - out.close(); - - // explicitly close the URLConnection. - if (con instanceof HttpURLConnection) - ((HttpURLConnection)con).disconnect(); - - /* - * If the file was compressed, uncompress it. - */ - if (contentEncoding != null) { - if (contentEncoding.equals("gzip")) { - GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil - .getCacheFile(downloadLocation, resource.downloadVersion))); - InputStream inputStream = new BufferedInputStream(gzInputStream); - - BufferedOutputStream outputStream = new BufferedOutputStream( - new FileOutputStream(CacheUtil.getCacheFile(resource.location, - resource.downloadVersion))); - - while (-1 != (rlen = inputStream.read(buf))) { - outputStream.write(buf, 0, rlen); - } - - outputStream.close(); - inputStream.close(); - gzInputStream.close(); - - } else if (contentEncoding.equals("pack200-gzip")) { - GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream( - CacheUtil.getCacheFile(downloadLocation, resource.downloadVersion))); - InputStream inputStream = new BufferedInputStream(gzInputStream); - - JarOutputStream outputStream = new JarOutputStream(new FileOutputStream( - CacheUtil.getCacheFile(resource.location, resource.downloadVersion))); - - Unpacker unpacker = Pack200.newUnpacker(); - unpacker.unpack(inputStream, outputStream); - - outputStream.close(); - inputStream.close(); - gzInputStream.close(); - } - } - - resource.changeStatus(DOWNLOADING, DOWNLOADED); - synchronized(lock) { - lock.notifyAll(); // wake up wait's to check for completion - } - resource.fireDownloadEvent(); // fire DOWNLOADED - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - resource.changeStatus(0, ERROR); - synchronized(lock) { - lock.notifyAll(); // wake up wait's to check for completion - } - resource.fireDownloadEvent(); // fire ERROR - } - } - - /** - * Open a URL connection and get the content length and other - * fields. - */ - private void initializeResource(Resource resource) { - resource.fireDownloadEvent(); // fire CONNECTING - - try { - File localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion); - - // connect - URLConnection connection = getVersionedResourceURL(resource).openConnection(); // this won't change so should be okay unsynchronized - connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip"); - - int size = connection.getContentLength(); - boolean current = CacheUtil.isCurrent(resource.location, resource.requestVersion, connection) && resource.getUpdatePolicy() != UpdatePolicy.FORCE; - - synchronized(resource) { - resource.localFile = localFile; - // resource.connection = connection; - resource.size = size; - resource.changeStatus(CONNECT|CONNECTING, CONNECTED); - - // check if up-to-date; if so set as downloaded - if (current) - resource.changeStatus(DOWNLOAD|DOWNLOADING, DOWNLOADED); - } - - // update cache entry - CacheEntry entry = new CacheEntry(resource.location, resource.requestVersion); - if (!current) - entry.initialize(connection); - - entry.setLastUpdated(System.currentTimeMillis()); - entry.store(); - - synchronized(lock) { - lock.notifyAll(); // wake up wait's to check for completion - } - resource.fireDownloadEvent(); // fire CONNECTED - - // explicitly close the URLConnection. - if (connection instanceof HttpURLConnection) - ((HttpURLConnection)connection).disconnect(); - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - resource.changeStatus(0, ERROR); - synchronized(lock) { - lock.notifyAll(); // wake up wait's to check for completion - } - resource.fireDownloadEvent(); // fire ERROR - } - } - - /** - * Returns the versioned url for a resource - * @param resource the resource to get the url for - */ - private URL getVersionedResourceURL(Resource resource) { - String actualLocation = resource.location.getProtocol() + "://" - + resource.location.getHost(); - if (resource.location.getPort() != -1) { - actualLocation += ":" + resource.location.getPort(); - } - actualLocation += resource.location.getPath(); - if (resource.requestVersion != null - && resource.requestVersion.isVersionId()) { - actualLocation += "?version-id=" + resource.requestVersion; - } - URL versionedURL; - try { - versionedURL = new URL(actualLocation); - } catch (MalformedURLException e) { - return resource.location; - } - return versionedURL; - } - - - /** - * Pick the next resource to download or initialize. If there - * are no more resources requested then one is taken from a - * resource tracker with prefetch enabled.<p> - * - * The resource state is advanced before it is returned - * (CONNECT->CONNECTING).<p> - * - * Calls to this method should be synchronized on lock.<p> - * - * @return the resource to initialize or download, or null - */ - private static Resource selectNextResource() { - Resource result; - - // pick from queue - result = selectByFlag(queue, CONNECT, ERROR); // connect but not error - if (result == null) - result = selectByFlag(queue, DOWNLOAD, ERROR|CONNECT|CONNECTING); - - // remove from queue if found - if (result != null) - queue.remove(result); - - // prefetch if nothing found so far and this is the last thread - if (result == null && threads == 1) - result = getPrefetch(); - - if (result == null) - return null; - - synchronized (result) { - if (result.isSet(CONNECT)) { - result.changeStatus(CONNECT, CONNECTING); - } - else if (result.isSet(DOWNLOAD)) { - // only download if *not* connecting, when done connecting - // select next will pick up the download part. This makes - // all requested connects happen before any downloads, so - // the size is known as early as possible. - result.changeStatus(DOWNLOAD, DOWNLOADING); - } - } - - return result; - } - - /** - * Returns the next resource to be prefetched before - * requested.<p> - * - * Calls to this method should be synchronized on lock.<p> - */ - private static Resource getPrefetch() { - Resource result = null; - Resource alternate = null; - - // first find one to initialize - synchronized (prefetchTrackers) { - for (int i=0; i < prefetchTrackers.size() && result == null; i++) { - ResourceTracker tracker = (ResourceTracker) prefetchTrackers.get(i); - if (tracker == null) - continue; - - synchronized (tracker.resources) { - result = selectByFlag(tracker.resources, UNINITIALIZED, ERROR); - - if (result == null && alternate == null) - alternate = selectByFlag(tracker.resources, CONNECTED, ERROR|DOWNLOADED|DOWNLOADING|DOWNLOAD); - } - } - } - - // if none to initialize, switch to download - if (result == null) - result = alternate; - - if (result == null) - return null; - - synchronized (result) { - ResourceTracker tracker = result.getTracker(); - if (tracker == null) - return null; // GC of tracker happened between above code and here - - // prevents startResource from putting it on queue since - // we're going to return it. - result.changeStatus(0, STARTED); - - tracker.startResource(result); - } - - return result; - } - - /** - * Selects a resource from the source list that has the - * specified flag set.<p> - * - * Calls to this method should be synchronized on lock and - * source list.<p> - */ - private static Resource selectByFlag(List source, int flag, int notflag) { - Resource result = null; - int score = Integer.MAX_VALUE; - - for (int i=0; i < source.size(); i++) { - Resource resource = (Resource) source.get(i); - boolean selectable = false; - - synchronized (resource) { - if (resource.isSet(flag) && !resource.isSet(notflag)) - selectable = true; - } - - if (selectable) { - int activeCount = 0; - - for (int j=0; j < active.size(); j++) - if ((ResourceTracker)active.get(j) == resource.getTracker()) - activeCount++; - - // try to spread out the downloads so that a slow host - // won't monopolize the downloads - if (activeCount < score) { - result = resource; - score = activeCount; - } - } - } - - return result; - } - - /** - * Return the resource matching the specified URL. - * - * @throws IllegalArgumentException if the resource is not being tracked - */ - private Resource getResource(URL location) { - synchronized (resources) { - for (int i=0; i < resources.size(); i++) { - Resource resource = (Resource) resources.get(i); - - if (CacheUtil.urlEquals(resource.location, location)) - return resource; - } - } - - throw new IllegalArgumentException("Location does not specify a resource being tracked."); - } - - /** - * Wait for some resources. - * - * @param resources the resources to wait for - * @param timeout the timeout, or 0 to wait until completed - * @returns true if the resources were downloaded or had errors, - * false if the timeout was reached - * @throws InterruptedException if another thread interrupted the wait - */ - private boolean wait(Resource resources[], long timeout) throws InterruptedException { - long startTime = System.currentTimeMillis(); - - // start them downloading / connecting in background - for (int i=0; i < resources.length; i++) - startResource(resources[i]); - - // wait for completion - while (true) { - boolean finished = true; - - synchronized (lock) { - // check for completion - for (int i=0; i < resources.length; i++) { - //NetX Deadlocking may be solved by removing this - //synch block. - synchronized (resources[i]) { - if (!resources[i].isSet(DOWNLOADED | ERROR)) { - finished = false; - break; - } - } - } - if (finished) - return true; - - // wait - long waitTime = 0; - - if (timeout > 0) { - waitTime = timeout - (System.currentTimeMillis()-startTime); - if (waitTime <= 0) - return false; - } - - lock.wait(waitTime); - } - } - } - - - // inner classes - - /** - * This class downloads and initializes the queued resources. - */ - class Downloader implements Runnable { - Resource resource = null; - - public void run() { - while (true) { - synchronized (lock) { - // remove from active list, used for load balancing - if (resource != null) - active.remove(resource.getTracker()); - - resource = selectNextResource(); - - if (resource == null) { - endThread(); - break; - } - - // add to active list, used for load balancing - active.add(resource.getTracker()); - } - - try { - processResource(resource); - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - } - // should have a finally in case some exception is thrown by - // selectNextResource(); - } - }; - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.cache; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarOutputStream; +import java.util.jar.Pack200; +import java.util.jar.Pack200.Unpacker; +import java.util.zip.GZIPInputStream; + +import net.sourceforge.jnlp.Version; +import net.sourceforge.jnlp.event.DownloadEvent; +import net.sourceforge.jnlp.event.DownloadListener; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.WeakList; + +/** + * This class tracks the downloading of various resources of a + * JNLP file to local files in the cache. It can be used to + * download icons, jnlp and extension files, jars, and jardiff + * files using the version based protocol or any file using the + * basic download protocol (jardiff and version not implemented + * yet).<p> + * + * The resource tracker can be configured to prefetch resources, + * which are downloaded in the order added to the media + * tracker.<p> + * + * Multiple threads are used to download and cache resources that + * are actively being waited for (blocking a caller) or those that + * have been started downloading by calling the startDownload + * method. Resources that are prefetched are downloaded one at a + * time and only if no other trackers have requested downloads. + * This allows the tracker to start downloading many items without + * using many system resources, but still quickly download items + * as needed.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.22 $ + */ +public class ResourceTracker { + + // todo: use event listener arrays instead of lists + + // todo: see if there is a way to set the socket options just + // for use by the tracker so checks for updates don't hang for + // a long time. + + // todo: ability to restart/retry a hung download + + // todo: move resource downloading/processing code into Resource + // class, threading stays in ResourceTracker + + // todo: get status method? and some way to convey error status + // to the caller. + + // todo: might make a tracker be able to download more than one + // version of a resource, but probably not very useful. + + + // defines + // ResourceTracker.Downloader (download threads) + + // separately locks on (in order of aquire order, ie, sync on prefetch never syncs on lock): + // lock, prefetch, this.resources, each resource, listeners + + /** notified on initialization or download of a resource */ + private static Object lock = new Integer(0); // used to lock static structures + + // shortcuts + private static final int UNINITIALIZED = Resource.UNINITIALIZED; + private static final int CONNECT = Resource.CONNECT; + private static final int CONNECTING = Resource.CONNECTING; + private static final int CONNECTED = Resource.CONNECTED; + private static final int DOWNLOAD = Resource.DOWNLOAD; + private static final int DOWNLOADING = Resource.DOWNLOADING; + private static final int DOWNLOADED = Resource.DOWNLOADED; + private static final int ERROR = Resource.ERROR; + private static final int STARTED = Resource.STARTED; + + /** max threads */ + private static final int maxThreads = 5; + + /** running threads */ + private static int threads = 0; + + /** weak list of resource trackers with resources to prefetch */ + private static WeakList prefetchTrackers = new WeakList(); + + /** resources requested to be downloaded */ + private static ArrayList queue = new ArrayList(); + + /** resource trackers threads are working for (used for load balancing across multi-tracker downloads) */ + private static ArrayList active = new ArrayList(); // + + /** the resources known about by this resource tracker */ + private List resources = new ArrayList(); + + /** download listeners for this tracker */ + private List listeners = new ArrayList(); + + /** whether to download parts before requested */ + private boolean prefetch; + + + /** + * Creates a resource tracker that does not prefetch resources. + */ + public ResourceTracker() { + this(false); + } + + /** + * Creates a resource tracker. + * + * @param prefetch whether to download resources before requested. + */ + public ResourceTracker(boolean prefetch) { + this.prefetch = prefetch; + + if (prefetch) { + synchronized (prefetchTrackers) { + prefetchTrackers.add(this); + prefetchTrackers.trimToSize(); + } + } + } + + /** + * Add a resource identified by the specified location and + * version. The tracker only downloads one version of a given + * resource per instance (ie cannot download both versions 1 and + * 2 of a resource in the same tracker). + * + * @param location the location of the resource + * @param version the resource version + * @param updatePolicy whether to check for updates if already in cache + */ + public void addResource(URL location, Version version, UpdatePolicy updatePolicy) { + if (location == null) + throw new IllegalArgumentException("location==null"); + + Resource resource = Resource.getResource(location, version, updatePolicy); + boolean downloaded = false; + + synchronized (resources) { + if (resources.contains(resource)) + return; + resource.addTracker(this); + resources.add(resource); + } + + // checkCache may take a while (loads properties file). this + // should really be synchronized on resources, but the worst + // case should be that the resource will be updated once even + // if unnecessary. + downloaded = checkCache(resource, updatePolicy); + + synchronized (lock) { + if (!downloaded) + if (prefetch && threads == 0) // existing threads do pre-fetch when queue empty + startThread(); + } + } + + /** + * Removes a resource from the tracker. This method is useful + * to allow memory to be reclaimed, but calling this method is + * not required as resources are reclaimed when the tracker is + * collected. + * + * @throws IllegalArgumentException if the resource is not being tracked + */ + public void removeResource(URL location) { + synchronized (resources) { + Resource resource = getResource(location); + + if (resource != null) { + resources.remove(resource); + resource.removeTracker(this); + } + + // should remove from queue? probably doesn't matter + } + } + + /** + * Check the cache for a resource, and initialize the resource + * as already downloaded if found. <p> + * + * @param updatePolicy whether to check for updates if already in cache + * @return whether the resource are already downloaded + */ + private boolean checkCache(Resource resource, UpdatePolicy updatePolicy) { + if (!CacheUtil.isCacheable(resource.location, resource.downloadVersion)) { + // pretend that they are already downloaded; essentially + // they will just 'pass through' the tracker as if they were + // never added (for example, not affecting the total download size). + synchronized (resource) { + resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED); + } + fireDownloadEvent(resource); + return true; + } + + if (updatePolicy != UpdatePolicy.ALWAYS && updatePolicy != UpdatePolicy.FORCE) { // save loading entry props file + CacheEntry entry = new CacheEntry(resource.location, resource.downloadVersion); + + if (entry.isCached() && !updatePolicy.shouldUpdate(entry)) { + if (JNLPRuntime.isDebug()) + System.out.println("not updating: "+resource.location); + + synchronized (resource) { + resource.localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion); + resource.size = resource.localFile.length(); + resource.transferred = resource.localFile.length(); + resource.changeStatus(0, DOWNLOADED|CONNECTED|STARTED); + } + fireDownloadEvent(resource); + return true; + } + } + + if (updatePolicy == UpdatePolicy.FORCE) { // ALWAYS update + // When we are "always" updating, we update for each instance. Reset resource status. + resource.changeStatus(Integer.MAX_VALUE, 0); + } + + // may or may not be cached, but check update when connection + // is open to possibly save network communication time if it + // has to be downloaded, and allow this call to return quickly + return false; + } + + /** + * Adds the listener to the list of objects interested in + * receivind DownloadEvents.<p> + * + * @param location the resource to add a callback for + * @param runnable the runnable to call when resource is completed + */ + public void addDownloadListener(DownloadListener listener) { + synchronized (listeners) { + if (!listeners.contains(listener)) + listeners.add(listener); + } + } + + /** + * Removes a download listener. + */ + public void removeDownloadListener(DownloadListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + /** + * Fires the download event corresponding to the resource's + * state. This method is typicall called by the Resource itself + * on each tracker that is monitoring the resource. Do not call + * this method with any locks because the listeners may call + * back to this ResourceTracker. + */ + protected void fireDownloadEvent(Resource resource) { + DownloadListener l[] = null; + synchronized (listeners) { + l = (DownloadListener[]) listeners.toArray(new DownloadListener[0]); + } + + int status; + synchronized (resource) { + status = resource.status; + } + + DownloadEvent event = new DownloadEvent(this, resource); + for (int i=0; i < l.length; i++) { + if (0 != ((ERROR|DOWNLOADED) & status)) + l[i].downloadCompleted(event); + else if (0 != (DOWNLOADING & status)) + l[i].downloadStarted(event); + else if (0 != (CONNECTING & status)) + l[i].updateStarted(event); + } + } + + /** + * Returns a URL pointing to the cached location of the + * resource, or the resource itself if it is a non-cacheable + * resource.<p> + * + * If the resource has not downloaded yet, the method will block + * until it has been transferred to the cache.<p> + * + * @param location the resource location + * @return the resource, or null if it could not be downloaded + * @throws IllegalArgumentException if the resource is not being tracked + * @see CacheUtil#isCacheable + */ + public URL getCacheURL(URL location) { + try { + File f = getCacheFile(location); + if (f != null) + return f.toURL(); + } + catch (MalformedURLException ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + + return location; + } + + /** + * Returns a file containing the downloaded resource. If the + * resource is non-cacheable then null is returned unless the + * resource is a local file (the original file is returned).<p> + * + * If the resource has not downloaded yet, the method will block + * until it has been transferred to the cache.<p> + * + * @param location the resource location + * @return a local file containing the resource, or null + * @throws IllegalArgumentException if the resource is not being tracked + * @see CacheUtil#isCacheable + */ + public File getCacheFile(URL location) { + try { + Resource resource = getResource(location); + if (!resource.isSet(DOWNLOADED|ERROR)) + waitForResource(location, 0); + + if (resource.isSet(ERROR)) + return null; + + if (resource.localFile != null) + return resource.localFile; + + if (location.getProtocol().equalsIgnoreCase("file")) { + File file = new File(location.getFile()); + if (file.exists()) + return file; + } + + return null; + } + catch (InterruptedException ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + return null; // need an error exception to throw + } + } + + /** + * Returns an input stream that reads the contents of the + * resource. For non-cacheable resources, an InputStream that + * reads from the source location is returned. Otherwise the + * InputStream reads the cached resource.<p> + * + * This method will block while the resource is downloaded to + * the cache. + * + * @throws IOException if there was an error opening the stream + * @throws IllegalArgumentException if the resource is not being tracked + */ + public InputStream getInputStream(URL location) throws IOException { + try { + Resource resource = getResource(location); + if (!resource.isSet(DOWNLOADED|ERROR)) + waitForResource(location, 0); + + if (resource.localFile != null) + return new FileInputStream(resource.localFile); + + return resource.location.openStream(); + } + catch (InterruptedException ex) { + throw new IOException("wait was interrupted"); + } + } + + /** + * Wait for a group of resources to be downloaded and made + * available locally. + * + * @param urls the resources to wait for + * @param timeout the time in ms to wait before returning, 0 for no timeout + * @return whether the resources downloaded before the timeout + * @throws IllegalArgumentException if the resource is not being tracked + */ + public boolean waitForResources(URL urls[], long timeout) throws InterruptedException { + Resource resources[] = new Resource[ urls.length ]; + + synchronized(resources) { + // keep the lock so getResource doesn't have to aquire it each time + for (int i=0; i < urls.length; i++) + resources[i] = getResource(urls[i]); + } + + if (resources.length > 0) + return wait(resources, timeout); + + return true; + } + + /** + * Wait for a particular resource to be downloaded and made + * available. + * + * @param location the resource to wait for + * @param timeout the timeout, or 0 to wait until completed + * @return whether the resource downloaded before the timeout + * @throws InterruptedException if another thread interrupted the wait + * @throws IllegalArgumentException if the resource is not being tracked + */ + public boolean waitForResource(URL location, long timeout) throws InterruptedException { + return wait(new Resource[] { getResource(location) }, timeout); + } + + /** + * Returns the number of bytes downloaded for a resource. + * + * @param location the resource location + * @return the number of bytes transferred + * @throws IllegalArgumentException if the resource is not being tracked + */ + public long getAmountRead(URL location) { + // not atomic b/c transferred is a long, but so what (each + // byte atomic? so probably won't affect anything...) + return getResource(location).transferred; + } + + /** + * Returns whether a resource is available for use (ie, can be + * accessed with the getCacheFile method). + * + * @throws IllegalArgumentException if the resource is not being tracked + */ + public boolean checkResource(URL location) { + return getResource(location).isSet(DOWNLOADED|ERROR); // isSet atomic + } + + /** + * Starts loading the resource if it is not already being + * downloaded or already cached. Resources started downloading + * using this method may download faster than those prefetched + * by the tracker because the tracker will only prefetch one + * resource at a time to conserve system resources. + * + * @return true if the resource is already downloaded (or an error occurred) + * @throws IllegalArgumentException if the resource is not being tracked + */ + public boolean startResource(URL location) { + Resource resource = getResource(location); + + return startResource(resource); + } + + /** + * Sets the resource status to connect and download, and + * enqueues the resource if not already started. + * + * @return true if the resource is already downloaded (or an error occurred) + * @throws IllegalArgumentException if the resource is not being tracked + */ + private boolean startResource(Resource resource) { + boolean enqueue = false; + + synchronized (resource) { + if (resource.isSet(ERROR)) + return true; + + enqueue = !resource.isSet(STARTED); + + if (!resource.isSet(CONNECTED | CONNECTING)) + resource.changeStatus(0, CONNECT|STARTED); + if (!resource.isSet(DOWNLOADED | DOWNLOADING)) + resource.changeStatus(0, DOWNLOAD|STARTED); + + if (!resource.isSet(DOWNLOAD|CONNECT)) + enqueue = false; + } + + if (enqueue) + queueResource(resource); + + return !enqueue; + } + + /** + * Returns the number of total size in bytes of a resource, or + * -1 it the size is not known. + * + * @param location the resource location + * @return the number of bytes, or -1 + * @throws IllegalArgumentException if the resource is not being tracked + */ + public long getTotalSize(URL location) { + return getResource(location).size; // atomic + } + + /** + * Start a new download thread if there are not too many threads + * already running.<p> + * + * Calls to this method should be synchronized on lock. + */ + protected void startThread() { + if (threads < maxThreads) { + threads++; + + Thread thread = new Thread(new Downloader()); + thread.start(); + } + } + + /** + * A thread is ending, called by the thread itself.<p> + * + * Calls to this method should be synchronized. + */ + private void endThread() { + threads--; + + if (threads < 0) { + // this should never happen but try to recover + threads = 0; + + if (queue.size() > 0) // if any on queue make sure a thread is running + startThread(); // look into whether this could create a loop + + throw new RuntimeException("tracker threads < 0"); + } + + if (threads == 0) { + synchronized (prefetchTrackers) { + queue.trimToSize(); // these only accessed by threads so no sync needed + active.clear(); // no threads so no trackers actively downloading + active.trimToSize(); + prefetchTrackers.trimToSize(); + } + } + } + + /** + * Add a resource to the queue and start a thread to download or + * initialize it. + */ + private void queueResource(Resource resource) { + synchronized (lock) { + if (!resource.isSet(CONNECT|DOWNLOAD)) + throw new IllegalArgumentException("Invalid resource state (resource: "+resource+")"); + + queue.add(resource); + startThread(); + } + } + + /** + * Process the resource by either downloading it or initializing + * it. + */ + private void processResource(Resource resource) { + boolean doConnect = false; + boolean doDownload = false; + + synchronized (resource) { + if (resource.isSet(CONNECTING)) + doConnect = true; + } + if (doConnect) + initializeResource(resource); + + synchronized (resource) { + // return to queue if we just initalized but it still needs + // to download (not cached locally / out of date) + if (resource.isSet(DOWNLOAD)) // would be DOWNLOADING if connected before this method + queueResource(resource); + + if (resource.isSet(DOWNLOADING)) + doDownload = true; + } + if (doDownload) + downloadResource(resource); + } + + /** + * Downloads a resource to a file, uncompressing it if required + * + * @param resource the resource to download + */ + private void downloadResource(Resource resource) { + resource.fireDownloadEvent(); // fire DOWNLOADING + + try { + // create out second in case in does not exist + URLConnection con = getVersionedResourceURL(resource).openConnection(); + con.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip"); + + con.connect(); + + /* + * We dont really know what we are downloading. If we ask for + * foo.jar, the server might send us foo.jar.pack.gz or foo.jar.gz + * instead. So we save the file with the appropriate extension + */ + URL downloadLocation = resource.location; + + String contentEncoding = con.getContentEncoding(); + + if (JNLPRuntime.isDebug()) { + System.err.println("Content encoding for " + resource.location + ": " + + contentEncoding); + } + + if (contentEncoding != null) { + if (contentEncoding.equals("gzip")) { + downloadLocation = new URL(downloadLocation.toString() + ".gz"); + } else if (contentEncoding.equals("pack200-gzip")) { + downloadLocation = new URL(downloadLocation.toString() + ".pack.gz"); + } + } + + InputStream in = new BufferedInputStream(con.getInputStream()); + OutputStream out = CacheUtil.getOutputStream(downloadLocation, resource.downloadVersion); + byte buf[] = new byte[1024]; + int rlen; + + while (-1 != (rlen = in.read(buf))) { + resource.transferred += rlen; + out.write(buf, 0, rlen); + } + + in.close(); + out.close(); + + // explicitly close the URLConnection. + if (con instanceof HttpURLConnection) + ((HttpURLConnection)con).disconnect(); + + /* + * If the file was compressed, uncompress it. + */ + if (contentEncoding != null) { + if (contentEncoding.equals("gzip")) { + GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil + .getCacheFile(downloadLocation, resource.downloadVersion))); + InputStream inputStream = new BufferedInputStream(gzInputStream); + + BufferedOutputStream outputStream = new BufferedOutputStream( + new FileOutputStream(CacheUtil.getCacheFile(resource.location, + resource.downloadVersion))); + + while (-1 != (rlen = inputStream.read(buf))) { + outputStream.write(buf, 0, rlen); + } + + outputStream.close(); + inputStream.close(); + gzInputStream.close(); + + } else if (contentEncoding.equals("pack200-gzip")) { + GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream( + CacheUtil.getCacheFile(downloadLocation, resource.downloadVersion))); + InputStream inputStream = new BufferedInputStream(gzInputStream); + + JarOutputStream outputStream = new JarOutputStream(new FileOutputStream( + CacheUtil.getCacheFile(resource.location, resource.downloadVersion))); + + Unpacker unpacker = Pack200.newUnpacker(); + unpacker.unpack(inputStream, outputStream); + + outputStream.close(); + inputStream.close(); + gzInputStream.close(); + } + } + + resource.changeStatus(DOWNLOADING, DOWNLOADED); + synchronized(lock) { + lock.notifyAll(); // wake up wait's to check for completion + } + resource.fireDownloadEvent(); // fire DOWNLOADED + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + resource.changeStatus(0, ERROR); + synchronized(lock) { + lock.notifyAll(); // wake up wait's to check for completion + } + resource.fireDownloadEvent(); // fire ERROR + } + } + + /** + * Open a URL connection and get the content length and other + * fields. + */ + private void initializeResource(Resource resource) { + resource.fireDownloadEvent(); // fire CONNECTING + + try { + File localFile = CacheUtil.getCacheFile(resource.location, resource.downloadVersion); + + // connect + URLConnection connection = getVersionedResourceURL(resource).openConnection(); // this won't change so should be okay unsynchronized + connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip"); + + int size = connection.getContentLength(); + boolean current = CacheUtil.isCurrent(resource.location, resource.requestVersion, connection) && resource.getUpdatePolicy() != UpdatePolicy.FORCE; + + synchronized(resource) { + resource.localFile = localFile; + // resource.connection = connection; + resource.size = size; + resource.changeStatus(CONNECT|CONNECTING, CONNECTED); + + // check if up-to-date; if so set as downloaded + if (current) + resource.changeStatus(DOWNLOAD|DOWNLOADING, DOWNLOADED); + } + + // update cache entry + CacheEntry entry = new CacheEntry(resource.location, resource.requestVersion); + if (!current) + entry.initialize(connection); + + entry.setLastUpdated(System.currentTimeMillis()); + entry.store(); + + synchronized(lock) { + lock.notifyAll(); // wake up wait's to check for completion + } + resource.fireDownloadEvent(); // fire CONNECTED + + // explicitly close the URLConnection. + if (connection instanceof HttpURLConnection) + ((HttpURLConnection)connection).disconnect(); + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + resource.changeStatus(0, ERROR); + synchronized(lock) { + lock.notifyAll(); // wake up wait's to check for completion + } + resource.fireDownloadEvent(); // fire ERROR + } + } + + /** + * Returns the versioned url for a resource + * @param resource the resource to get the url for + */ + private URL getVersionedResourceURL(Resource resource) { + String actualLocation = resource.location.getProtocol() + "://" + + resource.location.getHost(); + if (resource.location.getPort() != -1) { + actualLocation += ":" + resource.location.getPort(); + } + actualLocation += resource.location.getPath(); + if (resource.requestVersion != null + && resource.requestVersion.isVersionId()) { + actualLocation += "?version-id=" + resource.requestVersion; + } + URL versionedURL; + try { + versionedURL = new URL(actualLocation); + } catch (MalformedURLException e) { + return resource.location; + } + return versionedURL; + } + + + /** + * Pick the next resource to download or initialize. If there + * are no more resources requested then one is taken from a + * resource tracker with prefetch enabled.<p> + * + * The resource state is advanced before it is returned + * (CONNECT->CONNECTING).<p> + * + * Calls to this method should be synchronized on lock.<p> + * + * @return the resource to initialize or download, or null + */ + private static Resource selectNextResource() { + Resource result; + + // pick from queue + result = selectByFlag(queue, CONNECT, ERROR); // connect but not error + if (result == null) + result = selectByFlag(queue, DOWNLOAD, ERROR|CONNECT|CONNECTING); + + // remove from queue if found + if (result != null) + queue.remove(result); + + // prefetch if nothing found so far and this is the last thread + if (result == null && threads == 1) + result = getPrefetch(); + + if (result == null) + return null; + + synchronized (result) { + if (result.isSet(CONNECT)) { + result.changeStatus(CONNECT, CONNECTING); + } + else if (result.isSet(DOWNLOAD)) { + // only download if *not* connecting, when done connecting + // select next will pick up the download part. This makes + // all requested connects happen before any downloads, so + // the size is known as early as possible. + result.changeStatus(DOWNLOAD, DOWNLOADING); + } + } + + return result; + } + + /** + * Returns the next resource to be prefetched before + * requested.<p> + * + * Calls to this method should be synchronized on lock.<p> + */ + private static Resource getPrefetch() { + Resource result = null; + Resource alternate = null; + + // first find one to initialize + synchronized (prefetchTrackers) { + for (int i=0; i < prefetchTrackers.size() && result == null; i++) { + ResourceTracker tracker = (ResourceTracker) prefetchTrackers.get(i); + if (tracker == null) + continue; + + synchronized (tracker.resources) { + result = selectByFlag(tracker.resources, UNINITIALIZED, ERROR); + + if (result == null && alternate == null) + alternate = selectByFlag(tracker.resources, CONNECTED, ERROR|DOWNLOADED|DOWNLOADING|DOWNLOAD); + } + } + } + + // if none to initialize, switch to download + if (result == null) + result = alternate; + + if (result == null) + return null; + + synchronized (result) { + ResourceTracker tracker = result.getTracker(); + if (tracker == null) + return null; // GC of tracker happened between above code and here + + // prevents startResource from putting it on queue since + // we're going to return it. + result.changeStatus(0, STARTED); + + tracker.startResource(result); + } + + return result; + } + + /** + * Selects a resource from the source list that has the + * specified flag set.<p> + * + * Calls to this method should be synchronized on lock and + * source list.<p> + */ + private static Resource selectByFlag(List source, int flag, int notflag) { + Resource result = null; + int score = Integer.MAX_VALUE; + + for (int i=0; i < source.size(); i++) { + Resource resource = (Resource) source.get(i); + boolean selectable = false; + + synchronized (resource) { + if (resource.isSet(flag) && !resource.isSet(notflag)) + selectable = true; + } + + if (selectable) { + int activeCount = 0; + + for (int j=0; j < active.size(); j++) + if ((ResourceTracker)active.get(j) == resource.getTracker()) + activeCount++; + + // try to spread out the downloads so that a slow host + // won't monopolize the downloads + if (activeCount < score) { + result = resource; + score = activeCount; + } + } + } + + return result; + } + + /** + * Return the resource matching the specified URL. + * + * @throws IllegalArgumentException if the resource is not being tracked + */ + private Resource getResource(URL location) { + synchronized (resources) { + for (int i=0; i < resources.size(); i++) { + Resource resource = (Resource) resources.get(i); + + if (CacheUtil.urlEquals(resource.location, location)) + return resource; + } + } + + throw new IllegalArgumentException("Location does not specify a resource being tracked."); + } + + /** + * Wait for some resources. + * + * @param resources the resources to wait for + * @param timeout the timeout, or 0 to wait until completed + * @returns true if the resources were downloaded or had errors, + * false if the timeout was reached + * @throws InterruptedException if another thread interrupted the wait + */ + private boolean wait(Resource resources[], long timeout) throws InterruptedException { + long startTime = System.currentTimeMillis(); + + // start them downloading / connecting in background + for (int i=0; i < resources.length; i++) + startResource(resources[i]); + + // wait for completion + while (true) { + boolean finished = true; + + synchronized (lock) { + // check for completion + for (int i=0; i < resources.length; i++) { + //NetX Deadlocking may be solved by removing this + //synch block. + synchronized (resources[i]) { + if (!resources[i].isSet(DOWNLOADED | ERROR)) { + finished = false; + break; + } + } + } + if (finished) + return true; + + // wait + long waitTime = 0; + + if (timeout > 0) { + waitTime = timeout - (System.currentTimeMillis()-startTime); + if (waitTime <= 0) + return false; + } + + lock.wait(waitTime); + } + } + } + + + // inner classes + + /** + * This class downloads and initializes the queued resources. + */ + class Downloader implements Runnable { + Resource resource = null; + + public void run() { + while (true) { + synchronized (lock) { + // remove from active list, used for load balancing + if (resource != null) + active.remove(resource.getTracker()); + + resource = selectNextResource(); + + if (resource == null) { + endThread(); + break; + } + + // add to active list, used for load balancing + active.add(resource.getTracker()); + } + + try { + processResource(resource); + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + } + // should have a finally in case some exception is thrown by + // selectNextResource(); + } + }; + +} + +
--- a/rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/AppletEnvironment.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,354 +1,354 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.applet.*; -import java.awt.*; -import java.awt.event.*; -import java.util.*; -import java.util.List; -import java.lang.reflect.InvocationTargetException; -import java.net.*; -import java.io.*; -import javax.swing.*; - -import net.sourceforge.jnlp.*; -import net.sourceforge.jnlp.util.*; - -/** - * The applet environment including stub, context, and frame. The - * default environment puts the applet in a non-resiable frame; - * this can be changed by obtaining the frame and setting it - * resizable. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.12 $ - */ -public class AppletEnvironment implements AppletContext, AppletStub { - - /** the JNLP file */ - private JNLPFile file; - - /** the applet instance */ - private AppletInstance appletInstance; - - /** the applet */ - private Applet applet; - - /** the parameters */ - private Map parameters; - - /** the applet container */ - private Container cont; - - /** weak references to the audio clips */ - private WeakList weakClips = new WeakList(); - - /** whether the applet has been started / displayed */ - private boolean appletStarted = false; - - /** whether the applet has been destroyed */ - private boolean destroyed = false; - - - /** - * Create a new applet environment for the applet specified by - * the JNLP file. - */ - public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance, Container cont) { - this.file = file; - this.appletInstance = appletInstance; - this.applet = appletInstance.getApplet(); - - parameters = file.getApplet().getParameters(); - this.cont = cont; - } - - /** - * Create a new applet environment for the applet specified by - * the JNLP file, in a new frame. - */ - public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance) { - this(file, appletInstance, null); - - Frame frame = new Frame(file.getApplet().getName() + " - Applet"); - frame.setResizable(false); - - appletInstance.addWindow(frame); - // may not need this once security manager can close windows - // that do not have app code on the stack - WindowListener closer = new WindowAdapter() { - public void windowClosing(WindowEvent event) { - appletInstance.destroy(); - System.exit(0); - } - }; - frame.addWindowListener(closer); - this.cont = frame; - } - - /** - * Checks whether the applet has been destroyed, and throws an - * IllegalStateException if the applet has been destroyed of. - * - * @throws IllegalStateException - */ - private void checkDestroyed() { - if (destroyed) - throw new IllegalStateException("Illegal applet stub/context access: applet destroyed."); - } - - /** - * Disposes the applet's resources and disables the applet - * environment from further use; after calling this method the - * applet stub and context methods throw IllegalStateExceptions. - */ - public void destroy() { - destroyed = true; - - List clips = weakClips.hardList(); - for (int i = 0; i < clips.size(); i++) { - ((AppletAudioClip)clips.get(i)).dispose(); - } - } - - /** - * Returns the frame that contains the applet. Disposing this - * frame will destroy the applet. - */ - public Container getAppletFrame() { - // TODO: rename this method to getAppletContainer ? - return cont; - } - - /** - * Initialize, start, and show the applet. - */ - public void startApplet() { - checkDestroyed(); - - if (appletStarted) - return; - - appletStarted = true; - - try { - AppletDesc appletDesc = file.getApplet(); - - if (cont instanceof AppletStub) - applet.setStub((AppletStub)cont); - else - applet.setStub(this); - - cont.setLayout(new BorderLayout()); - cont.add("Center", applet); - cont.validate(); - - // This is only needed if the applet is in its own frame. - if (cont instanceof Frame) { - Frame frame = (Frame) cont; - frame.pack(); // cause insets to be calculated - - Insets insets = frame.getInsets(); - frame.setSize(appletDesc.getWidth() + insets.left + insets.right, - appletDesc.getHeight() + insets.top + insets.bottom); - } - - try { - SwingUtilities.invokeAndWait(new Runnable() { - public void run() { - // do first because some applets need to be displayed before - // starting (they use Component.getImage or something) - cont.setVisible(true); - - applet.init(); - applet.start(); - - cont.invalidate(); // this should force the applet to - cont.validate(); // the correct size and to repaint - cont.repaint(); - } - }); - } catch (InterruptedException ie) { - - } catch (InvocationTargetException ite) { - - } - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - // should also kill the applet? - } - } - - // applet context methods - - /** - * Returns the applet if the applet's name is specified, - * otherwise return null. - */ - public Applet getApplet(String name) { - checkDestroyed(); - - if (name != null && name.equals(file.getApplet().getName())) - return applet; - else - return null; - } - - /** - * Returns an enumeration that contains only the applet - * from the JNLP file. - */ - public Enumeration getApplets() { - checkDestroyed(); - - return Collections.enumeration( Arrays.asList(new Applet[] { applet }) ); - } - - /** - * Returns an audio clip. - */ - public AudioClip getAudioClip(URL location) { - checkDestroyed(); - - AppletAudioClip clip = new AppletAudioClip(location); - - weakClips.add(clip); - weakClips.trimToSize(); - - return clip; - } - - /** - * Return an image loaded from the specified location. - */ - public Image getImage(URL location) { - checkDestroyed(); - - //return Toolkit.getDefaultToolkit().createImage(location); - Image image = (new ImageIcon(location)).getImage(); - - return image; - } - - /** - * Not implemented yet. - */ - public void showDocument(java.net.URL uRL) { - checkDestroyed(); - - } - - /** - * Not implemented yet. - */ - public void showDocument(java.net.URL uRL, java.lang.String str) { - checkDestroyed(); - - } - - /** - * Not implemented yet. - */ - public void showStatus(java.lang.String str) { - checkDestroyed(); - - } - - /** - * Required for JRE1.4, but not implemented yet. - */ - public void setStream(String key, InputStream stream) { - checkDestroyed(); - - } - - /** - * Required for JRE1.4, but not implemented yet. - */ - public InputStream getStream(String key) { - checkDestroyed(); - - return null; - } - - /** - * Required for JRE1.4, but not implemented yet. - */ - public Iterator getStreamKeys() { - checkDestroyed(); - - return null; - } - - // stub methods - - public void appletResize(int width, int height) { - checkDestroyed(); - - if (cont instanceof Frame) { - Frame frame = (Frame) cont; - Insets insets = frame.getInsets(); - - frame.setSize(width + insets.left + insets.right, - height + insets.top + insets.bottom); - } - } - - public AppletContext getAppletContext() { - checkDestroyed(); - - return this; - } - - public URL getCodeBase() { - checkDestroyed(); - - return file.getCodeBase(); - } - - public URL getDocumentBase() { - checkDestroyed(); - - return file.getApplet().getDocumentBase(); - } - - // FIXME: Sun's applet code forces all parameters to lower case. - // Does Netx's JNLP code do the same, so we can remove the first lookup? - public String getParameter(String name) { - checkDestroyed(); - - String s = (String) parameters.get(name); - if (s != null) - return s; - - return (String) parameters.get(name.toLowerCase()); - } - - public boolean isActive() { - checkDestroyed(); - - // it won't be started or stopped, so if it can call it's running - return true; - } - - -} +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; +import java.lang.reflect.InvocationTargetException; +import java.net.*; +import java.io.*; +import javax.swing.*; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.util.*; + +/** + * The applet environment including stub, context, and frame. The + * default environment puts the applet in a non-resiable frame; + * this can be changed by obtaining the frame and setting it + * resizable. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.12 $ + */ +public class AppletEnvironment implements AppletContext, AppletStub { + + /** the JNLP file */ + private JNLPFile file; + + /** the applet instance */ + private AppletInstance appletInstance; + + /** the applet */ + private Applet applet; + + /** the parameters */ + private Map parameters; + + /** the applet container */ + private Container cont; + + /** weak references to the audio clips */ + private WeakList weakClips = new WeakList(); + + /** whether the applet has been started / displayed */ + private boolean appletStarted = false; + + /** whether the applet has been destroyed */ + private boolean destroyed = false; + + + /** + * Create a new applet environment for the applet specified by + * the JNLP file. + */ + public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance, Container cont) { + this.file = file; + this.appletInstance = appletInstance; + this.applet = appletInstance.getApplet(); + + parameters = file.getApplet().getParameters(); + this.cont = cont; + } + + /** + * Create a new applet environment for the applet specified by + * the JNLP file, in a new frame. + */ + public AppletEnvironment(JNLPFile file, final AppletInstance appletInstance) { + this(file, appletInstance, null); + + Frame frame = new Frame(file.getApplet().getName() + " - Applet"); + frame.setResizable(false); + + appletInstance.addWindow(frame); + // may not need this once security manager can close windows + // that do not have app code on the stack + WindowListener closer = new WindowAdapter() { + public void windowClosing(WindowEvent event) { + appletInstance.destroy(); + System.exit(0); + } + }; + frame.addWindowListener(closer); + this.cont = frame; + } + + /** + * Checks whether the applet has been destroyed, and throws an + * IllegalStateException if the applet has been destroyed of. + * + * @throws IllegalStateException + */ + private void checkDestroyed() { + if (destroyed) + throw new IllegalStateException("Illegal applet stub/context access: applet destroyed."); + } + + /** + * Disposes the applet's resources and disables the applet + * environment from further use; after calling this method the + * applet stub and context methods throw IllegalStateExceptions. + */ + public void destroy() { + destroyed = true; + + List clips = weakClips.hardList(); + for (int i = 0; i < clips.size(); i++) { + ((AppletAudioClip)clips.get(i)).dispose(); + } + } + + /** + * Returns the frame that contains the applet. Disposing this + * frame will destroy the applet. + */ + public Container getAppletFrame() { + // TODO: rename this method to getAppletContainer ? + return cont; + } + + /** + * Initialize, start, and show the applet. + */ + public void startApplet() { + checkDestroyed(); + + if (appletStarted) + return; + + appletStarted = true; + + try { + AppletDesc appletDesc = file.getApplet(); + + if (cont instanceof AppletStub) + applet.setStub((AppletStub)cont); + else + applet.setStub(this); + + cont.setLayout(new BorderLayout()); + cont.add("Center", applet); + cont.validate(); + + // This is only needed if the applet is in its own frame. + if (cont instanceof Frame) { + Frame frame = (Frame) cont; + frame.pack(); // cause insets to be calculated + + Insets insets = frame.getInsets(); + frame.setSize(appletDesc.getWidth() + insets.left + insets.right, + appletDesc.getHeight() + insets.top + insets.bottom); + } + + try { + SwingUtilities.invokeAndWait(new Runnable() { + public void run() { + // do first because some applets need to be displayed before + // starting (they use Component.getImage or something) + cont.setVisible(true); + + applet.init(); + applet.start(); + + cont.invalidate(); // this should force the applet to + cont.validate(); // the correct size and to repaint + cont.repaint(); + } + }); + } catch (InterruptedException ie) { + + } catch (InvocationTargetException ite) { + + } + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + // should also kill the applet? + } + } + + // applet context methods + + /** + * Returns the applet if the applet's name is specified, + * otherwise return null. + */ + public Applet getApplet(String name) { + checkDestroyed(); + + if (name != null && name.equals(file.getApplet().getName())) + return applet; + else + return null; + } + + /** + * Returns an enumeration that contains only the applet + * from the JNLP file. + */ + public Enumeration getApplets() { + checkDestroyed(); + + return Collections.enumeration( Arrays.asList(new Applet[] { applet }) ); + } + + /** + * Returns an audio clip. + */ + public AudioClip getAudioClip(URL location) { + checkDestroyed(); + + AppletAudioClip clip = new AppletAudioClip(location); + + weakClips.add(clip); + weakClips.trimToSize(); + + return clip; + } + + /** + * Return an image loaded from the specified location. + */ + public Image getImage(URL location) { + checkDestroyed(); + + //return Toolkit.getDefaultToolkit().createImage(location); + Image image = (new ImageIcon(location)).getImage(); + + return image; + } + + /** + * Not implemented yet. + */ + public void showDocument(java.net.URL uRL) { + checkDestroyed(); + + } + + /** + * Not implemented yet. + */ + public void showDocument(java.net.URL uRL, java.lang.String str) { + checkDestroyed(); + + } + + /** + * Not implemented yet. + */ + public void showStatus(java.lang.String str) { + checkDestroyed(); + + } + + /** + * Required for JRE1.4, but not implemented yet. + */ + public void setStream(String key, InputStream stream) { + checkDestroyed(); + + } + + /** + * Required for JRE1.4, but not implemented yet. + */ + public InputStream getStream(String key) { + checkDestroyed(); + + return null; + } + + /** + * Required for JRE1.4, but not implemented yet. + */ + public Iterator getStreamKeys() { + checkDestroyed(); + + return null; + } + + // stub methods + + public void appletResize(int width, int height) { + checkDestroyed(); + + if (cont instanceof Frame) { + Frame frame = (Frame) cont; + Insets insets = frame.getInsets(); + + frame.setSize(width + insets.left + insets.right, + height + insets.top + insets.bottom); + } + } + + public AppletContext getAppletContext() { + checkDestroyed(); + + return this; + } + + public URL getCodeBase() { + checkDestroyed(); + + return file.getCodeBase(); + } + + public URL getDocumentBase() { + checkDestroyed(); + + return file.getApplet().getDocumentBase(); + } + + // FIXME: Sun's applet code forces all parameters to lower case. + // Does Netx's JNLP code do the same, so we can remove the first lookup? + public String getParameter(String name) { + checkDestroyed(); + + String s = (String) parameters.get(name); + if (s != null) + return s; + + return (String) parameters.get(name.toLowerCase()); + } + + public boolean isActive() { + checkDestroyed(); + + // it won't be started or stopped, so if it can call it's running + return true; + } + + +}
--- a/rt/net/sourceforge/jnlp/runtime/AppletInstance.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/AppletInstance.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,139 +1,139 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.applet.*; -import java.awt.*; -import java.io.*; -import java.net.*; -import java.util.*; -import java.util.List; -import java.security.*; -import java.lang.reflect.*; -import java.lang.ref.*; - -import net.sourceforge.jnlp.*; - - -/** - * Represents a launched application instance created from a JNLP - * file. This class does not control the operation of the applet, - * use the AppletEnvironment class to start and stop the applet. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.9 $ - */ -public class AppletInstance extends ApplicationInstance { - - /** whether the applet's stop and destroy methods have been called */ - private boolean appletStopped = false; - - /** the applet */ - private Applet applet; - - /** the applet environment */ - private AppletEnvironment environment; - - - /** - * Create a New Task based on the Specified URL - */ - public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet) { - super(file, group, loader); - - this.applet = applet; - - this.environment = new AppletEnvironment(file, this); - } - - /** - * - */ - public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet, Container cont) { - super(file, group, loader); - this.applet = applet; - this.environment = new AppletEnvironment(file, this, cont); - } - - /** - * Sets whether the applet is resizable or not. Applets default - * to being not resizable. - */ - public void setResizable(boolean resizable) { - Container c = environment.getAppletFrame(); - if (c instanceof Frame) - ((Frame) c).setResizable(resizable); - } - - /** - * Returns whether the applet is resizable. - */ - public boolean isResizable() { - Container c = environment.getAppletFrame(); - if (c instanceof Frame) - return ((Frame) c).isResizable(); - - return false; - } - - /** - * Returns the application title. - */ - public String getTitle() { - return getJNLPFile().getApplet().getName(); - } - - /** - * Returns the applet environment. - */ - public AppletEnvironment getAppletEnvironment() { - return environment; - } - - /** - * Returns the applet. - */ - public Applet getApplet() { - return applet; - } - - /** - * Stop the application and destroy its resources. - */ - public void destroy() { - if (appletStopped) - return; - - appletStopped = true; - - try { - applet.stop(); - applet.destroy(); - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - - environment.destroy(); - - super.destroy(); - } - -} - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.applet.*; +import java.awt.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.List; +import java.security.*; +import java.lang.reflect.*; +import java.lang.ref.*; + +import net.sourceforge.jnlp.*; + + +/** + * Represents a launched application instance created from a JNLP + * file. This class does not control the operation of the applet, + * use the AppletEnvironment class to start and stop the applet. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.9 $ + */ +public class AppletInstance extends ApplicationInstance { + + /** whether the applet's stop and destroy methods have been called */ + private boolean appletStopped = false; + + /** the applet */ + private Applet applet; + + /** the applet environment */ + private AppletEnvironment environment; + + + /** + * Create a New Task based on the Specified URL + */ + public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet) { + super(file, group, loader); + + this.applet = applet; + + this.environment = new AppletEnvironment(file, this); + } + + /** + * + */ + public AppletInstance(JNLPFile file, ThreadGroup group, ClassLoader loader, Applet applet, Container cont) { + super(file, group, loader); + this.applet = applet; + this.environment = new AppletEnvironment(file, this, cont); + } + + /** + * Sets whether the applet is resizable or not. Applets default + * to being not resizable. + */ + public void setResizable(boolean resizable) { + Container c = environment.getAppletFrame(); + if (c instanceof Frame) + ((Frame) c).setResizable(resizable); + } + + /** + * Returns whether the applet is resizable. + */ + public boolean isResizable() { + Container c = environment.getAppletFrame(); + if (c instanceof Frame) + return ((Frame) c).isResizable(); + + return false; + } + + /** + * Returns the application title. + */ + public String getTitle() { + return getJNLPFile().getApplet().getName(); + } + + /** + * Returns the applet environment. + */ + public AppletEnvironment getAppletEnvironment() { + return environment; + } + + /** + * Returns the applet. + */ + public Applet getApplet() { + return applet; + } + + /** + * Stop the application and destroy its resources. + */ + public void destroy() { + if (appletStopped) + return; + + appletStopped = true; + + try { + applet.stop(); + applet.destroy(); + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + + environment.destroy(); + + super.destroy(); + } + +} +
--- a/rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/ApplicationInstance.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,293 +1,293 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.awt.*; -import java.util.*; -import java.util.List; -import java.security.*; -import javax.swing.event.EventListenerList; - -import net.sourceforge.jnlp.*; -import net.sourceforge.jnlp.event.*; -import net.sourceforge.jnlp.security.SecurityWarningDialog.AccessType; -import net.sourceforge.jnlp.services.ServiceUtil; -import net.sourceforge.jnlp.util.*; - -/** - * Represents a running instance of an application described in a - * JNLPFile. This class provides a way to track the application's - * resources and destroy the application.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.15 $ - */ -public class ApplicationInstance { - - // todo: should attempt to unload the environment variables - // installed by the application. - - - /** the file */ - private JNLPFile file; - - /** the thread group */ - private ThreadGroup group; - - /** the classloader */ - private ClassLoader loader; - - /** whether the application has stopped running */ - private boolean stopped = false; - - /** weak list of windows opened by the application */ - private WeakList weakWindows = new WeakList(); - - /** list of application listeners */ - private EventListenerList listeners = new EventListenerList(); - - /** whether or not this application is signed */ - private boolean isSigned = false; - - /** - * Create an application instance for the file. - */ - public ApplicationInstance(JNLPFile file, ThreadGroup group, ClassLoader loader) { - this.file = file; - this.group = group; - this.loader = loader; - this.isSigned = ((JNLPClassLoader) loader).getSigning(); - } - - /** - * Add an Application listener - */ - public void addApplicationListener(ApplicationListener listener) { - listeners.add(ApplicationListener.class, listener); - } - - /** - * Remove an Application Listener - */ - public void removeApplicationListener(ApplicationListener listener) { - listeners.remove(ApplicationListener.class, listener); - } - - /** - * Notify listeners that the application has been terminated. - */ - protected void fireDestroyed() { - Object list[] = listeners.getListenerList(); - ApplicationEvent event = null; - - for (int i=list.length-1; i>0; i-=2) { // last to first required - if (event == null) - event = new ApplicationEvent(this); - - ((ApplicationListener)list[i]).applicationDestroyed(event); - } - } - - /** - * Initialize the application's environment (installs - * environment variables, etc). - */ - public void initialize() { - installEnvironment(); - - /* - * FIXME: Disable creating desktop entries for now - * - * there are some major issues we need to work out before we can enable them - * 1. Playing nice with the altnatives system - * - use the system preferred jdk (/usr/bin/javaws) - * - dont assume what jdk javaws corresponds to - * - make sure our shortcuts work with the sun jdk and vice versa - * (may not be possible since sun's javaws creates a launcher that - * links to /usr/java/${ver}/bin/javaws) - * - we should use the same options and arguments as sun's javaws - * 2. Make shortcuts work offline - * - make the cache updates and replacements work properly - * - shortcuts should use the cache - * - * addMenuAndDesktopEntries(); - */ - } - - /** - * Creates menu and desktop entries if required by the jnlp file - */ - - private void addMenuAndDesktopEntries() { - XDesktopEntry entry = new XDesktopEntry(file); - - if (file.getInformation().getShortcut().onDesktop()) { - if (ServiceUtil.checkAccess(this, AccessType.CREATE_DESTKOP_SHORTCUT)) { - entry.createDesktopShortcut(); - } - } - - if (file.getInformation().getShortcut().getMenu() != null) { - /* - * Sun's WebStart implementation doesnt seem to do anything under GNOME - */ - if (JNLPRuntime.isDebug()) { - System.err.println("ApplicationInstance.addMenuAndDesktopEntries():" - + " Adding menu entries NOT IMPLEMENTED"); - } - } - - } - - /** - * Releases the application's resources before it is collected. - * Only collectable if classloader and thread group are - * also collectable so basically is almost never called (an - * application would have to close its windows and exit its - * threads but not call System.exit). - */ - public void finalize() { - destroy(); - } - - /** - * Install the environment variables. - */ - void installEnvironment() { - final PropertyDesc props[] = file.getResources().getProperties(); - - PrivilegedAction installProps = new PrivilegedAction() { - public Object run() { - for (int i=0; i < props.length; i++) { - System.setProperty(props[i].getKey(), props[i].getValue()); - } - - return null; - } - }; - AccessController.doPrivileged(installProps); - } - - /** - * Returns the JNLP file for this task. - */ - public JNLPFile getJNLPFile() { - return file; - } - - /** - * Returns the application title. - */ - public String getTitle() { - return file.getTitle(); - } - - /** - * Returns whether the application is running. - */ - public boolean isRunning() { - return !stopped; - } - - /** - * Stop the application and destroy its resources. - */ - public void destroy() { - if (stopped) - return; - - try { - // destroy resources - for (int i=0; i < weakWindows.size(); i++) { - Window w = (Window) weakWindows.get(i); - if (w != null) - w.dispose(); - } - - weakWindows.clear(); - - // interrupt threads - Thread threads[] = new Thread[ group.activeCount() * 2 ]; - int nthreads = group.enumerate(threads); - for (int i=0; i < nthreads; i++) { - if (JNLPRuntime.isDebug()) - System.out.println("Interrupt thread: "+threads[i]); - - threads[i].interrupt(); - } - - // then stop - Thread.currentThread().yield(); - nthreads = group.enumerate(threads); - for (int i=0; i < nthreads; i++) { - if (JNLPRuntime.isDebug()) - System.out.println("Stop thread: "+threads[i]); - - threads[i].stop(); - } - - // then destroy - except Thread.destroy() not implemented in jdk - - } - finally { - stopped = true; - fireDestroyed(); - } - } - - /** - * Returns the thread group. - * - * @throws IllegalStateException if the app is not running - */ - public ThreadGroup getThreadGroup() throws IllegalStateException { - if (stopped) - throw new IllegalStateException(); - - return group; - } - - /** - * Returns the classloader. - * - * @throws IllegalStateException if the app is not running - */ - public ClassLoader getClassLoader() throws IllegalStateException { - if (stopped) - throw new IllegalStateException(); - - return loader; - } - - /** - * Adds a window that this application opened. When the - * application is disposed, these windows will also be disposed. - */ - protected void addWindow(Window window) { - weakWindows.add(window); - weakWindows.trimToSize(); - } - - /** - * Returns whether or not this jar is signed. - */ - public boolean isSigned() { - return isSigned; - } -} - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.awt.*; +import java.util.*; +import java.util.List; +import java.security.*; +import javax.swing.event.EventListenerList; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.event.*; +import net.sourceforge.jnlp.security.SecurityWarningDialog.AccessType; +import net.sourceforge.jnlp.services.ServiceUtil; +import net.sourceforge.jnlp.util.*; + +/** + * Represents a running instance of an application described in a + * JNLPFile. This class provides a way to track the application's + * resources and destroy the application.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.15 $ + */ +public class ApplicationInstance { + + // todo: should attempt to unload the environment variables + // installed by the application. + + + /** the file */ + private JNLPFile file; + + /** the thread group */ + private ThreadGroup group; + + /** the classloader */ + private ClassLoader loader; + + /** whether the application has stopped running */ + private boolean stopped = false; + + /** weak list of windows opened by the application */ + private WeakList weakWindows = new WeakList(); + + /** list of application listeners */ + private EventListenerList listeners = new EventListenerList(); + + /** whether or not this application is signed */ + private boolean isSigned = false; + + /** + * Create an application instance for the file. + */ + public ApplicationInstance(JNLPFile file, ThreadGroup group, ClassLoader loader) { + this.file = file; + this.group = group; + this.loader = loader; + this.isSigned = ((JNLPClassLoader) loader).getSigning(); + } + + /** + * Add an Application listener + */ + public void addApplicationListener(ApplicationListener listener) { + listeners.add(ApplicationListener.class, listener); + } + + /** + * Remove an Application Listener + */ + public void removeApplicationListener(ApplicationListener listener) { + listeners.remove(ApplicationListener.class, listener); + } + + /** + * Notify listeners that the application has been terminated. + */ + protected void fireDestroyed() { + Object list[] = listeners.getListenerList(); + ApplicationEvent event = null; + + for (int i=list.length-1; i>0; i-=2) { // last to first required + if (event == null) + event = new ApplicationEvent(this); + + ((ApplicationListener)list[i]).applicationDestroyed(event); + } + } + + /** + * Initialize the application's environment (installs + * environment variables, etc). + */ + public void initialize() { + installEnvironment(); + + /* + * FIXME: Disable creating desktop entries for now + * + * there are some major issues we need to work out before we can enable them + * 1. Playing nice with the altnatives system + * - use the system preferred jdk (/usr/bin/javaws) + * - dont assume what jdk javaws corresponds to + * - make sure our shortcuts work with the sun jdk and vice versa + * (may not be possible since sun's javaws creates a launcher that + * links to /usr/java/${ver}/bin/javaws) + * - we should use the same options and arguments as sun's javaws + * 2. Make shortcuts work offline + * - make the cache updates and replacements work properly + * - shortcuts should use the cache + * + * addMenuAndDesktopEntries(); + */ + } + + /** + * Creates menu and desktop entries if required by the jnlp file + */ + + private void addMenuAndDesktopEntries() { + XDesktopEntry entry = new XDesktopEntry(file); + + if (file.getInformation().getShortcut().onDesktop()) { + if (ServiceUtil.checkAccess(this, AccessType.CREATE_DESTKOP_SHORTCUT)) { + entry.createDesktopShortcut(); + } + } + + if (file.getInformation().getShortcut().getMenu() != null) { + /* + * Sun's WebStart implementation doesnt seem to do anything under GNOME + */ + if (JNLPRuntime.isDebug()) { + System.err.println("ApplicationInstance.addMenuAndDesktopEntries():" + + " Adding menu entries NOT IMPLEMENTED"); + } + } + + } + + /** + * Releases the application's resources before it is collected. + * Only collectable if classloader and thread group are + * also collectable so basically is almost never called (an + * application would have to close its windows and exit its + * threads but not call System.exit). + */ + public void finalize() { + destroy(); + } + + /** + * Install the environment variables. + */ + void installEnvironment() { + final PropertyDesc props[] = file.getResources().getProperties(); + + PrivilegedAction installProps = new PrivilegedAction() { + public Object run() { + for (int i=0; i < props.length; i++) { + System.setProperty(props[i].getKey(), props[i].getValue()); + } + + return null; + } + }; + AccessController.doPrivileged(installProps); + } + + /** + * Returns the JNLP file for this task. + */ + public JNLPFile getJNLPFile() { + return file; + } + + /** + * Returns the application title. + */ + public String getTitle() { + return file.getTitle(); + } + + /** + * Returns whether the application is running. + */ + public boolean isRunning() { + return !stopped; + } + + /** + * Stop the application and destroy its resources. + */ + public void destroy() { + if (stopped) + return; + + try { + // destroy resources + for (int i=0; i < weakWindows.size(); i++) { + Window w = (Window) weakWindows.get(i); + if (w != null) + w.dispose(); + } + + weakWindows.clear(); + + // interrupt threads + Thread threads[] = new Thread[ group.activeCount() * 2 ]; + int nthreads = group.enumerate(threads); + for (int i=0; i < nthreads; i++) { + if (JNLPRuntime.isDebug()) + System.out.println("Interrupt thread: "+threads[i]); + + threads[i].interrupt(); + } + + // then stop + Thread.currentThread().yield(); + nthreads = group.enumerate(threads); + for (int i=0; i < nthreads; i++) { + if (JNLPRuntime.isDebug()) + System.out.println("Stop thread: "+threads[i]); + + threads[i].stop(); + } + + // then destroy - except Thread.destroy() not implemented in jdk + + } + finally { + stopped = true; + fireDestroyed(); + } + } + + /** + * Returns the thread group. + * + * @throws IllegalStateException if the app is not running + */ + public ThreadGroup getThreadGroup() throws IllegalStateException { + if (stopped) + throw new IllegalStateException(); + + return group; + } + + /** + * Returns the classloader. + * + * @throws IllegalStateException if the app is not running + */ + public ClassLoader getClassLoader() throws IllegalStateException { + if (stopped) + throw new IllegalStateException(); + + return loader; + } + + /** + * Adds a window that this application opened. When the + * application is disposed, these windows will also be disposed. + */ + protected void addWindow(Window window) { + weakWindows.add(window); + weakWindows.trimToSize(); + } + + /** + * Returns whether or not this jar is signed. + */ + public boolean isSigned() { + return isSigned; + } +} +
--- a/rt/net/sourceforge/jnlp/runtime/Boot.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/Boot.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,446 +1,446 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; - -import net.sourceforge.jnlp.AppletDesc; -import net.sourceforge.jnlp.ApplicationDesc; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.Launcher; -import net.sourceforge.jnlp.ParseException; -import net.sourceforge.jnlp.PropertyDesc; -import net.sourceforge.jnlp.ResourcesDesc; -import net.sourceforge.jnlp.cache.UpdatePolicy; -import net.sourceforge.jnlp.security.VariableX509TrustManager; -import net.sourceforge.jnlp.security.viewer.CertificateViewer; -import net.sourceforge.jnlp.services.ServiceUtil; - -/** - * This is the main entry point for the JNLP client. The main - * method parses the command line parameters and loads a JNLP - * file into the secure runtime environment. This class is meant - * to be called from the command line or file association; to - * initialize the netx engine from other code invoke the - * <code>JNLPRuntime.initialize</code> method after configuring - * the runtime. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.21 $ - */ -public final class Boot implements PrivilegedAction { - - // todo: decide whether a spawned netx (external launch) - // should inherit the same options as this instance (store argv?) - - private static String R(String key) { return JNLPRuntime.getMessage(key); } - private static String R(String key, Object param) { return JNLPRuntime.getMessage(key, new Object[] {param}); } - - private static final String version = "0.5"; - - /** the text to display before launching the about link */ - private static final String aboutMessage = "" - + "netx v"+version+" - (C)2001-2003 Jon A. Maxwell (jmaxwell@users.sourceforge.net)\n" - + "\n" - + R("BLaunchAbout"); - - private static final String miniLicense = "\n" - + " netx - an open-source JNLP client.\n" - + " Copyright (C) 2001-2003 Jon A. Maxwell (JAM)\n" - + "\n" - + " // This library is free software; you can redistribute it and/or\n" - + " modify it under the terms of the GNU Lesser General Public\n" - + " License as published by the Free Software Foundation; either\n" - + " version 2.1 of the License, or (at your option) any later version.\n" - + "\n" - + " This library is distributed in the hope that it will be useful,\n" - + " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" - + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" - + " Lesser General Public License for more details.\n" - + "\n" - + " You should have received a copy of the GNU Lesser General Public\n" - + " License along with this library; if not, write to the Free Software\n" - + " Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n" - + "\n"; - - private static final String helpMessage = "\n" - + "Usage: " + R("BOUsage")+"\n" - + " " + R("BOUsage2")+"\n" - + "\n" - + "control-options:"+"\n" - + " -about "+R("BOAbout")+"\n" - + " -viewer "+R("BOViewer")+"\n" - + "\n" - + "run-options:"+"\n" - + " -basedir dir "+R("BOBasedir")+"\n" - + " -arg arg "+R("BOArg")+"\n" - + " -param name=value "+R("BOParam")+"\n" - + " -property name=value "+R("BOProperty")+"\n" - + " -update seconds "+R("BOUpdate")+"\n" - + " -license "+R("BOLicense")+"\n" - + " -verbose "+R("BOVerbose")+"\n" - + " -nosecurity "+R("BONosecurity")+"\n" - + " -noupdate "+R("BONoupdate")+"\n" - + " -headless "+R("BOHeadless")+"\n" - + " -strict "+R("BOStrict")+"\n" - + " -umask=value "+R("BOUmask")+"\n" - + " -Xnofork "+R("BXnofork")+"\n" - + " -help "+R("BOHelp")+"\n"; - - private static final String doubleArgs = "-basedir -jnlp -arg -param -property -update"; - - private static String args[]; // avoid the hot potato - - - /** - * Launch the JNLP file specified by the command-line arguments. - */ - public static void main(String[] argsIn) { - args = argsIn; - - if (null != getOption("-viewer")) { - - try { - CertificateViewer.main(null); - System.exit(0); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - if (null != getOption("-license")) { - System.out.println(miniLicense); - System.exit(0); - } - - if (null != getOption("-help")) { - System.out.println(helpMessage); - System.exit(0); - } - - if (null != getOption("-about")) - System.out.println(aboutMessage); - - if (null != getOption("-verbose")) - JNLPRuntime.setDebug(true); - - if (null != getOption("-update")) { - int value = Integer.parseInt(getOption("-update")); - JNLPRuntime.setDefaultUpdatePolicy(new UpdatePolicy(value*1000l)); - } - - if (null != getOption("-headless")) - JNLPRuntime.setHeadless(true); - - - if (null != getOption("-noupdate")) - JNLPRuntime.setDefaultUpdatePolicy(UpdatePolicy.NEVER); - - if (null != getOption("-Xnofork")) { - JNLPRuntime.setForksAllowed(false); - } - - // wire in custom authenticator - try { - SSLSocketFactory sslSocketFactory; - SSLContext context = SSLContext.getInstance("SSL"); - TrustManager[] trust = new TrustManager[] { VariableX509TrustManager.getInstance() }; - context.init(null, trust, null); - sslSocketFactory = context.getSocketFactory(); - - HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); - } catch (Exception e) { - System.err.println("Unable to set SSLSocketfactory (may _prevent_ access to sites that should be trusted)! Continuing anyway..."); - e.printStackTrace(); - } - - JNLPRuntime.setInitialArgments(Arrays.asList(argsIn)); - - // do in a privileged action to clear the security context of - // the Boot13 class, which doesn't have any privileges in - // JRE1.3; JRE1.4 works without Boot13 or this PrivilegedAction. - AccessController.doPrivileged(new Boot()); - - } - - /** - * The privileged part (jdk1.3 compatibility). - */ - public Object run() { - JNLPRuntime.setBaseDir(getBaseDir()); - JNLPRuntime.setSecurityEnabled(null == getOption("-nosecurity")); - JNLPRuntime.initialize(true); - - try { - new Launcher().launch(getFile()); - } - catch (LaunchException ex) { - // default handler prints this - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - - fatalError(JNLPRuntime.getMessage("RUnexpected", - new Object[] {ex.toString(), ex.getStackTrace()[0]} )); - } - - return null; - } - - private static void fatalError(String message) { - System.err.println("netx: "+message); - System.exit(1); - } - - /** - * Returns the about.jnlp file in {java.home}/lib or null if this file - * does not exist. - */ - private static String getAboutFile() { - - if (new File(JNLPRuntime.NETX_ABOUT_FILE).exists()) - return JNLPRuntime.NETX_ABOUT_FILE; - else - return null; - } - - /** - * Returns the file to open; does not return if no file was - * specified. - */ - private static JNLPFile getFile() throws ParseException, MalformedURLException, IOException { - - String location = getJNLPFile(); - - // override -jnlp with aboutFile - if (getOption("-about") != null) { - location = getAboutFile(); - if (location == null) - fatalError("Unable to find about.jnlp in {java.home}/lib/"); - } else { - location = getJNLPFile(); - } - - if (location == null) { - System.out.println(helpMessage); - System.exit(1); - } - - if (JNLPRuntime.isDebug()) - System.out.println(R("BFileLoc")+": "+location); - - URL url = null; - - try { - if (new File(location).exists()) - url = new File(location).toURL(); // Why use file.getCanonicalFile? - else - url = new URL(ServiceUtil.getBasicService().getCodeBase(), location); - } catch (Exception e) { - fatalError("Invalid jnlp file " + location); - if (JNLPRuntime.isDebug()) - e.printStackTrace(); - } - - boolean strict = (null != getOption("-strict")); - - JNLPFile file = new JNLPFile(url, strict); - - // add in extra params from command line - addProperties(file); - - if (file.isApplet()) - addParameters(file); - - if (file.isApplication()) - addArguments(file); - - if (JNLPRuntime.isDebug()) { - if (getOption("-arg") != null) - if (file.isInstaller() || file.isApplet()) - System.out.println(R("BArgsNA")); - - if (getOption("-param") != null) - if (file.isApplication()) - System.out.println(R("BParamNA")); - } - - return file; - } - - /** - * Add the properties to the JNLP file. - */ - private static void addProperties(JNLPFile file) { - String props[] = getOptions("-property"); - ResourcesDesc resources = file.getResources(); - - for (int i=0; i < props.length; i++) { - // allows empty property, not sure about validity of that. - int equals = props[i].indexOf("="); - if (equals == -1) - fatalError(R("BBadProp", props[i])); - - String key = props[i].substring(0, equals); - String value = props[i].substring(equals+1, props[i].length()); - - resources.addResource(new PropertyDesc(key, value)); - } - } - - /** - * Add the params to the JNLP file; only call if file is - * actually an applet file. - */ - private static void addParameters(JNLPFile file) { - String params[] = getOptions("-param"); - AppletDesc applet = file.getApplet(); - - for (int i=0; i < params.length; i++) { - // allows empty param, not sure about validity of that. - int equals = params[i].indexOf("="); - if (equals == -1) - fatalError(R("BBadParam", params[i])); - - String name = params[i].substring(0, equals); - String value = params[i].substring(equals+1, params[i].length()); - - applet.addParameter(name, value); - } - } - - /** - * Add the arguments to the JNLP file; only call if file is - * actually an application (not installer). - */ - private static void addArguments(JNLPFile file) { - String args[] = getOptions("-arg"); // FYI args also global variable - ApplicationDesc app = file.getApplication(); - - for (int i=0; i < args.length; i++) { - app.addArgument(args[i]); - } - } - - /** - * Gets the JNLP file from the command line arguments, or exits upon error. - */ - private static String getJNLPFile() { - - if (args.length == 0) { - System.out.println(helpMessage); - System.exit(0); - } else if (args.length == 1) { - return args[args.length - 1]; - } else { - String lastArg = args[args.length - 1]; - String secondLastArg = args[args.length - 2]; - - if (doubleArgs.indexOf(secondLastArg) == -1) { - return lastArg; - } else { - System.out.println(helpMessage); - System.exit(0); - } - } - return null; - } - - /** - * Return value of the first occurence of the specified - * option, or null if the option is not present. If the - * option is a flag (0-parameter) and is present then the - * option name is returned. - */ - private static String getOption(String option) { - String result[] = getOptions(option); - - if (result.length == 0) - return null; - else - return result[0]; - } - - /** - * Return all the values of the specified option, or an empty - * array if the option is not present. If the option is a - * flag (0-parameter) and is present then the option name is - * returned once for each occurrence. - */ - private static String[] getOptions(String option) { - List result = new ArrayList(); - - for (int i=0; i < args.length; i++) { - if (option.equals(args[i])) { - if (-1 == doubleArgs.indexOf(option)) - result.add(option); - else - if (i+1 < args.length) - result.add(args[i+1]); - } - - if (args[i].startsWith("-") && -1 != doubleArgs.indexOf(args[i])) - i++; - } - - return (String[]) result.toArray( new String[result.size()] ); - } - - /** - * Return the base dir. If the base dir parameter is not set - * the value is read from JNLPRuntime.NETX_ABOUT_FILE file. - * If that file does not exist, an install dialog is displayed - * to select the base directory. - */ - private static File getBaseDir() { - if (getOption("-basedir") != null) { - File basedir = new File(getOption("-basedir")); - - if (!basedir.exists() || !basedir.isDirectory()) - fatalError(R("BNoDir", basedir)); - - return basedir; - } - - // check .netxrc - File basedir = JNLPRuntime.getDefaultBaseDir(); - if (basedir == null) - fatalError(R("BNoBase")); - - return basedir; - } - -} - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import net.sourceforge.jnlp.AppletDesc; +import net.sourceforge.jnlp.ApplicationDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.Launcher; +import net.sourceforge.jnlp.ParseException; +import net.sourceforge.jnlp.PropertyDesc; +import net.sourceforge.jnlp.ResourcesDesc; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.security.VariableX509TrustManager; +import net.sourceforge.jnlp.security.viewer.CertificateViewer; +import net.sourceforge.jnlp.services.ServiceUtil; + +/** + * This is the main entry point for the JNLP client. The main + * method parses the command line parameters and loads a JNLP + * file into the secure runtime environment. This class is meant + * to be called from the command line or file association; to + * initialize the netx engine from other code invoke the + * <code>JNLPRuntime.initialize</code> method after configuring + * the runtime. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.21 $ + */ +public final class Boot implements PrivilegedAction { + + // todo: decide whether a spawned netx (external launch) + // should inherit the same options as this instance (store argv?) + + private static String R(String key) { return JNLPRuntime.getMessage(key); } + private static String R(String key, Object param) { return JNLPRuntime.getMessage(key, new Object[] {param}); } + + private static final String version = "0.5"; + + /** the text to display before launching the about link */ + private static final String aboutMessage = "" + + "netx v"+version+" - (C)2001-2003 Jon A. Maxwell (jmaxwell@users.sourceforge.net)\n" + + "\n" + + R("BLaunchAbout"); + + private static final String miniLicense = "\n" + + " netx - an open-source JNLP client.\n" + + " Copyright (C) 2001-2003 Jon A. Maxwell (JAM)\n" + + "\n" + + " // This library is free software; you can redistribute it and/or\n" + + " modify it under the terms of the GNU Lesser General Public\n" + + " License as published by the Free Software Foundation; either\n" + + " version 2.1 of the License, or (at your option) any later version.\n" + + "\n" + + " This library is distributed in the hope that it will be useful,\n" + + " but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" + + " Lesser General Public License for more details.\n" + + "\n" + + " You should have received a copy of the GNU Lesser General Public\n" + + " License along with this library; if not, write to the Free Software\n" + + " Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n" + + "\n"; + + private static final String helpMessage = "\n" + + "Usage: " + R("BOUsage")+"\n" + + " " + R("BOUsage2")+"\n" + + "\n" + + "control-options:"+"\n" + + " -about "+R("BOAbout")+"\n" + + " -viewer "+R("BOViewer")+"\n" + + "\n" + + "run-options:"+"\n" + + " -basedir dir "+R("BOBasedir")+"\n" + + " -arg arg "+R("BOArg")+"\n" + + " -param name=value "+R("BOParam")+"\n" + + " -property name=value "+R("BOProperty")+"\n" + + " -update seconds "+R("BOUpdate")+"\n" + + " -license "+R("BOLicense")+"\n" + + " -verbose "+R("BOVerbose")+"\n" + + " -nosecurity "+R("BONosecurity")+"\n" + + " -noupdate "+R("BONoupdate")+"\n" + + " -headless "+R("BOHeadless")+"\n" + + " -strict "+R("BOStrict")+"\n" + + " -umask=value "+R("BOUmask")+"\n" + + " -Xnofork "+R("BXnofork")+"\n" + + " -help "+R("BOHelp")+"\n"; + + private static final String doubleArgs = "-basedir -jnlp -arg -param -property -update"; + + private static String args[]; // avoid the hot potato + + + /** + * Launch the JNLP file specified by the command-line arguments. + */ + public static void main(String[] argsIn) { + args = argsIn; + + if (null != getOption("-viewer")) { + + try { + CertificateViewer.main(null); + System.exit(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + if (null != getOption("-license")) { + System.out.println(miniLicense); + System.exit(0); + } + + if (null != getOption("-help")) { + System.out.println(helpMessage); + System.exit(0); + } + + if (null != getOption("-about")) + System.out.println(aboutMessage); + + if (null != getOption("-verbose")) + JNLPRuntime.setDebug(true); + + if (null != getOption("-update")) { + int value = Integer.parseInt(getOption("-update")); + JNLPRuntime.setDefaultUpdatePolicy(new UpdatePolicy(value*1000l)); + } + + if (null != getOption("-headless")) + JNLPRuntime.setHeadless(true); + + + if (null != getOption("-noupdate")) + JNLPRuntime.setDefaultUpdatePolicy(UpdatePolicy.NEVER); + + if (null != getOption("-Xnofork")) { + JNLPRuntime.setForksAllowed(false); + } + + // wire in custom authenticator + try { + SSLSocketFactory sslSocketFactory; + SSLContext context = SSLContext.getInstance("SSL"); + TrustManager[] trust = new TrustManager[] { VariableX509TrustManager.getInstance() }; + context.init(null, trust, null); + sslSocketFactory = context.getSocketFactory(); + + HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); + } catch (Exception e) { + System.err.println("Unable to set SSLSocketfactory (may _prevent_ access to sites that should be trusted)! Continuing anyway..."); + e.printStackTrace(); + } + + JNLPRuntime.setInitialArgments(Arrays.asList(argsIn)); + + // do in a privileged action to clear the security context of + // the Boot13 class, which doesn't have any privileges in + // JRE1.3; JRE1.4 works without Boot13 or this PrivilegedAction. + AccessController.doPrivileged(new Boot()); + + } + + /** + * The privileged part (jdk1.3 compatibility). + */ + public Object run() { + JNLPRuntime.setBaseDir(getBaseDir()); + JNLPRuntime.setSecurityEnabled(null == getOption("-nosecurity")); + JNLPRuntime.initialize(true); + + try { + new Launcher().launch(getFile()); + } + catch (LaunchException ex) { + // default handler prints this + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + + fatalError(JNLPRuntime.getMessage("RUnexpected", + new Object[] {ex.toString(), ex.getStackTrace()[0]} )); + } + + return null; + } + + private static void fatalError(String message) { + System.err.println("netx: "+message); + System.exit(1); + } + + /** + * Returns the about.jnlp file in {java.home}/lib or null if this file + * does not exist. + */ + private static String getAboutFile() { + + if (new File(JNLPRuntime.NETX_ABOUT_FILE).exists()) + return JNLPRuntime.NETX_ABOUT_FILE; + else + return null; + } + + /** + * Returns the file to open; does not return if no file was + * specified. + */ + private static JNLPFile getFile() throws ParseException, MalformedURLException, IOException { + + String location = getJNLPFile(); + + // override -jnlp with aboutFile + if (getOption("-about") != null) { + location = getAboutFile(); + if (location == null) + fatalError("Unable to find about.jnlp in {java.home}/lib/"); + } else { + location = getJNLPFile(); + } + + if (location == null) { + System.out.println(helpMessage); + System.exit(1); + } + + if (JNLPRuntime.isDebug()) + System.out.println(R("BFileLoc")+": "+location); + + URL url = null; + + try { + if (new File(location).exists()) + url = new File(location).toURL(); // Why use file.getCanonicalFile? + else + url = new URL(ServiceUtil.getBasicService().getCodeBase(), location); + } catch (Exception e) { + fatalError("Invalid jnlp file " + location); + if (JNLPRuntime.isDebug()) + e.printStackTrace(); + } + + boolean strict = (null != getOption("-strict")); + + JNLPFile file = new JNLPFile(url, strict); + + // add in extra params from command line + addProperties(file); + + if (file.isApplet()) + addParameters(file); + + if (file.isApplication()) + addArguments(file); + + if (JNLPRuntime.isDebug()) { + if (getOption("-arg") != null) + if (file.isInstaller() || file.isApplet()) + System.out.println(R("BArgsNA")); + + if (getOption("-param") != null) + if (file.isApplication()) + System.out.println(R("BParamNA")); + } + + return file; + } + + /** + * Add the properties to the JNLP file. + */ + private static void addProperties(JNLPFile file) { + String props[] = getOptions("-property"); + ResourcesDesc resources = file.getResources(); + + for (int i=0; i < props.length; i++) { + // allows empty property, not sure about validity of that. + int equals = props[i].indexOf("="); + if (equals == -1) + fatalError(R("BBadProp", props[i])); + + String key = props[i].substring(0, equals); + String value = props[i].substring(equals+1, props[i].length()); + + resources.addResource(new PropertyDesc(key, value)); + } + } + + /** + * Add the params to the JNLP file; only call if file is + * actually an applet file. + */ + private static void addParameters(JNLPFile file) { + String params[] = getOptions("-param"); + AppletDesc applet = file.getApplet(); + + for (int i=0; i < params.length; i++) { + // allows empty param, not sure about validity of that. + int equals = params[i].indexOf("="); + if (equals == -1) + fatalError(R("BBadParam", params[i])); + + String name = params[i].substring(0, equals); + String value = params[i].substring(equals+1, params[i].length()); + + applet.addParameter(name, value); + } + } + + /** + * Add the arguments to the JNLP file; only call if file is + * actually an application (not installer). + */ + private static void addArguments(JNLPFile file) { + String args[] = getOptions("-arg"); // FYI args also global variable + ApplicationDesc app = file.getApplication(); + + for (int i=0; i < args.length; i++) { + app.addArgument(args[i]); + } + } + + /** + * Gets the JNLP file from the command line arguments, or exits upon error. + */ + private static String getJNLPFile() { + + if (args.length == 0) { + System.out.println(helpMessage); + System.exit(0); + } else if (args.length == 1) { + return args[args.length - 1]; + } else { + String lastArg = args[args.length - 1]; + String secondLastArg = args[args.length - 2]; + + if (doubleArgs.indexOf(secondLastArg) == -1) { + return lastArg; + } else { + System.out.println(helpMessage); + System.exit(0); + } + } + return null; + } + + /** + * Return value of the first occurence of the specified + * option, or null if the option is not present. If the + * option is a flag (0-parameter) and is present then the + * option name is returned. + */ + private static String getOption(String option) { + String result[] = getOptions(option); + + if (result.length == 0) + return null; + else + return result[0]; + } + + /** + * Return all the values of the specified option, or an empty + * array if the option is not present. If the option is a + * flag (0-parameter) and is present then the option name is + * returned once for each occurrence. + */ + private static String[] getOptions(String option) { + List result = new ArrayList(); + + for (int i=0; i < args.length; i++) { + if (option.equals(args[i])) { + if (-1 == doubleArgs.indexOf(option)) + result.add(option); + else + if (i+1 < args.length) + result.add(args[i+1]); + } + + if (args[i].startsWith("-") && -1 != doubleArgs.indexOf(args[i])) + i++; + } + + return (String[]) result.toArray( new String[result.size()] ); + } + + /** + * Return the base dir. If the base dir parameter is not set + * the value is read from JNLPRuntime.NETX_ABOUT_FILE file. + * If that file does not exist, an install dialog is displayed + * to select the base directory. + */ + private static File getBaseDir() { + if (getOption("-basedir") != null) { + File basedir = new File(getOption("-basedir")); + + if (!basedir.exists() || !basedir.isDirectory()) + fatalError(R("BNoDir", basedir)); + + return basedir; + } + + // check .netxrc + File basedir = JNLPRuntime.getDefaultBaseDir(); + if (basedir == null) + fatalError(R("BNoBase")); + + return basedir; + } + +} +
--- a/rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,991 +1,991 @@ - -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.CodeSource; -import java.security.Permission; -import java.security.PermissionCollection; -import java.security.Permissions; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.TreeSet; -import java.util.Vector; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import net.sourceforge.jnlp.ExtensionDesc; -import net.sourceforge.jnlp.JARDesc; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.LaunchException; -import net.sourceforge.jnlp.ParseException; -import net.sourceforge.jnlp.PluginBridge; -import net.sourceforge.jnlp.ResourcesDesc; -import net.sourceforge.jnlp.SecurityDesc; -import net.sourceforge.jnlp.Version; -import net.sourceforge.jnlp.cache.CacheUtil; -import net.sourceforge.jnlp.cache.ResourceTracker; -import net.sourceforge.jnlp.cache.UpdatePolicy; -import net.sourceforge.jnlp.security.SecurityWarningDialog; -import net.sourceforge.jnlp.tools.JarSigner; -import sun.misc.JarIndex; - -/** - * Classloader that takes it's resources from a JNLP file. If the - * JNLP file defines extensions, separate classloaders for these - * will be created automatically. Classes are loaded with the - * security context when the classloader was created. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.20 $ - */ -public class JNLPClassLoader extends URLClassLoader { - - // todo: initializePermissions should get the permissions from - // extension classes too so that main file classes can load - // resources in an extension. - - /** shortcut for resources */ - private static String R(String key) { return JNLPRuntime.getMessage(key); } - - /** map from JNLPFile url to shared classloader */ - private static Map urlToLoader = new HashMap(); // never garbage collected! - - /** number of times a classloader with native code is created */ - private static int nativeCounter = 0; - - /** the directory for native code */ - private File nativeDir = null; // if set, some native code exists - - /** security context */ - private AccessControlContext acc = AccessController.getContext(); - - /** the permissions for the cached jar files */ - private List resourcePermissions; - - /** the app */ - private ApplicationInstance app = null; // here for faster lookup in security manager - - /** list of this, local and global loaders this loader uses */ - private JNLPClassLoader loaders[] = null; // ..[0]==this - - /** whether to strictly adhere to the spec or not */ - private boolean strict = true; - - /** loads the resources */ - private ResourceTracker tracker = new ResourceTracker(true); // prefetch - - /** the update policy for resources */ - private UpdatePolicy updatePolicy; - - /** the JNLP file */ - private JNLPFile file; - - /** the resources section */ - private ResourcesDesc resources; - - /** the security section */ - private SecurityDesc security; - - /** Permissions granted by the user during runtime. */ - private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>(); - - /** all jars not yet part of classloader or active */ - private List available = new ArrayList(); - - /** all of the jar files that were verified */ - private ArrayList<String> verifiedJars = null; - - /** all of the jar files that were not verified */ - private ArrayList<String> unverifiedJars = null; - - /** the jarsigner tool to verify our jars */ - private JarSigner js = null; - - private boolean signing = false; - - /** ArrayList containing jar indexes for various jars available to this classloader */ - private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>(); - - /** File entries in the jar files available to this classloader */ - private TreeSet jarEntries = new TreeSet(); - - /** - * Create a new JNLPClassLoader from the specified file. - * - * @param file the JNLP file - */ - protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException { - super(new URL[0], JNLPClassLoader.class.getClassLoader()); - - if (JNLPRuntime.isDebug()) - System.out.println("New classloader: "+file.getFileLocation()); - - this.file = file; - this.updatePolicy = policy; - this.resources = file.getResources(); - - // initialize extensions - initializeExtensions(); - - // initialize permissions - initializePermissions(); - - initializeResources(); - - setSecurity(); - - } - - private void setSecurity() { - /** - * When we're trying to load an applet, file.getSecurity() will return - * null since there is no jnlp file to specify permissions. We - * determine security settings here, after trying to verify jars. - */ - if (file instanceof PluginBridge) { - if (signing == true) { - this.security = new SecurityDesc(file, - SecurityDesc.ALL_PERMISSIONS, - file.getCodeBase().getHost()); - } else { - this.security = new SecurityDesc(file, - SecurityDesc.SANDBOX_PERMISSIONS, - file.getCodeBase().getHost()); - } - } else { //regular jnlp file - - /** - * If the application is signed, then we set the SecurityDesc to the - * <security> tag in the jnlp file. Note that if an application is - * signed, but there is no <security> tag in the jnlp file, the - * application will get sandbox permissions. - * If the application is unsigned, we ignore the <security> tag and - * use a sandbox instead. - */ - if (signing == true) { - this.security = file.getSecurity(); - } else { - this.security = new SecurityDesc(file, - SecurityDesc.SANDBOX_PERMISSIONS, - file.getCodeBase().getHost()); - } - } - } - - /** - * Returns a JNLP classloader for the specified JNLP file. - * - * @param file the file to load classes for - * @param policy the update policy to use when downloading resources - */ - public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException { - JNLPClassLoader loader = null; - URL location = file.getFileLocation(); - - if (location != null) - loader = (JNLPClassLoader) urlToLoader.get(location); - - try { - if (loader == null) - loader = new JNLPClassLoader(file, policy); - } catch (LaunchException e) { - throw e; - } - - if (file.getInformation().isSharingAllowed()) - urlToLoader.put(location, loader); - - return loader; - } - - /** - * Returns a JNLP classloader for the JNLP file at the specified - * location. - * - * @param location the file's location - * @param version the file's version - * @param policy the update policy to use when downloading resources - */ - public static JNLPClassLoader getInstance(URL location, Version version, UpdatePolicy policy) - throws IOException, ParseException, LaunchException { - JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(location); - - if (loader == null) - loader = getInstance(new JNLPFile(location, version, false, policy), policy); - - return loader; - } - - /** - * Load the extensions specified in the JNLP file. - */ - void initializeExtensions() { - ExtensionDesc[] ext = resources.getExtensions(); - - List loaderList = new ArrayList(); - - loaderList.add(this); - - //if (ext != null) { - for (int i=0; i < ext.length; i++) { - try { - JNLPClassLoader loader = getInstance(ext[i].getLocation(), ext[i].getVersion(), updatePolicy); - loaderList.add(loader); - } - catch (Exception ex) { - ex.printStackTrace(); - } - } - //} - - loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]); - } - - /** - * Make permission objects for the classpath. - */ - void initializePermissions() { - resourcePermissions = new ArrayList(); - - JARDesc jars[] = resources.getJARs(); - for (int i=0; i < jars.length; i++) { - Permission p = CacheUtil.getReadPermission(jars[i].getLocation(), - jars[i].getVersion()); - - if (JNLPRuntime.isDebug()) { - if (p == null) - System.out.println("Unable to add permission for " + jars[i].getLocation()); - else - System.out.println("Permission added: " + p.toString()); - } - if (p != null) - resourcePermissions.add(p); - } - } - - /** - * Load all of the JARs used in this JNLP file into the - * ResourceTracker for downloading. - */ - void initializeResources() throws LaunchException { - JARDesc jars[] = resources.getJARs(); - if (jars == null || jars.length == 0) - return; - /* - if (jars == null || jars.length == 0) { - throw new LaunchException(null, null, R("LSFatal"), - R("LCInit"), R("LFatalVerification"), "No jars!"); - } - */ - List initialJars = new ArrayList(); - - for (int i=0; i < jars.length; i++) { - - available.add(jars[i]); - - if (jars[i].isEager()) - initialJars.add(jars[i]); // regardless of part - - tracker.addResource(jars[i].getLocation(), - jars[i].getVersion(), - jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE - ); - } - - if (strict) - fillInPartJars(initialJars); // add in each initial part's lazy jars - - if (JNLPRuntime.isVerifying()) { - - JarSigner js; - waitForJars(initialJars); //download the jars first. - - try { - js = verifyJars(initialJars); - } catch (Exception e) { - //we caught an Exception from the JarSigner class. - //Note: one of these exceptions could be from not being able - //to read the cacerts or trusted.certs files. - e.printStackTrace(); - throw new LaunchException(null, null, R("LSFatal"), - R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo")); - } - - //Case when at least one jar has some signing - if (js.anyJarsSigned()){ - signing = true; - - //user does not trust this publisher - if (!js.getAlreadyTrustPublisher()) { - checkTrustWithUser(js); - } else { - /** - * If the user trusts this publisher (i.e. the publisher's certificate - * is in the user's trusted.certs file), we do not show any dialogs. - */ - } - } else { - - signing = false; - //otherwise this jar is simply unsigned -- make sure to ask - //for permission on certain actions - } - } - - activateJars(initialJars); - } - - private void checkTrustWithUser(JarSigner js) throws LaunchException { - if (!js.getRootInCacerts()) { //root cert is not in cacerts - boolean b = SecurityWarningDialog.showCertWarningDialog( - SecurityWarningDialog.AccessType.UNVERIFIED, file, js); - if (!b) - throw new LaunchException(null, null, R("LSFatal"), - R("LCLaunching"), R("LNotVerified"), ""); - } else if (js.getRootInCacerts()) { //root cert is in cacerts - boolean b = false; - if (js.noSigningIssues()) - b = SecurityWarningDialog.showCertWarningDialog( - SecurityWarningDialog.AccessType.VERIFIED, file, js); - else if (!js.noSigningIssues()) - b = SecurityWarningDialog.showCertWarningDialog( - SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js); - if (!b) - throw new LaunchException(null, null, R("LSFatal"), - R("LCLaunching"), R("LCancelOnUserRequest"), ""); - } - } - - /** - * Add applet's codebase URL. This allows compatibility with - * applets that load resources from their codebase instead of - * through JARs, but can slow down resource loading. Resources - * loaded from the codebase are not cached. - */ - public void enableCodeBase() { - addURL( file.getCodeBase() ); // nothing happens if called more that once? - } - - /** - * Sets the JNLP app this group is for; can only be called once. - */ - public void setApplication(ApplicationInstance app) { - if (this.app != null) { - if (JNLPRuntime.isDebug()) { - Exception ex = new IllegalStateException("Application can only be set once"); - ex.printStackTrace(); - } - return; - } - - this.app = app; - } - - /** - * Returns the JNLP app for this classloader - */ - public ApplicationInstance getApplication() { - return app; - } - - /** - * Returns the JNLP file the classloader was created from. - */ - public JNLPFile getJNLPFile() { - return file; - } - - /** - * Returns the permissions for the CodeSource. - */ - protected PermissionCollection getPermissions(CodeSource cs) { - Permissions result = new Permissions(); - - // should check for extensions or boot, automatically give all - // access w/o security dialog once we actually check certificates. - - // copy security permissions from SecurityDesc element - if (security != null) { - Enumeration e = security.getPermissions().elements(); - while (e.hasMoreElements()) - result.add((Permission) e.nextElement()); - } - - // add in permission to read the cached JAR files - for (int i=0; i < resourcePermissions.size(); i++) - result.add((Permission) resourcePermissions.get(i)); - - // add in the permissions that the user granted. - for (int i=0; i < runtimePermissions.size(); i++) - result.add(runtimePermissions.get(i)); - - return result; - } - - protected void addPermission(Permission p) { - runtimePermissions.add(p); - } - - /** - * Adds to the specified list of JARS any other JARs that need - * to be loaded at the same time as the JARs specified (ie, are - * in the same part). - */ - protected void fillInPartJars(List jars) { - for (int i=0; i < jars.size(); i++) { - String part = ((JARDesc) jars.get(i)).getPart(); - - for (int a=0; a < available.size(); a++) { - JARDesc jar = (JARDesc) available.get(a); - - if (part != null && part.equals(jar.getPart())) - if (!jars.contains(jar)) - jars.add(jar); - } - } - } - - /** - * Ensures that the list of jars have all been transferred, and - * makes them available to the classloader. If a jar contains - * native code, the libraries will be extracted and placed in - * the path. - * - * @param jars the list of jars to load - */ - protected void activateJars(final List jars) { - PrivilegedAction activate = new PrivilegedAction() { - - public Object run() { - // transfer the Jars - waitForJars(jars); - - for (int i=0; i < jars.size(); i++) { - JARDesc jar = (JARDesc) jars.get(i); - - available.remove(jar); - - // add jar - File localFile = tracker.getCacheFile(jar.getLocation()); - try { - URL location = jar.getLocation(); // non-cacheable, use source location - if (localFile != null) { - location = localFile.toURL(); // cached file - - // This is really not the best way.. but we need some way for - // PluginAppletViewer::getCachedImageRef() to check if the image - // is available locally, and it cannot use getResources() because - // that prefetches the resource, which confuses MediaTracker.waitForAll() - // which does a wait(), waiting for notification (presumably - // thrown after a resource is fetched). This bug manifests itself - // particularly when using The FileManager applet from Webmin. - - JarFile jarFile = new JarFile(localFile); - Enumeration e = jarFile.entries(); - while (e.hasMoreElements()) { - - JarEntry je = (JarEntry) e.nextElement(); - - // another jar in my jar? it is more likely than you think - if (je.getName().endsWith(".jar")) { - // We need to extract that jar so that it can be loaded - // (inline loading with "jar:..!/..." path will not work - // with standard classloader methods) - - String extractedJarLocation = localFile.getParent() + "/" + je.getName(); - File parentDir = new File(extractedJarLocation).getParentFile(); - if (!parentDir.isDirectory() && !parentDir.mkdirs()) { - throw new RuntimeException(R("RNestedJarExtration")); - } - FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation); - InputStream is = jarFile.getInputStream(je); - - byte[] bytes = new byte[1024]; - int read = is.read(bytes); - while (read > 0) { - extractedJar.write(bytes, 0, read); - read = is.read(bytes); - } - - is.close(); - extractedJar.close(); - - JarSigner signer = new JarSigner(); - signer.verifyJar(extractedJarLocation); - - if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) { - checkTrustWithUser(signer); - } - - try { - addURL(new URL("file://" + extractedJarLocation)); - } catch (MalformedURLException mfue) { - if (JNLPRuntime.isDebug()) - System.err.println("Unable to add extracted nested jar to classpath"); - - mfue.printStackTrace(); - } - } - - jarEntries.add(je.getName()); - } - - } - - addURL(location); - - // there is currently no mechanism to cache files per - // instance.. so only index cached files - if (localFile != null) { - JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null); - - if (index != null) - jarIndexes.add(index); - } - - if (JNLPRuntime.isDebug()) - System.err.println("Activate jar: "+location); - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - - if (jar.isNative()) - activateNative(jar); - } - - return null; - } - }; - - AccessController.doPrivileged(activate, acc); - } - - /** - * Enable the native code contained in a JAR by copying the - * native files into the filesystem. Called in the security - * context of the classloader. - */ - protected void activateNative(JARDesc jar) { - if (JNLPRuntime.isDebug()) - System.out.println("Activate native: "+jar.getLocation()); - - File localFile = tracker.getCacheFile(jar.getLocation()); - if (localFile == null) - return; - - if (nativeDir == null) - nativeDir = getNativeDir(); - - try { - JarFile jarFile = new JarFile(localFile, false); - Enumeration entries = jarFile.entries(); - - while (entries.hasMoreElements()) { - JarEntry e = (JarEntry) entries.nextElement(); - - if (e.isDirectory() || e.getName().indexOf('/') != -1) - continue; - - File outFile = new File(nativeDir, e.getName()); - - CacheUtil.streamCopy(jarFile.getInputStream(e), - new FileOutputStream(outFile)); - } - } - catch (IOException ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - } - - /** - * Return the base directory to store native code files in. - * This method does not need to return the same directory across - * calls. - */ - protected File getNativeDir() { - nativeDir = new File(System.getProperty("java.io.tmpdir") - + File.separator + "netx-native-" - + (new Random().nextInt() & 0xFFFF)); - - if (!nativeDir.mkdirs()) - return null; - else - return nativeDir; - } - - /** - * Return the absolute path to the native library. - */ - protected String findLibrary(String lib) { - if (nativeDir == null) - return null; - - String syslib = System.mapLibraryName(lib); - - File target = new File(nativeDir, syslib); - if (target.exists()) - return target.toString(); - else { - String result = super.findLibrary(lib); - if (result != null) - return result; - - return findLibraryExt(lib); - } - } - - /** - * Try to find the library path from another peer classloader. - */ - protected String findLibraryExt(String lib) { - for (int i=0; i < loaders.length; i++) { - String result = null; - - if (loaders[i] != this) - result = loaders[i].findLibrary(lib); - - if (result != null) - return result; - } - - return null; - } - - /** - * Wait for a group of JARs, and send download events if there - * is a download listener or display a progress window otherwise. - * - * @param jars the jars - */ - private void waitForJars(List jars) { - URL urls[] = new URL[jars.size()]; - - for (int i=0; i < jars.size(); i++) { - JARDesc jar = (JARDesc) jars.get(i); - - urls[i] = jar.getLocation(); - } - - CacheUtil.waitForResources(app, tracker, urls, file.getTitle()); - } - - /** - * Verifies code signing of jars to be used. - * - * @param jars the jars to be verified. - */ - private JarSigner verifyJars(List<JARDesc> jars) throws Exception { - - js = new JarSigner(); - js.verifyJars(jars, tracker); - return js; - } - - /** - * Find the loaded class in this loader or any of its extension loaders. - */ - protected Class findLoadedClassAll(String name) { - for (int i=0; i < loaders.length; i++) { - Class result = null; - - if (loaders[i] == this) - result = super.findLoadedClass(name); - else - result = loaders[i].findLoadedClassAll(name); - - if (result != null) - return result; - } - - return null; - } - - /** - * Find a JAR in the shared 'extension' classloaders, this - * classloader, or one of the classloaders for the JNLP file's - * extensions. - */ - public Class loadClass(String name) throws ClassNotFoundException { - - Class result = findLoadedClassAll(name); - - // try parent classloader - if (result == null) { - try { - ClassLoader parent = getParent(); - if (parent == null) - parent = ClassLoader.getSystemClassLoader(); - - return parent.loadClass(name); - } - catch (ClassNotFoundException ex) { } - } - - // filter out 'bad' package names like java, javax - // validPackage(name); - - // search this and the extension loaders - if (result == null) - try { - result = loadClassExt(name); - } catch (ClassNotFoundException cnfe) { - - // Not found in external loader either. As a last resort, look in any available indexes - - // Currently this loads jars directly from the site. We cannot cache it because this - // call is initiated from within the applet, which does not have disk read/write permissions - for (JarIndex index: jarIndexes) { - LinkedList<String> jarList = index.get(name.replace('.', '/')); - - if (jarList != null) { - for (String jarName: jarList) { - JARDesc desc; - try { - desc = new JARDesc(new URL(file.getCodeBase(), jarName), - null, null, false, true, false, true); - } catch (MalformedURLException mfe) { - throw new ClassNotFoundException(name); - } - - available.add(desc); - - tracker.addResource(desc.getLocation(), - desc.getVersion(), - JNLPRuntime.getDefaultUpdatePolicy() - ); - - URL remoteURL; - try { - remoteURL = new URL(file.getCodeBase() + jarName); - } catch (MalformedURLException mfe) { - throw new ClassNotFoundException(name); - } - - URL u; - - try { - u = tracker.getCacheURL(remoteURL); - } catch (Exception e) { - throw new ClassNotFoundException(name); - } - - if (u != null) - addURL(u); - - } - - // If it still fails, let it error out - result = loadClassExt(name); - } - } - } - - return result; - } - - /** - * Find the class in this loader or any of its extension loaders. - */ - protected Class findClass(String name) throws ClassNotFoundException { - for (int i=0; i < loaders.length; i++) { - try { - if (loaders[i] == this) - return super.findClass(name); - else - return loaders[i].findClass(name); - } - catch(ClassNotFoundException ex) { } - catch(ClassFormatError cfe) {} - } - - throw new ClassNotFoundException(name); - } - - /** - * Search for the class by incrementally adding resources to the - * classloader and its extension classloaders until the resource - * is found. - */ - private Class loadClassExt(String name) throws ClassNotFoundException { - // make recursive - addAvailable(); - - // find it - try { - return findClass(name); - } - catch(ClassNotFoundException ex) { - } - - // add resources until found - while (true) { - JNLPClassLoader addedTo = addNextResource(); - - if (addedTo == null) - throw new ClassNotFoundException(name); - - try { - return addedTo.findClass(name); - } - catch(ClassNotFoundException ex) { - } - } - } - - /** - * Finds the resource in this, the parent, or the extension - * class loaders. - */ - public URL getResource(String name) { - URL result = super.getResource(name); - - for (int i=1; i < loaders.length; i++) - if (result == null) - result = loaders[i].getResource(name); - - return result; - } - - /** - * Finds the resource in this, the parent, or the extension - * class loaders. - */ - public Enumeration findResources(String name) throws IOException { - Vector resources = new Vector(); - - for (int i=0; i < loaders.length; i++) { - Enumeration e; - - if (loaders[i] == this) - e = super.findResources(name); - else - e = loaders[i].findResources(name); - - while (e.hasMoreElements()) - resources.add(e.nextElement()); - } - - return resources.elements(); - } - - /** - * Returns if the specified resource is available locally from a cached jar - * - * @param s The name of the resource - * @return Whether or not the resource is available locally - */ - public boolean resourceAvailableLocally(String s) { - return jarEntries.contains(s); - } - - /** - * Adds whatever resources have already been downloaded in the - * background. - */ - protected void addAvailable() { - // go through available, check tracker for it and all of its - // part brothers being available immediately, add them. - - for (int i=1; i < loaders.length; i++) { - loaders[i].addAvailable(); - } - } - - /** - * Adds the next unused resource to the classloader. That - * resource and all those in the same part will be downloaded - * and added to the classloader before returning. If there are - * no more resources to add, the method returns immediately. - * - * @return the classloader that resources were added to, or null - */ - protected JNLPClassLoader addNextResource() { - if (available.size() == 0) { - for (int i=1; i < loaders.length; i++) { - JNLPClassLoader result = loaders[i].addNextResource(); - - if (result != null) - return result; - } - return null; - } - - // add jar - List jars = new ArrayList(); - jars.add(available.get(0)); - - fillInPartJars(jars); - - - activateJars(jars); - - return this; - } - - // this part compatibility with previous classloader - /** - * @deprecated - */ - public String getExtensionName() { - String result = file.getInformation().getTitle(); - - if (result == null) - result = file.getInformation().getDescription(); - if (result == null && file.getFileLocation() != null) - result = file.getFileLocation().toString(); - if (result == null && file.getCodeBase() != null) - result = file.getCodeBase().toString(); - - return result; - } - - /** - * @deprecated - */ - public String getExtensionHREF() { - return file.getFileLocation().toString(); - } - - public boolean getSigning() { - return signing; - } - - protected SecurityDesc getSecurity() { - return security; - } -} - - + +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TreeSet; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import net.sourceforge.jnlp.ExtensionDesc; +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.ParseException; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.ResourcesDesc; +import net.sourceforge.jnlp.SecurityDesc; +import net.sourceforge.jnlp.Version; +import net.sourceforge.jnlp.cache.CacheUtil; +import net.sourceforge.jnlp.cache.ResourceTracker; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.security.SecurityWarningDialog; +import net.sourceforge.jnlp.tools.JarSigner; +import sun.misc.JarIndex; + +/** + * Classloader that takes it's resources from a JNLP file. If the + * JNLP file defines extensions, separate classloaders for these + * will be created automatically. Classes are loaded with the + * security context when the classloader was created. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.20 $ + */ +public class JNLPClassLoader extends URLClassLoader { + + // todo: initializePermissions should get the permissions from + // extension classes too so that main file classes can load + // resources in an extension. + + /** shortcut for resources */ + private static String R(String key) { return JNLPRuntime.getMessage(key); } + + /** map from JNLPFile url to shared classloader */ + private static Map urlToLoader = new HashMap(); // never garbage collected! + + /** number of times a classloader with native code is created */ + private static int nativeCounter = 0; + + /** the directory for native code */ + private File nativeDir = null; // if set, some native code exists + + /** security context */ + private AccessControlContext acc = AccessController.getContext(); + + /** the permissions for the cached jar files */ + private List resourcePermissions; + + /** the app */ + private ApplicationInstance app = null; // here for faster lookup in security manager + + /** list of this, local and global loaders this loader uses */ + private JNLPClassLoader loaders[] = null; // ..[0]==this + + /** whether to strictly adhere to the spec or not */ + private boolean strict = true; + + /** loads the resources */ + private ResourceTracker tracker = new ResourceTracker(true); // prefetch + + /** the update policy for resources */ + private UpdatePolicy updatePolicy; + + /** the JNLP file */ + private JNLPFile file; + + /** the resources section */ + private ResourcesDesc resources; + + /** the security section */ + private SecurityDesc security; + + /** Permissions granted by the user during runtime. */ + private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>(); + + /** all jars not yet part of classloader or active */ + private List available = new ArrayList(); + + /** all of the jar files that were verified */ + private ArrayList<String> verifiedJars = null; + + /** all of the jar files that were not verified */ + private ArrayList<String> unverifiedJars = null; + + /** the jarsigner tool to verify our jars */ + private JarSigner js = null; + + private boolean signing = false; + + /** ArrayList containing jar indexes for various jars available to this classloader */ + private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>(); + + /** File entries in the jar files available to this classloader */ + private TreeSet jarEntries = new TreeSet(); + + /** + * Create a new JNLPClassLoader from the specified file. + * + * @param file the JNLP file + */ + protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException { + super(new URL[0], JNLPClassLoader.class.getClassLoader()); + + if (JNLPRuntime.isDebug()) + System.out.println("New classloader: "+file.getFileLocation()); + + this.file = file; + this.updatePolicy = policy; + this.resources = file.getResources(); + + // initialize extensions + initializeExtensions(); + + // initialize permissions + initializePermissions(); + + initializeResources(); + + setSecurity(); + + } + + private void setSecurity() { + /** + * When we're trying to load an applet, file.getSecurity() will return + * null since there is no jnlp file to specify permissions. We + * determine security settings here, after trying to verify jars. + */ + if (file instanceof PluginBridge) { + if (signing == true) { + this.security = new SecurityDesc(file, + SecurityDesc.ALL_PERMISSIONS, + file.getCodeBase().getHost()); + } else { + this.security = new SecurityDesc(file, + SecurityDesc.SANDBOX_PERMISSIONS, + file.getCodeBase().getHost()); + } + } else { //regular jnlp file + + /** + * If the application is signed, then we set the SecurityDesc to the + * <security> tag in the jnlp file. Note that if an application is + * signed, but there is no <security> tag in the jnlp file, the + * application will get sandbox permissions. + * If the application is unsigned, we ignore the <security> tag and + * use a sandbox instead. + */ + if (signing == true) { + this.security = file.getSecurity(); + } else { + this.security = new SecurityDesc(file, + SecurityDesc.SANDBOX_PERMISSIONS, + file.getCodeBase().getHost()); + } + } + } + + /** + * Returns a JNLP classloader for the specified JNLP file. + * + * @param file the file to load classes for + * @param policy the update policy to use when downloading resources + */ + public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException { + JNLPClassLoader loader = null; + URL location = file.getFileLocation(); + + if (location != null) + loader = (JNLPClassLoader) urlToLoader.get(location); + + try { + if (loader == null) + loader = new JNLPClassLoader(file, policy); + } catch (LaunchException e) { + throw e; + } + + if (file.getInformation().isSharingAllowed()) + urlToLoader.put(location, loader); + + return loader; + } + + /** + * Returns a JNLP classloader for the JNLP file at the specified + * location. + * + * @param location the file's location + * @param version the file's version + * @param policy the update policy to use when downloading resources + */ + public static JNLPClassLoader getInstance(URL location, Version version, UpdatePolicy policy) + throws IOException, ParseException, LaunchException { + JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(location); + + if (loader == null) + loader = getInstance(new JNLPFile(location, version, false, policy), policy); + + return loader; + } + + /** + * Load the extensions specified in the JNLP file. + */ + void initializeExtensions() { + ExtensionDesc[] ext = resources.getExtensions(); + + List loaderList = new ArrayList(); + + loaderList.add(this); + + //if (ext != null) { + for (int i=0; i < ext.length; i++) { + try { + JNLPClassLoader loader = getInstance(ext[i].getLocation(), ext[i].getVersion(), updatePolicy); + loaderList.add(loader); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + //} + + loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]); + } + + /** + * Make permission objects for the classpath. + */ + void initializePermissions() { + resourcePermissions = new ArrayList(); + + JARDesc jars[] = resources.getJARs(); + for (int i=0; i < jars.length; i++) { + Permission p = CacheUtil.getReadPermission(jars[i].getLocation(), + jars[i].getVersion()); + + if (JNLPRuntime.isDebug()) { + if (p == null) + System.out.println("Unable to add permission for " + jars[i].getLocation()); + else + System.out.println("Permission added: " + p.toString()); + } + if (p != null) + resourcePermissions.add(p); + } + } + + /** + * Load all of the JARs used in this JNLP file into the + * ResourceTracker for downloading. + */ + void initializeResources() throws LaunchException { + JARDesc jars[] = resources.getJARs(); + if (jars == null || jars.length == 0) + return; + /* + if (jars == null || jars.length == 0) { + throw new LaunchException(null, null, R("LSFatal"), + R("LCInit"), R("LFatalVerification"), "No jars!"); + } + */ + List initialJars = new ArrayList(); + + for (int i=0; i < jars.length; i++) { + + available.add(jars[i]); + + if (jars[i].isEager()) + initialJars.add(jars[i]); // regardless of part + + tracker.addResource(jars[i].getLocation(), + jars[i].getVersion(), + jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE + ); + } + + if (strict) + fillInPartJars(initialJars); // add in each initial part's lazy jars + + if (JNLPRuntime.isVerifying()) { + + JarSigner js; + waitForJars(initialJars); //download the jars first. + + try { + js = verifyJars(initialJars); + } catch (Exception e) { + //we caught an Exception from the JarSigner class. + //Note: one of these exceptions could be from not being able + //to read the cacerts or trusted.certs files. + e.printStackTrace(); + throw new LaunchException(null, null, R("LSFatal"), + R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo")); + } + + //Case when at least one jar has some signing + if (js.anyJarsSigned()){ + signing = true; + + //user does not trust this publisher + if (!js.getAlreadyTrustPublisher()) { + checkTrustWithUser(js); + } else { + /** + * If the user trusts this publisher (i.e. the publisher's certificate + * is in the user's trusted.certs file), we do not show any dialogs. + */ + } + } else { + + signing = false; + //otherwise this jar is simply unsigned -- make sure to ask + //for permission on certain actions + } + } + + activateJars(initialJars); + } + + private void checkTrustWithUser(JarSigner js) throws LaunchException { + if (!js.getRootInCacerts()) { //root cert is not in cacerts + boolean b = SecurityWarningDialog.showCertWarningDialog( + SecurityWarningDialog.AccessType.UNVERIFIED, file, js); + if (!b) + throw new LaunchException(null, null, R("LSFatal"), + R("LCLaunching"), R("LNotVerified"), ""); + } else if (js.getRootInCacerts()) { //root cert is in cacerts + boolean b = false; + if (js.noSigningIssues()) + b = SecurityWarningDialog.showCertWarningDialog( + SecurityWarningDialog.AccessType.VERIFIED, file, js); + else if (!js.noSigningIssues()) + b = SecurityWarningDialog.showCertWarningDialog( + SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js); + if (!b) + throw new LaunchException(null, null, R("LSFatal"), + R("LCLaunching"), R("LCancelOnUserRequest"), ""); + } + } + + /** + * Add applet's codebase URL. This allows compatibility with + * applets that load resources from their codebase instead of + * through JARs, but can slow down resource loading. Resources + * loaded from the codebase are not cached. + */ + public void enableCodeBase() { + addURL( file.getCodeBase() ); // nothing happens if called more that once? + } + + /** + * Sets the JNLP app this group is for; can only be called once. + */ + public void setApplication(ApplicationInstance app) { + if (this.app != null) { + if (JNLPRuntime.isDebug()) { + Exception ex = new IllegalStateException("Application can only be set once"); + ex.printStackTrace(); + } + return; + } + + this.app = app; + } + + /** + * Returns the JNLP app for this classloader + */ + public ApplicationInstance getApplication() { + return app; + } + + /** + * Returns the JNLP file the classloader was created from. + */ + public JNLPFile getJNLPFile() { + return file; + } + + /** + * Returns the permissions for the CodeSource. + */ + protected PermissionCollection getPermissions(CodeSource cs) { + Permissions result = new Permissions(); + + // should check for extensions or boot, automatically give all + // access w/o security dialog once we actually check certificates. + + // copy security permissions from SecurityDesc element + if (security != null) { + Enumeration e = security.getPermissions().elements(); + while (e.hasMoreElements()) + result.add((Permission) e.nextElement()); + } + + // add in permission to read the cached JAR files + for (int i=0; i < resourcePermissions.size(); i++) + result.add((Permission) resourcePermissions.get(i)); + + // add in the permissions that the user granted. + for (int i=0; i < runtimePermissions.size(); i++) + result.add(runtimePermissions.get(i)); + + return result; + } + + protected void addPermission(Permission p) { + runtimePermissions.add(p); + } + + /** + * Adds to the specified list of JARS any other JARs that need + * to be loaded at the same time as the JARs specified (ie, are + * in the same part). + */ + protected void fillInPartJars(List jars) { + for (int i=0; i < jars.size(); i++) { + String part = ((JARDesc) jars.get(i)).getPart(); + + for (int a=0; a < available.size(); a++) { + JARDesc jar = (JARDesc) available.get(a); + + if (part != null && part.equals(jar.getPart())) + if (!jars.contains(jar)) + jars.add(jar); + } + } + } + + /** + * Ensures that the list of jars have all been transferred, and + * makes them available to the classloader. If a jar contains + * native code, the libraries will be extracted and placed in + * the path. + * + * @param jars the list of jars to load + */ + protected void activateJars(final List jars) { + PrivilegedAction activate = new PrivilegedAction() { + + public Object run() { + // transfer the Jars + waitForJars(jars); + + for (int i=0; i < jars.size(); i++) { + JARDesc jar = (JARDesc) jars.get(i); + + available.remove(jar); + + // add jar + File localFile = tracker.getCacheFile(jar.getLocation()); + try { + URL location = jar.getLocation(); // non-cacheable, use source location + if (localFile != null) { + location = localFile.toURL(); // cached file + + // This is really not the best way.. but we need some way for + // PluginAppletViewer::getCachedImageRef() to check if the image + // is available locally, and it cannot use getResources() because + // that prefetches the resource, which confuses MediaTracker.waitForAll() + // which does a wait(), waiting for notification (presumably + // thrown after a resource is fetched). This bug manifests itself + // particularly when using The FileManager applet from Webmin. + + JarFile jarFile = new JarFile(localFile); + Enumeration e = jarFile.entries(); + while (e.hasMoreElements()) { + + JarEntry je = (JarEntry) e.nextElement(); + + // another jar in my jar? it is more likely than you think + if (je.getName().endsWith(".jar")) { + // We need to extract that jar so that it can be loaded + // (inline loading with "jar:..!/..." path will not work + // with standard classloader methods) + + String extractedJarLocation = localFile.getParent() + "/" + je.getName(); + File parentDir = new File(extractedJarLocation).getParentFile(); + if (!parentDir.isDirectory() && !parentDir.mkdirs()) { + throw new RuntimeException(R("RNestedJarExtration")); + } + FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation); + InputStream is = jarFile.getInputStream(je); + + byte[] bytes = new byte[1024]; + int read = is.read(bytes); + while (read > 0) { + extractedJar.write(bytes, 0, read); + read = is.read(bytes); + } + + is.close(); + extractedJar.close(); + + JarSigner signer = new JarSigner(); + signer.verifyJar(extractedJarLocation); + + if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) { + checkTrustWithUser(signer); + } + + try { + addURL(new URL("file://" + extractedJarLocation)); + } catch (MalformedURLException mfue) { + if (JNLPRuntime.isDebug()) + System.err.println("Unable to add extracted nested jar to classpath"); + + mfue.printStackTrace(); + } + } + + jarEntries.add(je.getName()); + } + + } + + addURL(location); + + // there is currently no mechanism to cache files per + // instance.. so only index cached files + if (localFile != null) { + JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null); + + if (index != null) + jarIndexes.add(index); + } + + if (JNLPRuntime.isDebug()) + System.err.println("Activate jar: "+location); + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + + if (jar.isNative()) + activateNative(jar); + } + + return null; + } + }; + + AccessController.doPrivileged(activate, acc); + } + + /** + * Enable the native code contained in a JAR by copying the + * native files into the filesystem. Called in the security + * context of the classloader. + */ + protected void activateNative(JARDesc jar) { + if (JNLPRuntime.isDebug()) + System.out.println("Activate native: "+jar.getLocation()); + + File localFile = tracker.getCacheFile(jar.getLocation()); + if (localFile == null) + return; + + if (nativeDir == null) + nativeDir = getNativeDir(); + + try { + JarFile jarFile = new JarFile(localFile, false); + Enumeration entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry e = (JarEntry) entries.nextElement(); + + if (e.isDirectory() || e.getName().indexOf('/') != -1) + continue; + + File outFile = new File(nativeDir, e.getName()); + + CacheUtil.streamCopy(jarFile.getInputStream(e), + new FileOutputStream(outFile)); + } + } + catch (IOException ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + } + + /** + * Return the base directory to store native code files in. + * This method does not need to return the same directory across + * calls. + */ + protected File getNativeDir() { + nativeDir = new File(System.getProperty("java.io.tmpdir") + + File.separator + "netx-native-" + + (new Random().nextInt() & 0xFFFF)); + + if (!nativeDir.mkdirs()) + return null; + else + return nativeDir; + } + + /** + * Return the absolute path to the native library. + */ + protected String findLibrary(String lib) { + if (nativeDir == null) + return null; + + String syslib = System.mapLibraryName(lib); + + File target = new File(nativeDir, syslib); + if (target.exists()) + return target.toString(); + else { + String result = super.findLibrary(lib); + if (result != null) + return result; + + return findLibraryExt(lib); + } + } + + /** + * Try to find the library path from another peer classloader. + */ + protected String findLibraryExt(String lib) { + for (int i=0; i < loaders.length; i++) { + String result = null; + + if (loaders[i] != this) + result = loaders[i].findLibrary(lib); + + if (result != null) + return result; + } + + return null; + } + + /** + * Wait for a group of JARs, and send download events if there + * is a download listener or display a progress window otherwise. + * + * @param jars the jars + */ + private void waitForJars(List jars) { + URL urls[] = new URL[jars.size()]; + + for (int i=0; i < jars.size(); i++) { + JARDesc jar = (JARDesc) jars.get(i); + + urls[i] = jar.getLocation(); + } + + CacheUtil.waitForResources(app, tracker, urls, file.getTitle()); + } + + /** + * Verifies code signing of jars to be used. + * + * @param jars the jars to be verified. + */ + private JarSigner verifyJars(List<JARDesc> jars) throws Exception { + + js = new JarSigner(); + js.verifyJars(jars, tracker); + return js; + } + + /** + * Find the loaded class in this loader or any of its extension loaders. + */ + protected Class findLoadedClassAll(String name) { + for (int i=0; i < loaders.length; i++) { + Class result = null; + + if (loaders[i] == this) + result = super.findLoadedClass(name); + else + result = loaders[i].findLoadedClassAll(name); + + if (result != null) + return result; + } + + return null; + } + + /** + * Find a JAR in the shared 'extension' classloaders, this + * classloader, or one of the classloaders for the JNLP file's + * extensions. + */ + public Class loadClass(String name) throws ClassNotFoundException { + + Class result = findLoadedClassAll(name); + + // try parent classloader + if (result == null) { + try { + ClassLoader parent = getParent(); + if (parent == null) + parent = ClassLoader.getSystemClassLoader(); + + return parent.loadClass(name); + } + catch (ClassNotFoundException ex) { } + } + + // filter out 'bad' package names like java, javax + // validPackage(name); + + // search this and the extension loaders + if (result == null) + try { + result = loadClassExt(name); + } catch (ClassNotFoundException cnfe) { + + // Not found in external loader either. As a last resort, look in any available indexes + + // Currently this loads jars directly from the site. We cannot cache it because this + // call is initiated from within the applet, which does not have disk read/write permissions + for (JarIndex index: jarIndexes) { + LinkedList<String> jarList = index.get(name.replace('.', '/')); + + if (jarList != null) { + for (String jarName: jarList) { + JARDesc desc; + try { + desc = new JARDesc(new URL(file.getCodeBase(), jarName), + null, null, false, true, false, true); + } catch (MalformedURLException mfe) { + throw new ClassNotFoundException(name); + } + + available.add(desc); + + tracker.addResource(desc.getLocation(), + desc.getVersion(), + JNLPRuntime.getDefaultUpdatePolicy() + ); + + URL remoteURL; + try { + remoteURL = new URL(file.getCodeBase() + jarName); + } catch (MalformedURLException mfe) { + throw new ClassNotFoundException(name); + } + + URL u; + + try { + u = tracker.getCacheURL(remoteURL); + } catch (Exception e) { + throw new ClassNotFoundException(name); + } + + if (u != null) + addURL(u); + + } + + // If it still fails, let it error out + result = loadClassExt(name); + } + } + } + + return result; + } + + /** + * Find the class in this loader or any of its extension loaders. + */ + protected Class findClass(String name) throws ClassNotFoundException { + for (int i=0; i < loaders.length; i++) { + try { + if (loaders[i] == this) + return super.findClass(name); + else + return loaders[i].findClass(name); + } + catch(ClassNotFoundException ex) { } + catch(ClassFormatError cfe) {} + } + + throw new ClassNotFoundException(name); + } + + /** + * Search for the class by incrementally adding resources to the + * classloader and its extension classloaders until the resource + * is found. + */ + private Class loadClassExt(String name) throws ClassNotFoundException { + // make recursive + addAvailable(); + + // find it + try { + return findClass(name); + } + catch(ClassNotFoundException ex) { + } + + // add resources until found + while (true) { + JNLPClassLoader addedTo = addNextResource(); + + if (addedTo == null) + throw new ClassNotFoundException(name); + + try { + return addedTo.findClass(name); + } + catch(ClassNotFoundException ex) { + } + } + } + + /** + * Finds the resource in this, the parent, or the extension + * class loaders. + */ + public URL getResource(String name) { + URL result = super.getResource(name); + + for (int i=1; i < loaders.length; i++) + if (result == null) + result = loaders[i].getResource(name); + + return result; + } + + /** + * Finds the resource in this, the parent, or the extension + * class loaders. + */ + public Enumeration findResources(String name) throws IOException { + Vector resources = new Vector(); + + for (int i=0; i < loaders.length; i++) { + Enumeration e; + + if (loaders[i] == this) + e = super.findResources(name); + else + e = loaders[i].findResources(name); + + while (e.hasMoreElements()) + resources.add(e.nextElement()); + } + + return resources.elements(); + } + + /** + * Returns if the specified resource is available locally from a cached jar + * + * @param s The name of the resource + * @return Whether or not the resource is available locally + */ + public boolean resourceAvailableLocally(String s) { + return jarEntries.contains(s); + } + + /** + * Adds whatever resources have already been downloaded in the + * background. + */ + protected void addAvailable() { + // go through available, check tracker for it and all of its + // part brothers being available immediately, add them. + + for (int i=1; i < loaders.length; i++) { + loaders[i].addAvailable(); + } + } + + /** + * Adds the next unused resource to the classloader. That + * resource and all those in the same part will be downloaded + * and added to the classloader before returning. If there are + * no more resources to add, the method returns immediately. + * + * @return the classloader that resources were added to, or null + */ + protected JNLPClassLoader addNextResource() { + if (available.size() == 0) { + for (int i=1; i < loaders.length; i++) { + JNLPClassLoader result = loaders[i].addNextResource(); + + if (result != null) + return result; + } + return null; + } + + // add jar + List jars = new ArrayList(); + jars.add(available.get(0)); + + fillInPartJars(jars); + + + activateJars(jars); + + return this; + } + + // this part compatibility with previous classloader + /** + * @deprecated + */ + public String getExtensionName() { + String result = file.getInformation().getTitle(); + + if (result == null) + result = file.getInformation().getDescription(); + if (result == null && file.getFileLocation() != null) + result = file.getFileLocation().toString(); + if (result == null && file.getCodeBase() != null) + result = file.getCodeBase().toString(); + + return result; + } + + /** + * @deprecated + */ + public String getExtensionHREF() { + return file.getFileLocation().toString(); + } + + public boolean getSigning() { + return signing; + } + + protected SecurityDesc getSecurity() { + return security; + } +} + +
--- a/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/JNLPRuntime.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,559 +1,559 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.io.*; -import java.awt.*; -import java.text.*; -import java.util.*; -import java.util.List; -import java.security.*; -import javax.jnlp.*; - -import net.sourceforge.jnlp.*; -import net.sourceforge.jnlp.cache.*; -import net.sourceforge.jnlp.services.*; -import net.sourceforge.jnlp.util.*; - - -/** - * Configure and access the runtime environment. This class - * stores global jnlp properties such as default download - * indicators, the install/base directory, the default resource - * update policy, etc. Some settings, such as the base directory, - * cannot be changed once the runtime has been initialized.<p> - * - * The JNLP runtime can be locked to prevent further changes to - * the runtime environment except by a specified class. If set, - * only instances of the <i>exit class</i> can exit the JVM or - * change the JNLP runtime settings once the runtime has been - * initialized.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.19 $ - */ -public class JNLPRuntime { - - static { - loadResources(); - } - - /** the localized resource strings */ - private static ResourceBundle resources; - - /** the security manager */ - private static JNLPSecurityManager security; - - /** the security policy */ - private static JNLPPolicy policy; - - /** the base dir for cache, etc */ - private static File baseDir; - - /** a default launch handler */ - private static LaunchHandler handler = null; - - /** default download indicator */ - private static DownloadIndicator indicator = null; - - /** update policy that controls when to check for updates */ - private static UpdatePolicy updatePolicy = UpdatePolicy.ALWAYS; - - /** netx window icon */ - private static Image windowIcon = null; - - /** whether initialized */ - private static boolean initialized = false; - - /** whether netx is in command-line mode (headless) */ - private static boolean headless = false; - - /** whether we'll be checking for jar signing */ - private static boolean verify = true; - - /** whether the runtime uses security */ - private static boolean securityEnabled = true; - - /** whether debug mode is on */ - private static boolean debug = false; // package access by Boot - - /** mutex to wait on, for initialization */ - public static Object initMutex = new Object(); - - /** set to true if this is a webstart application. */ - private static boolean isWebstartApplication; - - /** set to false to indicate another JVM should not be spawned, even if necessary */ - private static boolean forksAllowed = true; - - /** contains the arguments passed to the jnlp runtime */ - private static List<String> initialArguments; - - /** Username */ - public static final String USER = System.getProperty("user.name"); - - /** User's home directory */ - public static final String HOME_DIR = System.getProperty("user.home"); - - /** the ~/.netxrc file containing netx settings */ - public static final String NETXRC_FILE = HOME_DIR + File.separator + ".netxrc"; - - /** the ~/.netx directory containing user-specific data */ - public static final String NETX_DIR = HOME_DIR + File.separator + ".netx"; - - /** the ~/.netx/security directory containing security related information */ - public static final String SECURITY_DIR = NETX_DIR + File.separator + "security"; - - /** the ~/.netx/security/trusted.certs file containing trusted certificates */ - public static final String CERTIFICATES_FILE = SECURITY_DIR + File.separator + "trusted.certs"; - - /** the /tmp/ directory used for temporary files */ - public static final String TMP_DIR = System.getProperty("java.io.tmpdir"); - - /** - * the /tmp/$USER/netx/locks/ directory containing locks for single instance - * applications - */ - public static final String LOCKS_DIR = TMP_DIR + File.separator + USER + File.separator - + "netx" + File.separator + "locks"; - - /** the java.home directory */ - public static final String JAVA_HOME_DIR = System.getProperty("java.home"); - - /** the JNLP file to open to display the network-based about window */ - public static final String NETX_ABOUT_FILE = JAVA_HOME_DIR + File.separator + "lib" - + File.separator + "about.jnlp"; - - - - /** - * Returns whether the JNLP runtime environment has been - * initialized. Once initialized, some properties such as the - * base directory cannot be changed. Before - */ - public static boolean isInitialized() { - return initialized; - } - - /** - * Initialize the JNLP runtime environment by installing the - * security manager and security policy, initializing the JNLP - * standard services, etc.<p> - * - * This method cannot be called more than once. Once - * initialized, methods that alter the runtime can only be - * called by the exit class.<p> - * - * @param isApplication is true if a webstart application is being initialized - * - * @throws IllegalStateException if the runtime was previously initialized - */ - public static void initialize(boolean isApplication) throws IllegalStateException { - checkInitialized(); - - isWebstartApplication = isApplication; - - if (headless == false) - checkHeadless(); - - if (!headless && windowIcon == null) - loadWindowIcon(); - - if (!headless && indicator == null) - indicator = new DefaultDownloadIndicator(); - - if (handler == null) - handler = new DefaultLaunchHandler(); - - if (baseDir == null) - baseDir = getDefaultBaseDir(); - - if (baseDir == null) - throw new IllegalStateException(JNLPRuntime.getMessage("BNoBase")); - - ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start - - policy = new JNLPPolicy(); - security = new JNLPSecurityManager(); // side effect: create JWindow - - if (securityEnabled) { - Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy - System.setSecurityManager(security); - } - - initialized = true; - } - - /** - * Returns true if a webstart application has been initialized, and false - * for a plugin applet. - */ - public static boolean isWebstartApplication() { - return isWebstartApplication; - } - - /** - * Returns the window icon. - */ - public static Image getWindowIcon() { - return windowIcon; - } - - /** - * Sets the window icon that is displayed in Java applications - * and applets instead of the default Java icon. - * - * @throws IllegalStateException if caller is not the exit class - */ - public static void setWindowIcon(Image image) { - checkExitClass(); - windowIcon = image; - } - - /** - * Returns whether the JNLP client will use any AWT/Swing - * components. - */ - public static boolean isHeadless() { - return headless; - } - - /** - * Returns whether we are verifying code signing. - */ - public static boolean isVerifying() { - return verify; - } - /** - * Sets whether the JNLP client will use any AWT/Swing - * components. In headless mode, client features that use the - * AWT are disabled such that the client can be used in - * headless mode (<code>java.awt.headless=true</code>). - * - * @throws IllegalStateException if the runtime was previously initialized - */ - public static void setHeadless(boolean enabled) { - checkInitialized(); - headless = enabled; - } - - /** - * Sets whether we will verify code signing. - * @throws IllegalStateException if the runtime was previously initialized - */ - public static void setVerify(boolean enabled) { - checkInitialized(); - verify = enabled; - } - - /** - * Return the base directory containing the cache, persistence - * store, etc. - */ - public static File getBaseDir() { - return baseDir; - } - - /** - * Sets the base directory containing the cache, persistence - * store, etc. - * - * @throws IllegalStateException if caller is not the exit class - */ - public static void setBaseDir(File baseDirectory) { - checkInitialized(); - baseDir = baseDirectory; - } - - /** - * Returns whether the secure runtime environment is enabled. - */ - public static boolean isSecurityEnabled() { - return securityEnabled; - } - - /** - * Sets whether to enable the secure runtime environment. - * Disabling security can increase performance for some - * applications, and can be used to use netx with other code - * that uses its own security manager or policy. - * - * Disabling security is not recommended and should only be - * used if the JNLP files opened are trusted. This method can - * only be called before initalizing the runtime.<p> - * - * @param enabled whether security should be enabled - * @throws IllegalStateException if the runtime is already initialized - */ - public static void setSecurityEnabled(boolean enabled) { - checkInitialized(); - securityEnabled = enabled; - } - - /** - * Returns the system default base dir for or if not set, - * prompts the user for the location. - * - * @return the base dir, or null if the user canceled the dialog - * @throws IOException if there was an io exception - */ - public static File getDefaultBaseDir() { - PropertiesFile props = JNLPRuntime.getProperties(); - - String baseStr = props.getProperty("basedir"); - if (baseStr != null) - return new File(baseStr); - - String homeDir = HOME_DIR; - File baseDir = new File(NETX_DIR); - if (homeDir == null || (!baseDir.isDirectory() && !baseDir.mkdir())) - return null; - - props.setProperty("basedir", baseDir.toString()); - props.store(); - - return baseDir; - } - - /** - * Set a class that can exit the JVM; if not set then any class - * can exit the JVM. - * - * @throws IllegalStateException if caller is not the exit class - */ - public static void setExitClass(Class exitClass) { - checkExitClass(); - security.setExitClass(exitClass); - } - - /** - * Disables applets from calling exit. - * - * Once disabled, exit cannot be re-enabled for the duration of the JVM instance - */ - public static void disableExit() { - security.disableExit(); - } - - /** - * Return the current Application, or null if none can be - * determined. - */ - public static ApplicationInstance getApplication() { - return security.getApplication(); - } - - /** - * Return a PropertiesFile object backed by the runtime's - * properties file. - */ - public static PropertiesFile getProperties() { - File netxrc = new File(NETXRC_FILE); - return new PropertiesFile(netxrc); - } - - /** - * Return whether debug statements for the JNLP client code - * should be printed. - */ - public static boolean isDebug() { - return debug; - } - - /** - * Sets whether debug statements for the JNLP client code - * should be printed to the standard output. - * - * @throws IllegalStateException if caller is not the exit class - */ - public static void setDebug(boolean enabled) { - checkExitClass(); - debug = enabled; - } - - /** - * Sets the default update policy. - * - * @throws IllegalStateException if caller is not the exit class - */ - public static void setDefaultUpdatePolicy(UpdatePolicy policy) { - checkExitClass(); - updatePolicy = policy; - } - - /** - * Returns the default update policy. - */ - public static UpdatePolicy getDefaultUpdatePolicy() { - return updatePolicy; - } - - /** - * Sets the default launch handler. - */ - public static void setDefaultLaunchHandler(LaunchHandler handler) { - checkExitClass(); - JNLPRuntime.handler = handler; - } - - /** - * Returns the default launch handler. - */ - public static LaunchHandler getDefaultLaunchHandler() { - return handler; - } - - /** - * Sets the default download indicator. - * - * @throws IllegalStateException if caller is not the exit class - */ - public static void setDefaultDownloadIndicator(DownloadIndicator indicator) { - checkExitClass(); - JNLPRuntime.indicator = indicator; - } - - /** - * Returns the default download indicator. - */ - public static DownloadIndicator getDefaultDownloadIndicator() { - return indicator; - } - - /** - * Returns the localized resource string identified by the - * specified key. If the message is empty, a null is - * returned. - */ - public static String getMessage(String key) { - try { - String result = resources.getString(key); - if (result.length() == 0) - return null; - else - return result; - } - catch (Exception ex) { - if (!key.equals("RNoResource")) - return getMessage("RNoResource", new Object[] {key}); - else - return "Missing resource: "+key; - } - } - - /** - * Returns the localized resource string using the specified - * arguments. - * - * @param args the formatting arguments to the resource string - */ - public static String getMessage(String key, Object args[]) { - return MessageFormat.format(getMessage(key), args); - } - - /** - * Returns true if the current runtime will fork - */ - public static boolean getForksAllowed() { - return forksAllowed; - } - - public static void setForksAllowed(boolean value) { - checkInitialized(); - forksAllowed = value; - } - - /** - * Throws an exception if called when the runtime is - * already initialized. - */ - private static void checkInitialized() { - if (initialized) - throw new IllegalStateException("JNLPRuntime already initialized."); - } - - /** - * Throws an exception if called with security enabled but - * a caller is not the exit class and the runtime has been - * initialized. - */ - private static void checkExitClass() { - if (securityEnabled && initialized) - if (!security.isExitClass()) - throw new IllegalStateException("Caller is not the exit class"); - } - - /** - * Check whether the VM is in headless mode. - */ - private static void checkHeadless() { - //if (GraphicsEnvironment.isHeadless()) // jdk1.4+ only - // headless = true; - try { - if ("true".equalsIgnoreCase(System.getProperty("java.awt.headless"))) - headless = true; - } - catch (SecurityException ex) { - } - } - - /** - * Load the resources. - */ - private static void loadResources() { - try { - resources = ResourceBundle.getBundle("net.sourceforge.jnlp.resources.Messages"); - } - catch (Exception ex) { - throw new IllegalStateException("Missing resource bundle in netx.jar:net/sourceforge/jnlp/resource/Messages.properties"); - } - } - - /** - * Load the window icon. - */ - private static void loadWindowIcon() { - if (windowIcon != null) - return; - - try { - windowIcon = new javax.swing.ImageIcon((new sun.misc.Launcher()) - .getClassLoader().getResource("net/sourceforge/jnlp/resources/netx-icon.png")).getImage(); - } - catch (Exception ex) { - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - } - - - public static void setInitialArgments(List<String> args) { - checkInitialized(); - SecurityManager securityManager = System.getSecurityManager(); - if (securityManager != null) - securityManager.checkPermission(new AllPermission()); - initialArguments = args; - } - - public static List<String> getInitialArguments() { - return initialArguments; - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.io.*; +import java.awt.*; +import java.text.*; +import java.util.*; +import java.util.List; +import java.security.*; +import javax.jnlp.*; + +import net.sourceforge.jnlp.*; +import net.sourceforge.jnlp.cache.*; +import net.sourceforge.jnlp.services.*; +import net.sourceforge.jnlp.util.*; + + +/** + * Configure and access the runtime environment. This class + * stores global jnlp properties such as default download + * indicators, the install/base directory, the default resource + * update policy, etc. Some settings, such as the base directory, + * cannot be changed once the runtime has been initialized.<p> + * + * The JNLP runtime can be locked to prevent further changes to + * the runtime environment except by a specified class. If set, + * only instances of the <i>exit class</i> can exit the JVM or + * change the JNLP runtime settings once the runtime has been + * initialized.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.19 $ + */ +public class JNLPRuntime { + + static { + loadResources(); + } + + /** the localized resource strings */ + private static ResourceBundle resources; + + /** the security manager */ + private static JNLPSecurityManager security; + + /** the security policy */ + private static JNLPPolicy policy; + + /** the base dir for cache, etc */ + private static File baseDir; + + /** a default launch handler */ + private static LaunchHandler handler = null; + + /** default download indicator */ + private static DownloadIndicator indicator = null; + + /** update policy that controls when to check for updates */ + private static UpdatePolicy updatePolicy = UpdatePolicy.ALWAYS; + + /** netx window icon */ + private static Image windowIcon = null; + + /** whether initialized */ + private static boolean initialized = false; + + /** whether netx is in command-line mode (headless) */ + private static boolean headless = false; + + /** whether we'll be checking for jar signing */ + private static boolean verify = true; + + /** whether the runtime uses security */ + private static boolean securityEnabled = true; + + /** whether debug mode is on */ + private static boolean debug = false; // package access by Boot + + /** mutex to wait on, for initialization */ + public static Object initMutex = new Object(); + + /** set to true if this is a webstart application. */ + private static boolean isWebstartApplication; + + /** set to false to indicate another JVM should not be spawned, even if necessary */ + private static boolean forksAllowed = true; + + /** contains the arguments passed to the jnlp runtime */ + private static List<String> initialArguments; + + /** Username */ + public static final String USER = System.getProperty("user.name"); + + /** User's home directory */ + public static final String HOME_DIR = System.getProperty("user.home"); + + /** the ~/.netxrc file containing netx settings */ + public static final String NETXRC_FILE = HOME_DIR + File.separator + ".netxrc"; + + /** the ~/.netx directory containing user-specific data */ + public static final String NETX_DIR = HOME_DIR + File.separator + ".netx"; + + /** the ~/.netx/security directory containing security related information */ + public static final String SECURITY_DIR = NETX_DIR + File.separator + "security"; + + /** the ~/.netx/security/trusted.certs file containing trusted certificates */ + public static final String CERTIFICATES_FILE = SECURITY_DIR + File.separator + "trusted.certs"; + + /** the /tmp/ directory used for temporary files */ + public static final String TMP_DIR = System.getProperty("java.io.tmpdir"); + + /** + * the /tmp/$USER/netx/locks/ directory containing locks for single instance + * applications + */ + public static final String LOCKS_DIR = TMP_DIR + File.separator + USER + File.separator + + "netx" + File.separator + "locks"; + + /** the java.home directory */ + public static final String JAVA_HOME_DIR = System.getProperty("java.home"); + + /** the JNLP file to open to display the network-based about window */ + public static final String NETX_ABOUT_FILE = JAVA_HOME_DIR + File.separator + "lib" + + File.separator + "about.jnlp"; + + + + /** + * Returns whether the JNLP runtime environment has been + * initialized. Once initialized, some properties such as the + * base directory cannot be changed. Before + */ + public static boolean isInitialized() { + return initialized; + } + + /** + * Initialize the JNLP runtime environment by installing the + * security manager and security policy, initializing the JNLP + * standard services, etc.<p> + * + * This method cannot be called more than once. Once + * initialized, methods that alter the runtime can only be + * called by the exit class.<p> + * + * @param isApplication is true if a webstart application is being initialized + * + * @throws IllegalStateException if the runtime was previously initialized + */ + public static void initialize(boolean isApplication) throws IllegalStateException { + checkInitialized(); + + isWebstartApplication = isApplication; + + if (headless == false) + checkHeadless(); + + if (!headless && windowIcon == null) + loadWindowIcon(); + + if (!headless && indicator == null) + indicator = new DefaultDownloadIndicator(); + + if (handler == null) + handler = new DefaultLaunchHandler(); + + if (baseDir == null) + baseDir = getDefaultBaseDir(); + + if (baseDir == null) + throw new IllegalStateException(JNLPRuntime.getMessage("BNoBase")); + + ServiceManager.setServiceManagerStub(new XServiceManagerStub()); // ignored if we're running under Web Start + + policy = new JNLPPolicy(); + security = new JNLPSecurityManager(); // side effect: create JWindow + + if (securityEnabled) { + Policy.setPolicy(policy); // do first b/c our SM blocks setPolicy + System.setSecurityManager(security); + } + + initialized = true; + } + + /** + * Returns true if a webstart application has been initialized, and false + * for a plugin applet. + */ + public static boolean isWebstartApplication() { + return isWebstartApplication; + } + + /** + * Returns the window icon. + */ + public static Image getWindowIcon() { + return windowIcon; + } + + /** + * Sets the window icon that is displayed in Java applications + * and applets instead of the default Java icon. + * + * @throws IllegalStateException if caller is not the exit class + */ + public static void setWindowIcon(Image image) { + checkExitClass(); + windowIcon = image; + } + + /** + * Returns whether the JNLP client will use any AWT/Swing + * components. + */ + public static boolean isHeadless() { + return headless; + } + + /** + * Returns whether we are verifying code signing. + */ + public static boolean isVerifying() { + return verify; + } + /** + * Sets whether the JNLP client will use any AWT/Swing + * components. In headless mode, client features that use the + * AWT are disabled such that the client can be used in + * headless mode (<code>java.awt.headless=true</code>). + * + * @throws IllegalStateException if the runtime was previously initialized + */ + public static void setHeadless(boolean enabled) { + checkInitialized(); + headless = enabled; + } + + /** + * Sets whether we will verify code signing. + * @throws IllegalStateException if the runtime was previously initialized + */ + public static void setVerify(boolean enabled) { + checkInitialized(); + verify = enabled; + } + + /** + * Return the base directory containing the cache, persistence + * store, etc. + */ + public static File getBaseDir() { + return baseDir; + } + + /** + * Sets the base directory containing the cache, persistence + * store, etc. + * + * @throws IllegalStateException if caller is not the exit class + */ + public static void setBaseDir(File baseDirectory) { + checkInitialized(); + baseDir = baseDirectory; + } + + /** + * Returns whether the secure runtime environment is enabled. + */ + public static boolean isSecurityEnabled() { + return securityEnabled; + } + + /** + * Sets whether to enable the secure runtime environment. + * Disabling security can increase performance for some + * applications, and can be used to use netx with other code + * that uses its own security manager or policy. + * + * Disabling security is not recommended and should only be + * used if the JNLP files opened are trusted. This method can + * only be called before initalizing the runtime.<p> + * + * @param enabled whether security should be enabled + * @throws IllegalStateException if the runtime is already initialized + */ + public static void setSecurityEnabled(boolean enabled) { + checkInitialized(); + securityEnabled = enabled; + } + + /** + * Returns the system default base dir for or if not set, + * prompts the user for the location. + * + * @return the base dir, or null if the user canceled the dialog + * @throws IOException if there was an io exception + */ + public static File getDefaultBaseDir() { + PropertiesFile props = JNLPRuntime.getProperties(); + + String baseStr = props.getProperty("basedir"); + if (baseStr != null) + return new File(baseStr); + + String homeDir = HOME_DIR; + File baseDir = new File(NETX_DIR); + if (homeDir == null || (!baseDir.isDirectory() && !baseDir.mkdir())) + return null; + + props.setProperty("basedir", baseDir.toString()); + props.store(); + + return baseDir; + } + + /** + * Set a class that can exit the JVM; if not set then any class + * can exit the JVM. + * + * @throws IllegalStateException if caller is not the exit class + */ + public static void setExitClass(Class exitClass) { + checkExitClass(); + security.setExitClass(exitClass); + } + + /** + * Disables applets from calling exit. + * + * Once disabled, exit cannot be re-enabled for the duration of the JVM instance + */ + public static void disableExit() { + security.disableExit(); + } + + /** + * Return the current Application, or null if none can be + * determined. + */ + public static ApplicationInstance getApplication() { + return security.getApplication(); + } + + /** + * Return a PropertiesFile object backed by the runtime's + * properties file. + */ + public static PropertiesFile getProperties() { + File netxrc = new File(NETXRC_FILE); + return new PropertiesFile(netxrc); + } + + /** + * Return whether debug statements for the JNLP client code + * should be printed. + */ + public static boolean isDebug() { + return debug; + } + + /** + * Sets whether debug statements for the JNLP client code + * should be printed to the standard output. + * + * @throws IllegalStateException if caller is not the exit class + */ + public static void setDebug(boolean enabled) { + checkExitClass(); + debug = enabled; + } + + /** + * Sets the default update policy. + * + * @throws IllegalStateException if caller is not the exit class + */ + public static void setDefaultUpdatePolicy(UpdatePolicy policy) { + checkExitClass(); + updatePolicy = policy; + } + + /** + * Returns the default update policy. + */ + public static UpdatePolicy getDefaultUpdatePolicy() { + return updatePolicy; + } + + /** + * Sets the default launch handler. + */ + public static void setDefaultLaunchHandler(LaunchHandler handler) { + checkExitClass(); + JNLPRuntime.handler = handler; + } + + /** + * Returns the default launch handler. + */ + public static LaunchHandler getDefaultLaunchHandler() { + return handler; + } + + /** + * Sets the default download indicator. + * + * @throws IllegalStateException if caller is not the exit class + */ + public static void setDefaultDownloadIndicator(DownloadIndicator indicator) { + checkExitClass(); + JNLPRuntime.indicator = indicator; + } + + /** + * Returns the default download indicator. + */ + public static DownloadIndicator getDefaultDownloadIndicator() { + return indicator; + } + + /** + * Returns the localized resource string identified by the + * specified key. If the message is empty, a null is + * returned. + */ + public static String getMessage(String key) { + try { + String result = resources.getString(key); + if (result.length() == 0) + return null; + else + return result; + } + catch (Exception ex) { + if (!key.equals("RNoResource")) + return getMessage("RNoResource", new Object[] {key}); + else + return "Missing resource: "+key; + } + } + + /** + * Returns the localized resource string using the specified + * arguments. + * + * @param args the formatting arguments to the resource string + */ + public static String getMessage(String key, Object args[]) { + return MessageFormat.format(getMessage(key), args); + } + + /** + * Returns true if the current runtime will fork + */ + public static boolean getForksAllowed() { + return forksAllowed; + } + + public static void setForksAllowed(boolean value) { + checkInitialized(); + forksAllowed = value; + } + + /** + * Throws an exception if called when the runtime is + * already initialized. + */ + private static void checkInitialized() { + if (initialized) + throw new IllegalStateException("JNLPRuntime already initialized."); + } + + /** + * Throws an exception if called with security enabled but + * a caller is not the exit class and the runtime has been + * initialized. + */ + private static void checkExitClass() { + if (securityEnabled && initialized) + if (!security.isExitClass()) + throw new IllegalStateException("Caller is not the exit class"); + } + + /** + * Check whether the VM is in headless mode. + */ + private static void checkHeadless() { + //if (GraphicsEnvironment.isHeadless()) // jdk1.4+ only + // headless = true; + try { + if ("true".equalsIgnoreCase(System.getProperty("java.awt.headless"))) + headless = true; + } + catch (SecurityException ex) { + } + } + + /** + * Load the resources. + */ + private static void loadResources() { + try { + resources = ResourceBundle.getBundle("net.sourceforge.jnlp.resources.Messages"); + } + catch (Exception ex) { + throw new IllegalStateException("Missing resource bundle in netx.jar:net/sourceforge/jnlp/resource/Messages.properties"); + } + } + + /** + * Load the window icon. + */ + private static void loadWindowIcon() { + if (windowIcon != null) + return; + + try { + windowIcon = new javax.swing.ImageIcon((new sun.misc.Launcher()) + .getClassLoader().getResource("net/sourceforge/jnlp/resources/netx-icon.png")).getImage(); + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + } + + + public static void setInitialArgments(List<String> args) { + checkInitialized(); + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager != null) + securityManager.checkPermission(new AllPermission()); + initialArguments = args; + } + + public static List<String> getInitialArguments() { + return initialArguments; + } + +} + +
--- a/rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/runtime/JNLPSecurityManager.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,490 +1,490 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.runtime; - -import java.awt.Frame; -import java.awt.Window; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.lang.ref.WeakReference; -import java.net.SocketPermission; -import java.security.AccessControlException; -import java.security.AccessController; -import java.security.Permission; -import java.security.PrivilegedAction; - -import javax.swing.JWindow; - -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.security.SecurityWarningDialog; -import net.sourceforge.jnlp.services.ServiceUtil; -import net.sourceforge.jnlp.util.WeakList; -import sun.security.util.SecurityConstants; - -/** - * Security manager for JNLP environment. This security manager - * cannot be replaced as it always denies attempts to replace the - * security manager or policy.<p> - * - * The JNLP security manager tracks windows created by an - * application, allowing those windows to be disposed when the - * application exits but the JVM does not. If security is not - * enabled then the first application to call System.exit will - * halt the JVM.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.17 $ - */ -class JNLPSecurityManager extends SecurityManager { - - // todo: some apps like JDiskReport can close the VM even when - // an exit class is set - fix! - - // todo: create an event dispatch thread for each application, - // so that the context classloader doesn't have to be switched - // to the foreground application (the currently the approach - // since some apps need their classloader as event dispatch - // thread's context classloader). - - // todo: use a custom Permission object to identify the current - // application in an AccessControlContext by setting a side - // effect in its implies method. Use a custom - // AllPermissions-like permission to do this for apps granted - // all permissions (but investigate whether this will nuke - // the all-permission optimizations in the JRE). - - // todo: does not exit app if close button pressed on JFrame - // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an - // WindowListener to catch WindowClosing event, then if exit is - // called immediately afterwards from AWT thread. - - // todo: deny all permissions to applications that should have - // already been 'shut down' by closing their resources and - // interrupt the threads if operating in a shared-VM (exit class - // set). Deny will probably will slow checks down a lot though. - - // todo: weak remember last getProperty application and - // re-install properties if another application calls, or find - // another way for different apps to have different properties - // in java.lang.Sytem with the same names. - - private static String R(String key) { return JNLPRuntime.getMessage(key); } - - /** only class that can exit the JVM, if set */ - private Object exitClass = null; - - /** this exception prevents exiting the JVM */ - private SecurityException closeAppEx = // making here prevents huge stack traces - new SecurityException(JNLPRuntime.getMessage("RShutdown")); - - /** weak list of windows created */ - private WeakList weakWindows = new WeakList(); - - /** weak list of applications corresponding to window list */ - private WeakList weakApplications = new WeakList(); - - /** weak reference to most app who's windows was most recently activated */ - private WeakReference activeApplication = null; - - /** listener installs the app's classloader on the event dispatch thread */ - private ContextUpdater contextListener = new ContextUpdater(); - - /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */ - private boolean exitAllowed = true; - - private class ContextUpdater extends WindowAdapter implements PrivilegedAction { - private ApplicationInstance app = null; - - public void windowActivated(WindowEvent e) { - app = getApplication(e.getWindow()); - AccessController.doPrivileged(this); - app = null; - } - - public Object run() { - if (app != null) { - Thread.currentThread().setContextClassLoader(app.getClassLoader()); - activeApplication = new WeakReference(app); - } - else - Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); - - return null; - } - - public void windowDeactivated(WindowEvent e) { - activeApplication = null; - } - - public void windowClosing(WindowEvent e) { - System.err.println("Disposing window"); - e.getWindow().dispose(); - } - }; - - - /** - * Creates a JNLP SecurityManager. - */ - JNLPSecurityManager() { - // this has the side-effect of creating the Swing shared Frame - // owner. Since no application is running at this time, it is - // not added to any window list when checkTopLevelWindow is - // called for it (and not disposed). - - if (!JNLPRuntime.isHeadless()) - new JWindow().getOwner(); - } - - /** - * Returns whether the exit class is present on the stack, or - * true if no exit class is set. - */ - public boolean isExitClass() { - return isExitClass(getClassContext()); - } - - /** - * Returns whether the exit class is present on the stack, or - * true if no exit class is set. - */ - private boolean isExitClass(Class stack[]) { - if (exitClass == null) - return true; - - for (int i=0; i < stack.length; i++) - if (stack[i] == exitClass) - return true; - - return false; - } - - /** - * Set the exit class, which is the only class that can exit the - * JVM; if not set then any class can exit the JVM. - * - * @param exitClass the exit class - * @throws IllegalStateException if the exit class is already set - */ - public void setExitClass(Class exitClass) throws IllegalStateException { - if (this.exitClass != null) - throw new IllegalStateException(R("RExitTaken")); - - this.exitClass = exitClass; - } - - /** - * Return the current Application, or null if none can be - * determined. - */ - protected ApplicationInstance getApplication() { - return getApplication(getClassContext(), 0); - } - - /** - * Return the application the opened the specified window (only - * call from event dispatch thread). - */ - protected ApplicationInstance getApplication(Window window) { - for (int i = weakWindows.size(); i-->0;) { - Window w = (Window) weakWindows.get(i); - if (w == null) { - weakWindows.remove(i); - weakApplications.remove(i); - } - - if (w == window) - return (ApplicationInstance) weakApplications.get(i); - } - - return null; - } - - /** - * Return the current Application, or null. - */ - protected ApplicationInstance getApplication(Class stack[], int maxDepth) { - if (maxDepth <= 0) - maxDepth = stack.length; - - // this needs to be tightened up - for (int i=0; i < stack.length && i < maxDepth; i++) { - if (stack[i].getClassLoader() instanceof JNLPClassLoader) { - JNLPClassLoader loader = (JNLPClassLoader) stack[i].getClassLoader(); - - if (loader != null && loader.getApplication() != null) { - return loader.getApplication(); - } - } - } - - return null; - } - - /** - * Returns the application's thread group if the application can - * be determined; otherwise returns super.getThreadGroup() - */ - public ThreadGroup getThreadGroup() { - ApplicationInstance app = getApplication(); - if (app == null) - return super.getThreadGroup(); - - return app.getThreadGroup(); - } - - /** - * Throws a SecurityException if the permission is denied, - * otherwise return normally. This method always denies - * permission to change the security manager or policy. - */ - public void checkPermission(Permission perm) { - String name = perm.getName(); - - // Enable this manually -- it'll produce too much output for -verbose - // otherwise. - // if (true) - // System.out.println("Checking permission: " + perm.toString()); - - if (!JNLPRuntime.isWebstartApplication() && - ("setPolicy".equals(name) || "setSecurityManager".equals(name))) - throw new SecurityException(R("RCantReplaceSM")); - - try { - // deny all permissions to stopped applications - // The call to getApplication() below might not work if an - // application hasn't been fully initialized yet. -// if (JNLPRuntime.isDebug()) { -// if (!"getClassLoader".equals(name)) { -// ApplicationInstance app = getApplication(); -// if (app != null && !app.isRunning()) -// throw new SecurityException(R("RDenyStopped")); -// } -// } - - try { - super.checkPermission(perm); - } catch (SecurityException se) { - - //This section is a special case for dealing with SocketPermissions. - if (JNLPRuntime.isDebug()) - System.err.println("Requesting permission: " + perm.toString()); - - //Change this SocketPermission's action to connect and accept - //(and resolve). This is to avoid asking for connect permission - //on every address resolve. - Permission tmpPerm; - if (perm instanceof SocketPermission) { - tmpPerm = new SocketPermission(perm.getName(), - SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION); - - // before proceeding, check if we are trying to connect to same origin - ApplicationInstance app = getApplication(); - JNLPFile file = app.getJNLPFile(); - - String srcHost = file.getSourceLocation().getAuthority(); - String destHost = name; - - // host = abc.xyz.com or abc.xyz.com:<port> - if (destHost.indexOf(':') >= 0) - destHost = destHost.substring(0, destHost.indexOf(':')); - - // host = abc.xyz.com - String[] hostComponents = destHost.split("\\."); - - int length = hostComponents.length; - if (length >= 2) { - - // address is in xxx.xxx.xxx format - destHost = hostComponents[length -2] + "." + hostComponents[length -1]; - - // host = xyz.com i.e. origin - boolean isDestHostName = false; - - // make sure that it is not an ip address - try { - Integer.parseInt(hostComponents[length -1]); - } catch (NumberFormatException e) { - isDestHostName = true; - } - - if (isDestHostName) { - // okay, destination is hostname. Now figure out if it is a subset of origin - if (srcHost.endsWith(destHost)) { - addPermission(tmpPerm); - return; - } - } - } - - } else - tmpPerm = perm; - - //askPermission will only prompt the user on SocketPermission - //meaning we're denying all other SecurityExceptions that may arise. - if (askPermission(tmpPerm)) { - addPermission(tmpPerm); - //return quietly. - } else { - throw se; - } - } - } - catch (SecurityException ex) { - if (JNLPRuntime.isDebug()) { - System.out.println("Denying permission: "+perm); - } - throw ex; - } - } - - /** - * Asks the user whether or not to grant permission. - * @param perm the permission to be granted - * @return true if the permission was granted, false otherwise. - */ - private boolean askPermission(Permission perm) { - - ApplicationInstance app = getApplication(); - if (app != null && !app.isSigned()) { - if (perm instanceof SocketPermission - && ServiceUtil.checkAccess(SecurityWarningDialog.AccessType.NETWORK, perm.getName())) { - return true; - } - } - - return false; - } - - /** - * Adds a permission to the JNLPClassLoader. - * @param perm the permission to add to the JNLPClassLoader - */ - private void addPermission(Permission perm) { - if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) { - - JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); - cl.addPermission(perm); - if (JNLPRuntime.isDebug()) { - if (cl.getPermissions(null).implies(perm)) - System.err.println("Added permission: " + perm.toString()); - else - System.err.println("Unable to add permission: " + perm.toString()); - } - } else { - if (JNLPRuntime.isDebug()) - System.err.println("Unable to add permission: " + perm + ", classloader not JNLP."); - } - } - - /** - * Checks whether the window can be displayed without an applet - * warning banner, and adds the window to the list of windows to - * be disposed when the calling application exits. - */ - public boolean checkTopLevelWindow(Object window) { - ApplicationInstance app = getApplication(); - - // remember window -> application mapping for focus, close on exit - if (app != null && window instanceof Window) { - Window w = (Window) window; - - if (JNLPRuntime.isDebug()) - System.err.println("SM: app: "+app.getTitle()+" is adding a window: "+window); - - weakWindows.add(window); // for mapping window -> app - weakApplications.add(app); - - w.addWindowListener(contextListener); // for dynamic context classloader - - app.addWindow(w); - } - - // change coffee cup to netx for default icon - if (window instanceof Window) - for (Window w = (Window)window; w != null; w = w.getOwner()) - if (window instanceof Frame) - ((Frame)window).setIconImage(JNLPRuntime.getWindowIcon()); - - // todo: set awt.appletWarning to custom message - // todo: logo on with glass pane on JFrame/JWindow? - - return super.checkTopLevelWindow(window); - } - - /** - * Checks whether the caller can exit the system. This method - * identifies whether the caller is a real call to Runtime.exec - * and has special behavior when returning from this method - * would exit the JVM and an exit class is set: if the caller is - * not the exit class then the calling application will be - * stopped and its resources destroyed (when possible), and an - * exception will be thrown to prevent the JVM from shutting - * down.<p> - * - * Calls not from Runtime.exit or with no exit class set will - * behave normally, and the exit class can always exit the JVM. - */ - public void checkExit(int status) { - - // applets are not allowed to exit, but the plugin main class (primordial loader) is - Class stack[] = getClassContext(); - if (!exitAllowed) { - for (int i=0; i < stack.length; i++) - if (stack[i].getClassLoader() != null) - throw new AccessControlException("Applets may not call System.exit()"); - } - - super.checkExit(status); - - boolean realCall = (stack[1] == Runtime.class); - - if (isExitClass(stack)) // either exitClass called or no exitClass set - return; // to Runtime.exit or fake call to see if app has permission - - // not called from Runtime.exit() - if (!realCall) { - // apps that can't exit should think they can exit normally - super.checkExit(status); - return; - } - - // but when they really call, stop only the app instead of the JVM - ApplicationInstance app = getApplication(stack, 0); - if (app == null) { - // should check caller to make sure it is JFrame.close or - // other known System.exit call - if (activeApplication != null) - app = (ApplicationInstance) activeApplication.get(); - - if (app == null) - throw new SecurityException(R("RExitNoApp")); - } - - app.destroy(); - - throw closeAppEx; - } - - protected void disableExit() { - exitAllowed = false; - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.awt.Frame; +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.ref.WeakReference; +import java.net.SocketPermission; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; + +import javax.swing.JWindow; + +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.security.SecurityWarningDialog; +import net.sourceforge.jnlp.services.ServiceUtil; +import net.sourceforge.jnlp.util.WeakList; +import sun.security.util.SecurityConstants; + +/** + * Security manager for JNLP environment. This security manager + * cannot be replaced as it always denies attempts to replace the + * security manager or policy.<p> + * + * The JNLP security manager tracks windows created by an + * application, allowing those windows to be disposed when the + * application exits but the JVM does not. If security is not + * enabled then the first application to call System.exit will + * halt the JVM.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.17 $ + */ +class JNLPSecurityManager extends SecurityManager { + + // todo: some apps like JDiskReport can close the VM even when + // an exit class is set - fix! + + // todo: create an event dispatch thread for each application, + // so that the context classloader doesn't have to be switched + // to the foreground application (the currently the approach + // since some apps need their classloader as event dispatch + // thread's context classloader). + + // todo: use a custom Permission object to identify the current + // application in an AccessControlContext by setting a side + // effect in its implies method. Use a custom + // AllPermissions-like permission to do this for apps granted + // all permissions (but investigate whether this will nuke + // the all-permission optimizations in the JRE). + + // todo: does not exit app if close button pressed on JFrame + // with CLOSE_ON_EXIT (or whatever) set; if doesn't exit, use an + // WindowListener to catch WindowClosing event, then if exit is + // called immediately afterwards from AWT thread. + + // todo: deny all permissions to applications that should have + // already been 'shut down' by closing their resources and + // interrupt the threads if operating in a shared-VM (exit class + // set). Deny will probably will slow checks down a lot though. + + // todo: weak remember last getProperty application and + // re-install properties if another application calls, or find + // another way for different apps to have different properties + // in java.lang.Sytem with the same names. + + private static String R(String key) { return JNLPRuntime.getMessage(key); } + + /** only class that can exit the JVM, if set */ + private Object exitClass = null; + + /** this exception prevents exiting the JVM */ + private SecurityException closeAppEx = // making here prevents huge stack traces + new SecurityException(JNLPRuntime.getMessage("RShutdown")); + + /** weak list of windows created */ + private WeakList weakWindows = new WeakList(); + + /** weak list of applications corresponding to window list */ + private WeakList weakApplications = new WeakList(); + + /** weak reference to most app who's windows was most recently activated */ + private WeakReference activeApplication = null; + + /** listener installs the app's classloader on the event dispatch thread */ + private ContextUpdater contextListener = new ContextUpdater(); + + /** Sets whether or not exit is allowed (in the context of the plugin, this is always false) */ + private boolean exitAllowed = true; + + private class ContextUpdater extends WindowAdapter implements PrivilegedAction { + private ApplicationInstance app = null; + + public void windowActivated(WindowEvent e) { + app = getApplication(e.getWindow()); + AccessController.doPrivileged(this); + app = null; + } + + public Object run() { + if (app != null) { + Thread.currentThread().setContextClassLoader(app.getClassLoader()); + activeApplication = new WeakReference(app); + } + else + Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader()); + + return null; + } + + public void windowDeactivated(WindowEvent e) { + activeApplication = null; + } + + public void windowClosing(WindowEvent e) { + System.err.println("Disposing window"); + e.getWindow().dispose(); + } + }; + + + /** + * Creates a JNLP SecurityManager. + */ + JNLPSecurityManager() { + // this has the side-effect of creating the Swing shared Frame + // owner. Since no application is running at this time, it is + // not added to any window list when checkTopLevelWindow is + // called for it (and not disposed). + + if (!JNLPRuntime.isHeadless()) + new JWindow().getOwner(); + } + + /** + * Returns whether the exit class is present on the stack, or + * true if no exit class is set. + */ + public boolean isExitClass() { + return isExitClass(getClassContext()); + } + + /** + * Returns whether the exit class is present on the stack, or + * true if no exit class is set. + */ + private boolean isExitClass(Class stack[]) { + if (exitClass == null) + return true; + + for (int i=0; i < stack.length; i++) + if (stack[i] == exitClass) + return true; + + return false; + } + + /** + * Set the exit class, which is the only class that can exit the + * JVM; if not set then any class can exit the JVM. + * + * @param exitClass the exit class + * @throws IllegalStateException if the exit class is already set + */ + public void setExitClass(Class exitClass) throws IllegalStateException { + if (this.exitClass != null) + throw new IllegalStateException(R("RExitTaken")); + + this.exitClass = exitClass; + } + + /** + * Return the current Application, or null if none can be + * determined. + */ + protected ApplicationInstance getApplication() { + return getApplication(getClassContext(), 0); + } + + /** + * Return the application the opened the specified window (only + * call from event dispatch thread). + */ + protected ApplicationInstance getApplication(Window window) { + for (int i = weakWindows.size(); i-->0;) { + Window w = (Window) weakWindows.get(i); + if (w == null) { + weakWindows.remove(i); + weakApplications.remove(i); + } + + if (w == window) + return (ApplicationInstance) weakApplications.get(i); + } + + return null; + } + + /** + * Return the current Application, or null. + */ + protected ApplicationInstance getApplication(Class stack[], int maxDepth) { + if (maxDepth <= 0) + maxDepth = stack.length; + + // this needs to be tightened up + for (int i=0; i < stack.length && i < maxDepth; i++) { + if (stack[i].getClassLoader() instanceof JNLPClassLoader) { + JNLPClassLoader loader = (JNLPClassLoader) stack[i].getClassLoader(); + + if (loader != null && loader.getApplication() != null) { + return loader.getApplication(); + } + } + } + + return null; + } + + /** + * Returns the application's thread group if the application can + * be determined; otherwise returns super.getThreadGroup() + */ + public ThreadGroup getThreadGroup() { + ApplicationInstance app = getApplication(); + if (app == null) + return super.getThreadGroup(); + + return app.getThreadGroup(); + } + + /** + * Throws a SecurityException if the permission is denied, + * otherwise return normally. This method always denies + * permission to change the security manager or policy. + */ + public void checkPermission(Permission perm) { + String name = perm.getName(); + + // Enable this manually -- it'll produce too much output for -verbose + // otherwise. + // if (true) + // System.out.println("Checking permission: " + perm.toString()); + + if (!JNLPRuntime.isWebstartApplication() && + ("setPolicy".equals(name) || "setSecurityManager".equals(name))) + throw new SecurityException(R("RCantReplaceSM")); + + try { + // deny all permissions to stopped applications + // The call to getApplication() below might not work if an + // application hasn't been fully initialized yet. +// if (JNLPRuntime.isDebug()) { +// if (!"getClassLoader".equals(name)) { +// ApplicationInstance app = getApplication(); +// if (app != null && !app.isRunning()) +// throw new SecurityException(R("RDenyStopped")); +// } +// } + + try { + super.checkPermission(perm); + } catch (SecurityException se) { + + //This section is a special case for dealing with SocketPermissions. + if (JNLPRuntime.isDebug()) + System.err.println("Requesting permission: " + perm.toString()); + + //Change this SocketPermission's action to connect and accept + //(and resolve). This is to avoid asking for connect permission + //on every address resolve. + Permission tmpPerm; + if (perm instanceof SocketPermission) { + tmpPerm = new SocketPermission(perm.getName(), + SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION); + + // before proceeding, check if we are trying to connect to same origin + ApplicationInstance app = getApplication(); + JNLPFile file = app.getJNLPFile(); + + String srcHost = file.getSourceLocation().getAuthority(); + String destHost = name; + + // host = abc.xyz.com or abc.xyz.com:<port> + if (destHost.indexOf(':') >= 0) + destHost = destHost.substring(0, destHost.indexOf(':')); + + // host = abc.xyz.com + String[] hostComponents = destHost.split("\\."); + + int length = hostComponents.length; + if (length >= 2) { + + // address is in xxx.xxx.xxx format + destHost = hostComponents[length -2] + "." + hostComponents[length -1]; + + // host = xyz.com i.e. origin + boolean isDestHostName = false; + + // make sure that it is not an ip address + try { + Integer.parseInt(hostComponents[length -1]); + } catch (NumberFormatException e) { + isDestHostName = true; + } + + if (isDestHostName) { + // okay, destination is hostname. Now figure out if it is a subset of origin + if (srcHost.endsWith(destHost)) { + addPermission(tmpPerm); + return; + } + } + } + + } else + tmpPerm = perm; + + //askPermission will only prompt the user on SocketPermission + //meaning we're denying all other SecurityExceptions that may arise. + if (askPermission(tmpPerm)) { + addPermission(tmpPerm); + //return quietly. + } else { + throw se; + } + } + } + catch (SecurityException ex) { + if (JNLPRuntime.isDebug()) { + System.out.println("Denying permission: "+perm); + } + throw ex; + } + } + + /** + * Asks the user whether or not to grant permission. + * @param perm the permission to be granted + * @return true if the permission was granted, false otherwise. + */ + private boolean askPermission(Permission perm) { + + ApplicationInstance app = getApplication(); + if (app != null && !app.isSigned()) { + if (perm instanceof SocketPermission + && ServiceUtil.checkAccess(SecurityWarningDialog.AccessType.NETWORK, perm.getName())) { + return true; + } + } + + return false; + } + + /** + * Adds a permission to the JNLPClassLoader. + * @param perm the permission to add to the JNLPClassLoader + */ + private void addPermission(Permission perm) { + if (JNLPRuntime.getApplication().getClassLoader() instanceof JNLPClassLoader) { + + JNLPClassLoader cl = (JNLPClassLoader) JNLPRuntime.getApplication().getClassLoader(); + cl.addPermission(perm); + if (JNLPRuntime.isDebug()) { + if (cl.getPermissions(null).implies(perm)) + System.err.println("Added permission: " + perm.toString()); + else + System.err.println("Unable to add permission: " + perm.toString()); + } + } else { + if (JNLPRuntime.isDebug()) + System.err.println("Unable to add permission: " + perm + ", classloader not JNLP."); + } + } + + /** + * Checks whether the window can be displayed without an applet + * warning banner, and adds the window to the list of windows to + * be disposed when the calling application exits. + */ + public boolean checkTopLevelWindow(Object window) { + ApplicationInstance app = getApplication(); + + // remember window -> application mapping for focus, close on exit + if (app != null && window instanceof Window) { + Window w = (Window) window; + + if (JNLPRuntime.isDebug()) + System.err.println("SM: app: "+app.getTitle()+" is adding a window: "+window); + + weakWindows.add(window); // for mapping window -> app + weakApplications.add(app); + + w.addWindowListener(contextListener); // for dynamic context classloader + + app.addWindow(w); + } + + // change coffee cup to netx for default icon + if (window instanceof Window) + for (Window w = (Window)window; w != null; w = w.getOwner()) + if (window instanceof Frame) + ((Frame)window).setIconImage(JNLPRuntime.getWindowIcon()); + + // todo: set awt.appletWarning to custom message + // todo: logo on with glass pane on JFrame/JWindow? + + return super.checkTopLevelWindow(window); + } + + /** + * Checks whether the caller can exit the system. This method + * identifies whether the caller is a real call to Runtime.exec + * and has special behavior when returning from this method + * would exit the JVM and an exit class is set: if the caller is + * not the exit class then the calling application will be + * stopped and its resources destroyed (when possible), and an + * exception will be thrown to prevent the JVM from shutting + * down.<p> + * + * Calls not from Runtime.exit or with no exit class set will + * behave normally, and the exit class can always exit the JVM. + */ + public void checkExit(int status) { + + // applets are not allowed to exit, but the plugin main class (primordial loader) is + Class stack[] = getClassContext(); + if (!exitAllowed) { + for (int i=0; i < stack.length; i++) + if (stack[i].getClassLoader() != null) + throw new AccessControlException("Applets may not call System.exit()"); + } + + super.checkExit(status); + + boolean realCall = (stack[1] == Runtime.class); + + if (isExitClass(stack)) // either exitClass called or no exitClass set + return; // to Runtime.exit or fake call to see if app has permission + + // not called from Runtime.exit() + if (!realCall) { + // apps that can't exit should think they can exit normally + super.checkExit(status); + return; + } + + // but when they really call, stop only the app instead of the JVM + ApplicationInstance app = getApplication(stack, 0); + if (app == null) { + // should check caller to make sure it is JFrame.close or + // other known System.exit call + if (activeApplication != null) + app = (ApplicationInstance) activeApplication.get(); + + if (app == null) + throw new SecurityException(R("RExitNoApp")); + } + + app.destroy(); + + throw closeAppEx; + } + + protected void disableExit() { + exitAllowed = false; + } + +} + +
--- a/rt/net/sourceforge/jnlp/services/XBasicService.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/services/XBasicService.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,234 +1,234 @@ -// Copyright (C) 2001 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.services; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.jnlp.BasicService; -import javax.swing.JOptionPane; -import javax.swing.JPanel; - -import net.sourceforge.jnlp.InformationDesc; -import net.sourceforge.jnlp.JARDesc; -import net.sourceforge.jnlp.JNLPFile; -import net.sourceforge.jnlp.Launcher; -import net.sourceforge.jnlp.runtime.ApplicationInstance; -import net.sourceforge.jnlp.runtime.JNLPRuntime; -import net.sourceforge.jnlp.util.PropertiesFile; - -/** - * The BasicService JNLP service. - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.10 $ - */ -class XBasicService implements BasicService { - - /** command used to exec the native browser */ - private String command = null; - - /** whether the command was loaded / prompted for */ - private boolean initialized = false; - - - protected XBasicService() { - } - - /** - * Returns the codebase of the application, applet, or - * installer. If the codebase was not specified in the JNLP - * element then the main JAR's location is returned. If no main - * JAR was specified then the location of the JAR containing the - * main class is returned. - */ - public URL getCodeBase() { - ApplicationInstance app = JNLPRuntime.getApplication(); - - if (app != null) { - JNLPFile file = app.getJNLPFile(); - - // return the codebase. - if (file.getCodeBase() != null) - return file.getCodeBase(); - - // else return the main JAR's URL. - JARDesc mainJar = file.getResources().getMainJAR(); - if (mainJar != null) - return mainJar.getLocation(); - - // else find JAR where main class was defined. - // - // JNLPFile file = app.getJNLPFile(); - // String mainClass = file.getLaunchInfo().getMainClass()+".class"; - // URL jarUrl = app.getClassLoader().getResource(mainClass); - // go through list of JARDesc to find one matching jarUrl - } - - return null; - } - - /** - * Return true if the Environment is Offline - */ - public boolean isOffline() { - - URL url = findFirstURLFromJNLPFile(); - - try { - url.openConnection().getInputStream().close(); - return false; - } catch (IOException exception) { - return true; - } - } - - /** - * Return the first URL from the jnlp file - * Or a default URL if no url found in JNLP file - */ - private URL findFirstURLFromJNLPFile() { - - ApplicationInstance app = JNLPRuntime.getApplication(); - - if (app != null) { - JNLPFile jnlpFile = app.getJNLPFile(); - - URL sourceURL = jnlpFile.getSourceLocation(); - if (sourceURL != null) { - return sourceURL; - } - - URL codeBaseURL = jnlpFile.getCodeBase(); - if (codeBaseURL != null) { - return codeBaseURL; - } - - InformationDesc informationDesc = jnlpFile.getInformation(); - URL homePage = informationDesc.getHomepage(); - if (homePage != null) { - return homePage; - } - - JARDesc[] jarDescs = jnlpFile.getResources().getJARs(); - for (JARDesc jarDesc: jarDescs) { - return jarDesc.getLocation(); - } - } - - // this section is only reached if the jnlp file has no jars. - // that doesnt seem very likely. - URL arbitraryURL; - try { - arbitraryURL = new URL("http://icedtea.classpath.org"); - } catch (MalformedURLException malformedURL) { - throw new RuntimeException(malformedURL); - } - - return arbitraryURL; - } - - /** - * Return true if a Web Browser is Supported - */ - public boolean isWebBrowserSupported() { - initialize(); - - return command != null; - } - - /** - * Show a document. - * - * @return whether the document was opened - */ - public boolean showDocument(URL url) { - initialize(); - - if (url.toString().endsWith(".jnlp")) { - try { - new Launcher().launchExternal(url); - return true; - } - catch (Exception ex) { - return false; - } - } - - if (command != null) { - try { - // this is bogus because the command may require options; - // should use a StreamTokenizer or similar to get tokens - // outside of quotes. - Runtime.getRuntime().exec(command + url.toString()); - //Runtime.getRuntime().exec(new String[]{command,url.toString()}); - - return true; - } - catch(IOException ex){ - if (JNLPRuntime.isDebug()) - ex.printStackTrace(); - } - } - - return false; - } - - private void initialize() { - if (initialized) - return; - initialized = true; - - if(isWindows()) { - command = "rundll32 url.dll,FileProtocolHandler "; - } - else { - PropertiesFile props = JNLPRuntime.getProperties(); - command = props.getProperty("browser.command"); - - if(command == null) { // prompt & store - command = promptForCommand(null); - - if(command != null) { - props.setProperty("browser.command", command); - props.store(); - } - } - } - } - - private boolean isWindows() { - String os = System.getProperty("os.name"); - if(os != null && os.startsWith("Windows")) - return true; - else - return false; - } - - private String promptForCommand(String cmd) { - return JOptionPane.showInputDialog(new JPanel(), - "Browser Location:", - "Specify Browser Location", - JOptionPane.PLAIN_MESSAGE - ); - } - -} - - +// Copyright (C) 2001 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.services; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.jnlp.BasicService; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import net.sourceforge.jnlp.InformationDesc; +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.Launcher; +import net.sourceforge.jnlp.runtime.ApplicationInstance; +import net.sourceforge.jnlp.runtime.JNLPRuntime; +import net.sourceforge.jnlp.util.PropertiesFile; + +/** + * The BasicService JNLP service. + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.10 $ + */ +class XBasicService implements BasicService { + + /** command used to exec the native browser */ + private String command = null; + + /** whether the command was loaded / prompted for */ + private boolean initialized = false; + + + protected XBasicService() { + } + + /** + * Returns the codebase of the application, applet, or + * installer. If the codebase was not specified in the JNLP + * element then the main JAR's location is returned. If no main + * JAR was specified then the location of the JAR containing the + * main class is returned. + */ + public URL getCodeBase() { + ApplicationInstance app = JNLPRuntime.getApplication(); + + if (app != null) { + JNLPFile file = app.getJNLPFile(); + + // return the codebase. + if (file.getCodeBase() != null) + return file.getCodeBase(); + + // else return the main JAR's URL. + JARDesc mainJar = file.getResources().getMainJAR(); + if (mainJar != null) + return mainJar.getLocation(); + + // else find JAR where main class was defined. + // + // JNLPFile file = app.getJNLPFile(); + // String mainClass = file.getLaunchInfo().getMainClass()+".class"; + // URL jarUrl = app.getClassLoader().getResource(mainClass); + // go through list of JARDesc to find one matching jarUrl + } + + return null; + } + + /** + * Return true if the Environment is Offline + */ + public boolean isOffline() { + + URL url = findFirstURLFromJNLPFile(); + + try { + url.openConnection().getInputStream().close(); + return false; + } catch (IOException exception) { + return true; + } + } + + /** + * Return the first URL from the jnlp file + * Or a default URL if no url found in JNLP file + */ + private URL findFirstURLFromJNLPFile() { + + ApplicationInstance app = JNLPRuntime.getApplication(); + + if (app != null) { + JNLPFile jnlpFile = app.getJNLPFile(); + + URL sourceURL = jnlpFile.getSourceLocation(); + if (sourceURL != null) { + return sourceURL; + } + + URL codeBaseURL = jnlpFile.getCodeBase(); + if (codeBaseURL != null) { + return codeBaseURL; + } + + InformationDesc informationDesc = jnlpFile.getInformation(); + URL homePage = informationDesc.getHomepage(); + if (homePage != null) { + return homePage; + } + + JARDesc[] jarDescs = jnlpFile.getResources().getJARs(); + for (JARDesc jarDesc: jarDescs) { + return jarDesc.getLocation(); + } + } + + // this section is only reached if the jnlp file has no jars. + // that doesnt seem very likely. + URL arbitraryURL; + try { + arbitraryURL = new URL("http://icedtea.classpath.org"); + } catch (MalformedURLException malformedURL) { + throw new RuntimeException(malformedURL); + } + + return arbitraryURL; + } + + /** + * Return true if a Web Browser is Supported + */ + public boolean isWebBrowserSupported() { + initialize(); + + return command != null; + } + + /** + * Show a document. + * + * @return whether the document was opened + */ + public boolean showDocument(URL url) { + initialize(); + + if (url.toString().endsWith(".jnlp")) { + try { + new Launcher().launchExternal(url); + return true; + } + catch (Exception ex) { + return false; + } + } + + if (command != null) { + try { + // this is bogus because the command may require options; + // should use a StreamTokenizer or similar to get tokens + // outside of quotes. + Runtime.getRuntime().exec(command + url.toString()); + //Runtime.getRuntime().exec(new String[]{command,url.toString()}); + + return true; + } + catch(IOException ex){ + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + } + + return false; + } + + private void initialize() { + if (initialized) + return; + initialized = true; + + if(isWindows()) { + command = "rundll32 url.dll,FileProtocolHandler "; + } + else { + PropertiesFile props = JNLPRuntime.getProperties(); + command = props.getProperty("browser.command"); + + if(command == null) { // prompt & store + command = promptForCommand(null); + + if(command != null) { + props.setProperty("browser.command", command); + props.store(); + } + } + } + } + + private boolean isWindows() { + String os = System.getProperty("os.name"); + if(os != null && os.startsWith("Windows")) + return true; + else + return false; + } + + private String promptForCommand(String cmd) { + return JOptionPane.showInputDialog(new JPanel(), + "Browser Location:", + "Specify Browser Location", + JOptionPane.PLAIN_MESSAGE + ); + } + +} + +
--- a/rt/net/sourceforge/jnlp/util/PropertiesFile.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/util/PropertiesFile.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,148 +1,148 @@ -// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.util; - -import java.io.*; -import java.net.*; -import java.util.*; - -import net.sourceforge.jnlp.*; - -/** - * A properties object backed by a specified file without throwing - * exceptions. The properties are automatically loaded from the - * file when the first property is requested, but the save method - * must be called before changes are saved to the file.<p> - * - * This class does not report IO exceptions.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.4 $ - */ -public class PropertiesFile extends Properties { - - /** the file to save to */ - File file; - - /** the header string */ - String header = "netx file"; - - /** lazy loaded on getProperty */ - boolean loaded = false; - - - /** - * Create a properties object backed by the specified file. - * - * @param file the file to save and load to - */ - public PropertiesFile(File file) { - this.file = file; - } - - /** - * Create a properties object backed by the specified file. - * - * @param file the file to save and load to - * @param header the file header - */ - public PropertiesFile(File file, String header) { - this.file = file; - this.header = header; - } - - /** - * Returns the value of the specified key, or null if the key - * does not exist. - */ - public String getProperty(String key) { - if (!loaded) - load(); - - return super.getProperty(key); - } - - /** - * Returns the value of the specified key, or the default value - * if the key does not exist. - */ - public String getProperty(String key, String defaultValue) { - if (!loaded) - load(); - - return super.getProperty(key, defaultValue); - } - - /** - * Sets the value for the specified key. - * - * @return the previous value - */ - public Object setProperty(String key, String value) { - if (!loaded) - load(); - - return super.setProperty(key, value); - } - - /** - * Returns the file backing this properties object. - */ - public File getStoreFile() { - return file; - } - - /** - * Ensures that the file backing these properties has been - * loaded; call this method before calling any method defined by - * a superclass. - */ - public void load() { - loaded = true; - - try { - if (!file.exists()) - return; - - InputStream s = new FileInputStream(file); - load(s); - } - catch (IOException ex) { - // eat - } - } - - /** - * Saves the properties to the file. - */ - public void store() { - if (!loaded) - return; // nothing could have changed so save unnecessary load/save - - try { - OutputStream s = new FileOutputStream(file); - store(s, header); - } - catch (IOException ex) { - // eat - } - } - -} - - +// Copyright (C) 2001-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.util; + +import java.io.*; +import java.net.*; +import java.util.*; + +import net.sourceforge.jnlp.*; + +/** + * A properties object backed by a specified file without throwing + * exceptions. The properties are automatically loaded from the + * file when the first property is requested, but the save method + * must be called before changes are saved to the file.<p> + * + * This class does not report IO exceptions.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.4 $ + */ +public class PropertiesFile extends Properties { + + /** the file to save to */ + File file; + + /** the header string */ + String header = "netx file"; + + /** lazy loaded on getProperty */ + boolean loaded = false; + + + /** + * Create a properties object backed by the specified file. + * + * @param file the file to save and load to + */ + public PropertiesFile(File file) { + this.file = file; + } + + /** + * Create a properties object backed by the specified file. + * + * @param file the file to save and load to + * @param header the file header + */ + public PropertiesFile(File file, String header) { + this.file = file; + this.header = header; + } + + /** + * Returns the value of the specified key, or null if the key + * does not exist. + */ + public String getProperty(String key) { + if (!loaded) + load(); + + return super.getProperty(key); + } + + /** + * Returns the value of the specified key, or the default value + * if the key does not exist. + */ + public String getProperty(String key, String defaultValue) { + if (!loaded) + load(); + + return super.getProperty(key, defaultValue); + } + + /** + * Sets the value for the specified key. + * + * @return the previous value + */ + public Object setProperty(String key, String value) { + if (!loaded) + load(); + + return super.setProperty(key, value); + } + + /** + * Returns the file backing this properties object. + */ + public File getStoreFile() { + return file; + } + + /** + * Ensures that the file backing these properties has been + * loaded; call this method before calling any method defined by + * a superclass. + */ + public void load() { + loaded = true; + + try { + if (!file.exists()) + return; + + InputStream s = new FileInputStream(file); + load(s); + } + catch (IOException ex) { + // eat + } + } + + /** + * Saves the properties to the file. + */ + public void store() { + if (!loaded) + return; // nothing could have changed so save unnecessary load/save + + try { + OutputStream s = new FileOutputStream(file); + store(s, header); + } + catch (IOException ex) { + // eat + } + } + +} + +
--- a/rt/net/sourceforge/jnlp/util/Reflect.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/util/Reflect.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,148 +1,148 @@ -// Copyright (C) 2003 Jon A. Maxwell (JAM) -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -package net.sourceforge.jnlp.util; - -import java.util.*; -import java.lang.reflect.*; - - -/** - * Provides simply, convenient methods to invoke methods by - * name. This class is used to consolidate reflection needed to - * access methods specific to Sun's JVM or to remain backward - * compatible while supporting method in newer JVMs.<p> - * - * Most methods of this class invoke the first method on the - * specified object that matches the name and number of - * parameters. The type of the parameters are not considered, so - * do not attempt to use this class to invoke overloaded - * methods.<p> - * - * Instances of this class are not synchronized.<p> - * - * @author <a href="mailto:jon.maxwell@acm.org">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.1 $ - */ -public class Reflect { - - // todo: check non-null parameter types, try to send to proper - // method if overloaded ones exist on the target object - - // todo: optimize slightly using hashtable of Methods - - private boolean accessible; - - private static Object zero[] = new Object[0]; - - - /** - * Create a new Reflect instance. - */ - public Reflect() { - // - } - - /** - * Create a new Reflect instance. - * - * @param accessible whether to bypass access permissions - */ - public Reflect(boolean accessible) { - this.accessible = accessible; - } - - /** - * Invoke a zero-parameter static method by name. - */ - public Object invokeStatic(String className, String method) { - return invokeStatic(className, method, zero); - } - - /** - * Invoke the static method using the specified parameters. - */ - public Object invokeStatic(String className, String method, Object args[]) { - try { - Class c = Class.forName(className, true, Reflect.class.getClassLoader()); - - Method m = getMethod(c, method, args); - if (m.isAccessible() != accessible) - m.setAccessible(accessible); - - return m.invoke(null, args); - } - catch (Exception ex) { // eat - return null; - } - } - - /** - * Invoke a zero-parameter method by name on the specified - * object. - */ - public Object invoke(Object object, String method) { - return invoke(object, method, zero); - } - - /** - * Invoke a method by name with the specified parameters. - * - * @return the result of the method, or null on exception. - */ - public Object invoke(Object object, String method, Object args[]) { - try { - Method m = getMethod(object.getClass(), method, args); - if (m.isAccessible() != accessible) - m.setAccessible(accessible); - - return m.invoke(object, args); - } - catch (Exception ex) { // eat - ex.printStackTrace(); - return null; - } - } - - /** - * Return the Method matching the specified name and number of - * arguments. - */ - public Method getMethod(Class type, String method, Object args[]) { - try { - for (Class c = type; c != null; c = c.getSuperclass()) { - Method methods[] = c.getMethods(); - - for (int i=0; i < methods.length; i++) { - if (methods[i].getName().equals(method)) { - Class parameters[] = methods[i].getParameterTypes(); - - if (parameters.length == args.length) - return methods[i]; - } - } - } - } - catch (Exception ex) { // eat - ex.printStackTrace(); - } - - return null; - } - -} - - +// Copyright (C) 2003 Jon A. Maxwell (JAM) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package net.sourceforge.jnlp.util; + +import java.util.*; +import java.lang.reflect.*; + + +/** + * Provides simply, convenient methods to invoke methods by + * name. This class is used to consolidate reflection needed to + * access methods specific to Sun's JVM or to remain backward + * compatible while supporting method in newer JVMs.<p> + * + * Most methods of this class invoke the first method on the + * specified object that matches the name and number of + * parameters. The type of the parameters are not considered, so + * do not attempt to use this class to invoke overloaded + * methods.<p> + * + * Instances of this class are not synchronized.<p> + * + * @author <a href="mailto:jon.maxwell@acm.org">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.1 $ + */ +public class Reflect { + + // todo: check non-null parameter types, try to send to proper + // method if overloaded ones exist on the target object + + // todo: optimize slightly using hashtable of Methods + + private boolean accessible; + + private static Object zero[] = new Object[0]; + + + /** + * Create a new Reflect instance. + */ + public Reflect() { + // + } + + /** + * Create a new Reflect instance. + * + * @param accessible whether to bypass access permissions + */ + public Reflect(boolean accessible) { + this.accessible = accessible; + } + + /** + * Invoke a zero-parameter static method by name. + */ + public Object invokeStatic(String className, String method) { + return invokeStatic(className, method, zero); + } + + /** + * Invoke the static method using the specified parameters. + */ + public Object invokeStatic(String className, String method, Object args[]) { + try { + Class c = Class.forName(className, true, Reflect.class.getClassLoader()); + + Method m = getMethod(c, method, args); + if (m.isAccessible() != accessible) + m.setAccessible(accessible); + + return m.invoke(null, args); + } + catch (Exception ex) { // eat + return null; + } + } + + /** + * Invoke a zero-parameter method by name on the specified + * object. + */ + public Object invoke(Object object, String method) { + return invoke(object, method, zero); + } + + /** + * Invoke a method by name with the specified parameters. + * + * @return the result of the method, or null on exception. + */ + public Object invoke(Object object, String method, Object args[]) { + try { + Method m = getMethod(object.getClass(), method, args); + if (m.isAccessible() != accessible) + m.setAccessible(accessible); + + return m.invoke(object, args); + } + catch (Exception ex) { // eat + ex.printStackTrace(); + return null; + } + } + + /** + * Return the Method matching the specified name and number of + * arguments. + */ + public Method getMethod(Class type, String method, Object args[]) { + try { + for (Class c = type; c != null; c = c.getSuperclass()) { + Method methods[] = c.getMethods(); + + for (int i=0; i < methods.length; i++) { + if (methods[i].getName().equals(method)) { + Class parameters[] = methods[i].getParameterTypes(); + + if (parameters.length == args.length) + return methods[i]; + } + } + } + } + catch (Exception ex) { // eat + ex.printStackTrace(); + } + + return null; + } + +} + +
--- a/rt/net/sourceforge/jnlp/util/WeakList.java Tue Aug 04 11:34:49 2009 -0400 +++ b/rt/net/sourceforge/jnlp/util/WeakList.java Tue Aug 04 17:39:11 2009 +0100 @@ -1,128 +1,128 @@ -// Copyright (C) 2002-2003 Jon A. Maxwell (JAM) -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; either -// version 2.1 of the License, or (at your option) any later version. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - - -package net.sourceforge.jnlp.util; - -import java.lang.ref.*; -import java.util.*; - - -/** - * This list stores objects automatically using weak references. - * Objects are added and removed from the list as normal, but may - * turn to null at any point (ie, indexOf(x) followed by get(x) - * may return null). The weak references are only removed when - * the trimToSize method is called so that the indices remain - * constant otherwise.<p> - * - * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author - * @version $Revision: 1.3 $ - */ -public class WeakList extends AbstractList { - - /* list of weak references */ - private ArrayList refs = new ArrayList(); - - - /** - * Create a weak random-access list. - */ - public WeakList() { - } - - /** - * Extract the hard reference out of a weak reference. - */ - private Object deref(Object o) { - if (o != null && o instanceof WeakReference) - return ((WeakReference)o).get(); - else - return null; - } - - /** - * Returns the object at the specified index, or null if the - * object has been collected. - */ - public Object get(int index) { - return deref(refs.get(index)); - } - - /** - * Returns the size of the list, including already collected - * objects. - */ - public int size() { - return refs.size(); - } - - /** - * Sets the object at the specified position and returns the - * previous object at that position or null if it was already - * collected. - */ - public Object set(int index, Object element) { - return deref(refs.set(index, new WeakReference(element))); - } - - /** - * Inserts the object at the specified position in the list. - * Automatically creates a weak reference to the object. - */ - public void add(int index, Object element) { - refs.add(index, new WeakReference(element)); - } - - /** - * Removes the object at the specified position and returns it - * or returns null if it was already collected. - */ - public Object remove(int index) { - return deref(refs.remove(index)); - } - - /** - * Returns a list of hard references to the objects. The - * returned list does not include the collected elements, so its - * indices do not necessarily correlate with those of this list. - */ - public List hardList() { - List result = new ArrayList(); - - for (int i=0; i < size(); i++) { - Object tmp = get(i); - - if (tmp != null) - result.add(tmp); - } - - return result; - } - - /** - * Compacts the list by removing references to collected - * objects. - */ - public void trimToSize() { - for (int i=size(); i-->0;) - if (get(i)==null) - remove(i); - } - -} - - +// Copyright (C) 2002-2003 Jon A. Maxwell (JAM) +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.util; + +import java.lang.ref.*; +import java.util.*; + + +/** + * This list stores objects automatically using weak references. + * Objects are added and removed from the list as normal, but may + * turn to null at any point (ie, indexOf(x) followed by get(x) + * may return null). The weak references are only removed when + * the trimToSize method is called so that the indices remain + * constant otherwise.<p> + * + * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.3 $ + */ +public class WeakList extends AbstractList { + + /* list of weak references */ + private ArrayList refs = new ArrayList(); + + + /** + * Create a weak random-access list. + */ + public WeakList() { + } + + /** + * Extract the hard reference out of a weak reference. + */ + private Object deref(Object o) { + if (o != null && o instanceof WeakReference) + return ((WeakReference)o).get(); + else + return null; + } + + /** + * Returns the object at the specified index, or null if the + * object has been collected. + */ + public Object get(int index) { + return deref(refs.get(index)); + } + + /** + * Returns the size of the list, including already collected + * objects. + */ + public int size() { + return refs.size(); + } + + /** + * Sets the object at the specified position and returns the + * previous object at that position or null if it was already + * collected. + */ + public Object set(int index, Object element) { + return deref(refs.set(index, new WeakReference(element))); + } + + /** + * Inserts the object at the specified position in the list. + * Automatically creates a weak reference to the object. + */ + public void add(int index, Object element) { + refs.add(index, new WeakReference(element)); + } + + /** + * Removes the object at the specified position and returns it + * or returns null if it was already collected. + */ + public Object remove(int index) { + return deref(refs.remove(index)); + } + + /** + * Returns a list of hard references to the objects. The + * returned list does not include the collected elements, so its + * indices do not necessarily correlate with those of this list. + */ + public List hardList() { + List result = new ArrayList(); + + for (int i=0; i < size(); i++) { + Object tmp = get(i); + + if (tmp != null) + result.add(tmp); + } + + return result; + } + + /** + * Compacts the list by removing references to collected + * objects. + */ + public void trimToSize() { + for (int i=size(); i-->0;) + if (get(i)==null) + remove(i); + } + +} + +
--- a/test/jtreg/com/sun/javatest/exec/FileTable.java Tue Aug 04 11:34:49 2009 -0400 +++ b/test/jtreg/com/sun/javatest/exec/FileTable.java Tue Aug 04 17:39:11 2009 +0100 @@ -24,76 +24,76 @@ * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ -package com.sun.javatest.exec; - -import com.sun.javatest.tool.UIFactory; -import java.awt.Component; -import javax.swing.Icon; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; -import javax.swing.table.DefaultTableCellRenderer; - -public class FileTable extends JTable { - - - - public FileTable(FileSystemTableModel model, UIFactory uif) { - super(model); - this.uif = uif; - setCellSelectionEnabled(false); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - setRowSelectionAllowed(true); - setShowGrid(false); - getColumnModel().getColumn(0).setCellRenderer(new IconRenderer()); - } - - private class IconRenderer extends DefaultTableCellRenderer { - - { - up = uif.createIcon("upper"); - dir = uif.createIcon("folder"); - } - - public Component getTableCellRendererComponent(JTable table, Object value, - boolean isSelected, boolean hasFocus, int row, int column) { - - setFont(table.getFont()); - setIcon(null); - setText(""); - - if (isSelected) { - super.setForeground(table.getSelectionForeground()); - super.setBackground(table.getSelectionBackground()); - } else { - super.setForeground(table.getForeground()); - super.setBackground(table.getBackground()); - } - - if (value instanceof FileTableNode) { - FileTableNode fn = (FileTableNode) value; - if (fn.getMode() != 'f') { - if (fn.getMode() == 'u') { - setIcon(up); - return this; - } - if (fn.getMode() == 'd') { - setIcon(dir); - setText(fn.toString()); - return this; - } - } - } - return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - } - - private Icon up; - private Icon dir; - - } - - - - private UIFactory uif; - - -} +package com.sun.javatest.exec; + +import com.sun.javatest.tool.UIFactory; +import java.awt.Component; +import javax.swing.Icon; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.table.DefaultTableCellRenderer; + +public class FileTable extends JTable { + + + + public FileTable(FileSystemTableModel model, UIFactory uif) { + super(model); + this.uif = uif; + setCellSelectionEnabled(false); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + setRowSelectionAllowed(true); + setShowGrid(false); + getColumnModel().getColumn(0).setCellRenderer(new IconRenderer()); + } + + private class IconRenderer extends DefaultTableCellRenderer { + + { + up = uif.createIcon("upper"); + dir = uif.createIcon("folder"); + } + + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + + setFont(table.getFont()); + setIcon(null); + setText(""); + + if (isSelected) { + super.setForeground(table.getSelectionForeground()); + super.setBackground(table.getSelectionBackground()); + } else { + super.setForeground(table.getForeground()); + super.setBackground(table.getBackground()); + } + + if (value instanceof FileTableNode) { + FileTableNode fn = (FileTableNode) value; + if (fn.getMode() != 'f') { + if (fn.getMode() == 'u') { + setIcon(up); + return this; + } + if (fn.getMode() == 'd') { + setIcon(dir); + setText(fn.toString()); + return this; + } + } + } + return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } + + private Icon up; + private Icon dir; + + } + + + + private UIFactory uif; + + +}
--- a/test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java Tue Aug 04 11:34:49 2009 -0400 +++ b/test/jtreg/com/sun/javatest/mrep/ConflictResolutionDialog.java Tue Aug 04 17:39:11 2009 +0100 @@ -24,240 +24,240 @@ * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ -package com.sun.javatest.mrep; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Container; -import java.awt.FlowLayout; -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.ListSelectionModel; -import javax.swing.border.Border; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import com.sun.javatest.tool.UIFactory; -import javax.swing.JOptionPane; - -public class ConflictResolutionDialog extends JDialog { - - private String resolveButtonStr = "resolve"; - private String cancelButtonStr = "cancel"; - private String useMostRecentCheckBoxStr = "useMost"; - - private JCheckBox preferredReportCheckBox; - private JCheckBox useMostRecentCheckBox; - - private JButton resolveButton; - private JButton cancelButton; - - private DefaultListModel listModel; - private JList list; - - private int selectedIndex; - private boolean bPreferredReport; - private boolean bUseMostRecent; - - private UIFactory uif; - - private boolean cancel = false; - - - public ConflictResolutionDialog(JFrame parent, String testName, String[] reportsList, boolean bPreferredSet, UIFactory uif) { - super(parent, true); - this.uif = uif; - - setName("conflict"); - setTitle(uif.getI18NString("conflict.name")); - setResizable(false); - - Container cp = getContentPane(); - cp.setLayout(new BorderLayout()); - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - ConflictResolutionActionListener conflictResolutionListener = new ConflictResolutionActionListener(); - - JLabel text = uif.createLabel("conflict.text"); - text.setText(text.getText() + " " + testName); - - - text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); - JPanel textPanel = uif.createPanel("conflict.text.panel", new FlowLayout(FlowLayout.CENTER)); - textPanel.add(text); - - Box vBox = Box.createVerticalBox(); - - JLabel chooseText =uif.createLabel("conflict.chooseText"); - //text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); - JPanel chooseTextPanel = uif.createPanel("conflict.choosePanel", new FlowLayout(FlowLayout.CENTER)); - chooseTextPanel.add(chooseText); - - // Build list box - listModel=new DefaultListModel(); - for (int i=0; i< reportsList.length; i++) { - listModel.addElement(reportsList[i]); - } - list = uif.createList("conflict.list", listModel); - list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - ReportsListSelectionListener rl = new ReportsListSelectionListener(); - list.addListSelectionListener(rl); - Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK); - list.setBorder(brd); - JScrollPane scrollPane = uif.createScrollPane(list); - Box hBox = Box.createHorizontalBox(); - //hBox.add(Box.createHorizontalStrut(20)); - hBox.add(scrollPane); - //hBox.add(Box.createHorizontalStrut(20)); - - preferredReportCheckBox = uif.createCheckBox("conflict.preffered"); - preferredReportCheckBox.setMnemonic(0); - preferredReportCheckBox.setEnabled(false); - JPanel preferredReportPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); - preferredReportPanel.add(preferredReportCheckBox); - - - useMostRecentCheckBox = uif.createCheckBox("conflict.most.recent"); - useMostRecentCheckBox.setMnemonic(1); - useMostRecentCheckBox.addActionListener(conflictResolutionListener); - useMostRecentCheckBox.setActionCommand(useMostRecentCheckBoxStr); - JPanel useRecentPanel = uif.createPanel("conflict.recent", new FlowLayout(FlowLayout.LEFT)); - useRecentPanel.add(useMostRecentCheckBox); - - - - vBox.setBorder(BorderFactory.createEmptyBorder(0,20,0,20)); - vBox.add(chooseTextPanel); - vBox.add(hBox); - // if preferred report was already chosen, in previous dialogs, it should not be seen here - if (!bPreferredSet) { - vBox.add(preferredReportPanel); - } - vBox.add(useRecentPanel); - - - // Build control buttons - JPanel controlButtonsPanel = uif.createPanel("conflict.control", new FlowLayout(FlowLayout.CENTER)); - JPanel p2 = new JPanel(); - p2.setLayout(new GridLayout(1,0,5,5)); - - resolveButton = uif.createButton("conflict.resolve"); - resolveButton.setMnemonic(0); - resolveButton.addActionListener(conflictResolutionListener); - resolveButton.setActionCommand(resolveButtonStr); - resolveButton.setEnabled(false); - - cancelButton = uif.createButton("conflict.cancel"); - cancelButton.addActionListener(conflictResolutionListener); - cancelButton.setActionCommand(cancelButtonStr); - - p2.add(resolveButton); - p2.add(cancelButton); - - controlButtonsPanel.add(p2); - controlButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20,0,0,0)); - - cp.add(textPanel, BorderLayout.NORTH); - cp.add(vBox, BorderLayout.CENTER); - cp.add(controlButtonsPanel, BorderLayout.SOUTH); - - pack(); - setLocationRelativeTo(parent); - } - - public int getSelectedIndex() { - return selectedIndex; - } - public boolean getPreferredReport() { - return bPreferredReport; - } - - public boolean getUseMostRecent() { - return bUseMostRecent; - } - - public boolean wasCanceled() { - return cancel; - } - - class CancelException extends Exception { - - } - - - class ConflictResolutionActionListener implements ActionListener { - - public void actionPerformed(ActionEvent e) { - - String cmd = e.getActionCommand(); - if (cmd.equals(cancelButtonStr)) { - - if (uif.showYesNoDialog("conflict.areyousure") != JOptionPane.YES_OPTION) - return; - - ConflictResolutionDialog.this.cancel = true; - ConflictResolutionDialog.this.dispose(); - } else if (cmd.equals(resolveButtonStr)) { - bUseMostRecent = useMostRecentCheckBox.isSelected(); - bPreferredReport = preferredReportCheckBox.isSelected(); - selectedIndex = list.getSelectedIndex(); - ConflictResolutionDialog.this.dispose(); - - } else if (cmd.equals(useMostRecentCheckBoxStr)) { - if ((list.getSelectedValues().length == 0) && - (!useMostRecentCheckBox.isSelected())) { - resolveButton.setEnabled(false); - } else { - resolveButton.setEnabled(true); - } - - - if (useMostRecentCheckBox.isSelected()) { - list.setEnabled(false); - preferredReportCheckBox.setEnabled(false); - } else { - list.setEnabled(true); - preferredReportCheckBox.setEnabled(true); - } - } else - ; // ignore events on all other objects - } - } - - - class ReportsListSelectionListener implements ListSelectionListener { - public void valueChanged(ListSelectionEvent e) { - if(e.getValueIsAdjusting()) return; - - if ((list.getSelectedValues().length == 0) && - (!useMostRecentCheckBox.isSelected())) { - resolveButton.setEnabled(false); - } else { - resolveButton.setEnabled(true); - } - - - if (list.getSelectedValues().length == 0) { - preferredReportCheckBox.setEnabled(false); - } else { - preferredReportCheckBox.setEnabled(true); - } - - } - } - -} - +package com.sun.javatest.mrep; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.FlowLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.border.Border; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import com.sun.javatest.tool.UIFactory; +import javax.swing.JOptionPane; + +public class ConflictResolutionDialog extends JDialog { + + private String resolveButtonStr = "resolve"; + private String cancelButtonStr = "cancel"; + private String useMostRecentCheckBoxStr = "useMost"; + + private JCheckBox preferredReportCheckBox; + private JCheckBox useMostRecentCheckBox; + + private JButton resolveButton; + private JButton cancelButton; + + private DefaultListModel listModel; + private JList list; + + private int selectedIndex; + private boolean bPreferredReport; + private boolean bUseMostRecent; + + private UIFactory uif; + + private boolean cancel = false; + + + public ConflictResolutionDialog(JFrame parent, String testName, String[] reportsList, boolean bPreferredSet, UIFactory uif) { + super(parent, true); + this.uif = uif; + + setName("conflict"); + setTitle(uif.getI18NString("conflict.name")); + setResizable(false); + + Container cp = getContentPane(); + cp.setLayout(new BorderLayout()); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + ConflictResolutionActionListener conflictResolutionListener = new ConflictResolutionActionListener(); + + JLabel text = uif.createLabel("conflict.text"); + text.setText(text.getText() + " " + testName); + + + text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + JPanel textPanel = uif.createPanel("conflict.text.panel", new FlowLayout(FlowLayout.CENTER)); + textPanel.add(text); + + Box vBox = Box.createVerticalBox(); + + JLabel chooseText =uif.createLabel("conflict.chooseText"); + //text.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + JPanel chooseTextPanel = uif.createPanel("conflict.choosePanel", new FlowLayout(FlowLayout.CENTER)); + chooseTextPanel.add(chooseText); + + // Build list box + listModel=new DefaultListModel(); + for (int i=0; i< reportsList.length; i++) { + listModel.addElement(reportsList[i]); + } + list = uif.createList("conflict.list", listModel); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + ReportsListSelectionListener rl = new ReportsListSelectionListener(); + list.addListSelectionListener(rl); + Border brd = BorderFactory.createMatteBorder(1, 1, 1, 1, Color.BLACK); + list.setBorder(brd); + JScrollPane scrollPane = uif.createScrollPane(list); + Box hBox = Box.createHorizontalBox(); + //hBox.add(Box.createHorizontalStrut(20)); + hBox.add(scrollPane); + //hBox.add(Box.createHorizontalStrut(20)); + + preferredReportCheckBox = uif.createCheckBox("conflict.preffered"); + preferredReportCheckBox.setMnemonic(0); + preferredReportCheckBox.setEnabled(false); + JPanel preferredReportPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + preferredReportPanel.add(preferredReportCheckBox); + + + useMostRecentCheckBox = uif.createCheckBox("conflict.most.recent"); + useMostRecentCheckBox.setMnemonic(1); + useMostRecentCheckBox.addActionListener(conflictResolutionListener); + useMostRecentCheckBox.setActionCommand(useMostRecentCheckBoxStr); + JPanel useRecentPanel = uif.createPanel("conflict.recent", new FlowLayout(FlowLayout.LEFT)); + useRecentPanel.add(useMostRecentCheckBox); + + + + vBox.setBorder(BorderFactory.createEmptyBorder(0,20,0,20)); + vBox.add(chooseTextPanel); + vBox.add(hBox); + // if preferred report was already chosen, in previous dialogs, it should not be seen here + if (!bPreferredSet) { + vBox.add(preferredReportPanel); + } + vBox.add(useRecentPanel); + + + // Build control buttons + JPanel controlButtonsPanel = uif.createPanel("conflict.control", new FlowLayout(FlowLayout.CENTER)); + JPanel p2 = new JPanel(); + p2.setLayout(new GridLayout(1,0,5,5)); + + resolveButton = uif.createButton("conflict.resolve"); + resolveButton.setMnemonic(0); + resolveButton.addActionListener(conflictResolutionListener); + resolveButton.setActionCommand(resolveButtonStr); + resolveButton.setEnabled(false); + + cancelButton = uif.createButton("conflict.cancel"); + cancelButton.addActionListener(conflictResolutionListener); + cancelButton.setActionCommand(cancelButtonStr); + + p2.add(resolveButton); + p2.add(cancelButton); + + controlButtonsPanel.add(p2); + controlButtonsPanel.setBorder(BorderFactory.createEmptyBorder(20,0,0,0)); + + cp.add(textPanel, BorderLayout.NORTH); + cp.add(vBox, BorderLayout.CENTER); + cp.add(controlButtonsPanel, BorderLayout.SOUTH); + + pack(); + setLocationRelativeTo(parent); + } + + public int getSelectedIndex() { + return selectedIndex; + } + public boolean getPreferredReport() { + return bPreferredReport; + } + + public boolean getUseMostRecent() { + return bUseMostRecent; + } + + public boolean wasCanceled() { + return cancel; + } + + class CancelException extends Exception { + + } + + + class ConflictResolutionActionListener implements ActionListener { + + public void actionPerformed(ActionEvent e) { + + String cmd = e.getActionCommand(); + if (cmd.equals(cancelButtonStr)) { + + if (uif.showYesNoDialog("conflict.areyousure") != JOptionPane.YES_OPTION) + return; + + ConflictResolutionDialog.this.cancel = true; + ConflictResolutionDialog.this.dispose(); + } else if (cmd.equals(resolveButtonStr)) { + bUseMostRecent = useMostRecentCheckBox.isSelected(); + bPreferredReport = preferredReportCheckBox.isSelected(); + selectedIndex = list.getSelectedIndex(); + ConflictResolutionDialog.this.dispose(); + + } else if (cmd.equals(useMostRecentCheckBoxStr)) { + if ((list.getSelectedValues().length == 0) && + (!useMostRecentCheckBox.isSelected())) { + resolveButton.setEnabled(false); + } else { + resolveButton.setEnabled(true); + } + + + if (useMostRecentCheckBox.isSelected()) { + list.setEnabled(false); + preferredReportCheckBox.setEnabled(false); + } else { + list.setEnabled(true); + preferredReportCheckBox.setEnabled(true); + } + } else + ; // ignore events on all other objects + } + } + + + class ReportsListSelectionListener implements ListSelectionListener { + public void valueChanged(ListSelectionEvent e) { + if(e.getValueIsAdjusting()) return; + + if ((list.getSelectedValues().length == 0) && + (!useMostRecentCheckBox.isSelected())) { + resolveButton.setEnabled(false); + } else { + resolveButton.setEnabled(true); + } + + + if (list.getSelectedValues().length == 0) { + preferredReportCheckBox.setEnabled(false); + } else { + preferredReportCheckBox.setEnabled(true); + } + + } + } + +} +
--- a/test/jtreg/com/sun/javatest/report/XMLReportMaker.java Tue Aug 04 11:34:49 2009 -0400 +++ b/test/jtreg/com/sun/javatest/report/XMLReportMaker.java Tue Aug 04 17:39:11 2009 +0100 @@ -24,586 +24,586 @@ * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ -package com.sun.javatest.report; - -import com.sun.javatest.Status; -import java.io.File; -import java.io.IOException; -import java.io.Writer; -import java.text.ParseException; -import java.util.Date; -import java.util.Properties; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.sax.SAXTransformerFactory; -import javax.xml.transform.sax.TransformerHandler; -import javax.xml.transform.stream.StreamResult; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.AttributesImpl; -import org.xml.sax.Attributes; -import org.xml.sax.ext.LexicalHandler; - -public class XMLReportMaker { - - private TransformerHandler ser; - - XMLReportMaker(Writer w) throws IOException { - - Properties outputProps = new Properties(); - outputProps.put("indent", "yes"); - outputProps.put("encoding", XML_CHARSET); - SAXTransformerFactory stf = (SAXTransformerFactory )TransformerFactory.newInstance(); - stf.setAttribute("indent-number", 4); - try { - ser = stf.newTransformerHandler(); - } catch (TransformerConfigurationException ex) { - ex.printStackTrace(); - } - ser.getTransformer().setOutputProperties(outputProps); - ser.setResult(new StreamResult(w)); - } - - void sDocument() throws SAXException { - ser.startDocument(); - } - - void eDocument() throws SAXException, IOException { - ser.endDocument(); - } - - void sReport() throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.XSI, "String", Scheme.XSI_VAL); - atts.addAttribute("", "", Scheme.SCH_LOC, "String", Scheme.SCH_LOC_VAL); - atts.addAttribute("", "", Scheme.REPORT_FORMST, "String", "v1"); - atts.addAttribute("", "", Scheme.REPORT_GENTIME, "String", XMLReport.Utils.dateToISO8601(new Date())); - sE(Scheme.REPORT, atts); - } - - void eReport() throws SAXException { - eE(Scheme.REPORT); - } - - void sSummary() throws SAXException { - sE(Scheme.SUMMARY); - } - - void eSummary() throws SAXException { - eE(Scheme.SUMMARY); - } - - - void sWorkdirectories() throws SAXException { - sE(Scheme.WDS); - } - - void eWorkdirectories() throws SAXException { - eE(Scheme.WDS); - } - - void sEnvironment(String name, String descr) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (name != null && ! "".equals(name.trim())) { - atts.addAttribute("", "", Scheme.ENV_NAME, "String", name); - } - if (descr != null && ! "".equals(descr.trim())) { - atts.addAttribute("", "", Scheme.ENV_DESCR, "String", descr); - } - sE(Scheme.ENV, atts); - } - - void eEnvironment() throws SAXException { - eE(Scheme.ENV); - } - - void conCur(int val) throws SAXException { - sE(Scheme.CONC); - String sVal = Integer.toString(val); - ser.characters(sVal.toCharArray(), 0, sVal.length()); - eE(Scheme.CONC); - } - - void timeOut(float val) throws SAXException { - sE(Scheme.TIMO); - String sVal = Float.toString(val); - ser.characters(sVal.toCharArray(), 0, sVal.length()); - eE(Scheme.TIMO); - } - - void sWorkdirectory(String jti) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.WD_ID, "String", "1"); - if (jti != null) { - atts.addAttribute("", "", Scheme.WD_JTI, "String", jti); - } - sE(Scheme.WD, atts); - } - - void eWorkdirectory() throws SAXException { - eE(Scheme.WD); - } - - void sTestResults() throws SAXException { - sE(Scheme.TRS); - } - - void eTestResults() throws SAXException { - eE(Scheme.TRS); - } - - void sTestResult(String url, Status st, int id) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.TR_URL, "String", url); - atts.addAttribute("", "", Scheme.TR_STATUS, "String", XMLReport.Utils.statusToString(st)); - atts.addAttribute("", "", Scheme.TR_WDID, "Integer", Integer.toString(id)); - sE(Scheme.TR, atts); - } - - void eTestResult() throws SAXException { - eE(Scheme.TR); - } - - void sDescriptionData() throws SAXException { - sE(Scheme.DESCR_DATA); - } - - void eDescriptionData() throws SAXException { - eE(Scheme.DESCR_DATA); - } - - - void sKeyWords() throws SAXException { - sE(Scheme.KEY_WORDS); - } - - void sKeyWords(String expr) throws SAXException { - - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.KEYWORDS_EXPR, "String", expr); - sE(Scheme.KEY_WORDS, atts); - } - - void eKeyWords() throws SAXException { - eE(Scheme.KEY_WORDS); - } - - void sTestEnvironment() throws SAXException { - sE(Scheme.TEST_ENV); - } - - void eTestEnvironment() throws SAXException { - eE(Scheme.TEST_ENV); - } - - void sResultProps(String time) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (time != null) { - try { - time = XMLReport.Utils.dateToISO8601(XMLReport.Utils.jtrToDate(time)); - } catch (ParseException ex) { - throw new SAXException(ex); - } - atts.addAttribute("", "", Scheme.RES_PROP_TIM, "String", time); - } - sE(Scheme.RES_PROP, atts); - } - - void eResultProps() throws SAXException { - eE(Scheme.RES_PROP); - } - - void sSections() throws SAXException { - sE(Scheme.SES); - } - - void eSections() throws SAXException { - eE(Scheme.SES); - } - - void sStdValues() throws SAXException { - sE(Scheme.STD_VALS); - } - - void eStdValues() throws SAXException { - eE(Scheme.STD_VALS); - } - - void sPriorStatusList() throws SAXException { - sE(Scheme.PRIOS); - } - - void ePriorStatusList() throws SAXException { - eE(Scheme.PRIOS); - } - - void sExclList() throws SAXException { - sE(Scheme.EXCL_LIST); - } - - void eExclList() throws SAXException { - eE(Scheme.EXCL_LIST); - } - - void sTests() throws SAXException { - sE(Scheme.TESTS); - } - - void eTests() throws SAXException { - eE(Scheme.TESTS); - } - - void sInterview() throws SAXException { - sE(Scheme.INT); - } - - void eInterview() throws SAXException { - eE(Scheme.INT); - } - - void sQuestion(String value, String text, String summary) throws SAXException { - - AttributesImpl atts = new AttributesImpl(); - if (summary != null) { - atts.addAttribute("", "", Scheme.QUEST_SUMM, "String", summary); - } - if (value != null) { - atts.addAttribute("", "", Scheme.QUEST_VALUE, "String", value); - } - if (value != null) { - atts.addAttribute("", "", Scheme.QUEST_TEXT, "String", text); - } - - sE(Scheme.QUEST, atts); - } - - void eQuestion() throws SAXException { - eE(Scheme.QUEST); - } - - - void sListQuestion() throws SAXException { - sE(Scheme.LIST_QUEST); - } - - void eListQuestion() throws SAXException { - eE(Scheme.LIST_QUEST); - } - - void sChoiceQuestion() throws SAXException { - sE(Scheme.CHOICE_QUEST); - } - - void eChoiceQuestion() throws SAXException { - eE(Scheme.CHOICE_QUEST); - } - - void sPropertiesQuestion() throws SAXException { - sE(Scheme.PROP_QUEST); - } - - void ePropertiesQuestion() throws SAXException { - eE(Scheme.PROP_QUEST); - } - - void sGroup(String name, String hd1, String hd2) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (name != null) { - atts.addAttribute("", "", Scheme.GROUP_NAME, "String", name); - } - if (hd1 != null) { - atts.addAttribute("", "", Scheme.GROUP_HD1, "String", hd1); - } - if (hd2 != null) { - atts.addAttribute("", "", Scheme.GROUP_HD2, "String", hd2); - } - sE(Scheme.GROUP, atts); - } - - void eGroup(String name, String h1, String h2) throws SAXException { - eE(Scheme.GROUP); - } - - void makeRow(String key, String val) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (key != null) { - atts.addAttribute("", "", Scheme.ROW_KEY, "String", key); - } - if (val != null) { - atts.addAttribute("", "", Scheme.ROW_VAL, "String", val); - } - sE(Scheme.ROW, atts); - eE(Scheme.ROW); - } - - void makeChoices(String[] ch, String[] dispCh) throws SAXException { - if (ch != null) { - for (int i = 0; i < ch.length; i++) { - makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null); - } - } - } - - void makeChoices(String[] ch, String[] dispCh, boolean[] values) throws SAXException { - if (ch != null) { - for (int i = 0; i < ch.length; i++) { - makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null, i < values.length ? values[i] : false); - } - } - } - - - private void makeChoice(String ch, String di) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (ch != null) { - atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch); - } - if (di != null) { - atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di); - } - sE(Scheme.CHOICE, atts); - eE(Scheme.CHOICE); - } - - private void makeChoice(String ch, String di, boolean val) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch); - if (di != null) { - atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di); - } - atts.addAttribute("", "", Scheme.CHOICE_VAL, "String", Boolean.toString(val)); - sE(Scheme.CHOICE, atts); - eE(Scheme.CHOICE); - } - - - void makeEntireTestTree() throws SAXException { - sE(Scheme.ENTTREE); - eE(Scheme.ENTTREE); - } - - void sSection(String title, Status st) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.SE_TIT, "String", title); - if (st != null) { - atts.addAttribute("", "", Scheme.SE_ST, "String", XMLReport.Utils.statusToString(st)); - } - sE(Scheme.SE, atts); - } - - void eSection() throws SAXException { - eE(Scheme.SE); - } - - void sOutput(String title, String content) throws SAXException, IOException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.OU_TIT, "String", title); - sE(Scheme.OU, atts); - if (content != null && content.length() > 0) { - writeCDATA(ser, ser, content); - } - } - - void eOutput() throws SAXException { - eE(Scheme.OU); - } - - void makeTemplateInfo(String tPath, String name, String descr) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - if (tPath != null) { - atts.addAttribute("", "", Scheme.TEM_FILE, "String", tPath); - } - if (name != null) { - atts.addAttribute("", "", Scheme.TEM_NAME, "String", name); - } - if (descr != null) { - atts.addAttribute("", "", Scheme.TEM_DESCRIPTION, "String", descr); - } - sE(Scheme.TEMPLATE, atts); - eE(Scheme.TEMPLATE); - } - - - void makeProperty(String key, String val) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.PR_NAME, "String", key); - atts.addAttribute("", "", Scheme.PR_VAL, "CDATA", val); - sE(Scheme.PR, atts); - eE(Scheme.PR); - } - - void makeItem(String val) throws SAXException { - AttributesImpl atts = new AttributesImpl(); - atts.addAttribute("", "", Scheme.IT_VAL, "CDATA", val); - sE(Scheme.IT, atts); - eE(Scheme.IT); - } - - void makeItems(String[] vals) throws SAXException { - if (vals != null) { - for (int i = 0 ; i < vals.length; i++) { - if (vals[i] != null) { - makeItem(vals[i]); - } - } - } - } - - void makeItems(File[] files) throws SAXException { - if (files != null) { - for (int i = 0 ; i < files.length; i++) { - makeItem(files[i].getPath()); - } - } - } - - - private void sE(String name) throws SAXException { - ser.startElement("","",name, emptyAttr); - } - - private void sE(String name, Attributes atts) throws SAXException { - ser.startElement("","",name, atts); - } - - private void eE(String name) throws SAXException { - ser.endElement("","",name); - } - - public static void writeCDATA(LexicalHandler lh, ContentHandler ser, String cdata) throws IOException, SAXException { - - cdata = convertProhibitedChars(cdata); - - if (lh != null) { - int start = 0; - int end; - while ( start < cdata.length()) { - end = cdata.length(); int xpos = cdata.indexOf("]]>", start); - if (xpos != -1) { - end = xpos+1; - } - lh.startCDATA(); - String fragment= cdata.substring(start, end); - ser.characters(fragment.toCharArray(), 0, fragment.length()); - lh.endCDATA(); - start = end; - } - } - } - - public static String convertProhibitedChars(String cdata) { - StringBuffer sb = new StringBuffer(); - char [] data = cdata.toCharArray(); - for (int i = 0; i < data.length; i++) { - if (prohibited(data[i])) { - sb.append("\\u"); - String rX = Integer.toHexString((int)data[i]); - for (int ii = rX.length(); ii < 4; ii++) { - sb.append("0"); // - } - sb.append(rX); - } else { - sb.append(data[i]); - } - } - return sb.toString(); - } - - // XML 1.0 specification ( http://www.w3.org/TR/2004/REC-xml-20040204/ ) defines legal chars: - // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - public static boolean prohibited(int c) { - if (c == 0x0009 || c == 0x000A || c == 0x000D) return false; - if (c >= 0x0020 && c <= 0xD7FF) return false; - if (c >= 0xE000 && c <= 0xFFFD) return false; - if (c >= 0x10000 && c <= 0x10FFFF) return false; // can't be java char, but any way... - return true; - } - - - private final AttributesImpl emptyAttr = new AttributesImpl(); - - /** - * Elements and attributes names are defined here - */ - private static class Scheme { - - // ELEMENTS - private static final String REPORT = "Report"; - private static final String SUMMARY = "Summary"; - private static final String WDS = "WorkDirectories"; - private static final String WD = "WorkDirectory"; - private static final String TRS = "TestResults"; - private static final String TR = "TestResult"; - private static final String INT = "Interview"; - private static final String Q = "Question"; - private static final String DESCR_DATA ="DescriptionData"; - private static final String KEY_WORDS = "Keywords"; - private static final String TEST_ENV = "TestEnvironment"; - private static final String RES_PROP = "ResultProperties"; - private static final String SES = "Sections"; - private static final String SE = "Section"; - private static final String OU = "Output"; - private static final String PR = "Property"; - private static final String IT = "Item"; - private static final String ENV = "Environment"; - private static final String CONC = "Concurrency"; - private static final String TIMO = "TimeOut"; - private static final String PRIOS = "PriorStatusList"; - private static final String EXCL_LIST = "ExcludeList"; - private static final String STD_VALS = "StandardValues"; - private static final String TESTS = "Tests"; - private static final String ENTTREE = "EntireTestTree"; - private static final String QUEST = "Question"; - private static final String LIST_QUEST = "ListQuestion"; - private static final String CHOICE_QUEST = "ChoiceQuestion"; - private static final String CHOICE = "Choice"; - private static final String PROP_QUEST = "PropertiesQuestion"; - private static final String GROUP = "Group"; - private static final String ROW = "Row"; - private static final String TEMPLATE = "Template"; - - // ATTRS - private static final String TR_URL = "url"; - private static final String TR_STATUS = "status"; - private static final String TR_WDID = "workDirID"; - private static final String PR_NAME = "name"; - private static final String PR_VAL = "value"; - private static final String IT_VAL = "value"; - private static final String SE_TIT = "title"; - private static final String SE_ST = "status"; - private static final String OU_TIT = "title"; - private static final String RES_PROP_TIM = "endTime"; - private static final String WD_ID = "id"; - private static final String WD_JTI = "jti"; - private static final String KEYWORDS_EXPR = "expression"; - private static final String REPORT_FORMST = "formatVersion"; - private static final String REPORT_GENTIME = "generatedTime"; - private static final String XSI = "xmlns:xsi"; - private static final String SCH_LOC = "xsi:noNamespaceSchemaLocation"; - private static final String QUEST_VALUE = "value"; - private static final String QUEST_TEXT = "text"; - private static final String QUEST_SUMM = "summary"; - private static final String CHOICE_CH = "choice"; - private static final String CHOICE_DCH = "displayChoice"; - private static final String CHOICE_VAL = "value"; - private static final String GROUP_NAME = "name"; - private static final String GROUP_HD1 = "header1"; - private static final String GROUP_HD2 = "header2"; - private static final String ROW_KEY = "key"; - private static final String ROW_VAL = "value"; - private static final String ENV_NAME = "name"; - private static final String ENV_DESCR = "description"; - - private static final String TEM_NAME = "name"; - private static final String TEM_DESCRIPTION = "description"; - private static final String TEM_FILE = "fileName"; - - // VALUES - private static final String XSI_VAL = "http://www.w3.org/2001/XMLSchema-instance"; - private static final String SCH_LOC_VAL = "Report.xsd"; - - } - - public static String XML_CHARSET = "UTF-8"; - -} +package com.sun.javatest.report; + +import com.sun.javatest.Status; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.text.ParseException; +import java.util.Date; +import java.util.Properties; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.Attributes; +import org.xml.sax.ext.LexicalHandler; + +public class XMLReportMaker { + + private TransformerHandler ser; + + XMLReportMaker(Writer w) throws IOException { + + Properties outputProps = new Properties(); + outputProps.put("indent", "yes"); + outputProps.put("encoding", XML_CHARSET); + SAXTransformerFactory stf = (SAXTransformerFactory )TransformerFactory.newInstance(); + stf.setAttribute("indent-number", 4); + try { + ser = stf.newTransformerHandler(); + } catch (TransformerConfigurationException ex) { + ex.printStackTrace(); + } + ser.getTransformer().setOutputProperties(outputProps); + ser.setResult(new StreamResult(w)); + } + + void sDocument() throws SAXException { + ser.startDocument(); + } + + void eDocument() throws SAXException, IOException { + ser.endDocument(); + } + + void sReport() throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.XSI, "String", Scheme.XSI_VAL); + atts.addAttribute("", "", Scheme.SCH_LOC, "String", Scheme.SCH_LOC_VAL); + atts.addAttribute("", "", Scheme.REPORT_FORMST, "String", "v1"); + atts.addAttribute("", "", Scheme.REPORT_GENTIME, "String", XMLReport.Utils.dateToISO8601(new Date())); + sE(Scheme.REPORT, atts); + } + + void eReport() throws SAXException { + eE(Scheme.REPORT); + } + + void sSummary() throws SAXException { + sE(Scheme.SUMMARY); + } + + void eSummary() throws SAXException { + eE(Scheme.SUMMARY); + } + + + void sWorkdirectories() throws SAXException { + sE(Scheme.WDS); + } + + void eWorkdirectories() throws SAXException { + eE(Scheme.WDS); + } + + void sEnvironment(String name, String descr) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (name != null && ! "".equals(name.trim())) { + atts.addAttribute("", "", Scheme.ENV_NAME, "String", name); + } + if (descr != null && ! "".equals(descr.trim())) { + atts.addAttribute("", "", Scheme.ENV_DESCR, "String", descr); + } + sE(Scheme.ENV, atts); + } + + void eEnvironment() throws SAXException { + eE(Scheme.ENV); + } + + void conCur(int val) throws SAXException { + sE(Scheme.CONC); + String sVal = Integer.toString(val); + ser.characters(sVal.toCharArray(), 0, sVal.length()); + eE(Scheme.CONC); + } + + void timeOut(float val) throws SAXException { + sE(Scheme.TIMO); + String sVal = Float.toString(val); + ser.characters(sVal.toCharArray(), 0, sVal.length()); + eE(Scheme.TIMO); + } + + void sWorkdirectory(String jti) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.WD_ID, "String", "1"); + if (jti != null) { + atts.addAttribute("", "", Scheme.WD_JTI, "String", jti); + } + sE(Scheme.WD, atts); + } + + void eWorkdirectory() throws SAXException { + eE(Scheme.WD); + } + + void sTestResults() throws SAXException { + sE(Scheme.TRS); + } + + void eTestResults() throws SAXException { + eE(Scheme.TRS); + } + + void sTestResult(String url, Status st, int id) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.TR_URL, "String", url); + atts.addAttribute("", "", Scheme.TR_STATUS, "String", XMLReport.Utils.statusToString(st)); + atts.addAttribute("", "", Scheme.TR_WDID, "Integer", Integer.toString(id)); + sE(Scheme.TR, atts); + } + + void eTestResult() throws SAXException { + eE(Scheme.TR); + } + + void sDescriptionData() throws SAXException { + sE(Scheme.DESCR_DATA); + } + + void eDescriptionData() throws SAXException { + eE(Scheme.DESCR_DATA); + } + + + void sKeyWords() throws SAXException { + sE(Scheme.KEY_WORDS); + } + + void sKeyWords(String expr) throws SAXException { + + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.KEYWORDS_EXPR, "String", expr); + sE(Scheme.KEY_WORDS, atts); + } + + void eKeyWords() throws SAXException { + eE(Scheme.KEY_WORDS); + } + + void sTestEnvironment() throws SAXException { + sE(Scheme.TEST_ENV); + } + + void eTestEnvironment() throws SAXException { + eE(Scheme.TEST_ENV); + } + + void sResultProps(String time) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (time != null) { + try { + time = XMLReport.Utils.dateToISO8601(XMLReport.Utils.jtrToDate(time)); + } catch (ParseException ex) { + throw new SAXException(ex); + } + atts.addAttribute("", "", Scheme.RES_PROP_TIM, "String", time); + } + sE(Scheme.RES_PROP, atts); + } + + void eResultProps() throws SAXException { + eE(Scheme.RES_PROP); + } + + void sSections() throws SAXException { + sE(Scheme.SES); + } + + void eSections() throws SAXException { + eE(Scheme.SES); + } + + void sStdValues() throws SAXException { + sE(Scheme.STD_VALS); + } + + void eStdValues() throws SAXException { + eE(Scheme.STD_VALS); + } + + void sPriorStatusList() throws SAXException { + sE(Scheme.PRIOS); + } + + void ePriorStatusList() throws SAXException { + eE(Scheme.PRIOS); + } + + void sExclList() throws SAXException { + sE(Scheme.EXCL_LIST); + } + + void eExclList() throws SAXException { + eE(Scheme.EXCL_LIST); + } + + void sTests() throws SAXException { + sE(Scheme.TESTS); + } + + void eTests() throws SAXException { + eE(Scheme.TESTS); + } + + void sInterview() throws SAXException { + sE(Scheme.INT); + } + + void eInterview() throws SAXException { + eE(Scheme.INT); + } + + void sQuestion(String value, String text, String summary) throws SAXException { + + AttributesImpl atts = new AttributesImpl(); + if (summary != null) { + atts.addAttribute("", "", Scheme.QUEST_SUMM, "String", summary); + } + if (value != null) { + atts.addAttribute("", "", Scheme.QUEST_VALUE, "String", value); + } + if (value != null) { + atts.addAttribute("", "", Scheme.QUEST_TEXT, "String", text); + } + + sE(Scheme.QUEST, atts); + } + + void eQuestion() throws SAXException { + eE(Scheme.QUEST); + } + + + void sListQuestion() throws SAXException { + sE(Scheme.LIST_QUEST); + } + + void eListQuestion() throws SAXException { + eE(Scheme.LIST_QUEST); + } + + void sChoiceQuestion() throws SAXException { + sE(Scheme.CHOICE_QUEST); + } + + void eChoiceQuestion() throws SAXException { + eE(Scheme.CHOICE_QUEST); + } + + void sPropertiesQuestion() throws SAXException { + sE(Scheme.PROP_QUEST); + } + + void ePropertiesQuestion() throws SAXException { + eE(Scheme.PROP_QUEST); + } + + void sGroup(String name, String hd1, String hd2) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (name != null) { + atts.addAttribute("", "", Scheme.GROUP_NAME, "String", name); + } + if (hd1 != null) { + atts.addAttribute("", "", Scheme.GROUP_HD1, "String", hd1); + } + if (hd2 != null) { + atts.addAttribute("", "", Scheme.GROUP_HD2, "String", hd2); + } + sE(Scheme.GROUP, atts); + } + + void eGroup(String name, String h1, String h2) throws SAXException { + eE(Scheme.GROUP); + } + + void makeRow(String key, String val) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (key != null) { + atts.addAttribute("", "", Scheme.ROW_KEY, "String", key); + } + if (val != null) { + atts.addAttribute("", "", Scheme.ROW_VAL, "String", val); + } + sE(Scheme.ROW, atts); + eE(Scheme.ROW); + } + + void makeChoices(String[] ch, String[] dispCh) throws SAXException { + if (ch != null) { + for (int i = 0; i < ch.length; i++) { + makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null); + } + } + } + + void makeChoices(String[] ch, String[] dispCh, boolean[] values) throws SAXException { + if (ch != null) { + for (int i = 0; i < ch.length; i++) { + makeChoice(ch[i], i < dispCh.length ? dispCh[i] : null, i < values.length ? values[i] : false); + } + } + } + + + private void makeChoice(String ch, String di) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (ch != null) { + atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch); + } + if (di != null) { + atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di); + } + sE(Scheme.CHOICE, atts); + eE(Scheme.CHOICE); + } + + private void makeChoice(String ch, String di, boolean val) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.CHOICE_CH, "String", ch); + if (di != null) { + atts.addAttribute("", "", Scheme.CHOICE_DCH, "String", di); + } + atts.addAttribute("", "", Scheme.CHOICE_VAL, "String", Boolean.toString(val)); + sE(Scheme.CHOICE, atts); + eE(Scheme.CHOICE); + } + + + void makeEntireTestTree() throws SAXException { + sE(Scheme.ENTTREE); + eE(Scheme.ENTTREE); + } + + void sSection(String title, Status st) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.SE_TIT, "String", title); + if (st != null) { + atts.addAttribute("", "", Scheme.SE_ST, "String", XMLReport.Utils.statusToString(st)); + } + sE(Scheme.SE, atts); + } + + void eSection() throws SAXException { + eE(Scheme.SE); + } + + void sOutput(String title, String content) throws SAXException, IOException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.OU_TIT, "String", title); + sE(Scheme.OU, atts); + if (content != null && content.length() > 0) { + writeCDATA(ser, ser, content); + } + } + + void eOutput() throws SAXException { + eE(Scheme.OU); + } + + void makeTemplateInfo(String tPath, String name, String descr) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + if (tPath != null) { + atts.addAttribute("", "", Scheme.TEM_FILE, "String", tPath); + } + if (name != null) { + atts.addAttribute("", "", Scheme.TEM_NAME, "String", name); + } + if (descr != null) { + atts.addAttribute("", "", Scheme.TEM_DESCRIPTION, "String", descr); + } + sE(Scheme.TEMPLATE, atts); + eE(Scheme.TEMPLATE); + } + + + void makeProperty(String key, String val) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.PR_NAME, "String", key); + atts.addAttribute("", "", Scheme.PR_VAL, "CDATA", val); + sE(Scheme.PR, atts); + eE(Scheme.PR); + } + + void makeItem(String val) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", Scheme.IT_VAL, "CDATA", val); + sE(Scheme.IT, atts); + eE(Scheme.IT); + } + + void makeItems(String[] vals) throws SAXException { + if (vals != null) { + for (int i = 0 ; i < vals.length; i++) { + if (vals[i] != null) { + makeItem(vals[i]); + } + } + } + } + + void makeItems(File[] files) throws SAXException { + if (files != null) { + for (int i = 0 ; i < files.length; i++) { + makeItem(files[i].getPath()); + } + } + } + + + private void sE(String name) throws SAXException { + ser.startElement("","",name, emptyAttr); + } + + private void sE(String name, Attributes atts) throws SAXException { + ser.startElement("","",name, atts); + } + + private void eE(String name) throws SAXException { + ser.endElement("","",name); + } + + public static void writeCDATA(LexicalHandler lh, ContentHandler ser, String cdata) throws IOException, SAXException { + + cdata = convertProhibitedChars(cdata); + + if (lh != null) { + int start = 0; + int end; + while ( start < cdata.length()) { + end = cdata.length(); int xpos = cdata.indexOf("]]>", start); + if (xpos != -1) { + end = xpos+1; + } + lh.startCDATA(); + String fragment= cdata.substring(start, end); + ser.characters(fragment.toCharArray(), 0, fragment.length()); + lh.endCDATA(); + start = end; + } + } + } + + public static String convertProhibitedChars(String cdata) { + StringBuffer sb = new StringBuffer(); + char [] data = cdata.toCharArray(); + for (int i = 0; i < data.length; i++) { + if (prohibited(data[i])) { + sb.append("\\u"); + String rX = Integer.toHexString((int)data[i]); + for (int ii = rX.length(); ii < 4; ii++) { + sb.append("0"); // + } + sb.append(rX); + } else { + sb.append(data[i]); + } + } + return sb.toString(); + } + + // XML 1.0 specification ( http://www.w3.org/TR/2004/REC-xml-20040204/ ) defines legal chars: + // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + public static boolean prohibited(int c) { + if (c == 0x0009 || c == 0x000A || c == 0x000D) return false; + if (c >= 0x0020 && c <= 0xD7FF) return false; + if (c >= 0xE000 && c <= 0xFFFD) return false; + if (c >= 0x10000 && c <= 0x10FFFF) return false; // can't be java char, but any way... + return true; + } + + + private final AttributesImpl emptyAttr = new AttributesImpl(); + + /** + * Elements and attributes names are defined here + */ + private static class Scheme { + + // ELEMENTS + private static final String REPORT = "Report"; + private static final String SUMMARY = "Summary"; + private static final String WDS = "WorkDirectories"; + private static final String WD = "WorkDirectory"; + private static final String TRS = "TestResults"; + private static final String TR = "TestResult"; + private static final String INT = "Interview"; + private static final String Q = "Question"; + private static final String DESCR_DATA ="DescriptionData"; + private static final String KEY_WORDS = "Keywords"; + private static final String TEST_ENV = "TestEnvironment"; + private static final String RES_PROP = "ResultProperties"; + private static final String SES = "Sections"; + private static final String SE = "Section"; + private static final String OU = "Output"; + private static final String PR = "Property"; + private static final String IT = "Item"; + private static final String ENV = "Environment"; + private static final String CONC = "Concurrency"; + private static final String TIMO = "TimeOut"; + private static final String PRIOS = "PriorStatusList"; + private static final String EXCL_LIST = "ExcludeList"; + private static final String STD_VALS = "StandardValues"; + private static final String TESTS = "Tests"; + private static final String ENTTREE = "EntireTestTree"; + private static final String QUEST = "Question"; + private static final String LIST_QUEST = "ListQuestion"; + private static final String CHOICE_QUEST = "ChoiceQuestion"; + private static final String CHOICE = "Choice"; + private static final String PROP_QUEST = "PropertiesQuestion"; + private static final String GROUP = "Group"; + private static final String ROW = "Row"; + private static final String TEMPLATE = "Template"; + + // ATTRS + private static final String TR_URL = "url"; + private static final String TR_STATUS = "status"; + private static final String TR_WDID = "workDirID"; + private static final String PR_NAME = "name"; + private static final String PR_VAL = "value"; + private static final String IT_VAL = "value"; + private static final String SE_TIT = "title"; + private static final String SE_ST = "status"; + private static final String OU_TIT = "title"; + private static final String RES_PROP_TIM = "endTime"; + private static final String WD_ID = "id"; + private static final String WD_JTI = "jti"; + private static final String KEYWORDS_EXPR = "expression"; + private static final String REPORT_FORMST = "formatVersion"; + private static final String REPORT_GENTIME = "generatedTime"; + private static final String XSI = "xmlns:xsi"; + private static final String SCH_LOC = "xsi:noNamespaceSchemaLocation"; + private static final String QUEST_VALUE = "value"; + private static final String QUEST_TEXT = "text"; + private static final String QUEST_SUMM = "summary"; + private static final String CHOICE_CH = "choice"; + private static final String CHOICE_DCH = "displayChoice"; + private static final String CHOICE_VAL = "value"; + private static final String GROUP_NAME = "name"; + private static final String GROUP_HD1 = "header1"; + private static final String GROUP_HD2 = "header2"; + private static final String ROW_KEY = "key"; + private static final String ROW_VAL = "value"; + private static final String ENV_NAME = "name"; + private static final String ENV_DESCR = "description"; + + private static final String TEM_NAME = "name"; + private static final String TEM_DESCRIPTION = "description"; + private static final String TEM_FILE = "fileName"; + + // VALUES + private static final String XSI_VAL = "http://www.w3.org/2001/XMLSchema-instance"; + private static final String SCH_LOC_VAL = "Report.xsd"; + + } + + public static String XML_CHARSET = "UTF-8"; + +}