src/jdk.jpackager/macosx/classes/jdk/jpackager/internal/mac/MacDmgBundler.java
branchJDK-8200758-branch
changeset 57017 1b08af362a30
parent 56989 0f19096663d1
child 57020 a828547f7e50
equal deleted inserted replaced
57016:f63f13da91c0 57017:1b08af362a30
       
     1 /*
       
     2  * Copyright (c) 2012, 2018, 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.jpackager.internal.mac;
       
    27 
       
    28 import jdk.jpackager.internal.*;
       
    29 import jdk.jpackager.internal.IOUtils;
       
    30 import jdk.jpackager.internal.resources.mac.MacResources;
       
    31 import jdk.jpackager.internal.Arguments;
       
    32 
       
    33 import java.io.*;
       
    34 import java.nio.file.Files;
       
    35 import java.text.MessageFormat;
       
    36 import java.util.*;
       
    37 
       
    38 import static jdk.jpackager.internal.StandardBundlerParam.*;
       
    39 
       
    40 public class MacDmgBundler extends MacBaseInstallerBundler {
       
    41 
       
    42     private static final ResourceBundle I18N =
       
    43             ResourceBundle.getBundle(
       
    44             "jdk.jpackager.internal.resources.mac.MacDmgBundler");
       
    45 
       
    46     static final String DEFAULT_BACKGROUND_IMAGE="background_dmg.png";
       
    47     static final String DEFAULT_DMG_SETUP_SCRIPT="DMGsetup.scpt";
       
    48     static final String TEMPLATE_BUNDLE_ICON = "GenericApp.icns";
       
    49 
       
    50     static final String DEFAULT_LICENSE_PLIST="lic_template.plist";
       
    51 
       
    52     public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
       
    53             new StandardBundlerParam<> (
       
    54             I18N.getString("param.installer-suffix.name"),
       
    55             I18N.getString("param.installer-suffix.description"),
       
    56             "mac.dmg.installerName.suffix",
       
    57             String.class,
       
    58             params -> "",
       
    59             (s, p) -> s);
       
    60 
       
    61     public MacDmgBundler() {
       
    62         super();
       
    63         baseResourceLoader = MacResources.class;
       
    64     }
       
    65 
       
    66     public File bundle(Map<String, ? super Object> params, File outdir) {
       
    67         Log.info(MessageFormat.format(I18N.getString("message.building-dmg"),
       
    68                 APP_NAME.fetchFrom(params)));
       
    69         if (!outdir.isDirectory() && !outdir.mkdirs()) {
       
    70             throw new RuntimeException(MessageFormat.format(
       
    71                     I18N.getString("error.cannot-create-output-dir"),
       
    72                     outdir.getAbsolutePath()));
       
    73         }
       
    74         if (!outdir.canWrite()) {
       
    75             throw new RuntimeException(MessageFormat.format(
       
    76                     I18N.getString("error.cannot-write-to-output-dir"),
       
    77                     outdir.getAbsolutePath()));
       
    78         }
       
    79 
       
    80         File appImageDir = APP_IMAGE_BUILD_ROOT.fetchFrom(params);
       
    81         try {
       
    82             appImageDir.mkdirs();
       
    83 
       
    84             if (prepareAppBundle(params, true) != null &&
       
    85                     prepareConfigFiles(params)) {
       
    86                 File configScript = getConfig_Script(params);
       
    87                 if (configScript.exists()) {
       
    88                     Log.info(MessageFormat.format(
       
    89                             I18N.getString("message.running-script"),
       
    90                             configScript.getAbsolutePath()));
       
    91                     IOUtils.run("bash", configScript, false);
       
    92                 }
       
    93 
       
    94                 return buildDMG(params, outdir);
       
    95             }
       
    96             return null;
       
    97         } catch (IOException ex) {
       
    98             Log.verbose(ex);
       
    99             return null;
       
   100         } finally {
       
   101             try {
       
   102                 if (appImageDir != null &&
       
   103                         PREDEFINED_APP_IMAGE.fetchFrom(params) == null &&
       
   104                         (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ||
       
   105                         !Arguments.CREATE_JRE_INSTALLER.fetchFrom(params)) &&
       
   106                         !Log.isDebug()) {
       
   107                     IOUtils.deleteRecursive(appImageDir);
       
   108                 } else if (appImageDir != null) {
       
   109                     Log.info(MessageFormat.format(I18N.getString(
       
   110                             "message.intermediate-image-location"),
       
   111                             appImageDir.getAbsolutePath()));
       
   112                 }
       
   113 
       
   114                 //cleanup
       
   115                 cleanupConfigFiles(params);
       
   116             } catch (IOException ex) {
       
   117                 Log.debug(ex);
       
   118                 //noinspection ReturnInsideFinallyBlock
       
   119                 return null;
       
   120             }
       
   121         }
       
   122     }
       
   123 
       
   124     //remove
       
   125     protected void cleanupConfigFiles(Map<String, ? super Object> params) {
       
   126         if (getConfig_VolumeBackground(params) != null) {
       
   127             getConfig_VolumeBackground(params).delete();
       
   128         }
       
   129         if (getConfig_VolumeIcon(params) != null) {
       
   130             getConfig_VolumeIcon(params).delete();
       
   131         }
       
   132         if (getConfig_VolumeScript(params) != null) {
       
   133             getConfig_VolumeScript(params).delete();
       
   134         }
       
   135         if (getConfig_Script(params) != null) {
       
   136             getConfig_Script(params).delete();
       
   137         }
       
   138         if (getConfig_LicenseFile(params) != null) {
       
   139             getConfig_LicenseFile(params).delete();
       
   140         }
       
   141         APP_BUNDLER.fetchFrom(params).cleanupConfigFiles(params);
       
   142     }
       
   143 
       
   144     private static final String hdiutil = "/usr/bin/hdiutil";
       
   145 
       
   146     private void prepareDMGSetupScript(String volumeName,
       
   147             Map<String, ? super Object> p) throws IOException {
       
   148         File dmgSetup = getConfig_VolumeScript(p);
       
   149         Log.verbose(MessageFormat.format(
       
   150                 I18N.getString("message.preparing-dmg-setup"),
       
   151                 dmgSetup.getAbsolutePath()));
       
   152 
       
   153         //prepare config for exe
       
   154         Map<String, String> data = new HashMap<>();
       
   155         data.put("DEPLOY_ACTUAL_VOLUME_NAME", volumeName);
       
   156         data.put("DEPLOY_APPLICATION_NAME", APP_NAME.fetchFrom(p));
       
   157 
       
   158         data.put("DEPLOY_INSTALL_LOCATION", "(path to desktop folder)");
       
   159         data.put("DEPLOY_INSTALL_NAME", "Desktop");
       
   160 
       
   161         Writer w = new BufferedWriter(new FileWriter(dmgSetup));
       
   162         w.write(preprocessTextResource(
       
   163                 MacAppBundler.MAC_BUNDLER_PREFIX + dmgSetup.getName(),
       
   164                 I18N.getString("resource.dmg-setup-script"),
       
   165                         DEFAULT_DMG_SETUP_SCRIPT, data, VERBOSE.fetchFrom(p),
       
   166                 DROP_IN_RESOURCES_ROOT.fetchFrom(p)));
       
   167         w.close();
       
   168     }
       
   169 
       
   170     private File getConfig_VolumeScript(Map<String, ? super Object> params) {
       
   171         return new File(CONFIG_ROOT.fetchFrom(params),
       
   172                 APP_NAME.fetchFrom(params) + "-dmg-setup.scpt");
       
   173     }
       
   174 
       
   175     private File getConfig_VolumeBackground(
       
   176             Map<String, ? super Object> params) {
       
   177         return new File(CONFIG_ROOT.fetchFrom(params),
       
   178                 APP_NAME.fetchFrom(params) + "-background.png");
       
   179     }
       
   180 
       
   181     private File getConfig_VolumeIcon(Map<String, ? super Object> params) {
       
   182         return new File(CONFIG_ROOT.fetchFrom(params),
       
   183                 APP_NAME.fetchFrom(params) + "-volume.icns");
       
   184     }
       
   185 
       
   186     private File getConfig_LicenseFile(Map<String, ? super Object> params) {
       
   187         return new File(CONFIG_ROOT.fetchFrom(params),
       
   188                 APP_NAME.fetchFrom(params) + "-license.plist");
       
   189     }
       
   190 
       
   191     private void prepareLicense(Map<String, ? super Object> params) {
       
   192         try {
       
   193             File licFile = null;
       
   194 
       
   195             List<String> licFiles = LICENSE_FILE.fetchFrom(params);
       
   196             if (licFiles.isEmpty()) {
       
   197                 return;
       
   198             }
       
   199             String licFileStr = licFiles.get(0);
       
   200 
       
   201             for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) {
       
   202                 if (rfs.contains(licFileStr)) {
       
   203                     licFile = new File(rfs.getBaseDirectory(), licFileStr);
       
   204                     break;
       
   205                 }
       
   206             }
       
   207 
       
   208             if (licFile == null) {
       
   209                 // this is NPE protection,
       
   210                 // validate should have already caught it's absence
       
   211                 Log.error("Licence file is null");
       
   212                 return;
       
   213             }
       
   214 
       
   215             byte[] licenseContentOriginal = Files.readAllBytes(licFile.toPath());
       
   216             String licenseInBase64 =
       
   217                     Base64.getEncoder().encodeToString(licenseContentOriginal);
       
   218 
       
   219             Map<String, String> data = new HashMap<>();
       
   220             data.put("APPLICATION_LICENSE_TEXT", licenseInBase64);
       
   221 
       
   222             Writer w = new BufferedWriter(
       
   223                     new FileWriter(getConfig_LicenseFile(params)));
       
   224             w.write(preprocessTextResource(
       
   225                     MacAppBundler.MAC_BUNDLER_PREFIX
       
   226                     + getConfig_LicenseFile(params).getName(),
       
   227                     I18N.getString("resource.license-setup"),
       
   228                     DEFAULT_LICENSE_PLIST, data, VERBOSE.fetchFrom(params),
       
   229                     DROP_IN_RESOURCES_ROOT.fetchFrom(params)));
       
   230             w.close();
       
   231 
       
   232         } catch (IOException ex) {
       
   233             Log.verbose(ex);
       
   234         }
       
   235     }
       
   236 
       
   237     private boolean prepareConfigFiles(Map<String, ? super Object> params)
       
   238             throws IOException {
       
   239         File bgTarget = getConfig_VolumeBackground(params);
       
   240         fetchResource(MacAppBundler.MAC_BUNDLER_PREFIX + bgTarget.getName(),
       
   241                 I18N.getString("resource.dmg-background"),
       
   242                 DEFAULT_BACKGROUND_IMAGE,
       
   243                 bgTarget,
       
   244                 VERBOSE.fetchFrom(params),
       
   245                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   246 
       
   247         File iconTarget = getConfig_VolumeIcon(params);
       
   248         if (MacAppBundler.ICON_ICNS.fetchFrom(params) == null ||
       
   249                 !MacAppBundler.ICON_ICNS.fetchFrom(params).exists()) {
       
   250             fetchResource(
       
   251                     MacAppBundler.MAC_BUNDLER_PREFIX + iconTarget.getName(),
       
   252                     I18N.getString("resource.volume-icon"),
       
   253                     TEMPLATE_BUNDLE_ICON,
       
   254                     iconTarget,
       
   255                     VERBOSE.fetchFrom(params),
       
   256                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   257         } else {
       
   258             fetchResource(
       
   259                     MacAppBundler.MAC_BUNDLER_PREFIX + iconTarget.getName(),
       
   260                     I18N.getString("resource.volume-icon"),
       
   261                     MacAppBundler.ICON_ICNS.fetchFrom(params),
       
   262                     iconTarget,
       
   263                     VERBOSE.fetchFrom(params),
       
   264                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   265         }
       
   266 
       
   267 
       
   268         fetchResource(MacAppBundler.MAC_BUNDLER_PREFIX
       
   269                 + getConfig_Script(params).getName(),
       
   270                 I18N.getString("resource.post-install-script"),
       
   271                 (String) null,
       
   272                 getConfig_Script(params),
       
   273                 VERBOSE.fetchFrom(params),
       
   274                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
       
   275 
       
   276         prepareLicense(params);
       
   277 
       
   278         // In theory we need to extract name from results of attach command
       
   279         // However, this will be a problem for customization as name will
       
   280         // possibly change every time and developer will not be able to fix it
       
   281         // As we are using tmp dir chance we get "different" name are low =>
       
   282         // Use fixed name we used for bundle
       
   283         prepareDMGSetupScript(APP_NAME.fetchFrom(params), params);
       
   284 
       
   285         return true;
       
   286     }
       
   287 
       
   288     // name of post-image script
       
   289     private File getConfig_Script(Map<String, ? super Object> params) {
       
   290         return new File(CONFIG_ROOT.fetchFrom(params),
       
   291                 APP_NAME.fetchFrom(params) + "-post-image.sh");
       
   292     }
       
   293 
       
   294     // Location of SetFile utility may be different depending on MacOS version
       
   295     // We look for several known places and if none of them work will
       
   296     // try ot find it
       
   297     private String findSetFileUtility() {
       
   298         String typicalPaths[] = {"/Developer/Tools/SetFile",
       
   299                 "/usr/bin/SetFile", "/Developer/usr/bin/SetFile"};
       
   300 
       
   301         for (String path: typicalPaths) {
       
   302             File f = new File(path);
       
   303             if (f.exists() && f.canExecute()) {
       
   304                 return path;
       
   305             }
       
   306         }
       
   307 
       
   308         // generic find attempt
       
   309         try {
       
   310             ProcessBuilder pb = new ProcessBuilder("xcrun", "-find", "SetFile");
       
   311             Process p = pb.start();
       
   312             InputStreamReader isr = new InputStreamReader(p.getInputStream());
       
   313             BufferedReader br = new BufferedReader(isr);
       
   314             String lineRead = br.readLine();
       
   315             if (lineRead != null) {
       
   316                 File f = new File(lineRead);
       
   317                 if (f.exists() && f.canExecute()) {
       
   318                     return f.getAbsolutePath();
       
   319                 }
       
   320             }
       
   321         } catch (IOException ignored) {}
       
   322 
       
   323         return null;
       
   324     }
       
   325 
       
   326     private File buildDMG(
       
   327             Map<String, ? super Object> p, File outdir)
       
   328             throws IOException {
       
   329         File imagesRoot = IMAGES_ROOT.fetchFrom(p);
       
   330         if (!imagesRoot.exists()) imagesRoot.mkdirs();
       
   331 
       
   332         File protoDMG = new File(imagesRoot, APP_NAME.fetchFrom(p) +"-tmp.dmg");
       
   333         File finalDMG = new File(outdir, INSTALLER_NAME.fetchFrom(p)
       
   334                 + INSTALLER_SUFFIX.fetchFrom(p)
       
   335                 + ".dmg");
       
   336 
       
   337         File srcFolder = APP_IMAGE_BUILD_ROOT.fetchFrom(p);
       
   338         File predefinedImage = StandardBundlerParam.getPredefinedAppImage(p);
       
   339         if (predefinedImage != null) {
       
   340             srcFolder = predefinedImage;
       
   341         }
       
   342 
       
   343         Log.verbose(MessageFormat.format(I18N.getString(
       
   344                 "message.creating-dmg-file"), finalDMG.getAbsolutePath()));
       
   345 
       
   346         protoDMG.delete();
       
   347         if (finalDMG.exists() && !finalDMG.delete()) {
       
   348             throw new IOException(MessageFormat.format(I18N.getString(
       
   349                     "message.dmg-cannot-be-overwritten"),
       
   350                     finalDMG.getAbsolutePath()));
       
   351         }
       
   352 
       
   353         protoDMG.getParentFile().mkdirs();
       
   354         finalDMG.getParentFile().mkdirs();
       
   355 
       
   356         String hdiUtilVerbosityFlag = Log.isDebug() ? "-verbose" : "-quiet";
       
   357 
       
   358         // create temp image
       
   359         ProcessBuilder pb = new ProcessBuilder(
       
   360                 hdiutil,
       
   361                 "create",
       
   362                 hdiUtilVerbosityFlag,
       
   363                 "-srcfolder", srcFolder.getAbsolutePath(),
       
   364                 "-volname", APP_NAME.fetchFrom(p),
       
   365                 "-ov", protoDMG.getAbsolutePath(),
       
   366                 "-fs", "HFS+",
       
   367                 "-format", "UDRW");
       
   368         IOUtils.exec(pb, false);
       
   369 
       
   370         // mount temp image
       
   371         pb = new ProcessBuilder(
       
   372                 hdiutil,
       
   373                 "attach",
       
   374                 protoDMG.getAbsolutePath(),
       
   375                 hdiUtilVerbosityFlag,
       
   376                 "-mountroot", imagesRoot.getAbsolutePath());
       
   377         IOUtils.exec(pb, false);
       
   378 
       
   379         File mountedRoot =
       
   380                 new File(imagesRoot.getAbsolutePath(), APP_NAME.fetchFrom(p));
       
   381 
       
   382         // volume icon
       
   383         File volumeIconFile = new File(mountedRoot, ".VolumeIcon.icns");
       
   384         IOUtils.copyFile(getConfig_VolumeIcon(p),
       
   385                 volumeIconFile);
       
   386 
       
   387         pb = new ProcessBuilder("osascript",
       
   388                 getConfig_VolumeScript(p).getAbsolutePath());
       
   389         IOUtils.exec(pb, false);
       
   390 
       
   391         // Indicate that we want a custom icon
       
   392         // NB: attributes of the root directory are ignored
       
   393         // when creating the volume
       
   394         // Therefore we have to do this after we mount image
       
   395         String setFileUtility = findSetFileUtility();
       
   396         if (setFileUtility != null) {
       
   397                 //can not find utility => keep going without icon
       
   398             try {
       
   399                 volumeIconFile.setWritable(true);
       
   400                 // The "creator" attribute on a file is a legacy attribute
       
   401                 // but it seems Finder excepts these bytes to be
       
   402                 // "icnC" for the volume icon
       
   403                 // http://endrift.com/blog/2010/06/14/dmg-files-volume-icons-cli
       
   404                 // (might not work on Mac 10.13 with old XCode)
       
   405                 pb = new ProcessBuilder(
       
   406                         setFileUtility,
       
   407                         "-c", "icnC",
       
   408                         volumeIconFile.getAbsolutePath());
       
   409                 IOUtils.exec(pb, false);
       
   410                 volumeIconFile.setReadOnly();
       
   411 
       
   412                 pb = new ProcessBuilder(
       
   413                         setFileUtility,
       
   414                         "-a", "C",
       
   415                         mountedRoot.getAbsolutePath());
       
   416                 IOUtils.exec(pb, false);
       
   417             } catch (IOException ex) {
       
   418                 Log.info(ex.getMessage());
       
   419                 Log.verbose(
       
   420                     "Cannot enable custom icon using SetFile utility");
       
   421             }
       
   422         } else {
       
   423             Log.verbose(
       
   424                 "Skip enabling custom icon as SetFile utility is not found");
       
   425         }
       
   426 
       
   427         // Detach the temporary image
       
   428         pb = new ProcessBuilder(
       
   429                 hdiutil,
       
   430                 "detach",
       
   431                 hdiUtilVerbosityFlag,
       
   432                 mountedRoot.getAbsolutePath());
       
   433         IOUtils.exec(pb, false);
       
   434 
       
   435         // Compress it to a new image
       
   436         pb = new ProcessBuilder(
       
   437                 hdiutil,
       
   438                 "convert",
       
   439                 protoDMG.getAbsolutePath(),
       
   440                 hdiUtilVerbosityFlag,
       
   441                 "-format", "UDZO",
       
   442                 "-o", finalDMG.getAbsolutePath());
       
   443         IOUtils.exec(pb, false);
       
   444 
       
   445         //add license if needed
       
   446         if (getConfig_LicenseFile(p).exists()) {
       
   447             //hdiutil unflatten your_image_file.dmg
       
   448             pb = new ProcessBuilder(
       
   449                     hdiutil,
       
   450                     "unflatten",
       
   451                     finalDMG.getAbsolutePath()
       
   452             );
       
   453             IOUtils.exec(pb, false);
       
   454 
       
   455             //add license
       
   456             pb = new ProcessBuilder(
       
   457                     hdiutil,
       
   458                     "udifrez",
       
   459                     finalDMG.getAbsolutePath(),
       
   460                     "-xml",
       
   461                     getConfig_LicenseFile(p).getAbsolutePath()
       
   462             );
       
   463             IOUtils.exec(pb, false);
       
   464 
       
   465             //hdiutil flatten your_image_file.dmg
       
   466             pb = new ProcessBuilder(
       
   467                     hdiutil,
       
   468                     "flatten",
       
   469                     finalDMG.getAbsolutePath()
       
   470             );
       
   471             IOUtils.exec(pb, false);
       
   472 
       
   473         }
       
   474 
       
   475         //Delete the temporary image
       
   476         protoDMG.delete();
       
   477 
       
   478         Log.info(MessageFormat.format(I18N.getString(
       
   479                 "message.output-to-location"),
       
   480                 APP_NAME.fetchFrom(p), finalDMG.getAbsolutePath()));
       
   481 
       
   482         return finalDMG;
       
   483     }
       
   484 
       
   485 
       
   486     //////////////////////////////////////////////////////////////////////////
       
   487     // Implement Bundler
       
   488     //////////////////////////////////////////////////////////////////////////
       
   489 
       
   490     @Override
       
   491     public String getName() {
       
   492         return I18N.getString("bundler.name");
       
   493     }
       
   494 
       
   495     @Override
       
   496     public String getDescription() {
       
   497         return I18N.getString("bundler.description");
       
   498     }
       
   499 
       
   500     @Override
       
   501     public String getID() {
       
   502         return "dmg";
       
   503     }
       
   504 
       
   505     @Override
       
   506     public Collection<BundlerParamInfo<?>> getBundleParameters() {
       
   507         Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>();
       
   508         results.addAll(MacAppBundler.getAppBundleParameters());
       
   509         results.addAll(getDMGBundleParameters());
       
   510         return results;
       
   511     }
       
   512 
       
   513     public Collection<BundlerParamInfo<?>> getDMGBundleParameters() {
       
   514         Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>();
       
   515 
       
   516         results.addAll(MacAppBundler.getAppBundleParameters());
       
   517         results.addAll(Arrays.asList(
       
   518                 INSTALLER_SUFFIX,
       
   519                 LICENSE_FILE
       
   520         ));
       
   521 
       
   522         return results;
       
   523     }
       
   524 
       
   525 
       
   526     @Override
       
   527     public boolean validate(Map<String, ? super Object> params)
       
   528             throws UnsupportedPlatformException, ConfigException {
       
   529         try {
       
   530             if (params == null) throw new ConfigException(
       
   531                     I18N.getString("error.parameters-null"),
       
   532                     I18N.getString("error.parameters-null.advice"));
       
   533 
       
   534             //run basic validation to ensure requirements are met
       
   535             //we are not interested in return code, only possible exception
       
   536             validateAppImageAndBundeler(params);
       
   537 
       
   538             // validate license file, if used, exists in the proper place
       
   539             if (params.containsKey(LICENSE_FILE.getID())) {
       
   540                 List<RelativeFileSet> appResourcesList =
       
   541                     APP_RESOURCES_LIST.fetchFrom(params);
       
   542                 for (String license : LICENSE_FILE.fetchFrom(params)) {
       
   543                     boolean found = false;
       
   544                     for (RelativeFileSet appResources : appResourcesList) {
       
   545                         found = found || appResources.contains(license);
       
   546                     }
       
   547                     if (!found) {
       
   548                         throw new ConfigException(
       
   549                                 I18N.getString("error.license-missing"),
       
   550                                 MessageFormat.format(I18N.getString(
       
   551                                 "error.license-missing.advice"), license));
       
   552                     }
       
   553                 }
       
   554             }
       
   555 
       
   556             return true;
       
   557         } catch (RuntimeException re) {
       
   558             if (re.getCause() instanceof ConfigException) {
       
   559                 throw (ConfigException) re.getCause();
       
   560             } else {
       
   561                 throw new ConfigException(re);
       
   562             }
       
   563         }
       
   564     }
       
   565 
       
   566     @Override
       
   567     public File execute(
       
   568             Map<String, ? super Object> params, File outputParentDir) {
       
   569         return bundle(params, outputParentDir);
       
   570     }
       
   571 
       
   572     @Override
       
   573     public boolean supported() {
       
   574         return Platform.getPlatform() == Platform.MAC;
       
   575     }
       
   576 }