src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java
branchJDK-8200758-branch
changeset 58994 b09ba68c6a19
parent 58993 b5e1baa9d2c3
child 58995 de1413ae214c
equal deleted inserted replaced
58993:b5e1baa9d2c3 58994:b09ba68c6a19
     1 /*
       
     2  * Copyright (c) 2014, 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.io.IOException;
       
    30 import java.lang.module.ModuleDescriptor;
       
    31 import java.lang.module.ModuleDescriptor.Version;
       
    32 import java.nio.file.Files;
       
    33 import java.nio.file.Path;
       
    34 import java.nio.file.Paths;
       
    35 import java.text.MessageFormat;
       
    36 import java.util.ArrayList;
       
    37 import java.util.Arrays;
       
    38 import java.util.Collections;
       
    39 import java.util.Date;
       
    40 import java.util.LinkedHashSet;
       
    41 import java.util.List;
       
    42 import java.util.Map;
       
    43 import java.util.Optional;
       
    44 import java.util.ResourceBundle;
       
    45 import java.util.Set;
       
    46 import java.util.HashSet;
       
    47 import java.util.function.BiFunction;
       
    48 import java.util.function.Function;
       
    49 import java.util.jar.Attributes;
       
    50 import java.util.jar.JarFile;
       
    51 import java.util.jar.Manifest;
       
    52 import java.util.stream.Collectors;
       
    53 import java.util.stream.Stream;
       
    54 
       
    55 /**
       
    56  * StandardBundlerParam
       
    57  *
       
    58  * A parameter to a bundler.
       
    59  *
       
    60  * Also contains static definitions of all of the common bundler parameters.
       
    61  * (additional platform specific and mode specific bundler parameters
       
    62  * are defined in each of the specific bundlers)
       
    63  *
       
    64  * Also contains static methods that operate on maps of parameters.
       
    65  */
       
    66 class StandardBundlerParam<T> extends BundlerParamInfo<T> {
       
    67 
       
    68     private static final ResourceBundle I18N = ResourceBundle.getBundle(
       
    69             "jdk.jpackage.internal.resources.MainResources");
       
    70     private static final String JAVABASEJMOD = "java.base.jmod";
       
    71     private final static String DEFAULT_VERSION = "1.0";
       
    72     private final static String DEFAULT_RELEASE = "1";
       
    73 
       
    74     StandardBundlerParam(String id, Class<T> valueType,
       
    75             Function<Map<String, ? super Object>, T> defaultValueFunction,
       
    76             BiFunction<String, Map<String, ? super Object>, T> stringConverter)
       
    77     {
       
    78         this.id = id;
       
    79         this.valueType = valueType;
       
    80         this.defaultValueFunction = defaultValueFunction;
       
    81         this.stringConverter = stringConverter;
       
    82     }
       
    83 
       
    84     static final StandardBundlerParam<RelativeFileSet> APP_RESOURCES =
       
    85             new StandardBundlerParam<>(
       
    86                     BundleParams.PARAM_APP_RESOURCES,
       
    87                     RelativeFileSet.class,
       
    88                     null, // no default.  Required parameter
       
    89                     null  // no string translation,
       
    90                           // tool must provide complex type
       
    91             );
       
    92 
       
    93     @SuppressWarnings("unchecked")
       
    94     static final
       
    95             StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST =
       
    96             new StandardBundlerParam<>(
       
    97                     BundleParams.PARAM_APP_RESOURCES + "List",
       
    98                     (Class<List<RelativeFileSet>>) (Object) List.class,
       
    99                     // Default is appResources, as a single item list
       
   100                     p -> new ArrayList<>(Collections.singletonList(
       
   101                             APP_RESOURCES.fetchFrom(p))),
       
   102                     StandardBundlerParam::createAppResourcesListFromString
       
   103             );
       
   104 
       
   105     static final StandardBundlerParam<String> SOURCE_DIR =
       
   106             new StandardBundlerParam<>(
       
   107                     Arguments.CLIOptions.INPUT.getId(),
       
   108                     String.class,
       
   109                     p -> null,
       
   110                     (s, p) -> {
       
   111                         String value = String.valueOf(s);
       
   112                         if (value.charAt(value.length() - 1) ==
       
   113                                 File.separatorChar) {
       
   114                             return value.substring(0, value.length() - 1);
       
   115                         }
       
   116                         else {
       
   117                             return value;
       
   118                         }
       
   119                     }
       
   120             );
       
   121 
       
   122     // note that each bundler is likely to replace this one with
       
   123     // their own converter
       
   124     static final StandardBundlerParam<RelativeFileSet> MAIN_JAR =
       
   125             new StandardBundlerParam<>(
       
   126                     Arguments.CLIOptions.MAIN_JAR.getId(),
       
   127                     RelativeFileSet.class,
       
   128                     params -> {
       
   129                         extractMainClassInfoFromAppResources(params);
       
   130                         return (RelativeFileSet) params.get("mainJar");
       
   131                     },
       
   132                     (s, p) -> getMainJar(s, p)
       
   133             );
       
   134 
       
   135     static final StandardBundlerParam<String> CLASSPATH =
       
   136             new StandardBundlerParam<>(
       
   137                     "classpath",
       
   138                     String.class,
       
   139                     params -> {
       
   140                         extractMainClassInfoFromAppResources(params);
       
   141                         String cp = (String) params.get("classpath");
       
   142                         return cp == null ? "" : cp;
       
   143                     },
       
   144                     (s, p) -> s
       
   145             );
       
   146 
       
   147     static final StandardBundlerParam<String> MAIN_CLASS =
       
   148             new StandardBundlerParam<>(
       
   149                     Arguments.CLIOptions.APPCLASS.getId(),
       
   150                     String.class,
       
   151                     params -> {
       
   152                         if (isRuntimeInstaller(params)) {
       
   153                             return null;
       
   154                         }
       
   155                         extractMainClassInfoFromAppResources(params);
       
   156                         String s = (String) params.get(
       
   157                                 BundleParams.PARAM_APPLICATION_CLASS);
       
   158                         if (s == null) {
       
   159                             s = JLinkBundlerHelper.getMainClassFromModule(
       
   160                                     params);
       
   161                         }
       
   162                         return s;
       
   163                     },
       
   164                     (s, p) -> s
       
   165             );
       
   166 
       
   167     static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE =
       
   168             new StandardBundlerParam<>(
       
   169                     Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(),
       
   170                     File.class,
       
   171                     params -> null,
       
   172                     (s, p) -> new File(s)
       
   173             );
       
   174 
       
   175     static final StandardBundlerParam<String> APP_NAME =
       
   176             new StandardBundlerParam<>(
       
   177                     Arguments.CLIOptions.NAME.getId(),
       
   178                     String.class,
       
   179                     params -> {
       
   180                         String s = MAIN_CLASS.fetchFrom(params);
       
   181                         if (s != null) {
       
   182                             int idx = s.lastIndexOf(".");
       
   183                             if (idx >= 0) {
       
   184                                 return s.substring(idx+1);
       
   185                             }
       
   186                             return s;
       
   187                         } else if (isRuntimeInstaller(params)) {
       
   188                             File f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
       
   189                             if (f != null) {
       
   190                                 return f.getName();
       
   191                             }
       
   192                         }
       
   193                         return null;
       
   194                     },
       
   195                     (s, p) -> s
       
   196             );
       
   197 
       
   198     static final StandardBundlerParam<File> ICON =
       
   199             new StandardBundlerParam<>(
       
   200                     Arguments.CLIOptions.ICON.getId(),
       
   201                     File.class,
       
   202                     params -> null,
       
   203                     (s, p) -> new File(s)
       
   204             );
       
   205 
       
   206     static final StandardBundlerParam<String> VENDOR =
       
   207             new StandardBundlerParam<>(
       
   208                     Arguments.CLIOptions.VENDOR.getId(),
       
   209                     String.class,
       
   210                     params -> I18N.getString("param.vendor.default"),
       
   211                     (s, p) -> s
       
   212             );
       
   213 
       
   214     static final StandardBundlerParam<String> DESCRIPTION =
       
   215             new StandardBundlerParam<>(
       
   216                     Arguments.CLIOptions.DESCRIPTION.getId(),
       
   217                     String.class,
       
   218                     params -> params.containsKey(APP_NAME.getID())
       
   219                             ? APP_NAME.fetchFrom(params)
       
   220                             : I18N.getString("param.description.default"),
       
   221                     (s, p) -> s
       
   222             );
       
   223 
       
   224     static final StandardBundlerParam<String> COPYRIGHT =
       
   225             new StandardBundlerParam<>(
       
   226                     Arguments.CLIOptions.COPYRIGHT.getId(),
       
   227                     String.class,
       
   228                     params -> MessageFormat.format(I18N.getString(
       
   229                             "param.copyright.default"), new Date()),
       
   230                     (s, p) -> s
       
   231             );
       
   232 
       
   233     @SuppressWarnings("unchecked")
       
   234     static final StandardBundlerParam<List<String>> ARGUMENTS =
       
   235             new StandardBundlerParam<>(
       
   236                     Arguments.CLIOptions.ARGUMENTS.getId(),
       
   237                     (Class<List<String>>) (Object) List.class,
       
   238                     params -> Collections.emptyList(),
       
   239                     (s, p) -> null
       
   240             );
       
   241 
       
   242     @SuppressWarnings("unchecked")
       
   243     static final StandardBundlerParam<List<String>> JAVA_OPTIONS =
       
   244             new StandardBundlerParam<>(
       
   245                     Arguments.CLIOptions.JAVA_OPTIONS.getId(),
       
   246                     (Class<List<String>>) (Object) List.class,
       
   247                     params -> Collections.emptyList(),
       
   248                     (s, p) -> Arrays.asList(s.split("\n\n"))
       
   249             );
       
   250 
       
   251     // note that each bundler is likely to replace this one with
       
   252     // their own converter
       
   253     static final StandardBundlerParam<String> VERSION =
       
   254             new StandardBundlerParam<>(
       
   255                     Arguments.CLIOptions.VERSION.getId(),
       
   256                     String.class,
       
   257                     params -> getDefaultAppVersion(params),
       
   258                     (s, p) -> s
       
   259             );
       
   260 
       
   261     static final StandardBundlerParam<String> RELEASE =
       
   262             new StandardBundlerParam<>(
       
   263                     Arguments.CLIOptions.RELEASE.getId(),
       
   264                     String.class,
       
   265                     params -> DEFAULT_RELEASE,
       
   266                     (s, p) -> s
       
   267             );
       
   268 
       
   269     @SuppressWarnings("unchecked")
       
   270     public static final StandardBundlerParam<String> LICENSE_FILE =
       
   271             new StandardBundlerParam<>(
       
   272                     Arguments.CLIOptions.LICENSE_FILE.getId(),
       
   273                     String.class,
       
   274                     params -> null,
       
   275                     (s, p) -> s
       
   276             );
       
   277 
       
   278     static final StandardBundlerParam<File> TEMP_ROOT =
       
   279             new StandardBundlerParam<>(
       
   280                     Arguments.CLIOptions.TEMP_ROOT.getId(),
       
   281                     File.class,
       
   282                     params -> {
       
   283                         try {
       
   284                             return Files.createTempDirectory(
       
   285                                     "jdk.jpackage").toFile();
       
   286                         } catch (IOException ioe) {
       
   287                             return null;
       
   288                         }
       
   289                     },
       
   290                     (s, p) -> new File(s)
       
   291             );
       
   292 
       
   293     public static final StandardBundlerParam<File> CONFIG_ROOT =
       
   294             new StandardBundlerParam<>(
       
   295                 "configRoot",
       
   296                 File.class,
       
   297                 params -> {
       
   298                     File root =
       
   299                             new File(TEMP_ROOT.fetchFrom(params), "config");
       
   300                     root.mkdirs();
       
   301                     return root;
       
   302                 },
       
   303                 (s, p) -> null
       
   304             );
       
   305 
       
   306     static final StandardBundlerParam<String> IDENTIFIER =
       
   307             new StandardBundlerParam<>(
       
   308                     "identifier.default",
       
   309                     String.class,
       
   310                     params -> {
       
   311                         String s = MAIN_CLASS.fetchFrom(params);
       
   312                         if (s == null) return null;
       
   313 
       
   314                         int idx = s.lastIndexOf(".");
       
   315                         if (idx >= 1) {
       
   316                             return s.substring(0, idx);
       
   317                         }
       
   318                         return s;
       
   319                     },
       
   320                     (s, p) -> s
       
   321             );
       
   322 
       
   323     static final StandardBundlerParam<Boolean> BIND_SERVICES =
       
   324             new StandardBundlerParam<>(
       
   325                     Arguments.CLIOptions.BIND_SERVICES.getId(),
       
   326                     Boolean.class,
       
   327                     params -> false,
       
   328                     (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
       
   329                             true : Boolean.valueOf(s)
       
   330             );
       
   331 
       
   332 
       
   333     static final StandardBundlerParam<Boolean> VERBOSE  =
       
   334             new StandardBundlerParam<>(
       
   335                     Arguments.CLIOptions.VERBOSE.getId(),
       
   336                     Boolean.class,
       
   337                     params -> false,
       
   338                     // valueOf(null) is false, and we actually do want null
       
   339                     (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
       
   340                             true : Boolean.valueOf(s)
       
   341             );
       
   342 
       
   343     static final StandardBundlerParam<File> RESOURCE_DIR =
       
   344             new StandardBundlerParam<>(
       
   345                     Arguments.CLIOptions.RESOURCE_DIR.getId(),
       
   346                     File.class,
       
   347                     params -> null,
       
   348                     (s, p) -> new File(s)
       
   349             );
       
   350 
       
   351     static final BundlerParamInfo<String> INSTALL_DIR =
       
   352             new StandardBundlerParam<>(
       
   353                     Arguments.CLIOptions.INSTALL_DIR.getId(),
       
   354                     String.class,
       
   355                      params -> null,
       
   356                     (s, p) -> s
       
   357     );
       
   358 
       
   359     static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE =
       
   360             new StandardBundlerParam<>(
       
   361             Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(),
       
   362             File.class,
       
   363             params -> null,
       
   364             (s, p) -> new File(s));
       
   365 
       
   366     @SuppressWarnings("unchecked")
       
   367     static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS =
       
   368             new StandardBundlerParam<>(
       
   369                     Arguments.CLIOptions.ADD_LAUNCHER.getId(),
       
   370                     (Class<List<Map<String, ? super Object>>>) (Object)
       
   371                             List.class,
       
   372                     params -> new ArrayList<>(1),
       
   373                     // valueOf(null) is false, and we actually do want null
       
   374                     (s, p) -> null
       
   375             );
       
   376 
       
   377     @SuppressWarnings("unchecked")
       
   378     static final StandardBundlerParam
       
   379             <List<Map<String, ? super Object>>> FILE_ASSOCIATIONS =
       
   380             new StandardBundlerParam<>(
       
   381                     Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(),
       
   382                     (Class<List<Map<String, ? super Object>>>) (Object)
       
   383                             List.class,
       
   384                     params -> new ArrayList<>(1),
       
   385                     // valueOf(null) is false, and we actually do want null
       
   386                     (s, p) -> null
       
   387             );
       
   388 
       
   389     @SuppressWarnings("unchecked")
       
   390     static final StandardBundlerParam<List<String>> FA_EXTENSIONS =
       
   391             new StandardBundlerParam<>(
       
   392                     "fileAssociation.extension",
       
   393                     (Class<List<String>>) (Object) List.class,
       
   394                     params -> null, // null means not matched to an extension
       
   395                     (s, p) -> Arrays.asList(s.split("(,|\\s)+"))
       
   396             );
       
   397 
       
   398     @SuppressWarnings("unchecked")
       
   399     static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE =
       
   400             new StandardBundlerParam<>(
       
   401                     "fileAssociation.contentType",
       
   402                     (Class<List<String>>) (Object) List.class,
       
   403                     params -> null,
       
   404                             // null means not matched to a content/mime type
       
   405                     (s, p) -> Arrays.asList(s.split("(,|\\s)+"))
       
   406             );
       
   407 
       
   408     static final StandardBundlerParam<String> FA_DESCRIPTION =
       
   409             new StandardBundlerParam<>(
       
   410                     "fileAssociation.description",
       
   411                     String.class,
       
   412                     params -> APP_NAME.fetchFrom(params) + " File",
       
   413                     null
       
   414             );
       
   415 
       
   416     static final StandardBundlerParam<File> FA_ICON =
       
   417             new StandardBundlerParam<>(
       
   418                     "fileAssociation.icon",
       
   419                     File.class,
       
   420                     ICON::fetchFrom,
       
   421                     (s, p) -> new File(s)
       
   422             );
       
   423 
       
   424     @SuppressWarnings("unchecked")
       
   425     static final BundlerParamInfo<List<Path>> MODULE_PATH =
       
   426             new StandardBundlerParam<>(
       
   427                     Arguments.CLIOptions.MODULE_PATH.getId(),
       
   428                     (Class<List<Path>>) (Object)List.class,
       
   429                     p -> { return getDefaultModulePath(); },
       
   430                     (s, p) -> {
       
   431                         List<Path> modulePath = Arrays.asList(s
       
   432                                 .split(File.pathSeparator)).stream()
       
   433                                 .map(ss -> new File(ss).toPath())
       
   434                                 .collect(Collectors.toList());
       
   435                         Path javaBasePath = null;
       
   436                         if (modulePath != null) {
       
   437                             javaBasePath = JLinkBundlerHelper
       
   438                                     .findPathOfModule(modulePath, JAVABASEJMOD);
       
   439                         } else {
       
   440                             modulePath = new ArrayList<Path>();
       
   441                         }
       
   442 
       
   443                         // Add the default JDK module path to the module path.
       
   444                         if (javaBasePath == null) {
       
   445                             List<Path> jdkModulePath = getDefaultModulePath();
       
   446 
       
   447                             if (jdkModulePath != null) {
       
   448                                 modulePath.addAll(jdkModulePath);
       
   449                                 javaBasePath =
       
   450                                         JLinkBundlerHelper.findPathOfModule(
       
   451                                         modulePath, JAVABASEJMOD);
       
   452                             }
       
   453                         }
       
   454 
       
   455                         if (javaBasePath == null ||
       
   456                                 !Files.exists(javaBasePath)) {
       
   457                             Log.error(String.format(I18N.getString(
       
   458                                     "warning.no.jdk.modules.found")));
       
   459                         }
       
   460 
       
   461                         return modulePath;
       
   462                     });
       
   463 
       
   464     static final BundlerParamInfo<String> MODULE =
       
   465             new StandardBundlerParam<>(
       
   466                     Arguments.CLIOptions.MODULE.getId(),
       
   467                     String.class,
       
   468                     p -> null,
       
   469                     (s, p) -> {
       
   470                         return String.valueOf(s);
       
   471                     });
       
   472 
       
   473     @SuppressWarnings("unchecked")
       
   474     static final BundlerParamInfo<Set<String>> ADD_MODULES =
       
   475             new StandardBundlerParam<>(
       
   476                     Arguments.CLIOptions.ADD_MODULES.getId(),
       
   477                     (Class<Set<String>>) (Object) Set.class,
       
   478                     p -> new LinkedHashSet<String>(),
       
   479                     (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
       
   480             );
       
   481 
       
   482     @SuppressWarnings("unchecked")
       
   483     static final BundlerParamInfo<Set<String>> LIMIT_MODULES =
       
   484             new StandardBundlerParam<>(
       
   485                     "limit-modules",
       
   486                     (Class<Set<String>>) (Object) Set.class,
       
   487                     p -> new LinkedHashSet<String>(),
       
   488                     (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
       
   489             );
       
   490 
       
   491     static boolean isRuntimeInstaller(Map<String, ? super Object> params) {
       
   492         if (params.containsKey(MODULE.getID()) ||
       
   493                 params.containsKey(MAIN_JAR.getID()) ||
       
   494                 params.containsKey(PREDEFINED_APP_IMAGE.getID())) {
       
   495             return false; // we are building or are given an application
       
   496         }
       
   497         // runtime installer requires --runtime-image, if this is false
       
   498         // here then we should have thrown error validating args.
       
   499         return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID());
       
   500     }
       
   501 
       
   502     static File getPredefinedAppImage(Map<String, ? super Object> params) {
       
   503         File applicationImage = null;
       
   504         if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
       
   505             applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
       
   506             if (!applicationImage.exists()) {
       
   507                 throw new RuntimeException(
       
   508                         MessageFormat.format(I18N.getString(
       
   509                                 "message.app-image-dir-does-not-exist"),
       
   510                                 PREDEFINED_APP_IMAGE.getID(),
       
   511                                 applicationImage.toString()));
       
   512             }
       
   513         }
       
   514         return applicationImage;
       
   515     }
       
   516 
       
   517     static void copyPredefinedRuntimeImage(
       
   518             Map<String, ? super Object> params,
       
   519             AbstractAppImageBuilder appBuilder)
       
   520             throws IOException , ConfigException {
       
   521         File topImage = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
       
   522         if (!topImage.exists()) {
       
   523             throw new ConfigException(
       
   524                     MessageFormat.format(I18N.getString(
       
   525                     "message.runtime-image-dir-does-not-exist"),
       
   526                     PREDEFINED_RUNTIME_IMAGE.getID(),
       
   527                     topImage.toString()),
       
   528                     MessageFormat.format(I18N.getString(
       
   529                     "message.runtime-image-dir-does-not-exist.advice"),
       
   530                     PREDEFINED_RUNTIME_IMAGE.getID()));
       
   531         }
       
   532         File image = appBuilder.getRuntimeImageDir(topImage);
       
   533         // copy whole runtime, need to skip jmods and src.zip
       
   534         final List<String> excludes = Arrays.asList("jmods", "src.zip");
       
   535         IOUtils.copyRecursive(image.toPath(), appBuilder.getRuntimeRoot(), excludes);
       
   536 
       
   537         // if module-path given - copy modules to appDir/mods
       
   538         List<Path> modulePath =
       
   539                 StandardBundlerParam.MODULE_PATH.fetchFrom(params);
       
   540         List<Path> defaultModulePath = getDefaultModulePath();
       
   541         Path dest = appBuilder.getAppModsDir();
       
   542 
       
   543         if (dest != null) {
       
   544             for (Path mp : modulePath) {
       
   545                 if (!defaultModulePath.contains(mp)) {
       
   546                     Files.createDirectories(dest);
       
   547                     IOUtils.copyRecursive(mp, dest);
       
   548                 }
       
   549             }
       
   550         }
       
   551 
       
   552         appBuilder.prepareApplicationFiles(params);
       
   553     }
       
   554 
       
   555     static void extractMainClassInfoFromAppResources(
       
   556             Map<String, ? super Object> params) {
       
   557         boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
       
   558         boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
       
   559         boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID());
       
   560         boolean hasModule = params.containsKey(MODULE.getID());
       
   561 
       
   562         if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule ||
       
   563                 isRuntimeInstaller(params)) {
       
   564             return;
       
   565         }
       
   566 
       
   567         // it's a pair.
       
   568         // The [0] is the srcdir [1] is the file relative to sourcedir
       
   569         List<String[]> filesToCheck = new ArrayList<>();
       
   570 
       
   571         if (hasMainJar) {
       
   572             RelativeFileSet rfs = MAIN_JAR.fetchFrom(params);
       
   573             for (String s : rfs.getIncludedFiles()) {
       
   574                 filesToCheck.add(
       
   575                         new String[] {rfs.getBaseDirectory().toString(), s});
       
   576             }
       
   577         } else if (hasMainJarClassPath) {
       
   578             for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) {
       
   579                 if (APP_RESOURCES.fetchFrom(params) != null) {
       
   580                     filesToCheck.add(
       
   581                             new String[] {APP_RESOURCES.fetchFrom(params)
       
   582                             .getBaseDirectory().toString(), s});
       
   583                 }
       
   584             }
       
   585         } else {
       
   586             List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params);
       
   587             if (rfsl == null || rfsl.isEmpty()) {
       
   588                 return;
       
   589             }
       
   590             for (RelativeFileSet rfs : rfsl) {
       
   591                 if (rfs == null) continue;
       
   592 
       
   593                 for (String s : rfs.getIncludedFiles()) {
       
   594                     filesToCheck.add(
       
   595                             new String[]{rfs.getBaseDirectory().toString(), s});
       
   596                 }
       
   597             }
       
   598         }
       
   599 
       
   600         // presume the set iterates in-order
       
   601         for (String[] fnames : filesToCheck) {
       
   602             try {
       
   603                 // only sniff jars
       
   604                 if (!fnames[1].toLowerCase().endsWith(".jar")) continue;
       
   605 
       
   606                 File file = new File(fnames[0], fnames[1]);
       
   607                 // that actually exist
       
   608                 if (!file.exists()) continue;
       
   609 
       
   610                 try (JarFile jf = new JarFile(file)) {
       
   611                     Manifest m = jf.getManifest();
       
   612                     Attributes attrs = (m != null) ?
       
   613                             m.getMainAttributes() : null;
       
   614 
       
   615                     if (attrs != null) {
       
   616                         if (!hasMainJar) {
       
   617                             if (fnames[0] == null) {
       
   618                                 fnames[0] = file.getParentFile().toString();
       
   619                             }
       
   620                             params.put(MAIN_JAR.getID(), new RelativeFileSet(
       
   621                                     new File(fnames[0]),
       
   622                                     new LinkedHashSet<>(Collections
       
   623                                     .singletonList(file))));
       
   624                         }
       
   625                         if (!hasMainJarClassPath) {
       
   626                             String cp =
       
   627                                     attrs.getValue(Attributes.Name.CLASS_PATH);
       
   628                             params.put(CLASSPATH.getID(),
       
   629                                     cp == null ? "" : cp);
       
   630                         }
       
   631                         break;
       
   632                     }
       
   633                 }
       
   634             } catch (IOException ignore) {
       
   635                 ignore.printStackTrace();
       
   636             }
       
   637         }
       
   638     }
       
   639 
       
   640     static void validateMainClassInfoFromAppResources(
       
   641             Map<String, ? super Object> params) throws ConfigException {
       
   642         boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
       
   643         boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
       
   644         boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID());
       
   645         boolean hasModule = params.containsKey(MODULE.getID());
       
   646         boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID());
       
   647 
       
   648         if (hasMainClass && hasMainJar && hasMainJarClassPath ||
       
   649                hasAppImage || isRuntimeInstaller(params)) {
       
   650             return;
       
   651         }
       
   652         if (hasModule) {
       
   653             if (JLinkBundlerHelper.getMainClassFromModule(params) == null) {
       
   654                 throw new ConfigException(
       
   655                         I18N.getString("ERR_NoMainClass"), null);
       
   656             }
       
   657         } else {
       
   658             extractMainClassInfoFromAppResources(params);
       
   659 
       
   660             if (!params.containsKey(MAIN_CLASS.getID())) {
       
   661                 if (hasMainJar) {
       
   662                     throw new ConfigException(
       
   663                             MessageFormat.format(I18N.getString(
       
   664                             "error.no-main-class-with-main-jar"),
       
   665                             MAIN_JAR.fetchFrom(params)),
       
   666                             MessageFormat.format(I18N.getString(
       
   667                             "error.no-main-class-with-main-jar.advice"),
       
   668                             MAIN_JAR.fetchFrom(params)));
       
   669                 } else {
       
   670                     throw new ConfigException(
       
   671                             I18N.getString("error.no-main-class"),
       
   672                             I18N.getString("error.no-main-class.advice"));
       
   673                 }
       
   674             }
       
   675         }
       
   676     }
       
   677 
       
   678     private static List<RelativeFileSet>
       
   679             createAppResourcesListFromString(String s,
       
   680             Map<String, ? super Object> objectObjectMap) {
       
   681         List<RelativeFileSet> result = new ArrayList<>();
       
   682         for (String path : s.split("[:;]")) {
       
   683             File f = new File(path);
       
   684             if (f.getName().equals("*") || path.endsWith("/") ||
       
   685                     path.endsWith("\\")) {
       
   686                 if (f.getName().equals("*")) {
       
   687                     f = f.getParentFile();
       
   688                 }
       
   689                 Set<File> theFiles = new HashSet<>();
       
   690                 try {
       
   691                     try (Stream<Path> stream = Files.walk(f.toPath())) {
       
   692                         stream.filter(Files::isRegularFile)
       
   693                                 .forEach(p -> theFiles.add(p.toFile()));
       
   694                     }
       
   695                 } catch (IOException e) {
       
   696                     e.printStackTrace();
       
   697                 }
       
   698                 result.add(new RelativeFileSet(f, theFiles));
       
   699             } else {
       
   700                 result.add(new RelativeFileSet(f.getParentFile(),
       
   701                         Collections.singleton(f)));
       
   702             }
       
   703         }
       
   704         return result;
       
   705     }
       
   706 
       
   707     private static RelativeFileSet getMainJar(
       
   708             String mainJarValue, Map<String, ? super Object> params) {
       
   709         for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) {
       
   710             File appResourcesRoot = rfs.getBaseDirectory();
       
   711             File mainJarFile = new File(appResourcesRoot, mainJarValue);
       
   712 
       
   713             if (mainJarFile.exists()) {
       
   714                 return new RelativeFileSet(appResourcesRoot,
       
   715                      new LinkedHashSet<>(Collections.singletonList(
       
   716                      mainJarFile)));
       
   717             }
       
   718             mainJarFile = new File(mainJarValue);
       
   719             if (mainJarFile.exists()) {
       
   720                 // absolute path for main-jar may fail is not legal
       
   721                 // below contains explicit error message.
       
   722             } else {
       
   723                 List<Path> modulePath = MODULE_PATH.fetchFrom(params);
       
   724                 modulePath.removeAll(getDefaultModulePath());
       
   725                 if (!modulePath.isEmpty()) {
       
   726                     Path modularJarPath = JLinkBundlerHelper.findPathOfModule(
       
   727                             modulePath, mainJarValue);
       
   728                     if (modularJarPath != null &&
       
   729                             Files.exists(modularJarPath)) {
       
   730                         return new RelativeFileSet(appResourcesRoot,
       
   731                                 new LinkedHashSet<>(Collections.singletonList(
       
   732                                 modularJarPath.toFile())));
       
   733                     }
       
   734                 }
       
   735             }
       
   736         }
       
   737 
       
   738         throw new IllegalArgumentException(
       
   739                 new ConfigException(MessageFormat.format(I18N.getString(
       
   740                         "error.main-jar-does-not-exist"),
       
   741                         mainJarValue), I18N.getString(
       
   742                         "error.main-jar-does-not-exist.advice")));
       
   743     }
       
   744 
       
   745     static List<Path> getDefaultModulePath() {
       
   746         List<Path> result = new ArrayList<Path>();
       
   747         Path jdkModulePath = Paths.get(
       
   748                 System.getProperty("java.home"), "jmods").toAbsolutePath();
       
   749 
       
   750         if (jdkModulePath != null && Files.exists(jdkModulePath)) {
       
   751             result.add(jdkModulePath);
       
   752         }
       
   753         else {
       
   754             // On a developer build the JDK Home isn't where we expect it
       
   755             // relative to the jmods directory. Do some extra
       
   756             // processing to find it.
       
   757             Map<String, String> env = System.getenv();
       
   758 
       
   759             if (env.containsKey("JDK_HOME")) {
       
   760                 jdkModulePath = Paths.get(env.get("JDK_HOME"),
       
   761                         ".." + File.separator + "images"
       
   762                         + File.separator + "jmods").toAbsolutePath();
       
   763 
       
   764                 if (jdkModulePath != null && Files.exists(jdkModulePath)) {
       
   765                     result.add(jdkModulePath);
       
   766                 }
       
   767             }
       
   768         }
       
   769 
       
   770         return result;
       
   771     }
       
   772 
       
   773     static String getDefaultAppVersion(Map<String, ? super Object> params) {
       
   774         String appVersion = DEFAULT_VERSION;
       
   775 
       
   776         ModuleDescriptor descriptor = JLinkBundlerHelper.getMainModuleDescription(params);
       
   777         if (descriptor != null) {
       
   778             Optional<Version> oversion = descriptor.version();
       
   779             if (oversion.isPresent()) {
       
   780                 Log.verbose(MessageFormat.format(I18N.getString(
       
   781                         "message.module-version"),
       
   782                         oversion.get().toString(),
       
   783                         JLinkBundlerHelper.getMainModule(params)));
       
   784                 appVersion = oversion.get().toString();
       
   785             }
       
   786         }
       
   787 
       
   788         return appVersion;
       
   789     }
       
   790 }