1 /*
   2  * Copyright (c) 2015, 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 com.oracle.tools.packager;
  26 
  27 import com.sun.javafx.tools.packager.bundlers.BundleParams;
  28 
  29 import java.io.File;
  30 import java.io.FileOutputStream;
  31 import java.io.IOException;
  32 import java.io.PrintStream;
  33 import java.nio.file.Files;
  34 import java.text.MessageFormat;
  35 import java.util.Arrays;
  36 import java.util.List;
  37 import java.util.Map;
  38 import java.util.ResourceBundle;
  39 import java.util.regex.Matcher;
  40 import java.util.regex.Pattern;
  41 
  42 import static com.oracle.tools.packager.StandardBundlerParam.*;
  43 import static com.oracle.tools.packager.StandardBundlerParam.ARGUMENTS;
  44 
  45 /**
  46  * Common utility methods used by app image bundlers.
  47  */
  48 public abstract class AbstractImageBundler extends AbstractBundler {
  49 
  50     private static final ResourceBundle I18N =
  51             ResourceBundle.getBundle(AbstractImageBundler.class.getName());
  52 
  53     public static final String CFG_FORMAT_PROPERTIES="prop";
  54     public static final String CFG_FORMAT_INI="ini";
  55     
  56     public static final BundlerParamInfo<String> LAUNCHER_CFG_FORMAT =
  57             new StandardBundlerParam<>(
  58                     I18N.getString("param.launcher-cfg-format.name"),
  59                     I18N.getString("param.launcher-cfg-format.description"),
  60                     "launcher-cfg-format",
  61                     String.class,
  62                     params -> "ini",
  63                     (s, p) -> s);
  64 
  65     //helper method to test if required files are present in the runtime
  66     public void testRuntime(RelativeFileSet runtime, String[] file) throws ConfigException {
  67         if (runtime == null) {
  68             return; //null runtime is ok (request to use system)
  69         }
  70 
  71         Pattern[] weave = Arrays.stream(file).map(Pattern::compile).toArray(Pattern[]::new);
  72 
  73         if (!runtime.getIncludedFiles().stream().anyMatch(s ->
  74                         Arrays.stream(weave).anyMatch(pattern -> pattern.matcher(s).matches())
  75         )) {
  76             throw new ConfigException(
  77                     MessageFormat.format(I18N.getString("error.jre-missing-file"), Arrays.toString(file)),
  78                     I18N.getString("error.jre-missing-file.advice"));
  79         }
  80     }
  81 
  82     public void imageBundleValidation(Map<String, ? super Object> p) throws ConfigException {
  83         StandardBundlerParam.validateMainClassInfoFromAppResources(p);
  84 
  85         Map<String, String> userJvmOptions = USER_JVM_OPTIONS.fetchFrom(p);
  86         if (userJvmOptions != null) {
  87             for (Map.Entry<String, String> entry : userJvmOptions.entrySet()) {
  88                 if (entry.getValue() == null || entry.getValue().isEmpty()) {
  89                     throw new ConfigException(
  90                             MessageFormat.format(I18N.getString("error.empty-user-jvm-option-value"), entry.getKey()),
  91                             I18N.getString("error.empty-user-jvm-option-value.advice"));
  92                 }
  93             }
  94         }
  95 
  96         if (MAIN_JAR.fetchFrom(p) == null) {
  97             throw new ConfigException(
  98                     I18N.getString("error.no-application-jar"),
  99                     I18N.getString("error.no-application-jar.advice"));
 100         }
 101         
 102         extractRuntimeFlags(p);
 103         
 104         if (ENABLE_APP_CDS.fetchFrom(p)) {
 105             if (UNLOCK_COMMERCIAL_FEATURES.fetchFrom(p)) {
 106                 if (p.containsKey(BundleParams.PARAM_RUNTIME)
 107                         && (p.get(BundleParams.PARAM_RUNTIME) == null)) 
 108                 {
 109                     throw new ConfigException(
 110                             I18N.getString("error.app-cds-requires-runtime"),
 111                             I18N.getString("error.app-cds-requires-runtime.advice"));
 112                 }
 113                 Object majorV = p.get(".runtime.version.major");
 114                 Object minorV = p.get(".runtime.version.minor");
 115                 if (majorV != null && minorV != null) {
 116                     try {
 117                         int major = Integer.parseInt(majorV.toString());
 118                         int minor = Integer.parseInt(minorV.toString());
 119                         if ((major < 8) || (major == 8 && minor < 40)) {
 120                             throw new ConfigException(
 121                                     I18N.getString("error.app-cds-bad-version"),
 122                                     I18N.getString("error.app-cds-bad-version.advice"));
 123                         }
 124                     } catch (NumberFormatException nfe) {
 125                         //maybe log a failure to check versions?
 126                     }
 127                 }
 128             } else {
 129                 throw new ConfigException(
 130                         I18N.getString("error.app-cds-no-commercial-unlock"),
 131                         I18N.getString("error.app-cds-no-commercial-unlock.advice"));
 132             }
 133         }
 134         
 135     }
 136     
 137     public void writeCfgFile(Map<String, ? super Object> params, File cfgFileName, String runtimeLocation) throws IOException {
 138         cfgFileName.delete();
 139 
 140         boolean appCDEnabled = UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params);
 141         String appCDSCacheMode = APP_CDS_CACHE_MODE.fetchFrom(params);
 142         
 143         PrintStream out = new PrintStream(cfgFileName);
 144         
 145         out.println("[Application]");
 146         out.println("app.name=" + APP_NAME.fetchFrom(params));
 147         out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next());
 148         out.println("app.version=" + VERSION.fetchFrom(params));
 149         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 150         out.println("app.mainclass=" +
 151                 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/"));
 152         out.println("app.classpath=" +
 153                 String.join(File.pathSeparator, CLASSPATH.fetchFrom(params).split("[ :;]")));
 154         out.println("app.runtime=" + runtimeLocation);
 155         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 156         if (appCDEnabled) {
 157             out.println("app.appcds.cache=" + appCDSCacheMode.split("\\+")[0]);
 158         }
 159 
 160 
 161         out.println();
 162         out.println("[JVMOptions]");
 163         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 164         for (String arg : jvmargs) {
 165             out.println(arg);
 166         }
 167         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 168         for (Map.Entry<String, String> property : jvmProps.entrySet()) {
 169             out.println("-D" + property.getKey() + "=" + property.getValue());
 170         }
 171         String preloader = PRELOADER_CLASS.fetchFrom(params);
 172         if (preloader != null) {
 173             out.println("-Djavafx.preloader="+preloader);
 174         }
 175 
 176         
 177         out.println();
 178         out.println("[JVMUserOptions]");
 179         Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params);
 180         for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
 181             if (arg.getKey() == null || arg.getValue() == null) {
 182                 Log.info(I18N.getString("message.jvm-user-arg-is-null"));
 183             } else {
 184                 out.println(arg.getKey().replaceAll("([\\=])", "\\\\$1") + "=" + arg.getValue());
 185             }
 186         }
 187 
 188         if (appCDEnabled) {
 189             prepareAppCDS(params, out);
 190         }
 191         
 192         out.println();
 193         out.println("[ArgOptions]");
 194         List<String> args = ARGUMENTS.fetchFrom(params);
 195         for (String arg : args) {
 196             if (arg.endsWith("=") && (arg.indexOf("=") == arg.lastIndexOf("="))) {
 197                 out.print(arg.substring(0, arg.length() - 1));
 198                 out.println("\\=");
 199             } else {
 200                 out.println(arg);
 201             }
 202         }
 203 
 204         
 205         out.close();
 206     }
 207 
 208     protected abstract String getCacheLocation(Map<String, ? super Object> params);
 209     
 210     void prepareAppCDS(Map<String, ? super Object> params, PrintStream out) throws IOException {
 211         //TODO check 8u40 or later
 212 
 213         File tempDir = Files.createTempDirectory("javapackager").toFile();
 214         tempDir.deleteOnExit();
 215         File classList = new File(tempDir, APP_FS_NAME.fetchFrom(params)  + ".classlist");
 216 
 217         try (FileOutputStream fos = new FileOutputStream(classList);
 218              PrintStream ps = new PrintStream(fos)) {
 219             for (String className : APP_CDS_CLASS_ROOTS.fetchFrom(params)) {
 220                 String slashyName = className.replace(".", "/");
 221                 ps.println(slashyName);
 222             }
 223         }
 224         APP_RESOURCES_LIST.fetchFrom(params).add(new RelativeFileSet(classList.getParentFile(), Arrays.asList(classList)));
 225 
 226         out.println();
 227         out.println("[AppCDSJVMOptions]");
 228         out.println("-XX:+UnlockCommercialFeatures");
 229         out.print("-XX:SharedArchiveFile=");
 230         out.print(getCacheLocation(params));
 231         out.print(APP_FS_NAME.fetchFrom(params));
 232         out.println(".jpa");
 233         out.println("-Xshare:auto");
 234         out.println("-XX:+UseAppCDS");
 235         if (Log.isDebug()) {
 236             out.println("-verbose:class");
 237             out.println("-XX:+TraceClassPaths");
 238             out.println("-XX:+UnlockDiagnosticVMOptions");
 239         }
 240         out.println("");
 241         
 242         out.println("[AppCDSGenerateCacheJVMOptions]");
 243         out.println("-XX:+UnlockCommercialFeatures");
 244         out.println("-Xshare:dump");
 245         out.println("-XX:+UseAppCDS");
 246         out.print("-XX:SharedArchiveFile=");
 247         out.print(getCacheLocation(params));
 248         out.print(APP_FS_NAME.fetchFrom(params));
 249         out.println(".jpa");
 250         out.println("-XX:SharedClassListFile=$PACKAGEDIR/" + APP_FS_NAME.fetchFrom(params) + ".classlist");
 251         if (Log.isDebug()) {
 252             out.println("-XX:+UnlockDiagnosticVMOptions");
 253         }
 254     }
 255 
 256     abstract public void extractRuntimeFlags(Map<String, ? super Object> params); 
 257     
 258     public static void extractFlagsFromVersion(Map<String, ? super Object> params, String versionOutput) {
 259         Pattern bitArchPattern = Pattern.compile("(\\d*)[- ]?[bB]it");
 260         Matcher matcher = bitArchPattern.matcher(versionOutput);
 261         if (matcher.find()) {
 262             params.put(".runtime.bit-arch", matcher.group(1));
 263         } else {
 264             // presume 32 bit on no match
 265             params.put(".runtime.bit-arch", "32");
 266         }
 267 
 268         Pattern oldVersionMatcher = Pattern.compile("java version \"((\\d+.(\\d+).\\d+)(_(\\d+)))?(-(.*))?\"");
 269         matcher = oldVersionMatcher.matcher(versionOutput);
 270         if (matcher.find()) {
 271             params.put(".runtime.version", matcher.group(1));
 272             params.put(".runtime.version.release", matcher.group(2));
 273             params.put(".runtime.version.major", matcher.group(3));
 274             params.put(".runtime.version.update", matcher.group(5));
 275             params.put(".runtime.version.minor", matcher.group(5));
 276             params.put(".runtime.version.security", matcher.group(5));
 277             params.put(".runtime.version.patch", "0");
 278             params.put(".runtime.version.modifiers", matcher.group(7));
 279         } else {
 280             Pattern newVersionMatcher = Pattern.compile("java version \"((\\d+).(\\d+).(\\d+).(\\d+))(-(.*))?(\\+[^\"]*)?\"");
 281             matcher = newVersionMatcher.matcher(versionOutput);
 282             if (matcher.find()) {
 283                 params.put(".runtime.version", matcher.group(1));
 284                 params.put(".runtime.version.release", matcher.group(1));
 285                 params.put(".runtime.version.major", matcher.group(2));
 286                 params.put(".runtime.version.update", matcher.group(3));
 287                 params.put(".runtime.version.minor", matcher.group(3));
 288                 params.put(".runtime.version.security", matcher.group(4));
 289                 params.put(".runtime.version.patch", matcher.group(5));
 290                 params.put(".runtime.version.modifiers", matcher.group(7));
 291             } else {
 292                 params.put(".runtime.version", "");
 293                 params.put(".runtime.version.release", "");
 294                 params.put(".runtime.version.major", "");
 295                 params.put(".runtime.version.update", "");
 296                 params.put(".runtime.version.minor", "");
 297                 params.put(".runtime.version.security", "");
 298                 params.put(".runtime.version.patch", "");
 299                 params.put(".runtime.version.modifiers", "");
 300             }
 301         }
 302     }
 303 
 304 
 305 }