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 UnsupportedPlatformException, 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 UnsupportedPlatformException, ConfigException { 81 if (Platform.getPlatform() != Platform.WINDOWS) { 82 throw new UnsupportedPlatformException(); 83 } 84 85 imageBundleValidation(p); 86 87 if (StandardBundlerParam.getPredefinedAppImage(p) != null) { 88 return true; 89 } 90 91 // Make sure that jpackage.exe exists. 92 File tool = new File( 93 System.getProperty("java.home") + "\\bin\\jpackage.exe"); 94 95 if (!tool.exists()) { 96 throw new ConfigException( 97 I18N.getString("error.no-windows-resources"), 98 I18N.getString("error.no-windows-resources.advice")); 99 } 100 101 return true; 102 } 103 104 private static boolean usePredefineAppName(Map<String, ? super Object> p) { 105 return (PREDEFINED_APP_IMAGE.fetchFrom(p) != null); 106 } 107 108 private static String appName; 109 synchronized static String getAppName( 110 Map<String, ? super Object> p) { 111 // If we building from predefined app image, then we should use names 112 // from image and not from CLI. 113 if (usePredefineAppName(p)) { 114 if (appName == null) { 115 // Use WIN_APP_IMAGE here, since we already copy pre-defined 116 // image to WIN_APP_IMAGE 117 File appImageDir = WIN_APP_IMAGE.fetchFrom(p); 118 119 File appDir = new File(appImageDir.toString() + "\\app"); 120 File [] files = appDir.listFiles( 121 (File dir, String name) -> name.endsWith(".cfg")); 122 if (files == null || files.length == 0) { 123 String name = APP_NAME.fetchFrom(p); 124 Path exePath = appImageDir.toPath().resolve(name + ".exe"); 125 Path icoPath = appImageDir.toPath().resolve(name + ".ico"); 126 if (exePath.toFile().exists() && 127 icoPath.toFile().exists()) { 128 return name; 129 } else { 130 throw new RuntimeException(MessageFormat.format( 131 I18N.getString("error.cannot-find-launcher"), 132 appImageDir)); 133 } 134 } else { 135 appName = files[0].getName(); 136 int index = appName.indexOf("."); 137 if (index != -1) { 138 appName = appName.substring(0, index); 139 } 140 if (files.length > 1) { 141 Log.error(MessageFormat.format(I18N.getString( 142 "message.multiple-launchers"), appName)); 143 } 144 } 145 return appName; 146 } else { 147 return appName; 148 } 149 } 150 151 return APP_NAME.fetchFrom(p); 152 } 153 154 public static String getLauncherName(Map<String, ? super Object> p) { 155 return getAppName(p) + ".exe"; 156 } 157 158 public static String getLauncherCfgName(Map<String, ? super Object> p) { 159 return "app\\" + getAppName(p) +".cfg"; 160 } 161 162 public boolean bundle(Map<String, ? super Object> p, File outputDirectory) 163 throws PackagerException { 164 return doBundle(p, outputDirectory, false) != null; 165 } 166 167 File doBundle(Map<String, ? super Object> p, File outputDirectory, 168 boolean dependentTask) throws PackagerException { 169 if (StandardBundlerParam.isRuntimeInstaller(p)) { 170 return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p); 171 } else { 172 return doAppBundle(p, outputDirectory, dependentTask); 173 } 174 } 175 176 File doAppBundle(Map<String, ? super Object> p, File outputDirectory, 177 boolean dependentTask) throws PackagerException { 178 try { 179 File rootDirectory = createRoot(p, outputDirectory, dependentTask, 180 APP_NAME.fetchFrom(p)); 181 AbstractAppImageBuilder appBuilder = 182 new WindowsAppImageBuilder(p, outputDirectory.toPath()); 183 if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) { 184 JLinkBundlerHelper.execute(p, appBuilder); 185 } else { 186 StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder); 187 } 188 if (!dependentTask) { 189 Log.verbose(MessageFormat.format( 190 I18N.getString("message.result-dir"), 191 outputDirectory.getAbsolutePath())); 192 } 193 return rootDirectory; 194 } catch (PackagerException pe) { 195 throw pe; 196 } catch (Exception e) { 197 Log.verbose(e); 198 throw new PackagerException(e); 199 } 200 } 201 202 @Override 203 public String getName() { 204 return I18N.getString("app.bundler.name"); 205 } 206 207 @Override 208 public String getDescription() { 209 return I18N.getString("app.bundler.description"); 210 } 211 212 @Override 213 public String getID() { 214 return "windows.app"; 215 } 216 217 @Override 218 public String getBundleType() { 219 return "IMAGE"; 220 } 221 222 @Override 223 public Collection<BundlerParamInfo<?>> getBundleParameters() { 224 return getAppBundleParameters(); 225 } 226 227 public static Collection<BundlerParamInfo<?>> getAppBundleParameters() { 228 return Arrays.asList( 229 APP_NAME, 230 APP_RESOURCES, 231 ARGUMENTS, 232 CLASSPATH, 233 ICON_ICO, 234 JAVA_OPTIONS, 235 MAIN_CLASS, 236 MAIN_JAR, 237 VERSION, 238 VERBOSE 239 ); 240 } 241 242 @Override 243 public File execute(Map<String, ? super Object> params, 244 File outputParentDir) throws PackagerException { 245 return doBundle(params, outputParentDir, false); 246 } 247 248 @Override 249 public boolean supported(boolean platformInstaller) { 250 return (Platform.getPlatform() == Platform.WINDOWS); 251 } 252 253 }