1 /*
   2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.incubator.jpackage.internal;
  26 
  27 import java.io.IOException;
  28 import java.io.File;
  29 import java.nio.file.Files;
  30 import java.nio.file.Path;
  31 import java.util.ArrayList;
  32 import java.util.List;
  33 import java.util.Map;
  34 import java.util.stream.Stream;
  35 import static jdk.incubator.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA;
  36 import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
  37 import static jdk.incubator.jpackage.internal.StandardBundlerParam.JAVA_OPTIONS;
  38 import static jdk.incubator.jpackage.internal.StandardBundlerParam.ARGUMENTS;
  39 import static jdk.incubator.jpackage.internal.StandardBundlerParam.VERSION;
  40 
  41 /**
  42  * App launcher's config file.
  43  */
  44 final class CfgFile {
  45     CfgFile() {
  46         appLayout = ApplicationLayout.platformAppImage();
  47     }
  48 
  49     CfgFile initFromParams(Map<String, ? super Object> params) {
  50         launcherData = LAUNCHER_DATA.fetchFrom(params);
  51         launcherName = APP_NAME.fetchFrom(params);
  52         javaOptions = JAVA_OPTIONS.fetchFrom(params);
  53         arguments = ARGUMENTS.fetchFrom(params);
  54         version = VERSION.fetchFrom(params);
  55         return this;
  56     }
  57 
  58     void create(Path appImage) throws IOException {
  59         List<Map.Entry<String, Object>> content = new ArrayList<>();
  60 
  61         ApplicationLayout appCfgLayout = createAppCfgLayout();
  62 
  63         content.add(Map.entry("[Application]", SECTION_TAG));
  64 
  65         if (launcherData.isModular()) {
  66             content.add(Map.entry("app.mainmodule", launcherData.moduleName()
  67                     + "/" + launcherData.qualifiedClassName()));
  68         } else {
  69             // If the app is contained in an unnamed jar then launch it the
  70             // legacy way and the main class string must be
  71             // of the format com/foo/Main
  72             if (launcherData.mainJarName() != null) {
  73                 content.add(Map.entry("app.classpath",
  74                         appCfgLayout.appDirectory().resolve(
  75                                 launcherData.mainJarName())));
  76             }
  77             content.add(Map.entry("app.mainclass",
  78                     launcherData.qualifiedClassName()));
  79         }
  80 
  81         for (var value : launcherData.classPath()) {
  82             content.add(Map.entry("app.classpath",
  83                     appCfgLayout.appDirectory().resolve(value).toString()));
  84         }
  85 
  86         ApplicationLayout appImagelayout = appLayout.resolveAt(appImage);
  87         Path modsDir = appImagelayout.appModsDirectory();
  88 
  89         content.add(Map.entry("[JavaOptions]", SECTION_TAG));
  90 
  91         // always let app know it's version
  92         content.add(Map.entry(
  93                 "java-options", "-Djpackage.app-version=" + version));
  94 
  95         // add user supplied java options if there are any
  96         for (var value : javaOptions) {
  97             content.add(Map.entry("java-options", value));
  98         }
  99 
 100         // add module path if there is one
 101         if (Files.isDirectory(modsDir)) {
 102             content.add(Map.entry("java-options", "--module-path"));
 103             content.add(Map.entry("java-options",
 104                     appCfgLayout.appModsDirectory()));
 105         }
 106 
 107         if (!arguments.isEmpty()) {
 108             content.add(Map.entry("[ArgOptions]", SECTION_TAG));
 109             for (var value : arguments) {
 110                 content.add(Map.entry("arguments", value));
 111             }
 112         }
 113 
 114         Path cfgFile = appImagelayout.appDirectory().resolve(launcherName + ".cfg");
 115         Files.createDirectories(cfgFile.getParent());
 116 
 117         boolean[] addLineBreakAtSection = new boolean[1];
 118         Stream<String> lines = content.stream().map(entry -> {
 119             if (entry.getValue() == SECTION_TAG) {
 120                 if (!addLineBreakAtSection[0]) {
 121                     addLineBreakAtSection[0] = true;
 122                     return entry.getKey();
 123                 }
 124                 return "\n" + entry.getKey();
 125             }
 126             return entry.getKey() + "=" + entry.getValue();
 127         });
 128         Files.write(cfgFile, (Iterable<String>) lines::iterator);
 129     }
 130 
 131     private ApplicationLayout createAppCfgLayout() {
 132         ApplicationLayout appCfgLayout = appLayout.resolveAt(Path.of("$ROOTDIR"));
 133         appCfgLayout.pathGroup().setPath(ApplicationLayout.PathRole.APP,
 134                 Path.of("$APPDIR"));
 135         appCfgLayout.pathGroup().setPath(ApplicationLayout.PathRole.MODULES,
 136                 appCfgLayout.appDirectory().resolve(appCfgLayout.appModsDirectory().getFileName()));
 137         return appCfgLayout;
 138     }
 139 
 140     private String launcherName;
 141     private String version;
 142     private LauncherData launcherData;
 143     List<String> arguments;
 144     List<String> javaOptions;
 145     private final ApplicationLayout appLayout;
 146 
 147     private final static Object SECTION_TAG = new Object();
 148 }