1 /*
   2  * Copyright (c) 2012, 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.IOException;
  31 import java.io.PrintStream;
  32 import java.nio.file.Path;
  33 import java.text.MessageFormat;
  34 import java.util.Arrays;
  35 import java.util.Collection;
  36 import java.util.Map;
  37 import java.util.ResourceBundle;
  38 
  39 import static jdk.jpackage.internal.WindowsBundlerParam.*;
  40 import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE;
  41 
  42 public class WinAppBundler extends AbstractImageBundler {
  43 
  44     private static final ResourceBundle I18N = ResourceBundle.getBundle(
  45             "jdk.jpackage.internal.resources.WinResources");
  46 
  47     static final BundlerParamInfo<File> ICON_ICO =
  48             new StandardBundlerParam<>(
  49             I18N.getString("param.icon-ico.name"),
  50             I18N.getString("param.icon-ico.description"),
  51             "icon.ico",
  52             File.class,
  53             params -> {
  54                 File f = ICON.fetchFrom(params);
  55                 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
  56                     Log.error(MessageFormat.format(
  57                             I18N.getString("message.icon-not-ico"), f));
  58                     return null;
  59                 }
  60                 return f;
  61             },
  62             (s, p) -> new File(s));
  63 
  64     @Override
  65     public boolean validate(Map<String, ? super Object> params)
  66             throws UnsupportedPlatformException, ConfigException {
  67         try {
  68             if (params == null) throw new ConfigException(
  69                     I18N.getString("error.parameters-null"),
  70                     I18N.getString("error.parameters-null.advice"));
  71 
  72             return doValidate(params);
  73         } catch (RuntimeException re) {
  74             if (re.getCause() instanceof ConfigException) {
  75                 throw (ConfigException) re.getCause();
  76             } else {
  77                 throw new ConfigException(re);
  78             }
  79         }
  80     }
  81 
  82     // to be used by chained bundlers, e.g. by EXE bundler to avoid
  83     // skipping validation if p.type does not include "image"
  84     private boolean doValidate(Map<String, ? super Object> p)
  85             throws UnsupportedPlatformException, ConfigException {
  86         if (Platform.getPlatform() != Platform.WINDOWS) {
  87             throw new UnsupportedPlatformException();
  88         }
  89 
  90         imageBundleValidation(p);
  91 
  92         if (StandardBundlerParam.getPredefinedAppImage(p) != null) {
  93             return true;
  94         }
  95 
  96         // Make sure that jpackage.exe exists.
  97         File tool = new File(
  98                 System.getProperty("java.home") + "\\bin\\jpackage.exe");
  99 
 100         if (!tool.exists()) {
 101             throw new ConfigException(
 102                     I18N.getString("error.no-windows-resources"),
 103                     I18N.getString("error.no-windows-resources.advice"));
 104         }
 105 
 106         return true;
 107     }
 108 
 109     private static boolean usePredefineAppName(Map<String, ? super Object> p) {
 110         return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null);
 111     }
 112 
 113     private static String appName;
 114     synchronized static String getAppName(
 115             Map<String, ? super Object> p) {
 116         // If we building from predefined app image, then we should use names
 117         // from image and not from CLI.
 118         if (usePredefineAppName(p)) {
 119             if (appName == null) {
 120                 // Use WIN_APP_IMAGE here, since we already copy pre-defined
 121                 // image to WIN_APP_IMAGE
 122                 File appImageDir = WIN_APP_IMAGE.fetchFrom(p);
 123 
 124                 File appDir = new File(appImageDir.toString() + "\\app");
 125                 File [] files = appDir.listFiles(
 126                         (File dir, String name) -> name.endsWith(".cfg"));
 127                 if (files == null || files.length == 0) {
 128                     String name = APP_NAME.fetchFrom(p);
 129                     Path exePath = appImageDir.toPath().resolve(name + ".exe");
 130                     Path icoPath = appImageDir.toPath().resolve(name + ".ico");
 131                     if (exePath.toFile().exists() &&
 132                             icoPath.toFile().exists()) {
 133                         return name;
 134                     } else {
 135                         throw new RuntimeException(MessageFormat.format(
 136                                 I18N.getString("error.cannot-find-launcher"),
 137                                 appImageDir));
 138                     }
 139                 } else {
 140                     appName = files[0].getName();
 141                     int index = appName.indexOf(".");
 142                     if (index != -1) {
 143                         appName = appName.substring(0, index);
 144                     }
 145                     if (files.length > 1) {
 146                         Log.error(MessageFormat.format(I18N.getString(
 147                                 "message.multiple-launchers"), appName));
 148                     }
 149                 }
 150                 return appName;
 151             } else {
 152                 return appName;
 153             }
 154         }
 155 
 156         return APP_NAME.fetchFrom(p);
 157     }
 158 
 159     public static String getLauncherName(Map<String, ? super Object> p) {
 160         return getAppName(p) + ".exe";
 161     }
 162 
 163     public static String getLauncherCfgName(Map<String, ? super Object> p) {
 164         return "app\\" + getAppName(p) +".cfg";
 165     }
 166 
 167     public boolean bundle(Map<String, ? super Object> p, File outputDirectory)
 168             throws PackagerException {
 169         return doBundle(p, outputDirectory, false) != null;
 170     }
 171 
 172     File doBundle(Map<String, ? super Object> p, File outputDirectory,
 173             boolean dependentTask) throws PackagerException {
 174         if (RUNTIME_INSTALLER.fetchFrom(p)) {
 175             return doJreBundle(p, outputDirectory, dependentTask);
 176         } else {
 177             return doAppBundle(p, outputDirectory, dependentTask);
 178         }
 179     }
 180 
 181     File doJreBundle(Map<String, ? super Object> p, File outputDirectory,
 182             boolean dependentTask) throws PackagerException {
 183         try {
 184             File rootDirectory = createRoot(p, outputDirectory, dependentTask,
 185                 APP_NAME.fetchFrom(p), "windowsapp-image-builder");
 186             AbstractAppImageBuilder appBuilder = new WindowsAppImageBuilder(
 187                     APP_NAME.fetchFrom(p),
 188                     outputDirectory.toPath());
 189             File predefined = PREDEFINED_RUNTIME_IMAGE.fetchFrom(p);
 190             if (predefined == null ) {
 191                 JLinkBundlerHelper.generateJre(p, appBuilder);
 192             } else {
 193                 return predefined;
 194             }
 195             return rootDirectory;
 196         } catch (PackagerException pe) {
 197             throw pe;
 198         } catch (Exception e) {
 199             Log.verbose(e);
 200             throw new PackagerException(e);
 201         }
 202     }
 203 
 204     File doAppBundle(Map<String, ? super Object> p, File outputDirectory,
 205             boolean dependentTask) throws PackagerException {
 206         try {
 207             File rootDirectory = createRoot(p, outputDirectory, dependentTask,
 208                     APP_NAME.fetchFrom(p), "windowsapp-image-builder");
 209             AbstractAppImageBuilder appBuilder =
 210                     new WindowsAppImageBuilder(p, outputDirectory.toPath());
 211             if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) {
 212                 JLinkBundlerHelper.execute(p, appBuilder);
 213             } else {
 214                 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder);
 215             }
 216             if (!dependentTask) {
 217                 Log.verbose(MessageFormat.format(
 218                         I18N.getString("message.result-dir"),
 219                         outputDirectory.getAbsolutePath()));
 220             }
 221             return rootDirectory;
 222         } catch (PackagerException pe) {
 223             throw pe;
 224         } catch (Exception e) {
 225             Log.verbose(e);
 226             throw new PackagerException(e);
 227         }
 228     }
 229 
 230     @Override
 231     public String getName() {
 232         return I18N.getString("app.bundler.name");
 233     }
 234 
 235     @Override
 236     public String getDescription() {
 237         return I18N.getString("app.bundler.description");
 238     }
 239 
 240     @Override
 241     public String getID() {
 242         return "windows.app";
 243     }
 244 
 245     @Override
 246     public String getBundleType() {
 247         return "IMAGE";
 248     }
 249 
 250     @Override
 251     public Collection<BundlerParamInfo<?>> getBundleParameters() {
 252         return getAppBundleParameters();
 253     }
 254 
 255     public static Collection<BundlerParamInfo<?>> getAppBundleParameters() {
 256         return Arrays.asList(
 257                 APP_NAME,
 258                 APP_RESOURCES,
 259                 ARGUMENTS,
 260                 CLASSPATH,
 261                 ICON_ICO,
 262                 JVM_OPTIONS,
 263                 MAIN_CLASS,
 264                 MAIN_JAR,
 265                 PREFERENCES_ID,
 266                 VERSION,
 267                 VERBOSE
 268             );
 269     }
 270 
 271     @Override
 272     public File execute(Map<String, ? super Object> params,
 273             File outputParentDir) throws PackagerException {
 274         return doBundle(params, outputParentDir, false);
 275     }
 276 
 277     @Override
 278     public boolean supported(boolean platformInstaller) {
 279         return (Platform.getPlatform() == Platform.WINDOWS);
 280     }
 281 
 282 }