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.jpackage.internal;
  27 
  28 import java.io.ByteArrayOutputStream;
  29 import java.io.File;
  30 import java.io.FileInputStream;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.PrintStream;
  34 import java.nio.file.Files;
  35 import java.nio.file.Path;
  36 import java.text.MessageFormat;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.ResourceBundle;
  40 import java.util.ArrayList;
  41 
  42 import jdk.jpackage.internal.resources.ResourceLocator;
  43 
  44 import static jdk.jpackage.internal.StandardBundlerParam.*;
  45 
  46 /*
  47  * AbstractAppImageBuilder
  48  *     This is sub-classed by each of the platform dependent AppImageBuilder
  49  * classes, and contains resource processing code common to all platforms. 
  50  */
  51 
  52 public abstract class AbstractAppImageBuilder {
  53 
  54     private static final ResourceBundle I18N = ResourceBundle.getBundle(
  55             "jdk.jpackage.internal.resources.MainResources");
  56 
  57     private final Map<String, Object> properties;
  58     private final Path root;
  59 
  60     public AbstractAppImageBuilder(Map<String, Object> properties, Path root) {
  61         this.properties = properties;
  62         this.root = root;
  63     }
  64 
  65     public InputStream getResourceAsStream(String name) {
  66         return ResourceLocator.class.getResourceAsStream(name);
  67     }
  68 
  69     public abstract void prepareApplicationFiles() throws IOException;
  70     public abstract void prepareJreFiles() throws IOException;
  71     public abstract Path getAppDir();
  72     public abstract Path getAppModsDir();
  73 
  74     public Map<String, Object> getProperties() {
  75         return this.properties;
  76     }
  77 
  78     public Path getRoot() {
  79         return this.root;
  80     }
  81 
  82     protected void copyEntry(Path appDir, File srcdir, String fname)
  83             throws IOException {
  84         Path dest = appDir.resolve(fname);
  85         Files.createDirectories(dest.getParent());
  86         File src = new File(srcdir, fname);
  87         if (src.isDirectory()) {
  88             IOUtils.copyRecursive(src.toPath(), dest);
  89         } else {
  90             Files.copy(src.toPath(), dest);
  91         }
  92     }
  93 
  94     protected InputStream locateResource(String publicName, String category,
  95             String defaultName, File customFile,
  96             boolean verbose, File publicRoot) throws IOException {
  97         InputStream is = null;
  98         boolean customFromClasspath = false;
  99         boolean customFromFile = false;
 100         if (publicName != null) {
 101             if (publicRoot != null) {
 102                 File publicResource = new File(publicRoot, publicName);
 103                 if (publicResource.exists() && publicResource.isFile()) {
 104                     is = new FileInputStream(publicResource);
 105                 }
 106             } else {
 107                 is = getResourceAsStream(publicName);
 108             }
 109             customFromClasspath = (is != null);
 110         }
 111         if (is == null && customFile != null) {
 112             is = new FileInputStream(customFile);
 113             customFromFile = (is != null);
 114         }
 115         if (is == null && defaultName != null) {
 116             is = getResourceAsStream(defaultName);
 117         }
 118         if (verbose) {
 119             String msg = null;
 120             if (customFromClasspath) {
 121                 msg = MessageFormat.format(I18N.getString(
 122                     "message.using-custom-resource"),
 123                     category == null ? "" : "[" + category + "] ", publicName);
 124             } else if (customFromFile) {
 125                 msg = MessageFormat.format(I18N.getString(
 126                     "message.using-custom-resource-from-file"),
 127                     category == null ? "" : "[" + category + "] ",
 128                     customFile.getAbsoluteFile());
 129             } else if (is != null) {
 130                 msg = MessageFormat.format(I18N.getString(
 131                     "message.using-default-resource"),
 132                     defaultName,
 133                     category == null ? "" : "[" + category + "] ",
 134                     publicName);
 135             } else {
 136                 msg = MessageFormat.format(I18N.getString(
 137                     "message.no-default-resource"),
 138                     defaultName == null ? "" : defaultName,
 139                     category == null ? "" : "[" + category + "] ",
 140                     publicName);
 141             }
 142             if (msg != null) {
 143                 Log.verbose(msg);
 144             }
 145         }
 146         return is;
 147     }
 148 
 149 
 150     protected String preprocessTextResource(String publicName, String category,
 151             String defaultName, Map<String, String> pairs,
 152             boolean verbose, File publicRoot) throws IOException {
 153         InputStream inp = locateResource(publicName, category,
 154                 defaultName, null, verbose, publicRoot);
 155         if (inp == null) {
 156             throw new RuntimeException(
 157                     "Module corrupt? No "+defaultName+" resource!");
 158         }
 159 
 160         try (InputStream is = inp) {
 161             //read fully into memory
 162             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 163             byte[] buffer = new byte[1024];
 164             int length;
 165             while ((length = is.read(buffer)) != -1) {
 166                 baos.write(buffer, 0, length);
 167             }
 168 
 169             //substitute
 170             String result = new String(baos.toByteArray());
 171             for (Map.Entry<String, String> e : pairs.entrySet()) {
 172                 if (e.getValue() != null) {
 173                     result = result.replace(e.getKey(), e.getValue());
 174                 }
 175             }
 176             return result;
 177         }
 178     }
 179 
 180     public void writeCfgFile(Map<String, ? super Object> params,
 181             File cfgFileName, String runtimeLocation) throws IOException {
 182         cfgFileName.delete();
 183         File mainJar = JLinkBundlerHelper.getMainJar(params);
 184         ModFile.ModType mainJarType = ModFile.ModType.Unknown;
 185 
 186         if (mainJar != null) {
 187             mainJarType = new ModFile(mainJar).getModType();
 188         }
 189 
 190         String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
 191 
 192         try (PrintStream out = new PrintStream(cfgFileName)) {
 193 
 194             out.println("[Application]");
 195             out.println("app.name=" + APP_NAME.fetchFrom(params));
 196             out.println("app.version=" + VERSION.fetchFrom(params));
 197             out.println("app.runtime=" + runtimeLocation);
 198             out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 199             out.println("app.classpath=" + CLASSPATH.fetchFrom(params));
 200 
 201             // The main app is required to be a jar, modular or unnamed.
 202             if (mainModule != null &&
 203                     (mainJarType == ModFile.ModType.Unknown ||
 204                     mainJarType == ModFile.ModType.ModularJar)) {
 205                 out.println("app.mainmodule=" + mainModule);
 206             } else {
 207                 String mainClass = JLinkBundlerHelper.getMainClass(params);
 208                 // If the app is contained in an unnamed jar then launch it the
 209                 // legacy way and the main class string must be
 210                 // of the format com/foo/Main
 211                 if (mainJar != null) {
 212                     out.println("app.mainjar="
 213                             + mainJar.toPath().getFileName().toString());
 214                 }
 215                 if (mainClass != null) {
 216                     out.println("app.mainclass="
 217                             + mainClass.replaceAll("\\.", "/"));
 218                 }
 219             }
 220 
 221             out.println();
 222             out.println("[JavaOptions]");
 223             List<String> jvmargs = JAVA_OPTIONS.fetchFrom(params);
 224             for (String arg : jvmargs) {
 225                 out.println(arg);
 226             }
 227             Path modsDir = getAppModsDir();
 228             if (modsDir != null && modsDir.toFile().exists()) {
 229                 out.println("--module-path");
 230                 out.println(getAppDir().relativize(modsDir));
 231             }
 232 
 233             out.println();
 234             out.println("[ArgOptions]");
 235             List<String> args = ARGUMENTS.fetchFrom(params);
 236             for (String arg : args) {
 237                 if (arg.endsWith("=") &&
 238                         (arg.indexOf("=") == arg.lastIndexOf("="))) {
 239                     out.print(arg.substring(0, arg.length() - 1));
 240                     out.println("\\=");
 241                 } else {
 242                     out.println(arg);
 243                 }
 244             }
 245         }
 246     }
 247 
 248 }