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