1 /*
   2  * Copyright (c) 2015, 2019, 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 
  26 package jdk.incubator.jpackage.internal;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.PrintStream;
  32 import java.nio.file.Files;
  33 import java.nio.file.Path;
  34 import java.util.HashMap;
  35 import java.util.List;
  36 import java.util.Map;
  37 import java.util.ResourceBundle;
  38 import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
  39 import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
  40 
  41 import jdk.incubator.jpackage.internal.resources.ResourceLocator;
  42 
  43 
  44 /*
  45  * AbstractAppImageBuilder
  46  *     This is sub-classed by each of the platform dependent AppImageBuilder
  47  * classes, and contains resource processing code common to all platforms.
  48  */
  49 
  50 public abstract class AbstractAppImageBuilder {
  51 
  52     private static final ResourceBundle I18N = ResourceBundle.getBundle(
  53             "jdk.incubator.jpackage.internal.resources.MainResources");
  54 
  55     private final Path root;
  56 
  57     public AbstractAppImageBuilder(Map<String, Object> unused, Path root) {
  58         this.root = root;
  59     }
  60 
  61     public InputStream getResourceAsStream(String name) {
  62         return ResourceLocator.class.getResourceAsStream(name);
  63     }
  64 
  65     public abstract void prepareApplicationFiles(
  66             Map<String, ? super Object> params) throws IOException;
  67     public abstract void prepareJreFiles(
  68             Map<String, ? super Object> params) throws IOException;
  69     public abstract Path getAppDir();
  70     public abstract Path getAppModsDir();
  71 
  72     public Path getRuntimeRoot() {
  73         return this.root;
  74     }
  75 
  76     protected void copyEntry(Path appDir, File srcdir, String fname)
  77             throws IOException {
  78         Path dest = appDir.resolve(fname);
  79         Files.createDirectories(dest.getParent());
  80         File src = new File(srcdir, fname);
  81         if (src.isDirectory()) {
  82             IOUtils.copyRecursive(src.toPath(), dest);
  83         } else {
  84             Files.copy(src.toPath(), dest);
  85         }
  86     }
  87 
  88     public void writeCfgFile(Map<String, ? super Object> params,
  89             File cfgFileName) throws IOException {
  90         cfgFileName.getParentFile().mkdirs();
  91         cfgFileName.delete();
  92         File mainJar = JLinkBundlerHelper.getMainJar(params);
  93         ModFile.ModType mainJarType = ModFile.ModType.Unknown;
  94 
  95         if (mainJar != null) {
  96             mainJarType = new ModFile(mainJar).getModType();
  97         }
  98 
  99         String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
 100 
 101         try (PrintStream out = new PrintStream(cfgFileName)) {
 102 
 103             out.println("[Application]");
 104             out.println("app.name=" + APP_NAME.fetchFrom(params));
 105             out.println("app.version=" + VERSION.fetchFrom(params));
 106             out.println("app.runtime=" + getCfgRuntimeDir());
 107             out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 108             out.println("app.classpath="
 109                     + getCfgClassPath(CLASSPATH.fetchFrom(params)));
 110 
 111             // The main app is required to be a jar, modular or unnamed.
 112             if (mainModule != null &&
 113                     (mainJarType == ModFile.ModType.Unknown ||
 114                     mainJarType == ModFile.ModType.ModularJar)) {
 115                 out.println("app.mainmodule=" + mainModule);
 116             } else {
 117                 String mainClass =
 118                         StandardBundlerParam.MAIN_CLASS.fetchFrom(params);
 119                 // If the app is contained in an unnamed jar then launch it the
 120                 // legacy way and the main class string must be
 121                 // of the format com/foo/Main
 122                 if (mainJar != null) {
 123                     out.println("app.mainjar=" + getCfgAppDir()
 124                             + mainJar.toPath().getFileName().toString());
 125                 }
 126                 if (mainClass != null) {
 127                     out.println("app.mainclass="
 128                             + mainClass.replace("\\", "/"));
 129                 }
 130             }
 131 
 132             out.println();
 133             out.println("[JavaOptions]");
 134             List<String> jvmargs = JAVA_OPTIONS.fetchFrom(params);
 135             for (String arg : jvmargs) {
 136                 out.println(arg);
 137             }
 138             Path modsDir = getAppModsDir();
 139 
 140             if (modsDir != null && modsDir.toFile().exists()) {
 141                 out.println("--module-path");
 142                 out.println(getCfgAppDir().replace("\\","/") + "mods");
 143             }
 144 
 145             out.println();
 146             out.println("[ArgOptions]");
 147             List<String> args = ARGUMENTS.fetchFrom(params);
 148             for (String arg : args) {
 149                 if (arg.endsWith("=") &&
 150                         (arg.indexOf("=") == arg.lastIndexOf("="))) {
 151                     out.print(arg.substring(0, arg.length() - 1));
 152                     out.println("\\=");
 153                 } else {
 154                     out.println(arg);
 155                 }
 156             }
 157         }
 158     }
 159 
 160     File getRuntimeImageDir(File runtimeImageTop) {
 161         return runtimeImageTop;
 162     }
 163 
 164     protected String getCfgAppDir() {
 165         return "$ROOTDIR" + File.separator
 166                 + getAppDir().getFileName() + File.separator;
 167     }
 168 
 169     protected String getCfgRuntimeDir() {
 170         return "$ROOTDIR" + File.separator + "runtime";
 171     }
 172 
 173     String getCfgClassPath(String classpath) {
 174         String cfgAppDir = getCfgAppDir();
 175 
 176         StringBuilder sb = new StringBuilder();
 177         for (String path : classpath.split("[:;]")) {
 178             if (path.length() > 0) {
 179                 sb.append(cfgAppDir);
 180                 sb.append(path);
 181                 sb.append(File.pathSeparator);
 182             }
 183         }
 184         if (sb.length() > 0) {
 185             sb.deleteCharAt(sb.length() - 1);
 186         }
 187         return sb.toString();
 188     }
 189 
 190     public static OverridableResource createIconResource(String defaultIconName,
 191             BundlerParamInfo<File> iconParam, Map<String, ? super Object> params,
 192             Map<String, ? super Object> mainParams) throws IOException {
 193 
 194         if (mainParams != null) {
 195             params = AddLauncherArguments.merge(mainParams, params, ICON.getID(),
 196                     iconParam.getID());
 197         }
 198 
 199         final String resourcePublicName = APP_NAME.fetchFrom(params)
 200                 + IOUtils.getSuffix(Path.of(defaultIconName));
 201 
 202         IconType iconType = getLauncherIconType(params);
 203         if (iconType == IconType.NoIcon) {
 204             return null;
 205         }
 206 
 207         OverridableResource resource = createResource(defaultIconName, params)
 208                 .setCategory("icon")
 209                 .setExternal(iconParam.fetchFrom(params))
 210                 .setPublicName(resourcePublicName);
 211 
 212         if (iconType == IconType.DefaultOrResourceDirIcon && mainParams != null) {
 213             // No icon explicitly configured for this launcher.
 214             // Dry-run resource creation to figure out its source.
 215             final Path nullPath = null;
 216             if (resource.saveToFile(nullPath)
 217                     != OverridableResource.Source.ResourceDir) {
 218                 // No icon in resource dir for this launcher, inherit icon
 219                 // configured for the main launcher.
 220                 resource = createIconResource(defaultIconName, iconParam,
 221                         mainParams, null).setLogPublicName(resourcePublicName);
 222             }
 223         }
 224 
 225         return resource;
 226     }
 227 
 228     private enum IconType { DefaultOrResourceDirIcon, CustomIcon, NoIcon };
 229 
 230     private static IconType getLauncherIconType(Map<String, ? super Object> params) {
 231         File launcherIcon = ICON.fetchFrom(params);
 232         if (launcherIcon == null) {
 233             return IconType.DefaultOrResourceDirIcon;
 234         }
 235 
 236         if (launcherIcon.getName().isEmpty()) {
 237             return IconType.NoIcon;
 238         }
 239 
 240         return IconType.CustomIcon;
 241     }
 242 }