1 package jdk.packager.builders;
   2 
   3 import com.oracle.tools.packager.JLinkBundlerHelper;
   4 import com.oracle.tools.packager.RelativeFileSet;
   5 import jdk.tools.jlink.builder.*;
   6 import jdk.tools.jlink.plugin.Pool;
   7 
   8 import com.oracle.tools.packager.Log;
   9 
  10 import java.io.ByteArrayOutputStream;
  11 import java.io.File;
  12 import java.io.FileInputStream;
  13 import java.io.FileOutputStream;
  14 import java.io.IOException;
  15 import java.io.InputStream;
  16 import java.io.PrintStream;
  17 import java.nio.file.Files;
  18 import java.nio.file.Path;
  19 import java.text.MessageFormat;
  20 import java.util.Arrays;
  21 import java.util.List;
  22 import java.util.Map;
  23 import java.util.ResourceBundle;
  24 import java.util.Set;
  25 
  26 import static com.oracle.tools.packager.StandardBundlerParam.*;
  27 import static com.oracle.tools.packager.StandardBundlerParam.ARGUMENTS;
  28 import static com.oracle.tools.packager.StandardBundlerParam.USER_JVM_OPTIONS;
  29 
  30 /**
  31  *
  32  * Created by dferrin on 9/1/15.
  33  */
  34 public abstract class AbstractAppImageBuilder extends DefaultImageBuilder {
  35 
  36     private static final ResourceBundle I18N =
  37             ResourceBundle.getBundle(AbstractAppImageBuilder.class.getName());
  38 
  39     //do not use file separator -
  40     // we use it for classpath lookup and there / are not platform specific
  41     public final static String BUNDLER_PREFIX = "package/";
  42 
  43     public AbstractAppImageBuilder(Map<String, Object> properties, Path root) throws IOException {
  44         super(true, root);
  45     }
  46 
  47     abstract protected void prepareApplicationFiles(Pool files, Set<String> modules) throws IOException;
  48 
  49     abstract protected InputStream getResourceAsStream(String name);
  50 
  51     protected InputStream locateResource(String publicName, String category,
  52                                          String defaultName, File customFile,
  53                                          boolean verbose, File publicRoot) throws IOException {
  54         InputStream is = null;
  55         boolean customFromClasspath = false;
  56         boolean customFromFile = false;
  57         if (publicName != null) {
  58             if (publicRoot != null) {
  59                 File publicResource = new File(publicRoot, publicName);
  60                 if (publicResource.exists() && publicResource.isFile()) {
  61                     is = new FileInputStream(publicResource);
  62                 }
  63             } else {
  64                 is = getResourceAsStream(publicName);
  65             }
  66             customFromClasspath = (is != null);
  67         }
  68         if (is == null && customFile != null) {
  69             is = new FileInputStream(customFile);
  70             customFromFile = (is != null);
  71         }
  72         if (is == null && defaultName != null) {
  73             is = getResourceAsStream(defaultName);
  74         }
  75         String msg = null;
  76         if (customFromClasspath) {
  77             msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName);
  78         } else if (customFromFile) {
  79             msg = MessageFormat.format(I18N.getString("message.using-custom-resource-from-file"), category == null ? "" : "[" + category + "] ", customFile.getAbsoluteFile());
  80         } else if (is != null) {
  81             msg = MessageFormat.format(I18N.getString("message.using-default-resource-from-classpath"), category == null ? "" : "[" + category + "] ", publicName);
  82         } else {
  83             msg = MessageFormat.format(I18N.getString("message.using-default-resource"), category == null ? "" : "[" + category + "] ", publicName);
  84         }
  85         if (verbose) {
  86             Log.info(msg);
  87         }
  88         return is;
  89     }
  90 
  91 
  92     protected String preprocessTextResource(String publicName, String category,
  93                                             String defaultName, Map<String, String> pairs,
  94                                             boolean verbose, File publicRoot) throws IOException {
  95         InputStream inp = locateResource(publicName, category, defaultName, null, verbose, publicRoot);
  96         if (inp == null) {
  97             throw new RuntimeException("Module corrupt? No "+defaultName+" resource!");
  98         }
  99 
 100         try (InputStream is = inp) {
 101             //read fully into memory
 102             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 103             byte[] buffer = new byte[1024];
 104             int length;
 105             while ((length = is.read(buffer)) != -1) {
 106                 baos.write(buffer, 0, length);
 107             }
 108 
 109             //substitute
 110             String result = new String(baos.toByteArray());
 111             for (Map.Entry<String, String> e : pairs.entrySet()) {
 112                 if (e.getValue() != null) {
 113                     result = result.replace(e.getKey(), e.getValue());
 114                 }
 115             }
 116             return result;
 117         }
 118     }
 119 
 120     public void writeCfgFile(Map<String, ? super Object> params, File cfgFileName, String runtimeLocation) throws IOException {
 121         cfgFileName.delete();
 122 
 123         boolean appCDEnabled = UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params);
 124         String appCDSCacheMode = APP_CDS_CACHE_MODE.fetchFrom(params);
 125 
 126         PrintStream out = new PrintStream(cfgFileName);
 127 
 128         out.println("[Application]");
 129         out.println("app.name=" + APP_NAME.fetchFrom(params));
 130         out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next());
 131         out.println("app.version=" + VERSION.fetchFrom(params));
 132         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 133         out.println("app.mainclass=" +
 134                 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/"));
 135         out.println("app.classpath=" +
 136                 String.join(File.pathSeparator, CLASSPATH.fetchFrom(params).split("[ :;]")));
 137         out.println("app.modulepath=" +
 138                 String.join(File.pathSeparator, JLinkBundlerHelper.JDK_MODULE_PATH.fetchFrom(params)));
 139         out.println("app.runtime=" + runtimeLocation);
 140         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 141         if (appCDEnabled) {
 142             out.println("app.appcds.cache=" + appCDSCacheMode.split("\\+")[0]);
 143         }
 144 
 145 
 146         out.println();
 147         out.println("[JVMOptions]");
 148         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 149         for (String arg : jvmargs) {
 150             out.println(arg);
 151         }
 152         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 153         for (Map.Entry<String, String> property : jvmProps.entrySet()) {
 154             out.println("-D" + property.getKey() + "=" + property.getValue());
 155         }
 156         String preloader = PRELOADER_CLASS.fetchFrom(params);
 157         if (preloader != null) {
 158             out.println("-Djavafx.preloader="+preloader);
 159         }
 160 
 161 
 162         out.println();
 163         out.println("[JVMUserOptions]");
 164         Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params);
 165         for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
 166             if (arg.getKey() == null || arg.getValue() == null) {
 167                 Log.info(I18N.getString("message.jvm-user-arg-is-null"));
 168             } else {
 169                 out.println(arg.getKey().replaceAll("([\\=])", "\\\\$1") + "=" + arg.getValue());
 170             }
 171         }
 172 
 173         if (appCDEnabled) {
 174             prepareAppCDS(params, out);
 175         }
 176 
 177         out.println();
 178         out.println("[ArgOptions]");
 179         List<String> args = ARGUMENTS.fetchFrom(params);
 180         for (String arg : args) {
 181             if (arg.endsWith("=") && (arg.indexOf("=") == arg.lastIndexOf("="))) {
 182                 out.print(arg.substring(0, arg.length() - 1));
 183                 out.println("\\=");
 184             } else {
 185                 out.println(arg);
 186             }
 187         }
 188 
 189 
 190         out.close();
 191     }
 192 
 193     protected abstract String getCacheLocation(Map<String, ? super Object> params);
 194 
 195     void prepareAppCDS(Map<String, ? super Object> params, PrintStream out) throws IOException {
 196         //TODO check 8u40 or later
 197 
 198         File tempDir = Files.createTempDirectory("javapackager").toFile();
 199         tempDir.deleteOnExit();
 200         File classList = new File(tempDir, APP_FS_NAME.fetchFrom(params)  + ".classlist");
 201 
 202         try (FileOutputStream fos = new FileOutputStream(classList);
 203              PrintStream ps = new PrintStream(fos)) {
 204             for (String className : APP_CDS_CLASS_ROOTS.fetchFrom(params)) {
 205                 String slashyName = className.replace(".", "/");
 206                 ps.println(slashyName);
 207             }
 208         }
 209         APP_RESOURCES_LIST.fetchFrom(params).add(new RelativeFileSet(classList.getParentFile(), Arrays.asList(classList)));
 210 
 211         out.println();
 212         out.println("[AppCDSJVMOptions]");
 213         out.println("-XX:+UnlockCommercialFeatures");
 214         out.print("-XX:SharedArchiveFile=");
 215         out.print(getCacheLocation(params));
 216         out.print(APP_FS_NAME.fetchFrom(params));
 217         out.println(".jpa");
 218         out.println("-Xshare:auto");
 219         out.println("-XX:+UseAppCDS");
 220         if (Log.isDebug()) {
 221             out.println("-verbose:class");
 222             out.println("-XX:+TraceClassPaths");
 223             out.println("-XX:+UnlockDiagnosticVMOptions");
 224         }
 225         out.println("");
 226 
 227         out.println("[AppCDSGenerateCacheJVMOptions]");
 228         out.println("-XX:+UnlockCommercialFeatures");
 229         out.println("-Xshare:dump");
 230         out.println("-XX:+UseAppCDS");
 231         out.print("-XX:SharedArchiveFile=");
 232         out.print(getCacheLocation(params));
 233         out.print(APP_FS_NAME.fetchFrom(params));
 234         out.println(".jpa");
 235         out.println("-XX:SharedClassListFile=$PACKAGEDIR/" + APP_FS_NAME.fetchFrom(params) + ".classlist");
 236         if (Log.isDebug()) {
 237             out.println("-XX:+UnlockDiagnosticVMOptions");
 238         }
 239     }
 240 
 241 
 242 }