diff -r 64adf683bc7b -r 61c44899b4eb src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java Fri Oct 18 11:00:57 2019 -0400 +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java Fri Oct 18 14:14:37 2019 -0400 @@ -1,218 +1,233 @@ -/* - * Copyright (c) 2019, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 jdk.jpackage.internal; - -import java.io.BufferedWriter; -import java.io.FileInputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Writer; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.ArrayList; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamWriter; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; -import javax.xml.xpath.XPathFactory; -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import static jdk.jpackage.internal.StandardBundlerParam.*; - -class AppImageFile { - - // These values will be loaded from AppImage xml file. - private final String creatorVersion; - private final String creatorPlatform; - private final String launcherName; - private final List addLauncherNames; - - final static String XML_FILENAME = ".jpackage.xml"; - - private final static Map PLATFORM_LABELS = Map.of( - Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC, - "macOS"); - - - private AppImageFile() { - this(null, null, null, null); - } - - private AppImageFile(String launcherName, List addLauncherNames, - String creatorVersion, String creatorPlatform) { - this.launcherName = launcherName; - this.addLauncherNames = addLauncherNames; - this.creatorVersion = creatorVersion; - this.creatorPlatform = creatorPlatform; - } - - /** - * Would return null to indicate stored command line is invalid. - */ - List getAddLauncherNames() { - return addLauncherNames; - } - - String getLauncherName() { - return launcherName; - } - - void verifyCompatible() throws ConfigException { - // Just do nohing for now. - } - - static void save(Path appImage, Map params) - throws IOException { - Path xmlFile = appImage.resolve(XML_FILENAME); - XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance(); - - try (Writer w = new BufferedWriter(new FileWriter(xmlFile.toFile()))) { - XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w); - - xml.writeStartDocument(); - xml.writeStartElement("jpackage-state"); - xml.writeAttribute("version", getVersion()); - xml.writeAttribute("platform", getPlatform()); - - xml.writeStartElement("main-launcher"); - xml.writeCharacters(APP_NAME.fetchFrom(params)); - xml.writeEndElement(); - - List> addLaunchers = - ADD_LAUNCHERS.fetchFrom(params); - - for (int i = 0; i < addLaunchers.size(); i++) { - Map sl = addLaunchers.get(i); - xml.writeStartElement("add-launcher"); - xml.writeCharacters(APP_NAME.fetchFrom(sl)); - xml.writeEndElement(); - } - - xml.writeEndElement(); - xml.writeEndDocument(); - xml.flush(); - xml.close(); - - } catch (XMLStreamException ex) { - Log.verbose(ex); - throw new IOException(ex); - } - } - - static AppImageFile load(Path appImageDir) throws IOException { - try { - Path path = appImageDir.resolve(XML_FILENAME); - DocumentBuilderFactory dbf = - DocumentBuilderFactory.newDefaultInstance(); - dbf.setFeature( - "http://apache.org/xml/features/nonvalidating/load-external-dtd", - false); - DocumentBuilder b = dbf.newDocumentBuilder(); - Document doc = b.parse(new FileInputStream(path.toFile())); - - XPath xPath = XPathFactory.newInstance().newXPath(); - - String mainLauncher = xpathQueryNullable(xPath, - "/jpackage-state/main-launcher/text()", doc); - if (mainLauncher == null) { - // No main launcher, this is fatal. - return new AppImageFile(); - } - - List addLaunchers = new ArrayList(); - - String platform = xpathQueryNullable(xPath, - "/jpackage-state/@platform", doc); - - String version = xpathQueryNullable(xPath, - "/jpackage-state/@version", doc); - - NodeList launcherNameNodes = (NodeList) xPath.evaluate( - "/jpackage-state/add-launcher/text()", doc, - XPathConstants.NODESET); - - for (int i = 0; i != launcherNameNodes.getLength(); i++) { - addLaunchers.add(launcherNameNodes.item(i).getNodeValue()); - } - - AppImageFile file = new AppImageFile( - mainLauncher, addLaunchers, version, platform); - if (!file.isValid()) { - file = new AppImageFile(); - } - return file; - } catch (ParserConfigurationException | SAXException ex) { - // Let caller sort this out - throw new IOException(ex); - } catch (XPathExpressionException ex) { - // This should never happen as XPath expressions should be correct - throw new RuntimeException(ex); - } - } - - private static String xpathQueryNullable(XPath xPath, String xpathExpr, - Document xml) throws XPathExpressionException { - NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml, - XPathConstants.NODESET); - if (nodes != null && nodes.getLength() > 0) { - return nodes.item(0).getNodeValue(); - } - return null; - } - - private static String getVersion() { - return System.getProperty("java.version"); - } - - private static String getPlatform() { - return PLATFORM_LABELS.get(Platform.getPlatform()); - } - - private boolean isValid() { - if (launcherName == null || launcherName.length() == 0 || - addLauncherNames.indexOf("") != -1) { - // Some launchers have empty names. This is invalid. - return false; - } - - // Add more validation. - - return true; - } - -} +/* + * Copyright (c) 2019, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 jdk.jpackage.internal; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import static jdk.jpackage.internal.StandardBundlerParam.*; + +public class AppImageFile { + + // These values will be loaded from AppImage xml file. + private final String creatorVersion; + private final String creatorPlatform; + private final String launcherName; + private final List addLauncherNames; + + public final static String FILENAME = ".jpackage.xml"; + + private final static Map PLATFORM_LABELS = Map.of( + Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC, + "macOS"); + + + private AppImageFile() { + this(null, null, null, null); + } + + private AppImageFile(String launcherName, List addLauncherNames, + String creatorVersion, String creatorPlatform) { + this.launcherName = launcherName; + this.addLauncherNames = addLauncherNames; + this.creatorVersion = creatorVersion; + this.creatorPlatform = creatorPlatform; + } + + /** + * Returns list of additional launchers configured for the application. + * Each item in the list is not null or empty string. + * Returns empty list for application without additional launchers. + */ + List getAddLauncherNames() { + return addLauncherNames; + } + + /** + * Returns main application launcher name. Never returns null or empty value. + */ + String getLauncherName() { + return launcherName; + } + + void verifyCompatible() throws ConfigException { + // Just do nothing for now. + } + + /** + * Saves file with application image info in application image. + * @param appImageDir - path to application image + * @throws IOException + */ + static void save(Path appImage, Map params) + throws IOException { + IOUtils.createXml(appImage.resolve(FILENAME), xml -> { + xml.writeStartElement("jpackage-state"); + xml.writeAttribute("version", getVersion()); + xml.writeAttribute("platform", getPlatform()); + + xml.writeStartElement("main-launcher"); + xml.writeCharacters(APP_NAME.fetchFrom(params)); + xml.writeEndElement(); + + List> addLaunchers = + ADD_LAUNCHERS.fetchFrom(params); + + for (int i = 0; i < addLaunchers.size(); i++) { + Map sl = addLaunchers.get(i); + xml.writeStartElement("add-launcher"); + xml.writeCharacters(APP_NAME.fetchFrom(sl)); + xml.writeEndElement(); + } + }); + } + + /** + * Loads application image info from application image. + * @param appImageDir - path to application image + * @return valid info about application image or null + * @throws IOException + */ + static AppImageFile load(Path appImageDir) throws IOException { + try { + Path path = appImageDir.resolve(FILENAME); + DocumentBuilderFactory dbf = + DocumentBuilderFactory.newDefaultInstance(); + dbf.setFeature( + "http://apache.org/xml/features/nonvalidating/load-external-dtd", + false); + DocumentBuilder b = dbf.newDocumentBuilder(); + Document doc = b.parse(new FileInputStream(path.toFile())); + + XPath xPath = XPathFactory.newInstance().newXPath(); + + String mainLauncher = xpathQueryNullable(xPath, + "/jpackage-state/main-launcher/text()", doc); + if (mainLauncher == null) { + // No main launcher, this is fatal. + return new AppImageFile(); + } + + List addLaunchers = new ArrayList(); + + String platform = xpathQueryNullable(xPath, + "/jpackage-state/@platform", doc); + + String version = xpathQueryNullable(xPath, + "/jpackage-state/@version", doc); + + NodeList launcherNameNodes = (NodeList) xPath.evaluate( + "/jpackage-state/add-launcher/text()", doc, + XPathConstants.NODESET); + + for (int i = 0; i != launcherNameNodes.getLength(); i++) { + addLaunchers.add(launcherNameNodes.item(i).getNodeValue()); + } + + AppImageFile file = new AppImageFile( + mainLauncher, addLaunchers, version, platform); + if (!file.isValid()) { + file = new AppImageFile(); + } + return file; + } catch (ParserConfigurationException | SAXException ex) { + // Let caller sort this out + throw new IOException(ex); + } catch (XPathExpressionException ex) { + // This should never happen as XPath expressions should be correct + throw new RuntimeException(ex); + } + } + + /** + * Returns list of launcher names configured for the application. + * The first item in the returned list is man launcher name. + * Following items in the list are names of additional launchers. + */ + static List getLauncherNames(Path appImageDir, + Map params) { + List launchers = new ArrayList<>(); + try { + AppImageFile appImageInfo = AppImageFile.load(appImageDir); + if (appImageInfo != null) { + launchers.add(appImageInfo.getLauncherName()); + launchers.addAll(appImageInfo.getAddLauncherNames()); + return launchers; + } + } catch (IOException ioe) { + Log.verbose(ioe); + } + + launchers.add(APP_NAME.fetchFrom(params)); + ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).forEach( + launchers::add); + return launchers; + } + + private static String xpathQueryNullable(XPath xPath, String xpathExpr, + Document xml) throws XPathExpressionException { + NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml, + XPathConstants.NODESET); + if (nodes != null && nodes.getLength() > 0) { + return nodes.item(0).getNodeValue(); + } + return null; + } + + private static String getVersion() { + return System.getProperty("java.version"); + } + + private static String getPlatform() { + return PLATFORM_LABELS.get(Platform.getPlatform()); + } + + private boolean isValid() { + if (launcherName == null || launcherName.length() == 0 || + addLauncherNames.indexOf("") != -1) { + // Some launchers have empty names. This is invalid. + return false; + } + + // Add more validation. + + return true; + } + +}