src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java
author herrick
Fri, 18 Oct 2019 14:14:37 -0400
branchJDK-8200758-branch
changeset 58696 61c44899b4eb
parent 58414 a5f66aa04f68
child 58762 0fe62353385b
permissions -rw-r--r--
8223325: Improve wix sources generated by jpackage Submitted-by: asemenyuk Reviewed-by: aherrick, almatvee

/*
 * 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<String> addLauncherNames;

    public final static String FILENAME = ".jpackage.xml";

    private final static Map<Platform, String> 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<String> 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<String> 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<String, Object> 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<Map<String, ? super Object>> addLaunchers =
                ADD_LAUNCHERS.fetchFrom(params);

            for (int i = 0; i < addLaunchers.size(); i++) {
                Map<String, ? super Object> 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<String> addLaunchers = new ArrayList<String>();

            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<String> getLauncherNames(Path appImageDir,
            Map<String, ? super Object> params) {
        List<String> 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;
    }

}