# HG changeset patch # User herrick # Date 1539385251 14400 # Node ID eaca4369b0686aca7223907b68aa9a447efdff31 # Parent a769ad2d40d612104058ce29524b2e41abb17ee9 8198472: Add support for creating bundles from JNLP files Submitten-by: almatvee Reviewed-by: herrick, kcr, prr, asemenyuk diff -r a769ad2d40d6 -r eaca4369b068 make/CompileDemos.gmk --- a/make/CompileDemos.gmk Fri Oct 12 18:58:40 2018 -0400 +++ b/make/CompileDemos.gmk Fri Oct 12 19:00:51 2018 -0400 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -219,6 +219,11 @@ MAIN_CLASS := transparentruler.Ruler, \ )) +$(eval $(call SetupBuildDemo, JNLPConverter, \ + DEMO_SUBDIR := jpackager, \ + MAIN_CLASS := jnlp.converter.Main, \ +)) + ################################################################################ # Copy html and README files. diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/README.txt Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,23 @@ +About JNLPConverter +=================== + +JNLPConverter is a standalone tool which uses jpackager to create bundles from +Java Web Start(TM) Applications and helps to migrate from JNLP to jpackager. +JNLPConverter will locate and use the jpackager tool from the same JDK as used +to run JNLPConverter. JNLPConverter supports HTTP/HTTPS and FILE protocol. + +Running JNLPConverter +===================== + +To run the JNLPConverter: + + java -jar JNLPConverter.jar + +To get help on JNLPConverter options: + + java -jar JNLPConverter.jar --help + +These instructions assume that this installation's version of the java command +is in your path. If it isn't, then you should either specify the complete path +to the java command or update your PATH environment variable as described +in the installation instructions for the Java(TM) SE Development Kit. diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/HTTPHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/HTTPHelper.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +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.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import jnlp.converter.parser.GeneralUtil; + +public class HTTPHelper { + + public static final int BUFFER_SIZE = 4096; + + public static String downloadFile(String url, String destFolder, String destFileName) throws MalformedURLException, IOException { + HttpURLConnection connection = null; + String destFile = null; + + try { + if (url.contains(" ")) { + url = url.replace(" ", "%20"); + } + if (url.contains("\\")) { + url = url.replace("\\", "/"); + } + + URL resource = new URL(url); + connection = (HttpURLConnection) resource.openConnection(); + + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + destFile = destFolder + File.separator + destFileName; + Log.verbose("Downloading " + url + " to " + destFile); + + try (InputStream inputStream = connection.getInputStream(); + OutputStream outputStream = new FileOutputStream(destFile)) { + byte[] buffer = new byte[BUFFER_SIZE]; + + int length; + do { + length = inputStream.read(buffer); + if (length > 0) { + outputStream.write(buffer, 0, length); + } + } while (length > 0); + } + } else { + HTTPHelperException e = new HTTPHelperException("Error: Cannot download " + url + ". Server response code: " + responseCode); + e.setResponseCode(responseCode); + throw e; + } + } catch (IOException e) { + if (e instanceof HTTPHelperException) { + throw e; + } else { + throw new HTTPHelperException("Error: Cannot download " + url + ". " + e.getClass().getSimpleName() + ": " + e.getMessage()); + } + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + return destFile; + } + + public static String copyFile(String url, String destFolder, String destFileName) throws Exception { + if (url.contains(" ")) { + url = url.replace(" ", "%20"); + } + + URI sourceURI = new URI(url); + + String sourceFile = sourceURI.getPath(); + File file = new File(sourceFile); + if (!file.exists()) { + throw new FileNotFoundException("Error: " + sourceFile + " does not exist."); + } + + String destFile = destFolder + File.separator + destFileName; + file = new File(destFile); + if (file.exists()) { + file.delete(); + } + + Path sourcePath = Paths.get(sourceURI); + Path destPath = Paths.get(destFile); + Log.verbose("Copying " + url + " to " + destFile); + Files.copy(sourcePath, destPath); + + return destFile; + } + + public static boolean isHTTPUrl(String url) { + return (url.startsWith("http://") || url.startsWith("https://")); + } + + public static byte[] getJNLPBits(String versionedJNLP, String jnlp) throws Exception { + String jnlpFilePath = null; + byte[] bits = null; + + if (isHTTPUrl(jnlp)) { + try { + jnlpFilePath = downloadFile(versionedJNLP, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp)); + } catch (HTTPHelperException ex) { + if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND && + !versionedJNLP.equals(jnlp)) { + Log.warning("Downloading versioned JNLP from " + versionedJNLP + " failed."); + Log.warning(ex.getMessage()); + Log.warning("Downloading " + jnlp + " instead."); + jnlpFilePath = downloadFile(jnlp, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp)); + } else { + throw ex; + } + } + JNLPConverter.markFileToDelete(jnlpFilePath); + } else { + try { + jnlpFilePath = copyFile(versionedJNLP, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp)); + } catch (FileNotFoundException ex) { + System.out.println("Error copying versioned JNLP from " + versionedJNLP); + System.out.println(ex.getMessage()); + System.out.println("Copying " + jnlp + " instead."); + jnlpFilePath = HTTPHelper.copyFile(jnlp, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp)); + } + JNLPConverter.markFileToDelete(jnlpFilePath); + } + + File jnlpFile = new File(jnlpFilePath); + if (jnlpFile.exists()) { + bits = GeneralUtil.readBytes(new FileInputStream(jnlpFile), jnlpFile.length()); + } + + return bits; + } + + public static String getFileNameFromURL(String url) throws IOException { + int index; + int index1 = url.lastIndexOf('/'); + int index2 = url.lastIndexOf('\\'); + + if (index1 >= index2) { + index = index1; + } else { + index = index2; + } + + if (index != -1) { + String name = url.substring(index + 1, url.length()); + name = name.replace("%20", " "); + if (name.endsWith(".jnlp") || name.endsWith(".jar")) { // JNLP or JAR + return name; + } else if (name.endsWith(".ico")) { // Icons + return name; + } else { + throw new IOException("Error: Unsupported file extension for " + url); + } + } else { + throw new IOException("Error: URL (" + url + ") should end with file name."); + } + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/HTTPHelperException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/HTTPHelperException.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter; + +import java.io.IOException; + +public class HTTPHelperException extends IOException { + private int responseCode = -1; + + public HTTPHelperException(String msg) { + super(msg); + } + + public void setResponseCode(int code) { + responseCode = code; + } + + public int getResponseCode() { + return responseCode; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/JNLPConverter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/JNLPConverter.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,865 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import jnlp.converter.parser.JNLPDesc; +import jnlp.converter.parser.JNLPDesc.AssociationDesc; +import jnlp.converter.parser.JNLPDesc.IconDesc; +import jnlp.converter.parser.ResourcesDesc.JARDesc; +import jnlp.converter.parser.XMLFormat; + +public class JNLPConverter { + + private final Options options; + private JNLPDesc jnlpd = null; + private final List launchArgs = new ArrayList<>(); + + private String downloadFolder = null; + private String jnlpDownloadFolder = null; + private static String jnlpDownloadFolderStatic; + private String jarDownloadFolder = null; + private String iconDownloadFolder = null; + private String propDownloadFolder = null; + + private static String jpackagerPath = null; + + private static boolean markFileToDelete = false; + + private static final String FA_EXTENSIONS = "extension"; + private static final String FA_CONTENT_TYPE = "mime-type"; + private static final String FA_DESCRIPTION = "description"; + private static final String FA_ICON = "icon"; + + public JNLPConverter(Options options) { + this.options = options; + jnlpDownloadFolderStatic = getJnlpDownloadFolder(); + markFileToDelete = (options.keep() == null); + } + + public String [] getLaunchArgs() { + return launchArgs.toArray(new String[0]); + } + + public void convert() { + try { + loadJNLPDesc(); + downloadResources(); + validate(); + buildLaunchArgs(); + saveLaunchArgs(); + runJPackager(); + } catch (Exception ex) { + Log.error(ex.getLocalizedMessage()); + } + } + + private JNLPDesc getJNLPD(String jnlp) throws Exception { + URL codebase = getCodeBase(jnlp); + byte[] bits = HTTPHelper.getJNLPBits(jnlp, jnlp); + return XMLFormat.parse(bits, codebase, jnlp); + } + + private void loadJNLPDesc() throws Exception { + String jnlp = options.getJNLP(); + jnlpd = getJNLPD(jnlp); + + // Check for required options in case of FX + if (jnlpd.isFXApp()) { + if (!options.isRuntimeImageSet()) { + throw new Exception("This is a JavaFX Web-Start application which requires a runtime image capable of running JavaFX applications, which can be specified by the jpackager option --runtime-image (using --jpackager-options)."); + } + } + + // Check href. It can be same as URL we provided or new one + // if JNLP has different href or codebase. We assume that + // XMLFormat.parse() will handle any errors in href and codebase + // correctly. + String href = jnlpd.getHref(); + if (href != null && !href.equalsIgnoreCase(jnlp)) { + if (href.startsWith("file:")) { + URI hrefURI = new URI(href); + URI jnlpURI = new URI(jnlp); + + String hrefPath = hrefURI.getPath(); + String jnlpPath = jnlpURI.getPath(); + + if (!hrefPath.equalsIgnoreCase(jnlpPath)) { + jnlp = href; + jnlpd = getJNLPD(jnlp); + } + } else { + jnlp = href; + jnlpd = getJNLPD(jnlp); + } + } + + if (jnlpd.getName() == null) { + jnlpd.setName(getNameFromURL(jnlp)); + } + } + + private static String getNameFromURL(String url) throws IOException { + int index; + int index1 = url.lastIndexOf('/'); + int index2 = url.lastIndexOf('\\'); + + if (index1 >= index2) { + index = index1; + } else { + index = index2; + } + + if (index != -1) { + String name = url.substring(index + 1, url.length()); + if (name.endsWith(".jnlp")) { + return name.substring(0, name.length() - 5); + } + } + + return null; + } + + private URL getCodeBase(String jnlp) throws Exception { + int index = jnlp.lastIndexOf('/'); + if (index != -1) { + if (HTTPHelper.isHTTPUrl(jnlp)) { + return new URL(jnlp.substring(0, index + 1)); + } else { + String codeBasePath = jnlp.substring(0, index); + if (!codeBasePath.endsWith("/")) { + codeBasePath += "/"; + } + return new URI(codeBasePath).toURL(); + } + } + + return null; + } + + public static void markFileToDelete(String file) { + if (file == null || file.isEmpty()) { + return; + } + + if (markFileToDelete) { + try { + File f = new File(file); + f.deleteOnExit(); + } catch (Exception e) { + // Print exception, but do not fail conversion. + Log.warning(e.getLocalizedMessage()); + } + } + } + + public static void deleteFile(String file) { + try { + File f = new File(file); + f.delete(); + } catch (Exception e) { + Log.warning(e.getLocalizedMessage()); + } + } + + private void downloadResources() throws Exception { + List jars = jnlpd.getResources(); + for (JARDesc jar : jars) { + if (jar.getVersion() != null) { + if (!jnlpd.isVersionEnabled()) { + throw new Exception("Error: Version based download protocol is not supported without -Djnlp.versionEnabled=true."); + } + } + + String destFile = null; + if (HTTPHelper.isHTTPUrl(jar.getLocation().toString())) { + if (jar.getVersion() != null) { + try { + destFile = HTTPHelper.downloadFile(jar.getVersionLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString())); + } catch (HTTPHelperException ex) { + if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) { + System.out.println("Error downloading versioned JAR from " + jar.getVersionLocation()); + System.out.println(ex.getMessage()); + System.out.println("Downloading " + jar.getLocation() + " instead."); + destFile = HTTPHelper.downloadFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString())); + } else { + throw ex; + } + } + } else { + destFile = HTTPHelper.downloadFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString())); + } + markFileToDelete(destFile); + } else { + if (jar.getVersion() != null) { + try { + destFile = HTTPHelper.copyFile(jar.getVersionLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString())); + } catch (FileNotFoundException ex) { + System.out.println("Error copying versioned JAR from " + jar.getVersionLocation()); + System.out.println(ex.getMessage()); + System.out.println("Copying " + jar.getLocation() + " instead."); + destFile = HTTPHelper.copyFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString())); + } + } else { + destFile = HTTPHelper.copyFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString())); + } + markFileToDelete(destFile); + } + + if (jar.isNativeLib()) { + unpackNativeLib(destFile); + deleteFile(destFile); + } else { + jnlpd.addFile(jar.getName()); + } + } + + IconDesc icon = jnlpd.getIcon(); + if (icon != null) { + String destFile; + + if (HTTPHelper.isHTTPUrl(icon.getLocation())) { + destFile = HTTPHelper.downloadFile(icon.getLocation(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(icon.getLocation())); + } else { + destFile = HTTPHelper.copyFile(icon.getLocation(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(icon.getLocation())); + } + + markFileToDelete(destFile); + icon.setLocalLocation(destFile); + } + + AssociationDesc [] associations = jnlpd.getAssociations(); + if (associations != null) { + for (AssociationDesc association : associations) { + if (association.getIconUrl() != null) { + String destFile; + if (HTTPHelper.isHTTPUrl(association.getIconUrl())) { + destFile = HTTPHelper.downloadFile(association.getIconUrl(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(association.getIconUrl())); + } else { + destFile = HTTPHelper.copyFile(association.getIconUrl(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(association.getIconUrl())); + } + + markFileToDelete(destFile); + association.setIconLocalLocation(destFile); + } + } + } + } + + public void unpackNativeLib(String file) throws IOException { + try (JarFile jarFile = new JarFile(file)) { + Enumeration entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + + // Skip directories + if (entry.isDirectory()) { + continue; + } + + String entryName = entry.getName(); + // Skip anything in sub-directories + if (entryName.contains("\\") || entryName.contains("/")) { + continue; + } + + // Skip anything not ending with .dll, .dylib or .so + if (!entryName.endsWith(".dll") && !entryName.endsWith(".dylib") && !entryName.endsWith(".so")) { + continue; + } + + File destFile = new File(getJarDownloadFolder(), entryName); + if (destFile.exists()) { + Log.warning(destFile.getAbsolutePath() + " already exist and will not be overwriten by native library from " + file + "."); + continue; + } + + InputStream inputStream = jarFile.getInputStream(entry); + FileOutputStream outputStream = new FileOutputStream(destFile); + + byte[] buffer = new byte[HTTPHelper.BUFFER_SIZE]; + int length; + do { + length = inputStream.read(buffer); + if (length > 0) { + outputStream.write(buffer, 0, length); + } + } while (length > 0); + + jnlpd.addFile(entryName); + } + } + } + + private void validate() { + if (jnlpd.getMainJar() == null) { + Log.error("Cannot find main jar"); + } + + if (jnlpd.getMainClass() == null) { + Log.error("Cannot find main class"); + } + } + + private void addLaunchArg(String arg, List launchArgs) { + if (arg != null && !arg.isEmpty()) { + if (!options.isOptionPresent(arg)){ + launchArgs.add(arg); + } else { + Log.info(arg + " generated by JNLPConverter is dropped, since it is overwriten via --jpackager-options"); + } + } + } + + private void addLaunchArg(String arg, String value, List launchArgs) { + if (arg != null && !arg.isEmpty() && value != null && !value.isEmpty()) { + if (!options.isOptionPresent(arg)){ + launchArgs.add(arg); + launchArgs.add(value); + } else { + Log.info(arg + "=" + value +" generated by JNLPConverter is dropped, since it is overwriten via --jpackager-options"); + } + } + } + + private void displayLaunchArgs() { + if (Log.isVerbose()) { + System.out.println(); + System.out.println("jpackager launch arguments (each argument starts on new line):"); + launchArgs.forEach((arg) -> { + System.out.println(arg); + }); + } + } + + private static int fileAssociationsCount = 0; + private String getFileAssociationsFile() { + String file = getPropDownloadFolder(); + file += File.separator; + file += "fileAssociation"; + file += String.valueOf(fileAssociationsCount); + file += ".properties"; + + fileAssociationsCount++; + + return file; + } + + private void buildLaunchArgs() { + if (options.createImage()) { + addLaunchArg("create-image", launchArgs); + } else if (options.createInstaller()) { + if (options.getInstallerType() == null) { + addLaunchArg("create-installer", launchArgs); + } else { + addLaunchArg("create-installer", options.getInstallerType(), launchArgs); + } + } + + // Set verbose for jpackager if it is set for us. + if (options.verbose()) { + addLaunchArg("--verbose", launchArgs); + } + + addLaunchArg("--input", getJarDownloadFolder(), launchArgs); + addLaunchArg("--output", options.getOutput(), launchArgs); + addLaunchArg("--name", jnlpd.getName(), launchArgs); + addLaunchArg("--version", jnlpd.getVersion(), launchArgs); + addLaunchArg("--vendor", jnlpd.getVendor(), launchArgs); + addLaunchArg("--description", jnlpd.getDescription(), launchArgs); + addLaunchArg("--icon", jnlpd.getIconLocation(), launchArgs); + addLaunchArg("--main-jar", jnlpd.getMainJar(), launchArgs); + addLaunchArg("--class", jnlpd.getMainClass(), launchArgs); + + addFiles(launchArgs); + addArguments(launchArgs); + addJVMArgs(launchArgs); + + if (jnlpd.isDesktopHint()) { + if (Platform.isWindows()) { + addLaunchArg("--win-shortcut", launchArgs); + } else { + Log.warning("Ignoring shortcut hint, since it is not supported on current platform."); + } + } + + if (jnlpd.isMenuHint()) { + if (Platform.isWindows()) { + addLaunchArg("--win-menu", launchArgs); + addLaunchArg("--win-menu-group", jnlpd.getSubMenu(), launchArgs); + } else { + Log.warning("Ignoring menu hint, since it is not supported on current platform."); + } + } + + AssociationDesc [] associations = jnlpd.getAssociations(); + if (associations != null) { + for (AssociationDesc association : associations) { + String file = getFileAssociationsFile(); + markFileToDelete(file); + + try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) { + if (association.getExtensions() != null && association.getMimeType() != null) { + out.println(FA_EXTENSIONS + "=" + quote(association.getExtensions())); + out.println(FA_CONTENT_TYPE + "=" + quote(association.getMimeType())); + + if (association.getMimeDescription() != null) { + out.println(FA_DESCRIPTION + "=" + association.getMimeDescription()); + } + + if (association.getIconLocalLocation() != null) { + out.println(FA_ICON + "=" + quote(association.getIconLocalLocation())); + } + + addLaunchArg("--file-associations", file, launchArgs); + } + } catch (Exception ex) { + Log.warning(ex.toString()); + if (association.getExtensions() != null) { + Log.warning("File assoication for " + association.getExtensions() + " will be ignored due to exception above."); + } + } + } + } + + // Add options from --jpackager-options + List jpackagerOptions = options.getJPackagerOptions(); + jpackagerOptions.forEach((option) -> { + launchArgs.add(option); + }); + + displayLaunchArgs(); + } + + private String getCommandFileName() { + Platform platform = Platform.getPlatform(); + switch (platform) { + case WINDOWS: + return "run_jpackager.bat"; + case LINUX: + return "run_jpackager.sh"; + case MAC: + return "run_jpackager.sh"; + default: + Log.error("Cannot determine platform type."); + return ""; + } + } + + private void saveLaunchArgs() { + if (options.keep() != null) { + File keepFolder = new File(options.keep()); + String cmdFile = keepFolder.getAbsolutePath() + File.separator + getCommandFileName(); + try (PrintWriter out = new PrintWriter(cmdFile)) { + out.print(getJPackagerPath()); + launchArgs.forEach((arg) -> { + out.print(" "); + + if (arg.contains(" ")) { + int len = arg.length(); + if (len >= 1) { + if (arg.charAt(0) != '"' && arg.charAt(len - 1) != '"') { + out.print("\"" + arg + "\""); + } else { + if (Platform.isWindows()) { + out.print(arg); + } else { + arg = escapeQuote(arg); + out.print("\"" + arg + "\""); + } + } + } + } else { + out.print(arg); + } + }); + } catch (FileNotFoundException ex) { + Log.error("Cannot save file with command line: " + ex.getLocalizedMessage()); + } + } + } + + private void runJPackager() { + List command = new ArrayList<>(); + command.add(getJPackagerPath()); + command.addAll(launchArgs); + + ProcessBuilder builder = new ProcessBuilder(); + builder.inheritIO(); + builder.command(command); + + try { + Process process = builder.start(); + int exitCode = process.waitFor(); + if (exitCode != 0) { + Log.warning("jpackager retrun non zero code: " + exitCode); + } + } catch (IOException | InterruptedException ex) { + Log.error(ex.getMessage()); + } + } + + private void addFileList(String arg, List filesToAdd, List launchArgs) { + if (filesToAdd.isEmpty()) { + return; + } + + String filesArg = ""; + for (int i = 0; i < filesToAdd.size(); i++) { + filesArg += quote(filesToAdd.get(i)); + if ((i + 1) != filesToAdd.size()) { + filesArg += File.pathSeparator; + } + } + + launchArgs.add(arg); + launchArgs.add(filesArg); + } + + private void addFiles(List launchArgs) { + addFileList("--files", jnlpd.getFiles(), launchArgs); + } + + private void addArguments(List launchArgs) { + List arguments = jnlpd.getArguments(); + if (arguments.isEmpty()) { + return; + } + + String argsStr = ""; + for (int i = 0; i < arguments.size(); i++) { + String arg = arguments.get(i); + argsStr += quote(arg); + if ((i + 1) != arguments.size()) { + argsStr += " "; + } + } + + launchArgs.add("--arguments"); + if (Platform.isWindows()) { + if (argsStr.contains(" ")) { + if (argsStr.contains("\"")) { + argsStr = escapeQuote(argsStr); + } + argsStr = "\"" + argsStr + "\""; + } + } + launchArgs.add(argsStr); + } + + private void addJVMArgs(List launchArgs) { + List jvmArgs = jnlpd.getVMArgs(); + if (jvmArgs.isEmpty()) { + return; + } + + String jvmArgsStr = ""; + for (int i = 0; i < jvmArgs.size(); i++) { + String arg = jvmArgs.get(i); + jvmArgsStr += quote(arg); + if ((i + 1) != jvmArgs.size()) { + jvmArgsStr += " "; + } + } + + launchArgs.add("--jvm-args"); + if (Platform.isWindows()) { + if (jvmArgsStr.contains(" ")) { + if (jvmArgsStr.contains("\"")) { + jvmArgsStr = escapeQuote(jvmArgsStr); + } + jvmArgsStr = "\"" + jvmArgsStr + "\""; + } + } + launchArgs.add(jvmArgsStr); + } + + private String quote(String in) { + if (in == null) { + return null; + } + + if (in.isEmpty()) { + return ""; + } + + if (!in.contains("=")) { + // Not a property + if (in.contains(" ")) { + in = escapeQuote(in); + return "\"" + in + "\""; + } + return in; + } + + if (!in.contains(" ")) { + return in; // No need to quote + } + + int paramIndex = in.indexOf("="); + if (paramIndex <= 0) { + return in; // Something wrong, just skip quoting + } + + String param = in.substring(0, paramIndex); + String value = in.substring(paramIndex + 1); + + if (value.length() == 0) { + return in; // No need to quote + } + + value = escapeQuote(value); + + return param + "=" + "\"" + value + "\""; + } + + private String escapeQuote(String in) { + if (in == null) { + return null; + } + + if (in.isEmpty()) { + return ""; + } + + if (in.contains("\"")) { + // Use code points to preserve non-ASCII chars + StringBuilder sb = new StringBuilder(); + int codeLen = in.codePointCount(0, in.length()); + for (int i = 0; i < codeLen; i++) { + int code = in.codePointAt(i); + // Note: No need to escape '\' on Linux or OS X. + // jpackager expects us to pass arguments and properties with quotes and spaces as a map + // with quotes being escaped with additional \ for internal quotes. + // So if we want two properties below: + // -Djnlp.Prop1=Some "Value" 1 + // -Djnlp.Prop2=Some Value 2 + // jpackager will need: + // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\"" + // but since we using ProcessBuilder to run jpackager we will need to escape + // our escape symbols as well, so we will need to pass string below to ProcessBuilder: + // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\"" + switch (code) { + case '"': + // " -> \" -> \\\" + if (i == 0 || in.codePointAt(i - 1) != '\\') { + if (Platform.isWindows()) { + sb.appendCodePoint('\\'); + sb.appendCodePoint('\\'); + } + sb.appendCodePoint('\\'); + sb.appendCodePoint(code); + } + break; + case '\\': + // We need to escape already escaped symbols as well + if ((i + 1) < codeLen) { + int nextCode = in.codePointAt(i + 1); + if (nextCode == '"') { + // \" -> \\\" + sb.appendCodePoint('\\'); + sb.appendCodePoint('\\'); + sb.appendCodePoint('\\'); + sb.appendCodePoint(nextCode); + } else { + sb.appendCodePoint('\\'); + sb.appendCodePoint(code); + } + } else { + if (Platform.isWindows()) { + sb.appendCodePoint('\\'); + } + sb.appendCodePoint(code); + } + break; + default: + sb.appendCodePoint(code); + break; + } + } + return sb.toString(); + } + + return in; + } + + public synchronized String getDownloadFolder() { + if (downloadFolder == null) { + try { + File file; + if (options.keep() == null) { + Path path = Files.createTempDirectory("JNLPConverter"); + file = path.toFile(); + file.deleteOnExit(); + } else { + file = new File(options.keep()); + if (!file.exists()) { + file.mkdir(); + } + } + + downloadFolder = file.getAbsolutePath(); + } catch (IOException e) { + Log.error(e.getLocalizedMessage()); + } + } + + return downloadFolder; + } + + public final synchronized String getJnlpDownloadFolder() { + if (jnlpDownloadFolder == null) { + File file = new File(getDownloadFolder() + File.separator + "jnlp"); + file.mkdir(); + markFileToDelete(getDownloadFolder() + File.separator + "jnlp"); + jnlpDownloadFolder = file.getAbsolutePath(); + } + + return jnlpDownloadFolder; + } + + public static String getJnlpDownloadFolderStatic() { + return jnlpDownloadFolderStatic; + } + + public synchronized String getJarDownloadFolder() { + if (jarDownloadFolder == null) { + File file = new File(getDownloadFolder() + File.separator + "jar"); + file.mkdir(); + markFileToDelete(getDownloadFolder() + File.separator + "jar"); + jarDownloadFolder = file.getAbsolutePath(); + } + + return jarDownloadFolder; + } + + public synchronized String getIconDownloadFolder() { + if (iconDownloadFolder == null) { + File file = new File(getDownloadFolder() + File.separator + "icon"); + file.mkdir(); + markFileToDelete(getDownloadFolder() + File.separator + "icon"); + iconDownloadFolder = file.getAbsolutePath(); + } + + return iconDownloadFolder; + } + + public synchronized String getPropDownloadFolder() { + if (propDownloadFolder == null) { + File file = new File(getDownloadFolder() + File.separator + "prop"); + file.mkdir(); + markFileToDelete(getDownloadFolder() + File.separator + "prop"); + propDownloadFolder = file.getAbsolutePath(); + } + + return propDownloadFolder; + } + + public synchronized static String getJPackagerPath() { + if (jpackagerPath == null) { + jpackagerPath = System.getProperty("java.home"); + jpackagerPath += File.separator; + jpackagerPath += "bin"; + jpackagerPath += File.separator; + + Platform platform = Platform.getPlatform(); + switch (platform) { + case WINDOWS: + jpackagerPath += "jpackager.exe"; + break; + case LINUX: + jpackagerPath += "jpackager"; + break; + case MAC: + jpackagerPath += "jpackager"; + break; + default: + Log.error("Cannot determine platform type."); + break; + } + + Log.verbose("jpackager: " + jpackagerPath); + } + + return jpackagerPath; + } + + public static String getIconFormat(String icon) { + // GIF, JPEG, ICO, or PNG + if (icon.toLowerCase().endsWith(".gif")) { + return "GIF"; + } else if (icon.toLowerCase().endsWith(".jpg")) { + return "JPEG"; + } else if (icon.toLowerCase().endsWith(".ico")) { + return "ICO"; + } else if (icon.toLowerCase().endsWith(".png")) { + return "PNG"; + } + + return "UNKNOWN"; + } + + public static boolean isIconSupported(String icon) { + Platform platform = Platform.getPlatform(); + switch (platform) { + case WINDOWS: + if (icon.endsWith(".ico")) { + return true; + } else { + Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on Windows for file " + icon + "."); + return false; + } + case LINUX: + if (icon.endsWith(".png")) { + return true; + } else { + Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on Linux for file " + icon + "."); + return false; + } + case MAC: + Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on OS X for file " + icon + "."); + return false; + } + + return false; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Log.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Log.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + */ +package jnlp.converter; + +public class Log { + private static boolean verbose = false; + + public static void setVerbose(boolean verbose) { + Log.verbose = verbose; + } + + public static boolean isVerbose() { + return verbose; + } + + public static void verbose(String msg) { + if (verbose) { + System.out.println(msg); + } + } + + public static void info(String msg) { + System.out.println("Info: " + msg); + } + + public static void warning(String msg) { + System.err.println("Warning: " + msg); + } + + public static void error(String msg) { + System.err.println("Error: " + msg); + System.exit(1); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Main.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Main.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter; + +import java.io.File; + +public class Main { + + private static void showHelp() { + Options.showHelp(); + } + + private static void showVersion() { + System.out.println("Version: 1.0"); + } + + private static void createBundle(Options options) { + Log.verbose("Creating bundle for JNLP: " + options.getJNLP()); + Log.verbose("Output folder: " + options.getOutput()); + + JNLPConverter converter = new JNLPConverter(options); + converter.convert(); + } + + private static void validateJDK() { + String jpackagerPath = JNLPConverter.getJPackagerPath(); + File file = new File(jpackagerPath); + if (!file.exists()) { + Log.error("Cannot find " + jpackagerPath + ". Make sure you running JNLPConverter with supported JDK version."); + } + } + + public static void main(String[] args) { + Options options = Options.parseArgs(args); // Only valid options will be returned + + Log.setVerbose(options.verbose()); + + validateJDK(); + + if (options.help()) { + showHelp(); + } else if (options.version()) { + showVersion(); + } else { + createBundle(options); + } + + System.exit(0); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Options.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Options.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class Options { + + private boolean createImage = false; + private boolean createInstaller = false; + private String installerType = null; + private String jnlp = null; + private String output = null; + private String keep = null; + private boolean help = false; + private boolean verbose = false; + private boolean version = false; + private final List jpackagerOptions = new ArrayList<>(); + private boolean isRuntimeImageSet = false; + + private static final String JNLP_OPTION_PREFIX = "--jnlp="; + private static final String OUTPUT_OPTION_PREFIX = "--output="; + private static final String KEEP_OPTION_PREFIX = "--keep="; + private static final String JNLP_OPTION_SHORT_PREFIX = "-j"; + private static final String OUTPUT_OPTION_SHORT_PREFIX = "-o"; + private static final String KEEP_OPTION_SHORT_PREFIX = "-k"; + + private static final String [] INSTALLER_TYPES = {"msi", "rpm", "deb", + "dmg", "pkg", "pkg-app-store"}; + + // --output, -o, --input, -i, --files, -f, --main-jar, -j, --class, -c + private static final String [] BLOCKED_JPACKAGER_OPTIONS = {"--output", "-o", "--input", "-i", + "--files", "-f", "--main-jar", + "-j", "--class", "-c"}; + + private static final String RUNTIME_IMAGE_OPTION = "--runtime-image"; + + private static final String ERR_UNKNOWN_OPTION = "Unknown option: "; + private static final String ERR_MISSING_VALUE = "Value is required for option "; + private static final String ERR_MISSING_MODE = "Error: create-image or create-installer mode is required"; + private static final String ERR_MISSING_JNLP = "Error: --jnlp is required"; + private static final String ERR_MISSING_OUTPUT = "Error: --output is required"; + private static final String ERR_OUTPUT_EXISTS = "Error: output folder already exists"; + private static final String ERR_KEEP_EXISTS = "Error: folder for --keep argument already exists"; + private static final String ERR_INVALID_PROTOCOL_JNLP = "Error: Invalid protocol for JNLP file. Only HTTP, HTTPS and FILE protocols are supported."; + + public boolean createImage() { + return createImage; + } + + public boolean createInstaller() { + return createInstaller; + } + + public String getInstallerType() { + return installerType; + } + + public String getJNLP() { + return jnlp; + } + + public String getOutput() { + return output; + } + + public String keep() { + return keep; + } + + public boolean help() { + return help; + } + + public boolean verbose() { + return verbose; + } + + public boolean version() { + return version; + } + + public List getJPackagerOptions() { + return jpackagerOptions; + } + + public boolean isRuntimeImageSet() { + return isRuntimeImageSet; + } + + // Helper method to dump all options + private void display() { + System.out.println("Options:"); + System.out.println("createImage: " + createImage); + System.out.println("createInstaller: " + createInstaller); + System.out.println("installerType: " + installerType); + System.out.println("jnlp: " + jnlp); + System.out.println("output: " + output); + System.out.println("keep: " + keep); + System.out.println("help: " + help); + System.out.println("verbose: " + verbose); + System.out.println("version: " + version); + for (int i = 0; i < jpackagerOptions.size(); i++) { + System.out.println("jpackagerOptions[" + i + "]: " + jpackagerOptions.get(i)); + } + } + + private void validate() { + if (help || version) { + return; + } + + if (!createImage && !createInstaller) { + optionError(ERR_MISSING_MODE); + } + + if (jnlp == null) { + optionError(ERR_MISSING_JNLP); + } else { + int index = jnlp.indexOf(":"); + if (index == -1 || index == 0) { + optionError(ERR_INVALID_PROTOCOL_JNLP); + } else { + String protocol = jnlp.substring(0, index); + if (!protocol.equalsIgnoreCase("http") && + !protocol.equalsIgnoreCase("https") && + !protocol.equalsIgnoreCase("file")) { + optionError(ERR_INVALID_PROTOCOL_JNLP); + } + } + } + + if (output == null) { + optionError(ERR_MISSING_OUTPUT); + } else { + File file = new File(output); + if (file.exists()) { + optionErrorNoHelp(ERR_OUTPUT_EXISTS); + } + } + + if (keep != null) { + File file = new File(keep); + if (file.exists()) { + optionErrorNoHelp(ERR_KEEP_EXISTS); + } + } + + jpackagerOptions.forEach((option) -> { + if (isBlockedOption(option)) { + Log.error(option + " is not allowed via --jpackager-options, since it will conflict with " + + "same option generated by JNLPConverter."); + } + }); + } + + public boolean isOptionPresent(String option) { + for (String jpackagerOption : jpackagerOptions) { + if (jpackagerOption.equalsIgnoreCase(option)) { + return true; + } + } + + return false; + } + + private boolean isBlockedOption(String option) { + for (String blockedOption : BLOCKED_JPACKAGER_OPTIONS) { + if (blockedOption.equalsIgnoreCase(option)) { + return true; + } + } + + return false; + } + + public static void showHelp() { +// System.out.println("********* Help should not be longer then 80 characters as per JEP-293 *********"); + System.out.println("Usage: java -jar JNLPConverter.jar "); + System.out.println(""); + System.out.println("where mode is one of:"); + System.out.println(" create-image"); + System.out.println(" Generates a platform-specific application image."); + System.out.println(" create-installer "); + System.out.println(" Generates a platform-specific installer for the application."); + System.out.println(" Valid values for \"type\" are \"msi\", \"rpm\", \"deb\", \"dmg\", \"pkg\","); + System.out.println(" \"pkg-app-store\". If \"type\" is omitted, all supported types of installable"); + System.out.println(" packages for current platform will be generated."); + System.out.println(""); + System.out.println("Possible options include:"); + System.out.println(" -j, --jnlp "); + System.out.println(" Full path to JNLP file. Supported protocols are HTTP/HTTPS/FILE."); + System.out.println(" -o, --output "); + System.out.println(" Name of the directory where generated output files are placed."); + System.out.println(" -k, --keep "); + System.out.println(" Keep JNLP, JARs and command line arguments for jpackager"); + System.out.println(" in directory provided."); + System.out.println(" --jpackager-options "); + System.out.println(" Specify additional jpackager options or overwrite provided by JNLPConverter."); + System.out.println(" All jpackager options can be specified except: --output -o, --input -i,"); + System.out.println(" --files -f, --main-jar -j and --class -c."); + System.out.println(" -h, --help, -?"); + System.out.println(" Print this help message"); + System.out.println(" -v, --verbose"); + System.out.println(" Enable verbose output."); + System.out.println(" --version"); + System.out.println(" Version information."); + System.out.println("To specify an argument for a long option, you can use --= or"); + System.out.println("-- ."); + System.out.println("To specify proxy server use standard Java properties http.proxyHost and http.proxyPort."); + } + + private static boolean isInstallerType(String type) { + for (String installerType : INSTALLER_TYPES) { + if (installerType.equals(type)) { + return true; + } + } + + return false; + } + + public static Options parseArgs(String[] args) { + Options options = new Options(); + + int index = 0; + if (args.length >= 1) { + switch (args[0]) { + case "create-image": + options.createImage = true; + index = 1; + break; + case "create-installer": + options.createInstaller = true; + index = 1; + if (args.length >= 2) { + if (isInstallerType(args[1])) { + options.installerType = args[1]; + index = 2; + } + } + break; + case "-h": + case "--help": + case "-?": + case "--version": + break; + default: + optionError(Options.ERR_MISSING_MODE); + break; + } + } + + for (int i = index; i < args.length; i++) { + String arg = args[i]; + + if (arg.equals("--jnlp")) { + if (++i >= args.length) { + optionError(Options.ERR_MISSING_VALUE, "--jnlp"); + } + options.jnlp = args[i]; + } else if (arg.startsWith(JNLP_OPTION_PREFIX)) { + options.jnlp = arg.substring(JNLP_OPTION_PREFIX.length()); + } else if (arg.equals("--output")) { + if (++i >= args.length) { + optionError(Options.ERR_MISSING_VALUE, "--output"); + } + options.output = args[i]; + } else if (arg.startsWith(OUTPUT_OPTION_PREFIX)) { + options.output = arg.substring(OUTPUT_OPTION_PREFIX.length()); + } else if (arg.equals("--keep")) { + if (++i >= args.length) { + optionError(Options.ERR_MISSING_VALUE, "--keep"); + } + options.keep = args[i]; + } else if (arg.startsWith(KEEP_OPTION_PREFIX)) { + options.keep = arg.substring(KEEP_OPTION_PREFIX.length()); + } else if (arg.equals("--help")) { + options.help = true; + } else if (arg.equals("--verbose")) { + options.verbose = true; + } else if (arg.equals("--version")) { + options.version = true; + } else if (arg.equals("-j")) { // short options + if (++i >= args.length) { + optionError(Options.ERR_MISSING_VALUE, "-j"); + } + options.jnlp = args[i]; + } else if (arg.startsWith(JNLP_OPTION_SHORT_PREFIX)) { + options.jnlp = arg.substring(JNLP_OPTION_SHORT_PREFIX.length()); + } else if (arg.equals("-o")) { + if (++i >= args.length) { + optionError(Options.ERR_MISSING_VALUE, "-o"); + } + options.output = args[i]; + } else if (arg.startsWith(OUTPUT_OPTION_SHORT_PREFIX)) { + options.output = arg.substring(OUTPUT_OPTION_SHORT_PREFIX.length()); + } else if (arg.equals("-k")) { + if (++i >= args.length) { + optionError(Options.ERR_MISSING_VALUE, "-k"); + } + options.keep = args[i]; + } else if (arg.startsWith(KEEP_OPTION_SHORT_PREFIX)) { + options.keep = arg.substring(KEEP_OPTION_SHORT_PREFIX.length()); + } else if (arg.equals("-h") || arg.equals("-?")) { + options.help = true; + } else if (arg.equals("-v")) { + options.verbose = true; + } else if (arg.equals("--jpackager-options")) { + for (i = (i + 1); i < args.length; i++) { + if (!options.isRuntimeImageSet) { + if (args[i].equals(RUNTIME_IMAGE_OPTION)) { + options.isRuntimeImageSet = true; + } + } + options.jpackagerOptions.add(args[i]); + } + } else { + optionError(ERR_UNKNOWN_OPTION, arg); + } + } + + //options.display(); // For testing only + options.validate(); + + return options; + } + + private static void optionErrorNoHelp(String msg) { + System.out.println(msg); + System.exit(1); + } + + private static void optionError(String msg) { + System.out.println(msg); + System.out.println(); + showHelp(); + System.exit(1); + } + + private static void optionError(String msg, String option) { + System.out.println(msg + option); + System.out.println(); + showHelp(); + System.exit(1); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Platform.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/Platform.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter; + +import java.util.regex.Pattern; + +public enum Platform { + UNKNOWN, WINDOWS, LINUX, MAC; + private static final Platform platform; + private static final int majorVersion; + private static final int minorVersion; + + static { + String os = System.getProperty("os.name").toLowerCase(); + + if (os.contains("win")) { + platform = Platform.WINDOWS; + } else if (os.contains("nix") || os.contains("nux")) { + platform = Platform.LINUX; + } else if (os.contains("mac")) { + platform = Platform.MAC; + } else { + platform = Platform.UNKNOWN; + } + + String version = System.getProperty("os.version"); + String[] parts = version.split(Pattern.quote(".")); + + if (parts.length > 0) { + majorVersion = Integer.parseInt(parts[0]); + + if (parts.length > 1) { + minorVersion = Integer.parseInt(parts[0]); + } else { + minorVersion = -1; + } + } else { + majorVersion = -1; + minorVersion = -1; + } + } + + private Platform() { + } + + public static Platform getPlatform() { + return platform; + } + + public static boolean isWindows() { + return (platform == Platform.WINDOWS); + } + + public static int getMajorVersion() { + return majorVersion; + } + + public static int getMinorVersion() { + return minorVersion; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/GeneralUtil.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/GeneralUtil.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.util.Locale; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Handy class to add some utility methods for dealing with property matching + * etc. + */ +public class GeneralUtil { + + public static boolean prefixMatchStringList(String[] prefixList, String target) { + // No prefixes matches everything + if (prefixList == null) { + return true; + } + // No target, but a prefix list does not match anything + if (target == null) { + return false; + } + for (String prefix : prefixList) { + if (target.startsWith(prefix)) { + return true; + } + } + return false; + } + + private static String getOSArch() { + return System.getProperty("os.arch"); + } + + public static boolean prefixMatchArch(String[] prefixList) { + // No prefixes matches everything + if (prefixList == null) { + return true; + } + + // check for the current arch + String arch = getOSArch(); + for (String prefix : prefixList) { + if (arch.startsWith(prefix)) { + return true; + } + } + + return false; + } + + /** + * Converts a space delimited string to a list of strings + */ + public static String[] getStringList(String str) { + if (str == null) { + return null; + } + ArrayList list = new ArrayList<>(); + int i = 0; + int length = str.length(); + StringBuffer sb = null; + while (i < length) { + char ch = str.charAt(i); + switch (ch) { + case ' ': + // A space was hit. Add string to list + if (sb != null) { + list.add(sb.toString()); + sb = null; + } + break; + case '\\': + // It is a delimiter. Add next character + if (i + 1 < length) { + ch = str.charAt(++i); + if (sb == null) { + sb = new StringBuffer(); + } + sb.append(ch); + } + break; + default: + if (sb == null) { + sb = new StringBuffer(); + } sb.append(ch); + break; + } + i++; // Next character + } + // Make sure to add the last part to the list too + if (sb != null) { + list.add(sb.toString()); + } + if (list.isEmpty()) { + return null; + } + String[] results = new String[list.size()]; + return list.toArray(results); + } + + /** + * Checks if string list matches default locale + */ + public static boolean matchLocale(String[] localeList, Locale locale) { + // No locale specified matches everything + if (localeList == null) { + return true; + } + for (String localeList1 : localeList) { + if (matchLocale(localeList1, locale)) { + return true; + } + } + return false; + } + + /** + * Checks if string matches default locale + */ + public static boolean matchLocale(String localeStr, Locale locale) { + if (localeStr == null || localeStr.length() == 0) { + return true; + } + + // Compare against default locale + String language; + String country; + String variant; + + // The locale string is of the form language_country_variant + StringTokenizer st = new StringTokenizer(localeStr, "_", false); + if (st.hasMoreElements() && locale.getLanguage().length() > 0) { + language = st.nextToken(); + if (!language.equalsIgnoreCase(locale.getLanguage())) { + return false; + } + } + if (st.hasMoreElements() && locale.getCountry().length() > 0) { + country = st.nextToken(); + if (!country.equalsIgnoreCase(locale.getCountry())) { + return false; + } + } + if (st.hasMoreElements() && locale.getVariant().length() > 0) { + variant = st.nextToken(); + if (!variant.equalsIgnoreCase(locale.getVariant())) { + return false; + } + } + + return true; + } + + public static long heapValToLong(String heapValue) { + if (heapValue == null) { + return -1; + } + long multiplier = 1; + if (heapValue.toLowerCase().lastIndexOf('m') != -1) { + // units are megabytes, 1 megabyte = 1024 * 1024 bytes + multiplier = 1024 * 1024; + heapValue = heapValue.substring(0, heapValue.length() - 1); + } else if (heapValue.toLowerCase().lastIndexOf('k') != -1) { + // units are kilobytes, 1 kilobyte = 1024 bytes + multiplier = 1024; + heapValue = heapValue.substring(0, heapValue.length() - 1); + } + long theValue; + try { + theValue = Long.parseLong(heapValue); + theValue = theValue * multiplier; + } catch (NumberFormatException e) { + theValue = -1; + } + return theValue; + } + + public static byte[] readBytes(InputStream is, long size) throws IOException { + // Sanity on file size (restrict to 1M) + if (size > 1024 * 1024) { + throw new IOException("File too large"); + } + + BufferedInputStream bis; + if (is instanceof BufferedInputStream) { + bis = (BufferedInputStream) is; + } else { + bis = new BufferedInputStream(is); + } + + if (size <= 0) { + size = 10 * 1024; // Default to 10K + } + byte[] b = new byte[(int) size]; + int n; + int bytesRead = 0; + n = bis.read(b, bytesRead, b.length - bytesRead); + while (n != -1) { + bytesRead += n; + // Still room in array + if (b.length == bytesRead) { + byte[] bb = new byte[b.length * 2]; + System.arraycopy(b, 0, bb, 0, b.length); + b = bb; + } + // Read next line + n = bis.read(b, bytesRead, b.length - bytesRead); + } + bis.close(); + is.close(); + + if (bytesRead != b.length) { + byte[] bb = new byte[bytesRead]; + System.arraycopy(b, 0, bb, 0, bytesRead); + b = bb; + } + return b; + } + + public static String getOSFullName() { + return System.getProperty("os.name"); + } + + /** + * Makes sure a URL is a path URL, i.e., ends with '/' + */ + public static URL asPathURL(URL url) { + if (url == null) { + return null; + } + + String path = url.getFile(); + if (path != null && !path.endsWith("/")) { + try { + return new URL(url.getProtocol(), + url.getHost(), + url.getPort(), + url.getFile() + "/"); + } catch (MalformedURLException mue) { + // Should not happen + } + } + // Just return same URl + return url; + } + + public static Locale getDefaultLocale() { + return Locale.getDefault(); + } + + public static String toNormalizedString(URL u) { + if (u == null) { + return ""; + } + + try { + if (u.getPort() == u.getDefaultPort()) { + u = new URL(u.getProtocol().toLowerCase(), + u.getHost().toLowerCase(), -1, u.getFile()); + } else { + u = new URL(u.getProtocol().toLowerCase(), + u.getHost().toLowerCase(), u.getPort(), u.getFile()); + } + } catch (MalformedURLException ex) { + } + return u.toExternalForm(); + } + + public static boolean sameURLs(URL u1, URL u2) { + if (u1 == null || u2 == null || (u1 == u2)) { + return (u1 == u2); + } + //NB: do not use URL.sameFile() as it will do DNS lookup + // Also, do quick check before slow string comparisons + String f1 = u1.getFile(); + String f2 = u2.getFile(); + return (f1.length() == f2.length()) && sameBase(u1, u2) + && f1.equalsIgnoreCase(f2); + } + + public static boolean sameBase(URL u1, URL u2) { + return u1 != null && u2 != null && + sameHost(u1, u2) && samePort(u1, u2) && sameProtocol(u1, u2); + } + + private static boolean sameProtocol(URL u1, URL u2) { + //protocols are known to be lowercase + return u1.getProtocol().equals(u2.getProtocol()); + } + + private static boolean sameHost(URL u1, URL u2) { + String host = u1.getHost(); + String otherHost = u2.getHost(); + if (host == null || otherHost == null) { + return (host == null && otherHost == null); + } else { + //avoid slow comparison for strings of different length + return ((host.length() == otherHost.length()) + && host.equalsIgnoreCase(otherHost)); + } + } + + private static boolean samePort(URL u1, URL u2) { + return getPort(u1) == getPort(u2); + } + + public static int getPort(URL u) { + if (u.getPort() != -1) { + return u.getPort(); + } else { + return u.getDefaultPort(); + } + } + + public static URL getBase(URL url) { + if (url == null) return null; + String file = url.getFile(); + if (file != null) { + int idx = file.lastIndexOf('/'); + if (idx != -1 ) { + file = file.substring(0, idx + 1); + } + try { + return new URL( + url.getProtocol(), + url.getHost(), + url.getPort(), + file); + } catch(MalformedURLException mue) { + System.err.println(mue.getMessage()); + } + } + // Just return same URL + return url; + } + + private static String getEmbeddedVersionPath(String path, String version) { + int index = path.lastIndexOf("/"); + String filename = path.substring(index + 1); + path = path.substring(0, index + 1); + + String ext = null; + index = filename.lastIndexOf("."); + if (index != -1) { + ext = filename.substring(index + 1); + filename = filename.substring(0, index); + } + + StringBuilder filenameSB = new StringBuilder(filename); + if (version != null) { + filenameSB.append("__V"); + filenameSB.append(version); + } + if (ext != null) { + filenameSB.append("."); + filenameSB.append(ext); + } + + path += filenameSB.toString(); + return path; + } + + public static URL getEmbeddedVersionURL(URL u, String version) throws Exception { + if (u == null) { + return null; + } + + if (version == null || version.indexOf("*") != -1 + || version.indexOf("+") != -1) { + // Do not support * or + in version string + return u; + } + + URL versionURL = null; + + String protocol = u.getProtocol(); + String host = u.getHost(); + int port = u.getPort(); + String path = u.getPath(); + + path = getEmbeddedVersionPath(path, version); + + versionURL = new URL(protocol, host, port, path); + + return versionURL; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/JNLPDesc.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/JNLPDesc.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import jnlp.converter.Log; + +import jnlp.converter.parser.ResourcesDesc.JARDesc; +import jnlp.converter.parser.ResourcesDesc.JREDesc; + +public class JNLPDesc { + private String specVersion = null; + private String codebase = null; + private String version = null; + private String href = null; + private String name = null; + private String title = null; + private String vendor = null; + private String mainJar = null; + private String [] descriptions = null; + private IconDesc [] icons = null; + private ShortcutDesc shortcuts = null; + private AssociationDesc [] associations = null; + private String mainClass = null; + private final List arguments = new ArrayList<>(); + private final List files = new ArrayList<>(); + private final List resources = new ArrayList<>(); + private final List vmArgs = new ArrayList<>(); + private boolean isLibrary = false; + private boolean isInstaller = false; + private boolean isJRESet = false; + private ResourcesDesc resourcesDesc; + private boolean isVersionEnabled = false; + private boolean isSandbox = true; + private boolean isFXApp = false; + + public void setSpecVersion(String specVersion) { + this.specVersion = specVersion; + + // Valid values are 1.0, 1.5, 6.0, 6.0.10, 6.0.18, 7.0, 8.20, 9 or a wildcard such as 1.0+. + if (!specVersion.startsWith("1.0") && + !specVersion.startsWith("1.5") && + !specVersion.startsWith("6.0") && + !specVersion.startsWith("6.0.10") && + !specVersion.startsWith("6.0.18") && + !specVersion.startsWith("7.0") && + !specVersion.startsWith("8.20") && + !specVersion.startsWith("9")) { + System.out.println("Warning: Invalid version of the JNLP specification found: " + + specVersion + ". Valid values are 1.0, 1.5, 6.0, 6.0.10, 6.0.18, 7.0," + + " 8.20, 9 or a wildcard such as 1.0+."); + } + } + + public String getSpecVersion() { + return specVersion; + } + + public void setCodebase(String codebase) { + this.codebase = codebase; + } + + public String getCodebase() { + return codebase; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getVersion() { + return version; + } + + public void setHref(String href) { + this.href = href; + } + + public String getHref() { + return href; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + public void setVendor(String vendor) { + this.vendor = vendor; + } + + public String getVendor() { + return vendor; + } + + public void setMainJar(String mainJar) { + if (this.mainJar == null) { + this.mainJar = mainJar; + } else { + Log.warning("Main jar already set to '" + this.mainJar + "'. " + + "Attempt to set main jar to '" + mainJar + "' will be ignored."); + } + } + + public String getMainJar() { + return mainJar; + } + + public void setDescriptions(String [] descriptions) { + this.descriptions = descriptions; + } + + public String getDescription() { + String description = null; + if (descriptions != null) { + if (descriptions[InformationDesc.DESC_DEFAULT] != null) { + description = descriptions[InformationDesc.DESC_DEFAULT]; + } else if (descriptions[InformationDesc.DESC_SHORT] != null) { + description = descriptions[InformationDesc.DESC_SHORT]; + } else if (descriptions[InformationDesc.DESC_ONELINE] != null) { + description = descriptions[InformationDesc.DESC_ONELINE]; + } else if (descriptions[InformationDesc.DESC_TOOLTIP] != null) { + description = descriptions[InformationDesc.DESC_TOOLTIP]; + } + + if (description != null) { + if (description.contains("\r") || description.contains("\n")) { + Log.warning("Multiple lines of text in description is not supported and description will be converted to single line by replacing new lines with spaces."); + Log.warning("Original description:"); + Log.warning(description); + String descs[] = description.split("\n"); + description = ""; + for (String desc : descs) { + desc = desc.trim(); + if (desc.endsWith("\r")) { // In case new line was \r\n + if (desc.length() != 1) { + desc = desc.substring(0, desc.length() - 1); + } else { + continue; + } + } + + if (desc.isEmpty()) { + continue; + } + + if (!description.isEmpty()) { + description += " "; + } + + description += desc; + } + Log.warning("Converted description:"); + Log.warning(description); + } + } + } + + return description; + } + + public void setIcons(IconDesc [] icons) { + this.icons = icons; + } + + public IconDesc getIcon() { + for (IconDesc icon : icons) { + if (icon.getKind() == IconDesc.ICON_KIND_DEFAULT) { + return icon; + } + } + + for (IconDesc icon : icons) { + if (icon.getKind() == IconDesc.ICON_KIND_SHORTCUT) { + return icon; + } + } + + return null; + } + + public String getIconLocation() { + IconDesc icon = getIcon(); + if (icon != null) { + return icon.getLocalLocation(); + } + + return null; + } + + public void setShortcuts(ShortcutDesc shortcuts) { + this.shortcuts = shortcuts; + } + + public boolean isDesktopHint() { + if (shortcuts != null) { + return shortcuts.getDesktop(); + } + + return false; + } + + public boolean isMenuHint() { + if (shortcuts != null) { + return shortcuts.getMenu(); + } + + return false; + } + + public String getSubMenu() { + if (shortcuts != null) { + return shortcuts.getSubmenu(); + } + + return null; + } + + public void setAssociations(AssociationDesc [] associations) { + this.associations = associations; + } + + public AssociationDesc [] getAssociations() { + return associations; + } + + public void setMainClass(String mainClass, boolean isJavafxDesc) { + if (isJavafxDesc) { + this.mainClass = mainClass; + } else if (this.mainClass == null) { + this.mainClass = mainClass; + } + } + + public String getMainClass() { + return mainClass; + } + + public void addArguments(String argument) { + if (argument != null && !argument.isEmpty()) { + arguments.add(argument); + } + } + + public List getArguments() { + return arguments; + } + + public void setProperty(String name, String value) { + if (name.equalsIgnoreCase("jnlp.versionEnabled") && value.equalsIgnoreCase("true")) { + isVersionEnabled = true; + return; + } + + addVMArg("-D" + name + "=" + value); + } + + public boolean isVersionEnabled() { + return isVersionEnabled; + } + + public boolean isSandbox() { + return isSandbox; + } + + public void setIsSandbox(boolean value) { + isSandbox = value; + } + + public boolean isFXApp() { + return isFXApp; + } + + public void setIsFXApp(boolean value) { + isFXApp = value; + } + + public void addFile(String file) { + if (file != null) { + files.add(file); + } + } + + public List getFiles() { + return files; + } + + private boolean isResourceExists(JARDesc resource) { + for (JARDesc r : resources) { + if (r.getLocation().equals(resource.getLocation())) { + return true; + } + } + + return false; + } + + public void addResource(JARDesc resource) { + if (resource != null) { + if (isResourceExists(resource)) { + Log.warning("Ignoring repeated resource " + resource.getLocation()); + return; + } + resources.add(resource); + } + } + + public List getResources() { + return resources; + } + + public void addVMArg(String arg) { + if (arg != null) { + vmArgs.add(arg); + } + } + + public List getVMArgs() { + return vmArgs; + } + + public void setIsLibrary(boolean isLibrary) { + this.isLibrary = isLibrary; + } + + public boolean isLibrary() { + return isLibrary; + } + + public void setIsInstaller(boolean isInstaller) { + this.isInstaller = isInstaller; + } + + public boolean isInstaller() { + return isInstaller; + } + + public void setIsJRESet(boolean isJRESet) { + this.isJRESet = isJRESet; + } + + public boolean isJRESet() { + return isJRESet; + } + + public void setResourcesDesc(ResourcesDesc resourcesDesc) { + this.resourcesDesc = resourcesDesc; + } + + public ResourcesDesc getResourcesDesc() { + return resourcesDesc; + } + + public void parseResourceDesc() throws Exception { + if (resourcesDesc != null && !resourcesDesc.isEmpty()) { + setMainJar(resourcesDesc.getMainJar().getName()); + + JARDesc[] jars = resourcesDesc.getAllJarDescs(); + for (JARDesc jar : jars) { + addResource(jar); + } + + JREDesc jreDesc = resourcesDesc.getJreDesc(); + if (jreDesc != null) { + String [] args = jreDesc.getVmArgsList(); + if (args != null) { + for (String arg : args) { + addVMArg(arg); + } + } + + if (jreDesc.getMinHeap() != -1) { + addVMArg("-Xms" + jreDesc.getMinHeap()); + } + + if (jreDesc.getMaxHeap() != -1) { + addVMArg("-Xmx" + jreDesc.getMaxHeap()); + } + } + + Properties props = resourcesDesc.getResourceProperties(); + Enumeration e = props.propertyNames(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + String value = props.getProperty(key); + setProperty(key, value); + } + } + } + + public static class InformationDesc { + + private final String _title; + private final String _vendor; + private final String[] _descriptions; + private final IconDesc[] _icons; + private ShortcutDesc _shortcutHints; + private AssociationDesc[] _associations; + + public InformationDesc(String title, String vendor, + String[] descriptions, + IconDesc[] icons, + ShortcutDesc shortcutHints, + AssociationDesc[] associations) { + _title = (title == null) ? "" : title; + _vendor = (vendor == null) ? "" : vendor; + if (descriptions == null) { + descriptions = new String[NOF_DESC]; + } + _descriptions = descriptions; + _icons = icons; + _shortcutHints = shortcutHints; + _associations = associations; + } + + /** + * Constants for the getInfoDescription + */ + final public static int DESC_DEFAULT = 0; + final public static int DESC_SHORT = 1; + final public static int DESC_ONELINE = 2; + final public static int DESC_TOOLTIP = 3; + final public static int NOF_DESC = 4; + + /** + * Information + */ + public String getTitle() { + return _title; + } + + public String getVendor() { + return _vendor; + } + + public IconDesc[] getIcons() { + return _icons; + } + + public ShortcutDesc getShortcut() { + if (_shortcutHints == null) { + return null; + } + return new ShortcutDesc(_shortcutHints.getDesktop(), _shortcutHints.getMenu(), _shortcutHints.getSubmenu()); + } + + public AssociationDesc[] getAssociations() { + return _associations; + } + + /** + * Sets new shortcut hints. + * + * @param shortcutDesc the new shortcut hints to set + */ + public void setShortcut(ShortcutDesc shortcut) { + _shortcutHints = shortcut; + } + + /** + * Sets new associations. + * + * @param assoc the association to set + */ + public void setAssociation(AssociationDesc assoc) { + if (assoc == null) { + _associations = null; + } else { + _associations = new AssociationDesc[]{assoc}; + } + } + + /** + * Returns the description of the given kind. will return null if none + * there + */ + public String getDescription(int kind) { + return _descriptions[kind]; + } + + public String[] getDescription() { + return _descriptions; + } + } + + public static class IconDesc { + + private final String _location; + private String _localLocation; + private final int _kind; + + final public static int ICON_KIND_DEFAULT = 0; + final public static int ICON_KIND_SHORTCUT = 5; + + public IconDesc(URL location, int kind) { + _location = location.toExternalForm(); + _kind = kind; + } + + public String getLocation() { + return _location; + } + + public void setLocalLocation(String localLocation) { + _localLocation = localLocation; + } + + public String getLocalLocation() { + return _localLocation; + } + + public int getKind() { + return _kind; + } + } + + public static class ShortcutDesc { + + private final boolean _desktop; + private final boolean _menu; + private final String _submenu; + + public ShortcutDesc(boolean desktop, boolean menu, String submenu) { + _desktop = desktop; + _menu = menu; + _submenu = submenu; + } + + public boolean getDesktop() { + return _desktop; + } + + public boolean getMenu() { + return _menu; + } + + public String getSubmenu() { + return _submenu; + } + } + + public static class AssociationDesc { + + private final String _extensions; + private final String _mimeType; + private final String _description; + private final String _icon; + private String _iconLocalLocation; + + public AssociationDesc(String extensions, String mimeType, String description, URL icon) { + _extensions = extensions; + _mimeType = mimeType; + _description = description; + _icon = (icon != null) ? icon.toExternalForm() : null; + } + + public void setIconLocalLocation(String localLocation) { + _iconLocalLocation = localLocation; + } + + public String getIconLocalLocation() { + return _iconLocalLocation; + } + + public String getExtensions() { + return _extensions; + } + + public String getMimeType() { + return _mimeType; + } + + public String getMimeDescription() { + return _description; + } + + public String getIconUrl() { + return _icon; + } + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/ResourceType.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/ResourceType.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +/* + * Public super class for all resource entries + */ +interface ResourceType { + /** Visit this specific type */ + void visit(ResourceVisitor visitor) throws Exception; +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/ResourceVisitor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/ResourceVisitor.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import jnlp.converter.parser.ResourcesDesc.JARDesc; +import jnlp.converter.parser.ResourcesDesc.PropertyDesc; +import jnlp.converter.parser.ResourcesDesc.JREDesc; +import jnlp.converter.parser.ResourcesDesc.ExtensionDesc; + +/** + * A visitor class for the various ResourceType objects + * with dummy visit methods for all types. + */ +public class ResourceVisitor { + + public void visitJARDesc(JARDesc jad) { + } + + public void visitPropertyDesc(PropertyDesc prd) { + } + + public void visitExtensionDesc(ExtensionDesc ed) throws Exception { + } + + public void visitJREDesc(JREDesc jrd) { + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/ResourcesDesc.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/ResourcesDesc.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.net.URL; +import java.util.Properties; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import jnlp.converter.HTTPHelper; + +/** + * This class contains information about the codebase and properties, i.e., how + * to locate the classes and optional-packages + */ +public class ResourcesDesc implements ResourceType { + + private final List _list; + private volatile JNLPDesc _parent = null; + + /** + * Create empty resource list + */ + public ResourcesDesc() { + _list = new CopyOnWriteArrayList<>(); + } + + public JNLPDesc getParent() { + return _parent; + } + + void setParent(JNLPDesc parent) { + _parent = parent; + for (int i = 0; i < _list.size(); i++) { + Object o = _list.get(i); + if (o instanceof JREDesc) { + JREDesc jredesc = (JREDesc) o; + if (jredesc.getNestedResources() != null) { + jredesc.getNestedResources().setParent(parent); + } + } + } + } + + public void addResource(ResourceType rd) { + if (rd != null) { + _list.add(rd); + } + } + + boolean isEmpty() { + return _list.isEmpty(); + } + + public JARDesc[] getLocalJarDescs() { + ArrayList jds = new ArrayList<>(_list.size()); + for (ResourceType rt : _list) { + if (rt instanceof JARDesc) { + jds.add((JARDesc) rt); + } + } + return jds.toArray(new JARDesc[jds.size()]); + } + + public JREDesc getJreDesc() { + for (ResourceType rt : _list) { + if (rt instanceof JREDesc) { + return (JREDesc)rt; + } + } + + return null; + } + + public ExtensionDesc[] getExtensionDescs() throws Exception { + final ArrayList extList = new ArrayList<>(); + visit(new ResourceVisitor() { + @Override + public void visitExtensionDesc(ExtensionDesc ed) throws Exception { + // add all extensiondesc recursively + addExtToList(extList); + } + }); + return extList.toArray(new ExtensionDesc[extList.size()]); + } + + public JARDesc[] getAllJarDescs() throws Exception { + List jarList = new ArrayList<>(); + addJarsToList(jarList); + return jarList.toArray(new JARDesc[jarList.size()]); + } + + /** + * Add to a list of all the ExtensionDesc. This method goes recusivly through + * all ExtensionDesc + */ + private void addExtToList(final List list) throws Exception { + // Iterate through list an add ext jnlp to the list. + visit(new ResourceVisitor() { + @Override + public void visitExtensionDesc(ExtensionDesc ed) throws Exception { + if (ed.getExtensionDesc() != null) { + ed.getExtensionDesc().getMainJar(); + ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc(); + if (rd != null) { + rd.addExtToList(list); + } + } + list.add(ed); + } + }); + } + + private void addJarsToList(final List list) throws Exception { + + // Iterate through list an add resources to the list. + // The ordering of resources are preserved + visit(new ResourceVisitor() { + @Override + public void visitJARDesc(JARDesc jd) { + list.add(jd); + } + + @Override + public void visitExtensionDesc(ExtensionDesc ed) throws Exception { + if (ed.getExtensionDesc() != null) { + ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc(); + if (rd != null) { + rd.addJarsToList(list); + } + } + } + }); + } + + /** + * Get all the resources needed when a specific resource is requested. + * Returns null if no resource was found + */ + public JARDesc[] getResource(final URL location) throws Exception { + final JARDesc[] resources = new JARDesc[1]; + // Find the given resource + visit(new ResourceVisitor() { + @Override + public void visitJARDesc(JARDesc jd) { + if (GeneralUtil.sameURLs(jd.getLocation(), location)) { + resources[0] = jd; + } + } + }); + + // Found no resource? + if (resources[0] == null) { + return null; + } + + // No part, so just one resource + return resources; + } + + /* Returns the Expected Main Jar + * first jar with attribute main="true" + * else first jar if none has that attribute + * will look in extensions, and nested resource blocks if matching + */ + protected JARDesc getMainJar() throws Exception { + // Normal trick to get around final arguments to inner classes + final JARDesc[] results = new JARDesc[2]; + + visit(new ResourceVisitor() { + @Override + public void visitJARDesc(JARDesc jd) { + if (jd.isJavaFile()) { + // Keep track of first Java File + if (results[0] == null || results[0].isNativeLib()) { + results[0] = jd; + } + // Keep tack of Java File marked main + if (jd.isMainJarFile()) { + results[1] = jd; + } + } else if (jd.isNativeLib()) { + // if jnlp extension has only native lib + if (results[0] == null) { + results[0] = jd; + } + } + } + + @Override + public void visitExtensionDesc(ExtensionDesc ed) throws Exception { + // only check if no main yet and it is not an installer + if (results[1] == null && !ed.isInstaller()) { + JNLPDesc extLd = ed.getExtensionDesc(); + if (extLd != null && extLd.isLibrary()) { + ResourcesDesc rd = extLd.getResourcesDesc(); + if (rd != null) { + // look for main jar in extension resource + rd.visit(this); + } + } + } + } + }); + + // Default is the first, if none is specified as main. This might + // return NULL if there is no JAR resources. + JARDesc first = results[0]; + JARDesc main = results[1]; + + // if main is null then return first; + // libraries have no such thing as a main jar, so return first; + // otherwise return main + // only returns null if there are no jars. + return (main == null) ? first : main; + } + + /* + * Get the properties defined for this object + */ + public Properties getResourceProperties() throws Exception { + final Properties props = new Properties(); + visit(new ResourceVisitor() { + @Override + public void visitPropertyDesc(PropertyDesc pd) { + props.setProperty(pd.getKey(), pd.getValue()); + } + + @Override + public void visitExtensionDesc(ExtensionDesc ed) throws Exception { + JNLPDesc jnlpd = ed.getExtensionDesc(); + ResourcesDesc rd = jnlpd.getResourcesDesc(); + if (rd != null) { + Properties extProps = rd.getResourceProperties(); + Enumeration e = extProps.propertyNames(); + while (e.hasMoreElements()) { + String key = (String) e.nextElement(); + String value = extProps.getProperty(key); + props.setProperty(key, value); + } + } + } + }); + return props; + } + + /* + * Get the properties defined for this object, in the right order. + */ + public List getResourcePropertyList() throws Exception { + final LinkedList propList = new LinkedList<>(); + visit(new ResourceVisitor() { + @Override + public void visitPropertyDesc(PropertyDesc pd) { + propList.add(new Property(pd.getKey(), pd.getValue())); + } + }); + return propList; + } + + /** + * visitor dispatch + */ + @Override + public void visit(ResourceVisitor rv) throws Exception { + for (int i = 0; i < _list.size(); i++) { + ResourceType rt = _list.get(i); + rt.visit(rv); + } + } + + public void addNested(ResourcesDesc nested) throws Exception { + if (nested != null) { + nested.visit(new ResourceVisitor() { + @Override + public void visitJARDesc(JARDesc jd) { + _list.add(jd); + } + + @Override + public void visitPropertyDesc(PropertyDesc pd) { + _list.add(pd); + } + + @Override + public void visitExtensionDesc(ExtensionDesc ed) { + _list.add(ed); + } + }); + } + + } + + public static class JARDesc implements ResourceType { + + private URL _location; + private String _locationString; + private String _version; + private boolean _isNativeLib; + private boolean _isMainFile; // Only used for Java JAR files (a main JAR file is implicitly eager) + private ResourcesDesc _parent; // Back-pointer to the Resources that contains this JAR + + public JARDesc(URL location, String version, boolean isMainFile, boolean isNativeLib, ResourcesDesc parent) { + _location = location; + _locationString = GeneralUtil.toNormalizedString(location); + _version = version; + _isMainFile = isMainFile; + _isNativeLib = isNativeLib; + _parent = parent; + } + + /** + * Type of JAR resource + */ + public boolean isNativeLib() { + return _isNativeLib; + } + + public boolean isJavaFile() { + return !_isNativeLib; + } + + /** + * Returns URL/version for JAR file + */ + public URL getVersionLocation() throws Exception { + if (getVersion() == null) { + return _location; + } else { + return GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion()); + } + } + + public URL getLocation() { + return _location; + } + + public String getVersion() { + return _version; + } + + public String getName() { + // File can be separated by '/' or '\\' + int index; + int index1 = _locationString.lastIndexOf('/'); + int index2 = _locationString.lastIndexOf('\\'); + + if (index1 >= index2) { + index = index1; + } else { + index = index2; + } + + if (index != -1) { + return _locationString.substring(index + 1, _locationString.length()); + } + + return null; + } + + /** + * Returns if this is the main JAR file + */ + public boolean isMainJarFile() { + return _isMainFile; + } + + /** + * Get parent LaunchDesc + */ + public ResourcesDesc getParent() { + return _parent; + } + + /** + * Visitor dispatch + */ + public void visit(ResourceVisitor rv) { + rv.visitJARDesc(this); + } + } + + public static class PropertyDesc implements ResourceType { + + private String _key; + private String _value; + + public PropertyDesc(String key, String value) { + _key = key; + _value = value; + } + + // Accessors + public String getKey() { + return _key; + } + + public String getValue() { + return _value; + } + + /** + * Visitor dispatch + */ + public void visit(ResourceVisitor rv) { + rv.visitPropertyDesc(this); + } + + } + + public static class JREDesc implements ResourceType { + + private String _version; + private long _maxHeap; + private long _minHeap; + private String _vmargs; + private ResourcesDesc _resourceDesc; + private JNLPDesc _extensioDesc; + private String _archList; + + /* + * Constructor to create new instance based on the requirements from JNLP file. + */ + public JREDesc(String version, long minHeap, long maxHeap, String vmargs, + ResourcesDesc resourcesDesc, String archList) { + + _version = version; + _maxHeap = maxHeap; + _minHeap = minHeap; + _vmargs = vmargs; + _resourceDesc = resourcesDesc; + _extensioDesc = null; + _archList = archList; + } + + public String[] getArchList() { + return GeneralUtil.getStringList(_archList); + } + + public String getVersion() { + return _version; + } + + public long getMinHeap() { + return _minHeap; + } + + public long getMaxHeap() { + return _maxHeap; + } + + public String getVmArgs() { + return _vmargs; + } + + public String[] getVmArgsList() { + return GeneralUtil.getStringList(_vmargs); + } + + public ResourcesDesc getNestedResources() { + return _resourceDesc; + } + + public JNLPDesc getExtensionDesc() { + return _extensioDesc; + } + + public void setExtensionDesc(JNLPDesc ld) { + _extensioDesc = ld; + } + + /* visitor dispatch */ + public void visit(ResourceVisitor rv) { + rv.visitJREDesc(this); + } + } + + public static class Property implements Cloneable { + + public static final String JNLP_VERSION_ENABLED = "jnlp.versionEnabled"; + + String key; + String value; + + public Property(String spec) { + spec = spec.trim(); + if (!spec.startsWith("-D") || spec.length() < 3) { + throw new IllegalArgumentException("Property invalid"); + } + + int endKey = spec.indexOf("="); + if (endKey < 0) { + // it's legal to have no assignment + this.key = spec.substring(2); // skip "-D" + this.value = ""; + } else { + this.key = spec.substring(2, endKey); + this.value = spec.substring(endKey + 1); + } + } + + public static Property createProperty(String spec) { + Property prop = null; + try { + prop = new Property(spec); + } catch (IllegalArgumentException iae) { + } + return prop; + } + + public Property(String key, String value) { + this.key = key; + if (value != null) { + this.value = value; + } else { + this.value = ""; + } + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + // @return String representation, unquoted, unified presentation + public String toString() { + if (value.length() == 0) { + return "-D" + key; + } + return "-D" + key + "=" + value; + } + + public void addTo(Properties props) { + props.setProperty(key, value); + } + + // Hash Object + public boolean equals(Object o) { + if (!(o instanceof Property)) { + return false; + } + Property op = (Property) o; + int hashTheirs = op.hashCode(); + int hashThis = hashCode(); + return hashTheirs == hashThis; + } + + public int hashCode() { + return key.hashCode(); + } + + private static List jnlpProps = Arrays.asList(new Object[]{ + JNLP_VERSION_ENABLED + }); + + public static boolean isJnlpProperty(String spec) { + try { + Property p = new Property(spec); + return isJnlpPropertyKey(p.getKey()); + } catch (Exception e) { + return false; + } + } + + public static boolean isJnlpPropertyKey(String key) { + return key != null && jnlpProps.contains(key); + } + } + + public static class ExtensionDesc implements ResourceType { + // Tag elements + + private final URL _location; + private final String _locationString; + private final String _version; + private final URL _codebase; + + // Link to launchDesc + private JNLPDesc _extensionLd; // Link to launchDesc for extension + + public ExtensionDesc(URL location, String version) { + _location = location; + _locationString = GeneralUtil.toNormalizedString(location); + _version = version; + _codebase = GeneralUtil.asPathURL(GeneralUtil.getBase(location)); + _extensionLd = null; + } + + public boolean isInstaller() throws Exception { + if (getExtensionDesc() != null) { + return _extensionLd.isInstaller(); + } + return false; + } + + public URL getLocation() { + return _location; + } + + public String getVersionLocation() throws Exception { + if (getVersion() == null) { + return _locationString; + } else { + return GeneralUtil.toNormalizedString(GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion())); + } + } + + public String getVersion() { + return _version; + } + + public URL getCodebase() { + return _codebase; + } + + /* + * Information about the resources + */ + public JNLPDesc getExtensionDesc() throws Exception { + if (_extensionLd == null) { + byte[] bits = HTTPHelper.getJNLPBits(getVersionLocation(), _locationString); + _extensionLd = XMLFormat.parse(bits, getCodebase(), getVersionLocation()); + } + return _extensionLd; + } + + public void setExtensionDesc(JNLPDesc desc) { + _extensionLd = desc; + } + + /** + * Visitor dispatch + */ + public void visit(ResourceVisitor rv) throws Exception { + rv.visitExtensionDesc(this); + } + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/VersionID.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/VersionID.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * VersionID contains a JNLP version ID. + * + * The VersionID also contains a prefix indicator that can + * be used when stored with a VersionString + * + */ +public class VersionID implements Comparable { + private final String[] _tuple; // Array of Integer or String objects + private final boolean _usePrefixMatch; // star (*) prefix + private final boolean _useGreaterThan; // plus (+) greather-than + private final boolean _isCompound; // and (&) operator + private final VersionID _rest; // remaining part after the & + + /** + * Creates a VersionID object from a given String. + * @param str version string to parse + */ + public VersionID(String str) { + if (str == null || str.length() == 0) { + _tuple = new String[0]; + _useGreaterThan = false; + _usePrefixMatch = false; + _isCompound = false; + _rest = null; + return; + } + + // Check for compound + int amp = str.indexOf("&"); + if (amp >= 0) { + _isCompound = true; + VersionID firstPart = new VersionID(str.substring(0, amp)); + _rest = new VersionID(str.substring(amp+1)); + _tuple = firstPart._tuple; + _usePrefixMatch = firstPart._usePrefixMatch; + _useGreaterThan = firstPart._useGreaterThan; + } else { + _isCompound = false; + _rest = null; + // Check for postfix + if (str.endsWith("+")) { + _useGreaterThan = true; + _usePrefixMatch = false; + str = str.substring(0, str.length() - 1); + } else if (str.endsWith("*")) { + _useGreaterThan = false; + _usePrefixMatch = true; + str = str.substring(0, str.length() - 1); + } else { + _useGreaterThan = false; + _usePrefixMatch = false; + } + + ArrayList list = new ArrayList<>(); + int start = 0; + for (int i = 0; i < str.length(); i++) { + // Split at each separator character + if (".-_".indexOf(str.charAt(i)) != -1) { + if (start < i) { + String value = str.substring(start, i); + list.add(value); + } + start = i + 1; + } + } + if (start < str.length()) { + list.add(str.substring(start, str.length())); + } + _tuple = list.toArray(new String[0]); + } + } + + /** @return true if no flags are set */ + public boolean isSimpleVersion() { + return !_useGreaterThan && !_usePrefixMatch && !_isCompound; + } + + /** Match 'this' versionID against vid. + * The _usePrefixMatch/_useGreaterThan flag is used to determine if a + * prefix match of an exact match should be performed + * if _isCompound, must match _rest also. + */ + public boolean match(VersionID vid) { + if (_isCompound) { + if (!_rest.match(vid)) { + return false; + } + } + return (_usePrefixMatch) ? this.isPrefixMatchTuple(vid) : + (_useGreaterThan) ? vid.isGreaterThanOrEqualTuple(this) : + matchTuple(vid); + } + + /** Compares if two version IDs are equal */ + @Override + public boolean equals(Object o) { + if (matchTuple(o)) { + VersionID ov = (VersionID) o; + if (_rest == null || _rest.equals(ov._rest)) { + if ((_useGreaterThan == ov._useGreaterThan) && + (_usePrefixMatch == ov._usePrefixMatch)) { + return true; + } + } + } + return false; + } + + /** Computes a hash code for a VersionID */ + @Override + public int hashCode() { + boolean first = true; + int hashCode = 0; + for (String tuple : _tuple) { + if (first) { + first = false; + hashCode = tuple.hashCode(); + } else { + hashCode = hashCode ^ tuple.hashCode(); + } + } + return hashCode; + } + + /** Compares if two version IDs are equal */ + private boolean matchTuple(Object o) { + // Check for null and type + if (o == null || !(o instanceof VersionID)) { + return false; + } + VersionID vid = (VersionID) o; + + // Normalize arrays + String[] t1 = normalize(_tuple, vid._tuple.length); + String[] t2 = normalize(vid._tuple, _tuple.length); + + // Check contents + for (int i = 0; i < t1.length; i++) { + Object o1 = getValueAsObject(t1[i]); + Object o2 = getValueAsObject(t2[i]); + if (!o1.equals(o2)) { + return false; + } + } + + return true; + } + + private Object getValueAsObject(String value) { + if (value.length() > 0 && value.charAt(0) != '-') { + try { + return Integer.valueOf(value); + } catch (NumberFormatException nfe) { + /* fall through */ + } + } + return value; + } + + public boolean isGreaterThan(VersionID vid) { + if (vid == null) { + return false; + } + return isGreaterThanOrEqualHelper(vid, false, true); + } + + public boolean isGreaterThanOrEqual(VersionID vid) { + if (vid == null) { + return false; + } + return isGreaterThanOrEqualHelper(vid, true, true); + } + + boolean isGreaterThanOrEqualTuple(VersionID vid) { + return isGreaterThanOrEqualHelper(vid, true, false); + } + + /** Compares if 'this' is greater than vid */ + private boolean isGreaterThanOrEqualHelper(VersionID vid, + boolean allowEqual, boolean useRest) { + + if (useRest && _isCompound) { + if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual, true)) { + return false; + } + } + // Normalize the two strings + String[] t1 = normalize(_tuple, vid._tuple.length); + String[] t2 = normalize(vid._tuple, _tuple.length); + + for (int i = 0; i < t1.length; i++) { + // Compare current element + Object e1 = getValueAsObject(t1[i]); + Object e2 = getValueAsObject(t2[i]); + if (e1.equals(e2)) { + // So far so good + } else { + if (e1 instanceof Integer && e2 instanceof Integer) { + // if both can be parsed as ints, compare ints + return ((Integer)e1).intValue() > ((Integer)e2).intValue(); + } else { + if (e1 instanceof Integer) { + return false; // e1 (numeric) < e2 (non-numeric) + } else if (e2 instanceof Integer) { + return true; // e1 (non-numeric) > e2 (numeric) + } + + String s1 = t1[i]; + String s2 = t2[i]; + + return s1.compareTo(s2) > 0; + } + + } + } + // If we get here, they are equal + return allowEqual; + } + + /** Checks if 'this' is a prefix of vid */ + private boolean isPrefixMatchTuple(VersionID vid) { + + // Make sure that vid is at least as long as the prefix + String[] t2 = normalize(vid._tuple, _tuple.length); + + for (int i = 0; i < _tuple.length; i++) { + Object e1 = _tuple[i]; + Object e2 = t2[i]; + if (e1.equals(e2)) { + // So far so good + } else { + // Not a prefix + return false; + } + } + return true; + } + + /** Normalize an array to a certain lengh */ + private String[] normalize(String[] list, int minlength) { + if (list.length < minlength) { + // Need to do padding + String[] newlist = new String[minlength]; + System.arraycopy(list, 0, newlist, 0, list.length); + Arrays.fill(newlist, list.length, newlist.length, "0"); + return newlist; + } else { + return list; + } + } + + @Override + public int compareTo(VersionID o) { + if (o == null || !(o instanceof VersionID)) { + return -1; + } + VersionID vid = o; + return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1); + } + + /** Show it as a string */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < _tuple.length - 1; i++) { + sb.append(_tuple[i]); + sb.append('.'); + } + if (_tuple.length > 0) { + sb.append(_tuple[_tuple.length - 1]); + } + if (_useGreaterThan) { + sb.append('+'); + } + if (_usePrefixMatch) { + sb.append('*'); + } + if (_isCompound) { + sb.append("&"); + sb.append(_rest); + } + return sb.toString(); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/VersionString.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/VersionString.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.util.List; +import java.util.ArrayList; +import java.util.StringTokenizer; + +/* + * Utility class that knows to handle version strings + * A version string is of the form: + * + * (version-id ('+'?) ' ') * + * + */ +public class VersionString { + private final ArrayList _versionIds; + + /** Constructs a VersionString object from string */ + public VersionString(String vs) { + _versionIds = new ArrayList<>(); + if (vs != null) { + StringTokenizer st = new StringTokenizer(vs, " ", false); + while (st.hasMoreElements()) { + // Note: The VersionID class takes care of a postfixed '+' + _versionIds.add(new VersionID(st.nextToken())); + } + } + } + + public VersionString(VersionID id) { + _versionIds = new ArrayList<>(); + if (id != null) { + _versionIds.add(id); + } + } + + public boolean isSimpleVersion() { + if (_versionIds.size() == 1) { + return _versionIds.get(0).isSimpleVersion(); + } + return false; + } + + /** Check if this VersionString object contains the VersionID m */ + public boolean contains(VersionID m) { + for (int i = 0; i < _versionIds.size(); i++) { + VersionID vi = _versionIds.get(i); + boolean check = vi.match(m); + if (check) { + return true; + } + } + return false; + } + + /** Check if this VersionString object contains the VersionID m, given as a string */ + public boolean contains(String versionid) { + return contains(new VersionID(versionid)); + } + + /** Check if this VersionString object contains anything greater than m */ + public boolean containsGreaterThan(VersionID m) { + for (int i = 0; i < _versionIds.size(); i++) { + VersionID vi = _versionIds.get(i); + boolean check = vi.isGreaterThan(m); + if (check) { + return true; + } + } + return false; + } + + /** Check if this VersionString object contains anything greater than the VersionID m, given as a string */ + public boolean containsGreaterThan(String versionid) { + return containsGreaterThan(new VersionID(versionid)); + } + + /** Check if the versionString 'vs' contains the VersionID 'vi' */ + public static boolean contains(String vs, String vi) { + return (new VersionString(vs)).contains(vi); + } + + /** Pretty-print object */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < _versionIds.size(); i++) { + sb.append(_versionIds.get(i).toString()); + sb.append(' '); + } + return sb.toString(); + } + + public List getAllVersionIDs() { + return _versionIds; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/XMLFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/XMLFormat.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.net.URL; +import java.util.Arrays; +import java.util.ArrayList; +import jnlp.converter.JNLPConverter; +import jnlp.converter.parser.exception.MissingFieldException; +import jnlp.converter.parser.exception.BadFieldException; +import jnlp.converter.parser.exception.JNLParseException; +import jnlp.converter.parser.xml.XMLEncoding; +import jnlp.converter.parser.xml.XMLParser; +import jnlp.converter.parser.xml.XMLNode; +import jnlp.converter.parser.JNLPDesc.AssociationDesc; +import jnlp.converter.parser.JNLPDesc.IconDesc; +import jnlp.converter.parser.JNLPDesc.InformationDesc; +import jnlp.converter.parser.JNLPDesc.ShortcutDesc; +import jnlp.converter.parser.ResourcesDesc.JARDesc; +import jnlp.converter.parser.ResourcesDesc.JREDesc; +import jnlp.converter.Log; +import jnlp.converter.parser.ResourcesDesc.ExtensionDesc; +import jnlp.converter.parser.ResourcesDesc.PropertyDesc; +import org.xml.sax.SAXParseException; + +public class XMLFormat { + + public static XMLNode parseBits(byte[] bits) throws JNLParseException { + return parse(decode(bits)); + } + + private static String decode(byte[] bits) throws JNLParseException { + try { + return XMLEncoding.decodeXML(bits); + } catch (Exception e) { + throw new JNLParseException(e, + "exception determining encoding of jnlp file", 0); + } + } + + private static XMLNode parse(String source) throws JNLParseException { + try { + return (new XMLParser(source).parse()); + } catch (SAXParseException spe) { + throw new JNLParseException(spe, + "exception parsing jnlp file", spe.getLineNumber()); + } catch (Exception e) { + throw new JNLParseException(e, + "exception parsing jnlp file", 0); + } + } + + /** + * thisCodebase, if set, is used to determine the codebase, + * if JNLP codebase is not absolute. + * + * @param thisCodebase base URL of this JNLPDesc location + */ + public static JNLPDesc parse(byte[] bits, URL thisCodebase, String jnlp) + throws Exception { + + JNLPDesc jnlpd = new JNLPDesc(); + String source = decode(bits).trim(); + XMLNode root = parse(source); + + if (root == null || root.getName() == null) { + throw new JNLParseException(null, null, 0); + } + + // Check that root element is a tag + if (!root.getName().equals("jnlp")) { + throw (new MissingFieldException(source, "")); + } + + // Read attributes (path is empty, i.e., "") + // (spec, version, codebase, href) + String specVersion = XMLUtils.getAttribute(root, "", "spec", "1.0+"); + jnlpd.setSpecVersion(specVersion); + String version = XMLUtils.getAttribute(root, "", "version"); + jnlpd.setVersion(version); + + // Make sure the codebase URL ends with a '/'. + // + // Regarding the JNLP spec, + // the thisCodebase is used to determine the codebase. + // codebase = new URL(thisCodebase, codebase) + URL codebase = GeneralUtil.asPathURL(XMLUtils.getAttributeURL(source, + thisCodebase, root, "", "codebase")); + if (codebase == null && thisCodebase != null) { + codebase = thisCodebase; + } + jnlpd.setCodebase(codebase.toExternalForm()); + + // Get href for JNLP file + URL href = XMLUtils.getAttributeURL(source, codebase, root, "", "href"); + jnlpd.setHref(href.toExternalForm()); + + // Read attributes + if (XMLUtils.isElementPath(root, "")) { + jnlpd.setIsSandbox(false); + } else if (XMLUtils.isElementPath(root, + "")) { + jnlpd.setIsSandbox(false); + } + + // We can be fxapp, and also be applet, or application, or neither + boolean isFXApp = false; + boolean isApplet = false; + if (XMLUtils.isElementPath(root, "")) { + // no new type for javafx-desc - needs one of the others + buildFXAppDesc(source, root, "", jnlpd); + jnlpd.setIsFXApp(true); + isFXApp = true; + } + + /* + * Note - the jnlp specification says there must be exactly one of + * the descriptor types. This code has always violated (or at least + * not checked for) that condition. + * Instead it uses precedent order app, component, installer, applet + * and ignores any other descriptors given. + */ + if (XMLUtils.isElementPath(root, "")) { + buildApplicationDesc(source, root, jnlpd); + } else if (XMLUtils.isElementPath(root, "")) { + jnlpd.setIsLibrary(true); + } else if (XMLUtils.isElementPath(root, "")) { + Log.warning(" is not supported and will be ignored in " + jnlp); + jnlpd.setIsInstaller(true); + } else if (XMLUtils.isElementPath(root, "")) { + isApplet = true; + } else { + if (!isFXApp) { + throw (new MissingFieldException(source, + "(||" + + "|)")); + } + } + + if (isApplet && !isFXApp) { + Log.error("Applet based applications deployed with element are not supported."); + } + + if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) { + buildInformationDesc(source, codebase, root, jnlpd); + } + + if (!jnlpd.isInstaller()) { + buildResourcesDesc(source, codebase, root, false, jnlpd); + } + + if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) { + jnlpd.parseResourceDesc(); + } + + if (!jnlpd.isInstaller()) { + if (jnlpd.isSandbox()) { + if (jnlpd.isLibrary()) { + Log.warning(jnlp + " is sandbox extension. JNLPConverter does not support sandbox environment and converted application will run without security manager."); + } else { + Log.warning("This is sandbox Web-Start application. JNLPConverter does not support sandbox environment and converted application will run without security manager."); + } + } + } + + return jnlpd; + } + + /** + * Create a combine informationDesc in the two informationDesc. + * The information present in id1 overwrite the information present in id2 + */ + private static InformationDesc combineInformationDesc( + InformationDesc id1, InformationDesc id2) { + if (id1 == null) { + return id2; + } + if (id2 == null) { + return id1; + } + + String t1 = id1.getTitle(); + String title = (t1 != null && t1.length() > 0) ? + t1 : id2.getTitle(); + String v1 = id1.getVendor(); + String vendor = (v1 != null && v1.length() > 0) ? + v1 : id2.getVendor(); + + /** Copy descriptions */ + String[] descriptions = new String[InformationDesc.NOF_DESC]; + for (int i = 0; i < descriptions.length; i++) { + descriptions[i] = (id1.getDescription(i) != null) + ? id1.getDescription(i) : id2.getDescription(i); + } + + /** Icons */ + ArrayList iconList = new ArrayList<>(); + if (id2.getIcons() != null) { + iconList.addAll(Arrays.asList(id2.getIcons())); + } + if (id1.getIcons() != null) { + iconList.addAll(Arrays.asList(id1.getIcons())); + } + IconDesc[] icons = new IconDesc[iconList.size()]; + icons = iconList.toArray(icons); + + ShortcutDesc hints = (id1.getShortcut() != null) ? + id1.getShortcut() : id2.getShortcut(); + + AssociationDesc[] asd = ( AssociationDesc[] ) addArrays( + (Object[])id1.getAssociations(), (Object[])id2.getAssociations()); + + return new InformationDesc(title, + vendor, + descriptions, + icons, + hints, + asd); + } + + /** Extract data from tag */ + private static void buildInformationDesc(final String source, final URL codebase, XMLNode root, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + final ArrayList list = new ArrayList<>(); + + // Iterates over all nodes ignoring the type + XMLUtils.visitElements(root, + "", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws + BadFieldException, MissingFieldException { + + // Check for right os, arch, and locale + String[] os = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "os", null)); + String[] arch = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "arch", null)); + String[] locale = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "locale", null)); + if (GeneralUtil.prefixMatchStringList( + os, GeneralUtil.getOSFullName()) && + GeneralUtil.prefixMatchArch(arch) && + matchDefaultLocale(locale)) + { + // Title, vendor + String title = XMLUtils.getElementContents(e, ""); + String vendor = XMLUtils.getElementContents(e, "<vendor>"); + + // Descriptions + String[] descriptions = + new String[InformationDesc.NOF_DESC]; + descriptions[InformationDesc.DESC_DEFAULT] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "", null); + descriptions[InformationDesc.DESC_ONELINE] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "one-line", null); + descriptions[InformationDesc.DESC_SHORT] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "short", null); + descriptions[InformationDesc.DESC_TOOLTIP] = + XMLUtils.getElementContentsWithAttribute( + e, "<description>", "kind", "tooltip", null); + + // Icons + IconDesc[] icons = getIconDescs(source, codebase, e); + + // Shortcut hints + ShortcutDesc shortcuts = getShortcutDesc(e); + + // Association hints + AssociationDesc[] associations = getAssociationDesc( + source, codebase, e); + + list.add(new InformationDesc( + title, vendor, descriptions, icons, + shortcuts, associations)); + } + } + }); + + /* Combine all information desc. information in a single one for + * the current locale using the following priorities: + * 1. locale == language_country_variant + * 2. locale == lauguage_country + * 3. locale == lauguage + * 4. no or empty locale + */ + InformationDesc normId = new InformationDesc(null, null, null, null, null, null); + for (InformationDesc id : list) { + normId = combineInformationDesc(id, normId); + } + + jnlpd.setTitle(normId.getTitle()); + jnlpd.setVendor(normId.getVendor()); + jnlpd.setDescriptions(normId.getDescription()); + jnlpd.setIcons(normId.getIcons()); + jnlpd.setShortcuts(normId.getShortcut()); + jnlpd.setAssociations(normId.getAssociations()); + } + + private static Object[] addArrays (Object[] a1, Object[] a2) { + if (a1 == null) { + return a2; + } + if (a2 == null) { + return a1; + } + ArrayList<Object> list = new ArrayList<>(); + int i; + for (i=0; i<a1.length; list.add(a1[i++])); + for (i=0; i<a2.length; list.add(a2[i++])); + return list.toArray(a1); + } + + public static boolean matchDefaultLocale(String[] localeStr) { + return GeneralUtil.matchLocale(localeStr, GeneralUtil.getDefaultLocale()); + } + + /** Extract data from <resources> tag. There is only one. */ + static void buildResourcesDesc(final String source, + final URL codebase, XMLNode root, final boolean ignoreJres, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + // Extract classpath directives + final ResourcesDesc rdesc = new ResourcesDesc(); + + // Iterate over all entries + XMLUtils.visitElements(root, "<resources>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) + throws MissingFieldException, BadFieldException { + // Check for right os, archictecture, and locale + String[] os = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "os", null)); + final String arch = XMLUtils.getAttribute(e, "", "arch", null); + String[] locale = GeneralUtil.getStringList( + XMLUtils.getAttribute(e, "", "locale", null)); + if (GeneralUtil.prefixMatchStringList( + os, GeneralUtil.getOSFullName()) + && matchDefaultLocale(locale)) { + // Now visit all children in this node + XMLUtils.visitChildrenElements(e, + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e2) + throws MissingFieldException, BadFieldException { + handleResourceElement(source, codebase, + e2, rdesc, ignoreJres, arch, jnlpd); + } + }); + } + } + }); + + if (!rdesc.isEmpty()) { + jnlpd.setResourcesDesc(rdesc); + } + } + + private static IconDesc[] getIconDescs(final String source, + final URL codebase, XMLNode e) + throws MissingFieldException, BadFieldException { + final ArrayList<IconDesc> answer = new ArrayList<>(); + XMLUtils.visitElements(e, "<icon>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode icon) throws + MissingFieldException, BadFieldException { + String kindStr = XMLUtils.getAttribute(icon, "", "kind", ""); + URL href = XMLUtils.getRequiredURL(source, codebase, icon, "", "href"); + + if (href != null) { + if (!JNLPConverter.isIconSupported(href.toExternalForm())) { + return; + } + } + + int kind; + if (kindStr == null || kindStr.isEmpty() || kindStr.equals("default")) { + kind = IconDesc.ICON_KIND_DEFAULT; + } else if (kindStr.equals("shortcut")) { + kind = IconDesc.ICON_KIND_SHORTCUT; + } else { + Log.warning("Ignoring unsupported icon \"" + href + "\" with kind \"" + kindStr + "\"."); + return; + } + + answer.add(new IconDesc(href, kind)); + } + }); + return answer.toArray(new IconDesc[answer.size()]); + } + + private static ShortcutDesc getShortcutDesc(XMLNode e) + throws MissingFieldException, BadFieldException { + final ArrayList<ShortcutDesc> shortcuts = new ArrayList<>(); + + XMLUtils.visitElements(e, "<shortcut>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode shortcutNode) + throws MissingFieldException, BadFieldException { + boolean desktopHinted = + XMLUtils.isElementPath(shortcutNode, "<desktop>"); + boolean menuHinted = + XMLUtils.isElementPath(shortcutNode, "<menu>"); + String submenuHinted = + XMLUtils.getAttribute(shortcutNode, "<menu>", "submenu"); + shortcuts.add(new ShortcutDesc(desktopHinted, menuHinted, submenuHinted)); + } + }); + + if (shortcuts.size() > 0) { + return shortcuts.get(0); + } + return null; + } + + private static AssociationDesc[] getAssociationDesc(final String source, + final URL codebase, XMLNode e) + throws MissingFieldException, BadFieldException { + final ArrayList<AssociationDesc> answer = new ArrayList<>(); + XMLUtils.visitElements(e, "<association>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode node) + throws MissingFieldException, BadFieldException { + + String extensions = XMLUtils.getAttribute( + node, "", "extensions"); + + String mimeType = XMLUtils.getAttribute( + node, "", "mime-type"); + String description = XMLUtils.getElementContents( + node, "<description>"); + + URL icon = XMLUtils.getAttributeURL( + source, codebase, node, "<icon>", "href"); + + if (!JNLPConverter.isIconSupported(icon.toExternalForm())) { + icon = null; + } + + if (extensions == null && mimeType == null) { + throw new MissingFieldException(source, + "<association>(<extensions><mime-type>)"); + } else if (extensions == null) { + throw new MissingFieldException(source, + "<association><extensions>"); + } else if (mimeType == null) { + throw new MissingFieldException(source, + "<association><mime-type>"); + } + + // don't support uppercase extension and mime-type on gnome. + if ("gnome".equals(System.getProperty("sun.desktop"))) { + extensions = extensions.toLowerCase(); + mimeType = mimeType.toLowerCase(); + } + + answer.add(new AssociationDesc(extensions, mimeType, + description, icon)); + } + }); + return answer.toArray( + new AssociationDesc[answer.size()]); + } + + /** Handle the individual entries in a resource desc */ + private static void handleResourceElement(String source, URL codebase, + XMLNode e, ResourcesDesc rdesc, boolean ignoreJres, String arch, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + + String tag = e.getName(); + + boolean matchArch = GeneralUtil.prefixMatchArch( + GeneralUtil.getStringList(arch)); + + + if (matchArch && (tag.equals("jar") || tag.equals("nativelib"))) { + /* + * jar/nativelib elements + */ + URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href"); + String version = XMLUtils.getAttribute(e, "", "version", null); + + String mainStr = XMLUtils.getAttribute(e, "", "main"); + boolean isNativeLib = tag.equals("nativelib"); + + boolean isMain = "true".equalsIgnoreCase(mainStr); + + JARDesc jd = new JARDesc(href, version, isMain, isNativeLib, rdesc); + rdesc.addResource(jd); + } else if (matchArch && tag.equals("property")) { + /* + * property tag + */ + String name = XMLUtils.getRequiredAttribute(source, e, "", "name"); + String value = XMLUtils.getRequiredAttributeEmptyOK( + source, e, "", "value"); + + rdesc.addResource(new PropertyDesc(name, value)); + } else if (matchArch && tag.equals("extension")) { + URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href"); + String version = XMLUtils.getAttribute(e, "", "version", null); + rdesc.addResource(new ExtensionDesc(href, version)); + } else if ((tag.equals("java") || tag.equals("j2se")) && !ignoreJres) { + /* + * j2se element + */ + String version = + XMLUtils.getRequiredAttribute(source, e, "", "version"); + String minheapstr = + XMLUtils.getAttribute(e, "", "initial-heap-size"); + String maxheapstr = + XMLUtils.getAttribute(e, "", "max-heap-size"); + + String vmargs = + XMLUtils.getAttribute(e, "", "java-vm-args"); + + if (jnlpd.isJRESet()) { + if (vmargs == null) { + vmargs = "none"; + } + Log.warning("Ignoring repeated element <" + tag + "> with version " + version + + " and java-vm-args: " + vmargs); + return; + } + + long minheap = GeneralUtil.heapValToLong(minheapstr); + long maxheap = GeneralUtil.heapValToLong(maxheapstr); + + ResourcesDesc cbs = null; + buildResourcesDesc(source, codebase, e, true, null); + + // JRE + JREDesc jreDesc = new JREDesc( + version, + minheap, + maxheap, + vmargs, + cbs, + arch); + + rdesc.addResource(jreDesc); + + jnlpd.setIsJRESet(true); + } + } + + /** Extract data from the application-desc tag */ + private static void buildApplicationDesc(final String source, + XMLNode root, JNLPDesc jnlpd) throws MissingFieldException, BadFieldException { + + String mainclass = XMLUtils.getClassName(source, root, + "<application-desc>", "main-class", false); + String appType = XMLUtils.getAttribute(root, "<application-desc>", + "type", "Java"); + String progressclass = XMLUtils.getClassName(source, root, + "<application-desc>", "progress-class", false); + if (progressclass != null && !progressclass.isEmpty()) { + Log.warning("JNLPConverter does not support progress indication. \"" + progressclass + "\" will not be loaded and will be ignored."); + } + + if (!("Java".equalsIgnoreCase(appType) || + "JavaFx".equalsIgnoreCase(appType))) { + throw new BadFieldException(source, XMLUtils.getPathString(root) + + "<application-desc>type", appType); + } + + if ("JavaFx".equalsIgnoreCase(appType)) { + jnlpd.setIsFXApp(true); + } + + XMLUtils.visitElements(root, "<application-desc><argument>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException { + String arg = XMLUtils.getElementContents(e, "", null); + if (arg == null) { + throw new BadFieldException(source, XMLUtils.getPathString(e), ""); + } + jnlpd.addArguments(arg); + } + }); + + XMLUtils.visitElements(root, "<application-desc><param>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, + BadFieldException { + String pn = XMLUtils.getRequiredAttribute( + source, e, "", "name"); + String pv = XMLUtils.getRequiredAttributeEmptyOK( + source, e, "", "value"); + jnlpd.setProperty(pn, pv); + } + }); + jnlpd.setMainClass(mainclass, false); + } + + /** Extract data from the javafx-desc tag */ + private static void buildFXAppDesc(final String source, + XMLNode root, String element, JNLPDesc jnlpd) + throws MissingFieldException, BadFieldException { + String mainclass = XMLUtils.getClassName(source, root, element, + "main-class", true); + String name = XMLUtils.getRequiredAttribute(source, root, + "<javafx-desc>", "name"); + + /* extract arguments */ + XMLUtils.visitElements(root, "<javafx-desc><argument>", new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException { + String arg = XMLUtils.getElementContents(e, "", null); + if (arg == null) { + throw new BadFieldException(source, XMLUtils.getPathString(e), ""); + } + jnlpd.addArguments(arg); + } + }); + + /* extract parameters */ + XMLUtils.visitElements(root, "<javafx-desc><param>", + new XMLUtils.ElementVisitor() { + @Override + public void visitElement(XMLNode e) throws MissingFieldException, + BadFieldException { + String pn = XMLUtils.getRequiredAttribute( + source, e, "", "name"); + String pv = XMLUtils.getRequiredAttributeEmptyOK( + source, e, "", "value"); + jnlpd.setProperty(pn, pv); + } + }); + + jnlpd.setMainClass(mainclass, true); + jnlpd.setName(name); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/XMLUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/XMLUtils.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser; + +import java.net.URL; +import java.net.MalformedURLException; + +import jnlp.converter.parser.exception.BadFieldException; +import jnlp.converter.parser.exception.MissingFieldException; +import jnlp.converter.parser.xml.XMLNode; + +/** Contains handy methods for looking up information + * stored in XMLNodes. + */ +public class XMLUtils { + + /** Returns the value of an integer attribute */ + public static int getIntAttribute(String source, XMLNode root, String path, String name, int defaultvalue) + throws BadFieldException { + String value = getAttribute(root, path, name); + if (value == null) { + return defaultvalue; + } + + try { + return Integer.parseInt(value); + } catch (NumberFormatException nfe) { + throw new BadFieldException(source, getPathString(root) + path + name, value); + } + } + + /** Returns the value of a given attribute, or null if not set */ + public static String getAttribute(XMLNode root, String path, String name) + throws BadFieldException { + return getAttribute(root, path, name, null); + } + + /** Returns the value of a given attribute */ + public static String getRequiredAttributeEmptyOK(String source, + XMLNode root, String path, String name) throws MissingFieldException { + String value = null; + XMLNode elem = findElementPath(root, path); + if (elem != null) { + value = elem.getAttribute(name); + } + if (value == null) { + throw new MissingFieldException(source, + getPathString(root)+ path + name); + } + return value; + } + + /*** Returns the value of an attribute, which must be a valid class name */ + public static String getClassName(String source, XMLNode root, + String path, String name, boolean required) + throws BadFieldException, MissingFieldException { + + String className; + if (required) { + className = getRequiredAttribute(source, root, path, name); + } else { + className = getAttribute(root, path, name); + } + if (className != null && className.endsWith(".class")) { + int i = className.lastIndexOf(".class"); + String cname = className.substring(0, i); + return cname; + } + return className; + } + + /** Returns the value of a given attribute, or null if not set */ + public static String getRequiredAttribute(String source, XMLNode root, + String path, String name) throws MissingFieldException, BadFieldException { + String s = getAttribute(root, path, name, null); + if (s == null) { + throw new MissingFieldException(source, getPathString(root) + path + name); + } + s = s.trim(); + return (s.length() == 0) ? null : s; + } + + /** Returns the value of a given attribute, or the default value 'def' if not set */ + public static String getAttribute(XMLNode root, String path, String name, + String def) throws BadFieldException { + XMLNode elem = findElementPath(root, path); + if (elem == null) { + return def; + } + String value = elem.getAttribute(name); + return (value == null || value.length() == 0) ? def : value; + } + + /** Expands a URL into an absolute URL from a relative URL */ + public static URL getAttributeURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException { + String value = getAttribute(root, path, name); + if (value == null) return null; + try { + if (value.startsWith("jar:")) { + int bang = value.indexOf("!/"); + if (bang > 0) { + String entry = value.substring(bang); + String urlString = value.substring(4, bang); + URL url = (base == null) ? + new URL(urlString) : new URL(base, urlString); + return new URL("jar:" + url.toString() + entry); + } + } + return (base == null) ? new URL(value) : new URL(base, value); + } catch(MalformedURLException mue) { + if (mue.getMessage().contains("https")) { + throw new BadFieldException(source, "<jnlp>", "https"); + } + throw new BadFieldException(source, getPathString(root) + path + name, value); + } + } + + /** Returns the value of an attribute as a URL or null if not set */ + public static URL getAttributeURL(String source, XMLNode root, String path, String name) throws BadFieldException { + return getAttributeURL(source, null, root, path, name); + } + + public static URL getRequiredURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException { + URL url = getAttributeURL(source, base, root, path, name); + if (url == null) { + throw new MissingFieldException(source, getPathString(root) + path + name); + } + return url; + } + + /** Returns the value of an attribute as a URL. Throws a MissingFieldException if the + * attribute is not defined + */ + public static URL getRequiredURL(String source, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException { + return getRequiredURL(source, null, root, path, name); + } + + /** Returns true if the path exists in the document, otherwise false */ + public static boolean isElementPath(XMLNode root, String path) { + return findElementPath(root, path) != null; + } + + public static URL getElementURL(String source, XMLNode root, String path) throws BadFieldException { + String value = getElementContents(root, path); + try { + return new URL(value); + } catch(MalformedURLException mue) { + throw new BadFieldException(source, getPathString(root) + path, value); + } + } + + /** Returns a string describing the current location in the DOM */ + public static String getPathString(XMLNode e) { + return (e == null || !(e.isElement())) ? "" : getPathString(e.getParent()) + "<" + e.getName() + ">"; + } + + /** Returns the contents of an element with the given path and an attribute matching a specific value. Returns + * NULL if not found + */ + public static String getElementContentsWithAttribute(XMLNode root, String path, String attr, String val, String defaultvalue) + throws BadFieldException, MissingFieldException { + XMLNode e = getElementWithAttribute(root, path, attr, val); + if (e == null) { + return defaultvalue; + } + return getElementContents(e, "", defaultvalue); + } + + public static URL getAttributeURLWithAttribute(String source, XMLNode root, String path, String attrcond, String val, + String name, URL defaultvalue) + throws BadFieldException, MissingFieldException { + XMLNode e = getElementWithAttribute(root, path, attrcond, val); + if (e == null) { + return defaultvalue; + } + URL url = getAttributeURL(source, e, "", name); + if (url == null) { + return defaultvalue; + } + return url; + } + + /** Returns an element with the given path and an attribute matching a specific value. Returns + * NULL if not found + */ + public static XMLNode getElementWithAttribute(XMLNode root, String path, final String attr, final String val) + throws BadFieldException, MissingFieldException { + final XMLNode[] result = {null}; + visitElements(root, path, new ElementVisitor() { + public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException { + if (result[0] == null && e.getAttribute(attr).equals(val)) { + result[0] = e; + } + } + }); + return result[0]; + } + + /** Like getElementContents(...) but with a defaultValue of null */ + public static String getElementContents(XMLNode root, String path) { + return getElementContents(root, path, null); + } + + /** Returns the value of the last element tag in the path, e.g., <..><tag>value</tag>. The DOM is assumes + * to be normalized. If no value is found, the defaultvalue is returned + */ + public static String getElementContents(XMLNode root, String path, String defaultvalue) { + XMLNode e = findElementPath(root, path); + if (e == null) { + return defaultvalue; + } + XMLNode n = e.getNested(); + if (n != null && !n.isElement()) { + return n.getName(); + } + return defaultvalue; + } + + /** Parses a path string of the form <tag1><tag2><tag3> and returns the specific Element + * node for that tag, or null if it does not exist. If multiple elements exists with same + * path the first is returned + */ + public static XMLNode findElementPath(XMLNode elem, String path) { + // End condition. Root null -> path does not exist + if (elem == null) { + return null; + } + + // End condition. String empty, return current root + if (path == null || path.length() == 0) { + return elem; + } + + // Strip of first tag + int idx = path.indexOf('>'); + if (!(path.charAt(0) == '<')) { + throw new IllegalArgumentException("bad path. Missing begin tag"); + } + if (idx == -1) { + throw new IllegalArgumentException("bad path. Missing end tag"); + } + String head = path.substring(1, idx); + String tail = path.substring(idx + 1); + return findElementPath(findChildElement(elem, head), tail); + } + + /** Returns an child element with the current tag name or null. */ + public static XMLNode findChildElement(XMLNode elem, String tag) { + XMLNode n = elem.getNested(); + while (n != null) { + if (n.isElement() && n.getName().equals(tag)) { + return n; + } + n = n.getNext(); + } + return null; + } + + /** Iterator class */ + public abstract static class ElementVisitor { + abstract public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException; + } + + /** Visits all elements which matches the <path>. The iteration is only + * done on the last element in the path. + */ + public static void visitElements(XMLNode root, String path, ElementVisitor ev) + throws BadFieldException, MissingFieldException { + // Get last element in path + int idx = path.lastIndexOf('<'); + if (idx == -1) { + throw new IllegalArgumentException( + "bad path. Must contain atleast one tag"); + } + if (path.length() == 0 || path.charAt(path.length() - 1) != '>') { + throw new IllegalArgumentException("bad path. Must end with a >"); + } + String head = path.substring(0, idx); + String tag = path.substring(idx + 1, path.length() - 1); + + XMLNode elem = findElementPath(root, head); + if (elem == null) { + return; + } + + // Iterate through all child nodes + XMLNode n = elem.getNested(); + while (n != null) { + if (n.isElement() && n.getName().equals(tag)) { + ev.visitElement(n); + } + n = n.getNext(); + } + } + + public static void visitChildrenElements(XMLNode elem, ElementVisitor ev) + throws BadFieldException, MissingFieldException { + // Iterate through all child nodes + XMLNode n = elem.getNested(); + while (n != null) { + if (n.isElement()) { + ev.visitElement(n); + } + n = n.getNext(); + } + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/exception/BadFieldException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/exception/BadFieldException.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.exception; + +public class BadFieldException extends Exception { + + private final String _field; + private final String _value; + + public BadFieldException(String source, String field, String value) { + super(); + _value = value; + _field = field; + } + + /** + * Returns the name of the offending field + */ + public String getField() { + return _field; + } + + /** + * Returns the value of the offending field + */ + public String getValue() { + return _value; + } + + /** + * toString implementation + */ + @Override + public String toString() { + return "BadFieldException[ " + getField() + "," + getValue() + "]"; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/exception/JNLParseException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/exception/JNLParseException.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.exception; + +/** + * Exception thrown if a parse error occurred when interpreting + * the launch descriptor + */ + +public class JNLParseException extends Exception { + + private final String _msg; + private final int _line; + + public JNLParseException(Exception exception, String msg, int line) { + super(exception); + _msg = msg; + _line = line; + } + + @Override + public String getMessage() { + return _msg; + } + + private int getLine() { + return _line; + } + + @Override + public String toString() { + return "JNLParseException[ " + getMessage() + "] at " + getLine(); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/exception/MissingFieldException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/exception/MissingFieldException.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.exception; + +public class MissingFieldException extends Exception { + + private final String _field; + + public MissingFieldException(String source, String field) { + super(); + _field = field; + } + + /** + * Returns the name of the offending field + */ + private String getField() { + return _field; + } + + /** + * toString implementation + */ + @Override + public String toString() { + return "MissingFieldException[ " + getField() + "]"; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLAttribute.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLAttribute.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.xml; + +/** + * Class that contains information about a specific attribute + */ +public class XMLAttribute { + + private final String _name; + private final String _value; + private XMLAttribute _next; + + public XMLAttribute(String name, String value) { + _name = XMLNode.stripNameSpace(name); + _value = value; + } + + public String getName() { + return _name; + } + + public String getValue() { + return _value; + } + + public XMLAttribute getNext() { + return _next; + } + + public void setNext(XMLAttribute next) { + _next = next; + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLEncoding.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLEncoding.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.xml; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.Reader; +import java.io.UnsupportedEncodingException; + +public class XMLEncoding { + /** + * Decodes a byte stream into a String by testing for a Byte Order Mark + * (BOM) or an XML declaration. + * <br /> + * Detection begins by examining the first four octets of the stream for a + * BOM. If a BOM is not found, then an encoding declaration is looked for + * at the beginning of the stream. If the encoding still can not be + * determined at this point, then UTF-8 is assumed. + * + * @param data an array of bytes containing an encoded XML document. + * + * @return A string containing the decoded XML document. + */ + public static String decodeXML(byte [] data) throws IOException { + int start = 0; + String encoding; + + if (data.length < BOM_LENGTH) { + throw (new EOFException("encoding.error.not.xml")); + } + // no else required; successfully read stream + int firstFour = ((0xff000000 & ((int) data[0] << 24)) | + (0x00ff0000 & ((int) data[1] << 16)) | + (0x0000ff00 & ((int) data[2] << 8)) | + (0x000000ff & (int) data[3])); + + // start by examining the first four bytes for a BOM + switch (firstFour) { + case EBCDIC: + // examine the encoding declaration + encoding = examineEncodingDeclaration(data, IBM037_ENC); + break; + + case XML_DECLARATION: + // assume UTF-8, but examine the encoding declaration + encoding = examineEncodingDeclaration(data, UTF_8_ENC); + break; + + case UTF_16BE: + encoding = UTF_16BE_ENC; + break; + + case UTF_16LE: + encoding = UTF_16LE_ENC; + break; + + case UNUSUAL_OCTET_1: + case UNUSUAL_OCTET_2: + throw (new UnsupportedEncodingException("encoding.error.unusual.octet")); + + case UTF_32_BE_BOM: + case UTF_32_LE_BOM: + encoding = UTF_32_ENC; + break; + + default: + int firstThree = firstFour & 0xffffff00; + + switch (firstThree) { + case UTF_8_BOM: + // the InputStreamReader class doen't properly handle + // the Byte Order Mark (BOM) in UTF-8 streams, so don't + // putback those 3 bytes. + start = 3; + encoding = UTF_8_ENC; + break; + + default: + int firstTwo = firstFour & 0xffff0000; + + switch (firstTwo) { + case UTF_16_BE_BOM: + case UTF_16_LE_BOM: + encoding = UTF_16_ENC; + break; + + default: + // this is probably UTF-8 without the encoding + // declaration + encoding = UTF_8_ENC; + break; + } + break; + } + break; + } + + return (new String(data, start, data.length - start, encoding)); + } + + /** + * [3] S ::= ( #x20 | #x09 | #x0d | #x0a ) + * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' + * [24] VersionInfo ::= S 'version' Eq ( '"' VersionNum '"' | + * "'" VersionNum "'" ) + * [25] Eq ::= S? '=' S? + * [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')+ + * [80] EncodingDecl ::= S 'encoding' Eq ( '"' EncName '"' | + * "'" EncName "'" ) + * [81] EncName ::= [a-zA-Z] ([a-zA-Z0-9_.] | '-')* + */ + private static String examineEncodingDeclaration(byte [] data, + String encoding) throws IOException { + boolean loop = false; + boolean recognized = false; + boolean almost = false; + boolean question = false; + boolean done = false; + boolean found = false; + int pos = 0; + int ch = -1; + Reader reader = null; + String result = ((encoding != null) ? encoding : UTF_8_ENC); + + reader = new InputStreamReader(new ByteArrayInputStream(data), result); + ch = reader.read(); + + // if this is an XML declaration, it will start with the text '<?xml' + for (int i = 0; ((i < XML_DECL_START.length()) && (done == false)); i++) { + if (ch != XML_DECL_START.charAt(i)) { + // This doesn't look like an XML declaration. This method + // should only be called if the stream contains an XML + // declaration in the encoding that is passed into the method. + done = true; + break; + } + // no else required; still matches + ch = reader.read(); + } + + // there must be at least one whitespace character next. + loop = true; + while ((loop == true) && (done == false)) { + switch (ch) { + case SPACE: + case TAB: // intentional + case LINEFEED: // fall + case RETURN: // through + ch = reader.read(); + break; + + case -1: + // unexpected EOF + done = true; + break; + + default: + // non-whitespace + loop = false; + break; + } + } + + // now look for the text 'encoding', but if the end of the XML + // declaration (signified by the text '?>') comes first, then + // assume the encoding is UTF-8 + loop = true; + while ((loop == true) && (done == false)) { + if (ch == -1) { + // unexpected EOF + done = true; + break; + } else if (recognized == true) { + // this is the encoding declaration as long as the next few + // characters are whitespace and/or the equals ('=') sign + switch (ch) { + case SPACE: // intentional + case TAB: // fall + case LINEFEED: // through + case RETURN: + // don't need to do anything + break; + + case EQUAL: + if (almost == false) { + // got the equal, now find a quote + almost = true; + } else { + // this is not valid XML, so punt + recognized = false; + done = true; + } + break; + + case DOUBLE_QUOTE: // intentional + case SINGLE_QUOTE: // fall through + if (almost == true) { + // got the quote, so move on to get the value + loop = false; + } else { + // got a quote before the equal; this is not valid + // XML, so punt + recognized = false; + done = true; + } + break; + + default: + // non-whitespace + recognized = false; + if (almost == true) { + // this is not valid XML, so punt + done = true; + } + // no else required; this wasn't the encoding + // declaration + break; + } + + if (recognized == false) { + // this isn't the encoding declaration, so go back to the + // top without reading the next character + pos = 0; + continue; + } + // no else required; still looking good + } else if (ch == ENCODING_DECL.charAt(pos++)) { + if (ENCODING_DECL.length() == pos) { + // this looks like the encoding declaration + recognized = true; + } + // no else required; this might be the encoding declaration + } else if (ch == '?') { + question = true; + pos = 0; + } else if ((ch == '>') && (question == true)) { + // there is no encoding declaration, so assume that the initial + // encoding guess was correct + done = true; + continue; + } else { + // still searching for the encoding declaration + pos = 0; + } + + ch = reader.read(); + } + + if (done == false) { + StringBuilder buffer = new StringBuilder(MAX_ENC_NAME); + + if (((ch >= 'a') && (ch <= 'z')) | + ((ch >= 'A') && (ch <= 'Z'))) { + // add the character to the result + buffer.append((char) ch); + + loop = true; + while ((loop == true) && (done == false)) { + ch = reader.read(); + + if (((ch >= 'a') && (ch <= 'z')) || + ((ch >= 'A') && (ch <= 'Z')) || + ((ch >= '0') && (ch <= '9')) || + (ch == '_') || (ch == '.') || (ch == '-')) { + // add the character to the result + buffer.append((char) ch); + } else if ((ch == DOUBLE_QUOTE) || (ch == SINGLE_QUOTE)) { + // finished! + found = true; + done = true; + result = buffer.toString(); + } else { + // this is not a valid encoding name, so punt + done = true; + } + } + } else { + // this is not a valid encoding name, so punt + done = true; + } + } + // no else required; already failed to find the encoding somewhere else + + return (result); + } + + private static final int BOM_LENGTH = 4; + private static final int MAX_ENC_NAME = 512; + + private static final int SPACE = 0x00000020; + private static final int TAB = 0x00000009; + private static final int LINEFEED = 0x0000000a; + private static final int RETURN = 0x0000000d; + private static final int EQUAL = '='; + private static final int DOUBLE_QUOTE = '\"'; + private static final int SINGLE_QUOTE = '\''; + + private static final int UTF_32_BE_BOM = 0x0000feff; + private static final int UTF_32_LE_BOM = 0xfffe0000; + private static final int UTF_16_BE_BOM = 0xfeff0000; + private static final int UTF_16_LE_BOM = 0xfffe0000; + private static final int UTF_8_BOM = 0xefbbbf00; + private static final int UNUSUAL_OCTET_1 = 0x00003c00; + private static final int UNUSUAL_OCTET_2 = 0x003c0000; + private static final int UTF_16BE = 0x003c003f; + private static final int UTF_16LE = 0x3c003f00; + private static final int EBCDIC = 0x4c6fa794; + private static final int XML_DECLARATION = 0x3c3f786d; + + private static final String UTF_32_ENC = "UTF-32"; + private static final String UTF_16_ENC = "UTF-16"; + private static final String UTF_16BE_ENC = "UTF-16BE"; + private static final String UTF_16LE_ENC = "UTF-16LE"; + private static final String UTF_8_ENC = "UTF-8"; + private static final String IBM037_ENC = "IBM037"; + + private static final String XML_DECL_START = "<?xml"; + private static final String ENCODING_DECL = "encoding"; +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLNode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLNode.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.xml; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * Class that contains information about an XML Node + */ +public class XMLNode { + private final boolean _isElement; // Element/PCTEXT + private final String _name; + private final XMLAttribute _attr; + private XMLNode _parent; // Parent Node + private XMLNode _nested; // Nested XML tags + private XMLNode _next; // Following XML tag on the same level + + public final static String WILDCARD = "*"; + + /** Creates a PCTEXT node */ + public XMLNode(String name) { + _isElement = false; + _name = name; + _attr = null; + _nested = null; + _next = null; + _parent = null; + } + + /** Creates a ELEMENT node */ + public XMLNode(String name, XMLAttribute attr) { + _isElement = true; + _name = stripNameSpace(name); + _attr = attr; + _nested = null; + _next = null; + _parent = null; + } + + public String getName() { + return _name; + } + + public XMLAttribute getAttributes() { + return _attr; + } + + public XMLNode getNested() { + return _nested; + } + + public XMLNode getNext() { + return _next; + } + + public boolean isElement() { + return _isElement; + } + + public void setParent(XMLNode parent) { + _parent = parent; + } + + public XMLNode getParent() { + return _parent; + } + + public void setNext(XMLNode next) { + _next = next; + } + + public void setNested(XMLNode nested) { + _nested = nested; + } + + public static String stripNameSpace(String name) { + if (name != null && !name.startsWith("xmlns:")) { + int i = name.lastIndexOf(":"); + if (i >= 0 && i < name.length()) { + return name.substring(i+1); + } + } + return name; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 83 * hash + (this._name != null ? this._name.hashCode() : 0); + hash = 83 * hash + (this._attr != null ? this._attr.hashCode() : 0); + hash = 83 * hash + (this._nested != null ? this._nested.hashCode() : 0); + hash = 83 * hash + (this._next != null ? this._next.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object o) { + if (o == null || !(o instanceof XMLNode)) return false; + XMLNode other = (XMLNode)o; + boolean result = + match(_name, other._name) && + match(_attr, other._attr) && + match(_nested, other._nested) && + match(_next, other._next); + return result; + } + + public String getAttribute(String name) { + XMLAttribute cur = _attr; + while(cur != null) { + if (name.equals(cur.getName())) return cur.getValue(); + cur = cur.getNext(); + } + return ""; + } + + private static boolean match(Object o1, Object o2) { + if (o1 == null) { + return (o2 == null); + } + return o1.equals(o2); + } + + public void printToStream(PrintWriter out) { + printToStream(out, false); + } + + public void printToStream(PrintWriter out, boolean trim) { + printToStream(out, 0, trim); + } + + public void printToStream(PrintWriter out, int n, boolean trim) { + if (!isElement()) { + String value = _name; // value node (where name is data of parent) + if (trim && value.length() > 512) { + value = "..."; + } + out.print(value); + } else { + if (_nested == null) { + String attrString = (_attr == null) ? "" : (" " + _attr.toString()); + lineln(out, n, "<" + _name + attrString + "/>"); + } else { + String attrString = (_attr == null) ? "" : (" " + _attr.toString()); + lineln(out, n, "<" + _name + attrString + ">"); + _nested.printToStream(out, n + 1, trim); + if (_nested.isElement()) { + lineln(out, n, "</" + _name + ">"); + } else { + out.print("</" + _name + ">"); + } + } + } + if (_next != null) { + _next.printToStream(out, n, trim); + } + } + + private static void lineln(PrintWriter out, int indent, String s) { + out.println(""); + for(int i = 0; i < indent; i++) { + out.print(" "); + } + out.print(s); + } + + @Override + public String toString() { + return toString(false); + } + + public String toString(boolean hideLongElementValue) { + StringWriter sw = new StringWriter(1000); + PrintWriter pw = new PrintWriter(sw); + printToStream(pw, hideLongElementValue); + pw.close(); + return sw.toString(); + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLParser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/jpackager/JNLPConverter/src/jnlp/converter/parser/xml/XMLParser.java Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jnlp.converter.parser.xml; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.InputSource; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.StringReader; +import java.io.IOException; +import java.util.Stack; + +public class XMLParser extends DefaultHandler { + + private XMLNode _root; + private final String _source; + private Stack<XMLNode> _inProgress; + private String _characters; + + // although defined in com.sun.org.apache.xerces.internal.impl.Constants, + // we should not be able to access that, so defined here + private final static String DTD_DOWNLOAD = + "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + + private static final SAXParserFactory SPF = SAXParserFactory.newInstance(); + + /* + * Construct an <code>XMLParser</code>. + * + * @param source - the source text to parse. + */ + public XMLParser(String source) { + _source = source.trim(); + } + + public XMLNode parse() throws SAXException { + // normally we parse without validating, but leave option to parse + // with validation, possibly controlled by config option. + return parse(false); + } + + public XMLNode parse(boolean validating) throws SAXException { + _root = null; + _inProgress = new Stack<>(); + + try { + InputSource is = new InputSource(new StringReader(_source)); + SPF.setValidating(validating); + // only download dtd file from DOCTYPE if we are doing validation + try { + SPF.setFeature(DTD_DOWNLOAD, validating); + } catch (Exception e) { + } + SAXParser sp = SPF.newSAXParser(); + sp.parse(is, this); + } catch (ParserConfigurationException | IOException pce) { + throw new SAXException(pce); + } + + return _root; + } + + @Override + public void startElement(String uri, String localeName, String qName, + Attributes attributes) throws SAXException { + + XMLAttribute first = null; + XMLAttribute last = null; + int len = attributes.getLength(); + + for (int i = 0; i < len; i++) { + XMLAttribute att = new XMLAttribute( + // in old implementation attribute names and values were trimmed + attributes.getQName(i).trim(), attributes.getValue(i).trim()); + if (first == null) { + first = att; + } + if (last != null) { + last.setNext(att); + } + last = att; + } + _inProgress.push(new XMLNode(qName, first)); + _characters = null; + } + + @Override + public void endElement(String uri, String localeName, String elementName) + throws SAXException { + XMLNode node = _inProgress.pop(); + // <information> + // <title>Title + // Vendor "Some whitespaces" + // + // In example above when we receive end of we will + // have _characters set to whitespace and new line and it will be + // added as child node to . This will break our cache code + // which will think that JNLP file changed on server even if it is not + // and thus we might not load properly. + // + // + // test with whitespaces + // + // From example above we want to include whitespaces for . + // + // + // abc + // xyz (might be whitespaces) + // + // In JNLP spec we do not have cases when node have nested nodes as + // well as text which is whitespaces only. + // + // So to fix it lets check if ending node have nested nodes, then do + // not add whitespaces only node. + if (node != null && node.getNested() != null && _characters != null) { + String trimCharacters = _characters.trim(); + if ((trimCharacters == null) || (trimCharacters.length() == 0)) { + _characters = null; // No need to add whitespaces only + } + } + if ((_characters != null) && (_characters.trim().length() > 0)) { + addChild(node, new XMLNode(_characters)); + } + + if (_inProgress.isEmpty()) { + _root = node; + } else { + addChild(_inProgress.peek(), node); + } + _characters = null; + } + + @Override + public void ignorableWhitespace(char[] chars, int start, int length) + throws SAXException { + String s = new String(chars, start, length); + _characters = ((_characters == null) ? s : _characters + s); + } + + private void addChild(XMLNode parent, XMLNode child) { + child.setParent(parent); + + XMLNode sibling = parent.getNested(); + if (sibling == null) { + parent.setNested(child); // set us as only child + } else { + while (sibling.getNext() != null) { + sibling = sibling.getNext(); + } + sibling.setNext(child); // sets us as youngest child + } + } +} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/nbproject/jpackager/JNLPConverter/build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/nbproject/jpackager/JNLPConverter/build.xml Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,80 @@ + + + + + + + + + + + Builds, tests, and runs the project JNLPConverter. + + + + + + + + + + diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/nbproject/jpackager/JNLPConverter/manifest.mf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/nbproject/jpackager/JNLPConverter/manifest.mf Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/build-impl.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/build-impl.xml Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,1403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/genfiles.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/genfiles.properties Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,8 @@ +build.xml.data.CRC32=9017e41e +build.xml.script.CRC32=5cf818d6 +build.xml.stylesheet.CRC32=8064a381@1.80.1.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=9017e41e +nbproject/build-impl.xml.script.CRC32=d0290d6d +nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48 diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/project.properties --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/project.properties Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,96 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=JNLPConverter +application.vendor= +auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/JNLPConverter.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +file.reference.jnlpconverter-src=../../../jpackager/jnlpconverter/src +includes=** +jar.archive.disabled=${jnlp.enabled} +jar.compress=false +jar.index=${jnlp.enabled} +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=true +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +jnlp.codebase.type=no.codebase +jnlp.descriptor=application +jnlp.enabled=false +jnlp.mixed.code=default +jnlp.offline-allowed=false +jnlp.signed=false +jnlp.signing= +jnlp.signing.alias= +jnlp.signing.keystore= +main.class=jnlp.converter.Main +# Optional override of default Application-Library-Allowable-Codebase attribute identifying the locations where your signed RIA is expected to be found. +manifest.custom.application.library.allowable.codebase= +# Optional override of default Caller-Allowable-Codebase attribute identifying the domains from which JavaScript code can make calls to your RIA without security prompts. +manifest.custom.caller.allowable.codebase= +# Optional override of default Codebase manifest attribute, use to prevent RIAs from being repurposed +manifest.custom.codebase= +# Optional override of default Permissions manifest attribute (supported values: sandbox, all-permissions) +manifest.custom.permissions= +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.src.dir=${file.reference.jnlpconverter-src} diff -r a769ad2d40d6 -r eaca4369b068 src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/project.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/demo/share/nbproject/jpackager/JNLPConverter/nbproject/project.xml Fri Oct 12 19:00:51 2018 -0400 @@ -0,0 +1,13 @@ + + + org.netbeans.modules.java.j2seproject + + + JNLPConverter + + + + + + +