1 /*
   2  * Copyright (c) 2015, 2018, 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.packager.internal.builders;
  27 
  28 import jdk.packager.internal.IOUtils;
  29 import jdk.packager.internal.Log;
  30 import jdk.packager.internal.StandardBundlerParam;
  31 import jdk.packager.internal.JLinkBundlerHelper;
  32 import jdk.packager.internal.ModFile;
  33 
  34 import java.io.ByteArrayOutputStream;
  35 import java.io.File;
  36 import java.io.FileInputStream;
  37 import java.io.IOException;
  38 import java.io.InputStream;
  39 import java.io.PrintStream;
  40 import java.nio.file.Files;
  41 import java.nio.file.Path;
  42 import java.text.MessageFormat;
  43 import java.util.List;
  44 import java.util.Map;
  45 import java.util.ResourceBundle;
  46 import java.util.ArrayList;
  47 
  48 import static jdk.packager.internal.StandardBundlerParam.*;
  49 import static jdk.packager.internal.StandardBundlerParam.ARGUMENTS;
  50 
  51 public abstract class AbstractAppImageBuilder {
  52 
  53     private static final ResourceBundle I18N =
  54             ResourceBundle.getBundle(
  55             "jdk.packager.internal.resources.builders.AbstractAppImageBuilder");
  56 
  57     //do not use file separator -
  58     // we use it for classpath lookup and there / are not platform specific
  59     public final static String BUNDLER_PREFIX = "package/";
  60 
  61     private final Map<String, Object> properties;
  62     private final Path root;
  63     protected List<String> excludeFileList = new ArrayList<>();
  64 
  65     public AbstractAppImageBuilder(Map<String, Object> properties,
  66             Path root) throws IOException {
  67         this.properties = properties;
  68         this.root = root;
  69         excludeFileList.add(".*\\.diz");
  70     }
  71 
  72     public abstract InputStream getResourceAsStream(String name);
  73     public abstract void prepareApplicationFiles() throws IOException;
  74     public abstract void prepareServerJreFiles() throws IOException;
  75 
  76     public Map<String, Object> getProperties() {
  77         return this.properties;
  78     }
  79 
  80     public Path getRoot() {
  81         return this.root;
  82     }
  83 
  84     public String getExcludeFileList() {
  85         return String.join(",", excludeFileList);
  86     }
  87 
  88     protected void copyEntry(Path appDir, File srcdir, String fname)
  89             throws IOException {
  90         Path dest = appDir.resolve(fname);
  91         Files.createDirectories(dest.getParent());
  92         File src = new File(srcdir, fname);
  93         if (src.isDirectory()) {
  94             IOUtils.copyRecursive(src.toPath(), dest);
  95         } else {
  96             Files.copy(src.toPath(), dest);
  97         }
  98     }
  99 
 100     protected InputStream locateResource(String publicName, String category,
 101             String defaultName, File customFile,
 102             boolean verbose, File publicRoot) throws IOException {
 103         InputStream is = null;
 104         boolean customFromClasspath = false;
 105         boolean customFromFile = false;
 106         if (publicName != null) {
 107             if (publicRoot != null) {
 108                 File publicResource = new File(publicRoot, publicName);
 109                 if (publicResource.exists() && publicResource.isFile()) {
 110                     is = new FileInputStream(publicResource);
 111                 }
 112             } else {
 113                 is = getResourceAsStream(publicName);
 114             }
 115             customFromClasspath = (is != null);
 116         }
 117         if (is == null && customFile != null) {
 118             is = new FileInputStream(customFile);
 119             customFromFile = (is != null);
 120         }
 121         if (is == null && defaultName != null) {
 122             is = getResourceAsStream(defaultName);
 123         }
 124         if (verbose) {
 125             String msg = null;
 126             if (customFromClasspath) {
 127                 msg = MessageFormat.format(I18N.getString(
 128                     "message.using-custom-resource-from-classpath"),
 129                     category == null ? "" : "[" + category + "] ", publicName);
 130             } else if (customFromFile) {
 131                 msg = MessageFormat.format(I18N.getString(
 132                     "message.using-custom-resource-from-file"),
 133                     category == null ? "" : "[" + category + "] ",
 134                     customFile.getAbsoluteFile());
 135             } else if (is != null) {
 136                 msg = MessageFormat.format(I18N.getString(
 137                     "message.using-default-resource-from-classpath"),
 138                     category == null ? "" : "[" + category + "] ", publicName);
 139             } else {
 140                 msg = MessageFormat.format(I18N.getString(
 141                     "message.using-default-resource"),
 142                     category == null ? "" : "[" + category + "] ", publicName);
 143             }
 144             if (msg != null) {
 145                 Log.info(msg);
 146             }
 147         }
 148         return is;
 149     }
 150 
 151 
 152     protected String preprocessTextResource(String publicName, String category,
 153             String defaultName, Map<String, String> pairs,
 154             boolean verbose, File publicRoot) throws IOException {
 155         InputStream inp = locateResource(publicName, category,
 156                 defaultName, null, verbose, publicRoot);
 157         if (inp == null) {
 158             throw new RuntimeException(
 159                     "Module corrupt? No "+defaultName+" resource!");
 160         }
 161 
 162         try (InputStream is = inp) {
 163             //read fully into memory
 164             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 165             byte[] buffer = new byte[1024];
 166             int length;
 167             while ((length = is.read(buffer)) != -1) {
 168                 baos.write(buffer, 0, length);
 169             }
 170 
 171             //substitute
 172             String result = new String(baos.toByteArray());
 173             for (Map.Entry<String, String> e : pairs.entrySet()) {
 174                 if (e.getValue() != null) {
 175                     result = result.replace(e.getKey(), e.getValue());
 176                 }
 177             }
 178             return result;
 179         }
 180     }
 181 
 182     public void writeCfgFile(Map<String, ? super Object> params,
 183             File cfgFileName, String runtimeLocation) throws IOException {
 184         cfgFileName.delete();
 185 
 186         File mainJar = JLinkBundlerHelper.getMainJar(params);
 187         ModFile.ModType mainJarType = ModFile.ModType.Unknown;
 188 
 189         if (mainJar != null) {
 190             mainJarType = new ModFile(mainJar).getModType();
 191         }
 192 
 193         String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
 194 
 195         PrintStream out = new PrintStream(cfgFileName);
 196 
 197         out.println("[Application]");
 198         out.println("app.name=" + APP_NAME.fetchFrom(params));
 199         out.println("app.version=" + VERSION.fetchFrom(params));
 200         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 201         out.println("app.runtime=" + runtimeLocation);
 202         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 203         out.println("app.classpath=" + String.join(File.pathSeparator,
 204                 CLASSPATH.fetchFrom(params).split("[ :;]")));
 205         out.println("app.application.instance=" +
 206                 (SINGLETON.fetchFrom(params) ? "single" : "multiple"));
 207 
 208         // The main app is required to be a jar, modular or unnamed.
 209         if (mainModule != null && 
 210                 (mainJarType == ModFile.ModType.Unknown ||
 211                 mainJarType == ModFile.ModType.ModularJar)) {
 212             out.println("app.mainmodule=" + mainModule);
 213         } else {
 214             String mainClass = JLinkBundlerHelper.getMainClass(params);
 215             // If the app is contained in an unnamed jar then launch it the
 216             // legacy way and the main class string must be
 217             // of the format com/foo/Main
 218             if (mainJar != null) {
 219                 out.println("app.mainjar="
 220                         + mainJar.toPath().getFileName().toString());
 221             }
 222             if (mainClass != null) {
 223                 out.println("app.mainclass="
 224                         + mainClass.replaceAll("\\.", "/"));
 225             }
 226         }
 227 
 228         String version = JLinkBundlerHelper.getJDKVersion(params);
 229 
 230         if (!version.isEmpty()) {
 231             out.println("app.java.version=" + version);
 232         }
 233 
 234         out.println("packager.java.version="
 235                 + System.getProperty("java.version"));
 236 
 237         Integer port = JLinkBundlerHelper.DEBUG.fetchFrom(params);
 238 
 239         if (port != null) {
 240             out.println(
 241                     "app.debug=-agentlib:jdwp=transport=dt_socket,"
 242                     + "server=y,suspend=y,address=localhost:"
 243                     + port);
 244         }
 245 
 246         out.println();
 247         out.println("[JVMOptions]");
 248         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 249         for (String arg : jvmargs) {
 250             out.println(arg);
 251         }
 252         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 253         for (Map.Entry<String, String> property : jvmProps.entrySet()) {
 254             out.println("-D" + property.getKey() + "=" + property.getValue());
 255         }
 256 
 257         out.println();
 258         out.println("[ArgOptions]");
 259         List<String> args = ARGUMENTS.fetchFrom(params);
 260         for (String arg : args) {
 261             if (arg.endsWith("=") &&
 262                     (arg.indexOf("=") == arg.lastIndexOf("="))) {
 263                 out.print(arg.substring(0, arg.length() - 1));
 264                 out.println("\\=");
 265             } else {
 266                 out.println(arg);
 267             }
 268         }
 269 
 270 
 271         out.close();
 272     }
 273 
 274     public String getPlatformSpecificModulesFile() {
 275         return null;
 276     }
 277 
 278 }