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