1 /* 2 * Copyright (c) 2007, 2023, 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 sun.launcher; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.PrintStream; 31 import java.io.UnsupportedEncodingException; 32 import java.lang.module.Configuration; 33 import java.lang.module.ModuleDescriptor; 34 import java.lang.module.ModuleDescriptor.Exports; 35 import java.lang.module.ModuleDescriptor.Opens; 36 import java.lang.module.ModuleDescriptor.Provides; 37 import java.lang.module.ModuleDescriptor.Requires; 38 import java.lang.module.ModuleFinder; 39 import java.lang.module.ModuleReference; 40 import java.lang.module.ResolvedModule; 41 import java.lang.reflect.Constructor; 42 import java.lang.reflect.InvocationTargetException; 43 import java.lang.reflect.Method; 44 import java.lang.reflect.Modifier; 45 import java.math.BigDecimal; 46 import java.math.RoundingMode; 47 import java.net.URI; 48 import java.nio.charset.Charset; 49 import java.nio.file.DirectoryStream; 50 import java.nio.file.Files; 51 import java.nio.file.Path; 52 import java.text.MessageFormat; 53 import java.text.Normalizer; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Comparator; 57 import java.util.Iterator; 58 import java.util.List; 59 import java.util.Locale; 60 import java.util.Locale.Category; 61 import java.util.Map; 62 import java.util.Optional; 63 import java.util.Properties; 64 import java.util.ResourceBundle; 65 import java.util.Set; 66 import java.util.TreeSet; 67 import java.util.function.Function; 68 import java.util.jar.Attributes; 69 import java.util.jar.JarFile; 70 import java.util.jar.Manifest; 71 import java.util.stream.Collectors; 72 import java.util.stream.Stream; 73 74 import jdk.internal.misc.MainMethodFinder; 75 import jdk.internal.misc.PreviewFeatures; 76 import jdk.internal.misc.VM; 77 import jdk.internal.module.ModuleBootstrap; 78 import jdk.internal.module.Modules; 79 import jdk.internal.platform.Container; 80 import jdk.internal.platform.Metrics; 81 import jdk.internal.util.OperatingSystem; 82 import sun.util.calendar.ZoneInfoFile; 83 84 /** 85 * A utility package for the java(1), javaw(1) launchers. 86 * The following are helper methods that the native launcher uses 87 * to perform checks etc. using JNI, see src/share/bin/java.c 88 */ 89 public final class LauncherHelper { 90 91 // No instantiation 92 private LauncherHelper() {} 93 94 // used to identify JavaFX applications 95 private static final String JAVAFX_APPLICATION_MARKER = 96 "JavaFX-Application-Class"; 97 private static final String JAVAFX_APPLICATION_CLASS_NAME = 98 "javafx.application.Application"; 99 private static final String JAVAFX_FXHELPER_CLASS_NAME_SUFFIX = 100 "sun.launcher.LauncherHelper$FXHelper"; 101 private static final String LAUNCHER_AGENT_CLASS = "Launcher-Agent-Class"; 102 private static final String MAIN_CLASS = "Main-Class"; 103 private static final String ADD_EXPORTS = "Add-Exports"; 104 private static final String ADD_OPENS = "Add-Opens"; 105 106 private static StringBuilder outBuf = new StringBuilder(); 107 108 private static final String INDENT = " "; 109 private static final String VM_SETTINGS = "VM settings:"; 110 private static final String PROP_SETTINGS = "Property settings:"; 111 private static final String LOCALE_SETTINGS = "Locale settings:"; 112 113 // sync with java.c and jdk.internal.misc.VM 114 private static final String diagprop = "sun.java.launcher.diag"; 115 static final boolean trace = VM.getSavedProperty(diagprop) != null; 116 117 private static final String defaultBundleName = 118 "sun.launcher.resources.launcher"; 119 120 private static class ResourceBundleHolder { 121 private static final ResourceBundle RB = 122 ResourceBundle.getBundle(defaultBundleName); 123 } 124 private static PrintStream ostream; 125 private static Class<?> appClass; // application class, for GUI/reporting purposes 126 127 enum Option { DEFAULT, ALL, LOCALE, PROPERTIES, SECURITY, 128 SECURITY_ALL, SECURITY_PROPERTIES, SECURITY_PROVIDERS, 129 SECURITY_TLS, SYSTEM, VM }; 130 131 /* 132 * A method called by the launcher to print out the standard settings. 133 * -XshowSettings prints details of all supported components in non-verbose 134 * mode. -XshowSettings:all prints all settings in verbose mode. 135 * Specific settings information may be obtained by using suboptions. 136 * 137 * Suboption values include "all", "locale", "properties", "security", 138 * "system" (Linux only) and "vm". A error message is printed for an 139 * unknown suboption value and the VM launch aborts. 140 * 141 * printToStderr: choose between stdout and stderr 142 * 143 * optionFlag: specifies which options to print default is all other 144 * possible values are vm, properties, locale. 145 * 146 * initialHeapSize: in bytes, as set by the launcher, a zero-value indicates 147 * this code should determine this value, using a suitable method or 148 * the line could be omitted. 149 * 150 * maxHeapSize: in bytes, as set by the launcher, a zero-value indicates 151 * this code should determine this value, using a suitable method. 152 * 153 * stackSize: in bytes, as set by the launcher, a zero-value indicates 154 * this code determine this value, using a suitable method or omit the 155 * line entirely. 156 */ 157 @SuppressWarnings("fallthrough") 158 static void showSettings(boolean printToStderr, String optionFlag, 159 long initialHeapSize, long maxHeapSize, long stackSize) { 160 161 initOutput(printToStderr); 162 Option component = validateOption(optionFlag); 163 switch (component) { 164 case ALL -> printAllSettings(initialHeapSize, maxHeapSize, stackSize, true); 165 case LOCALE -> printLocale(true); 166 case PROPERTIES -> printProperties(); 167 case SECURITY, 168 SECURITY_ALL, 169 SECURITY_PROPERTIES, 170 SECURITY_PROVIDERS, 171 SECURITY_TLS -> SecuritySettings.printSecuritySettings(component, ostream, true); 172 case SYSTEM -> printSystemMetrics(); 173 case VM -> printVmSettings(initialHeapSize, maxHeapSize, stackSize); 174 case DEFAULT -> printAllSettings(initialHeapSize, maxHeapSize, stackSize, false); 175 } 176 } 177 178 /* 179 * Validate that the -XshowSettings value is allowed 180 * If a valid option is parsed, return enum corresponding 181 * to that option. Abort if a bad option is parsed. 182 */ 183 private static Option validateOption(String optionFlag) { 184 if (optionFlag.equals("-XshowSettings")) { 185 return Option.DEFAULT; 186 } 187 188 if (optionFlag.equals("-XshowSetings:")) { 189 abort(null, "java.launcher.bad.option", ":"); 190 } 191 192 Map<String, Option> validOpts = Arrays.stream(Option.values()) 193 .filter(o -> !o.equals(Option.DEFAULT)) // non-valid option 194 .collect(Collectors.toMap(o -> o.name() 195 .toLowerCase(Locale.ROOT) 196 .replace("_", ":"), Function.identity())); 197 198 String optStr = optionFlag.substring("-XshowSettings:".length()); 199 Option component = validOpts.get(optStr); 200 if (component == null) { 201 abort(null, "java.launcher.bad.option", optStr); 202 } 203 return component; 204 } 205 206 /* 207 * Print settings for all supported components. 208 * verbose value used to determine if verbose information 209 * should be printed for components that support printing 210 * in verbose or non-verbose mode. 211 */ 212 private static void printAllSettings(long initialHeapSize, long maxHeapSize, 213 long stackSize, boolean verbose) { 214 printVmSettings(initialHeapSize, maxHeapSize, stackSize); 215 printProperties(); 216 printLocale(verbose); 217 SecuritySettings.printSecuritySettings( 218 Option.SECURITY_ALL, ostream, verbose); 219 if (OperatingSystem.isLinux()) { 220 printSystemMetrics(); 221 } 222 } 223 224 private static void printVmSettings( 225 long initialHeapSize, long maxHeapSize, 226 long stackSize) { 227 228 ostream.println(VM_SETTINGS); 229 if (stackSize != 0L) { 230 ostream.println(INDENT + "Stack Size: " + 231 SizePrefix.scaleValue(stackSize)); 232 } 233 if (initialHeapSize != 0L) { 234 ostream.println(INDENT + "Min. Heap Size: " + 235 SizePrefix.scaleValue(initialHeapSize)); 236 } 237 if (maxHeapSize != 0L) { 238 ostream.println(INDENT + "Max. Heap Size: " + 239 SizePrefix.scaleValue(maxHeapSize)); 240 } else { 241 ostream.println(INDENT + "Max. Heap Size (Estimated): " 242 + SizePrefix.scaleValue(Runtime.getRuntime().maxMemory())); 243 } 244 ostream.println(INDENT + "Using VM: " 245 + System.getProperty("java.vm.name")); 246 ostream.println(); 247 } 248 249 /* 250 * prints the properties subopt/section 251 */ 252 private static void printProperties() { 253 Properties p = System.getProperties(); 254 ostream.println(PROP_SETTINGS); 255 for (String key : p.stringPropertyNames().stream().sorted().toList()) { 256 printPropertyValue(key, p.getProperty(key)); 257 } 258 ostream.println(); 259 } 260 261 private static boolean isPath(String key) { 262 return key.endsWith(".dirs") || key.endsWith(".path"); 263 } 264 265 private static void printPropertyValue(String key, String value) { 266 ostream.print(INDENT + key + " = "); 267 if (key.equals("line.separator")) { 268 for (byte b : value.getBytes()) { 269 switch (b) { 270 case 0xd: 271 ostream.print("\\r "); 272 break; 273 case 0xa: 274 ostream.print("\\n "); 275 break; 276 default: 277 // print any bizarre line separators in hex, but really 278 // shouldn't happen. 279 ostream.printf("0x%02X", b & 0xff); 280 break; 281 } 282 } 283 ostream.println(); 284 return; 285 } 286 if (!isPath(key)) { 287 ostream.println(value); 288 return; 289 } 290 String[] values = value.split(System.getProperty("path.separator")); 291 boolean first = true; 292 for (String s : values) { 293 if (first) { // first line treated specially 294 ostream.println(s); 295 first = false; 296 } else { // following lines prefix with indents 297 ostream.println(INDENT + INDENT + s); 298 } 299 } 300 } 301 302 /* 303 * prints the locale subopt/section 304 */ 305 private static void printLocale(boolean verbose) { 306 Locale locale = Locale.getDefault(); 307 if (verbose) { 308 ostream.println(LOCALE_SETTINGS); 309 } else { 310 ostream.println("Locale settings summary:"); 311 ostream.println(INDENT + "Use \"-XshowSettings:locale\" " + 312 "option for verbose locale settings options"); 313 } 314 ostream.println(INDENT + "default locale = " + 315 locale.getDisplayName()); 316 ostream.println(INDENT + "default display locale = " + 317 Locale.getDefault(Category.DISPLAY).getDisplayName()); 318 ostream.println(INDENT + "default format locale = " + 319 Locale.getDefault(Category.FORMAT).getDisplayName()); 320 ostream.println(INDENT + "tzdata version = " + 321 ZoneInfoFile.getVersion()); 322 if (verbose) { 323 printLocales(); 324 } 325 ostream.println(); 326 } 327 328 private static void printLocales() { 329 Locale[] tlocales = Locale.getAvailableLocales(); 330 final int len = tlocales == null ? 0 : tlocales.length; 331 if (len < 1 ) { 332 return; 333 } 334 // Locale does not implement Comparable so we convert it to String 335 // and sort it for pretty printing. 336 Set<String> sortedSet = new TreeSet<>(); 337 for (Locale l : tlocales) { 338 sortedSet.add(l.toString()); 339 } 340 341 ostream.print(INDENT + "available locales = "); 342 Iterator<String> iter = sortedSet.iterator(); 343 final int last = len - 1; 344 for (int i = 0 ; iter.hasNext() ; i++) { 345 String s = iter.next(); 346 ostream.print(s); 347 if (i != last) { 348 ostream.print(", "); 349 } 350 // print columns of 8 351 if ((i + 1) % 8 == 0) { 352 ostream.println(); 353 ostream.print(INDENT + INDENT); 354 } 355 } 356 ostream.println(); 357 } 358 359 private static void printSystemMetrics() { 360 Metrics c = Container.metrics(); 361 362 ostream.println("Operating System Metrics:"); 363 364 if (c == null) { 365 ostream.println(INDENT + "No metrics available for this platform"); 366 return; 367 } 368 369 final long longRetvalNotSupported = -2; 370 371 ostream.println(INDENT + "Provider: " + c.getProvider()); 372 ostream.println(INDENT + "Effective CPU Count: " + c.getEffectiveCpuCount()); 373 ostream.println(formatCpuVal(c.getCpuPeriod(), INDENT + "CPU Period: ", longRetvalNotSupported)); 374 ostream.println(formatCpuVal(c.getCpuQuota(), INDENT + "CPU Quota: ", longRetvalNotSupported)); 375 ostream.println(formatCpuVal(c.getCpuShares(), INDENT + "CPU Shares: ", longRetvalNotSupported)); 376 377 int cpus[] = c.getCpuSetCpus(); 378 if (cpus != null) { 379 ostream.println(INDENT + "List of Processors, " 380 + cpus.length + " total: "); 381 382 ostream.print(INDENT); 383 for (int i = 0; i < cpus.length; i++) { 384 ostream.print(cpus[i] + " "); 385 } 386 if (cpus.length > 0) { 387 ostream.println(""); 388 } 389 } else { 390 ostream.println(INDENT + "List of Processors: N/A"); 391 } 392 393 cpus = c.getEffectiveCpuSetCpus(); 394 if (cpus != null) { 395 ostream.println(INDENT + "List of Effective Processors, " 396 + cpus.length + " total: "); 397 398 ostream.print(INDENT); 399 for (int i = 0; i < cpus.length; i++) { 400 ostream.print(cpus[i] + " "); 401 } 402 if (cpus.length > 0) { 403 ostream.println(""); 404 } 405 } else { 406 ostream.println(INDENT + "List of Effective Processors: N/A"); 407 } 408 409 int mems[] = c.getCpuSetMems(); 410 if (mems != null) { 411 ostream.println(INDENT + "List of Memory Nodes, " 412 + mems.length + " total: "); 413 414 ostream.print(INDENT); 415 for (int i = 0; i < mems.length; i++) { 416 ostream.print(mems[i] + " "); 417 } 418 if (mems.length > 0) { 419 ostream.println(""); 420 } 421 } else { 422 ostream.println(INDENT + "List of Memory Nodes: N/A"); 423 } 424 425 mems = c.getEffectiveCpuSetMems(); 426 if (mems != null) { 427 ostream.println(INDENT + "List of Available Memory Nodes, " 428 + mems.length + " total: "); 429 430 ostream.print(INDENT); 431 for (int i = 0; i < mems.length; i++) { 432 ostream.print(mems[i] + " "); 433 } 434 if (mems.length > 0) { 435 ostream.println(""); 436 } 437 } else { 438 ostream.println(INDENT + "List of Available Memory Nodes: N/A"); 439 } 440 441 long limit = c.getMemoryLimit(); 442 ostream.println(formatLimitString(limit, INDENT + "Memory Limit: ", longRetvalNotSupported)); 443 444 limit = c.getMemorySoftLimit(); 445 ostream.println(formatLimitString(limit, INDENT + "Memory Soft Limit: ", longRetvalNotSupported)); 446 447 limit = c.getMemoryAndSwapLimit(); 448 ostream.println(formatLimitString(limit, INDENT + "Memory & Swap Limit: ", longRetvalNotSupported)); 449 450 limit = c.getPidsMax(); 451 ostream.println(formatLimitString(limit, INDENT + "Maximum Processes Limit: ", 452 longRetvalNotSupported, false)); 453 ostream.println(""); 454 } 455 456 private static String formatLimitString(long limit, String prefix, long unavailable) { 457 return formatLimitString(limit, prefix, unavailable, true); 458 } 459 460 private static String formatLimitString(long limit, String prefix, long unavailable, boolean scale) { 461 if (limit >= 0) { 462 if (scale) { 463 return prefix + SizePrefix.scaleValue(limit); 464 } else { 465 return prefix + limit; 466 } 467 } else if (limit == unavailable) { 468 return prefix + "N/A"; 469 } else { 470 return prefix + "Unlimited"; 471 } 472 } 473 474 private static String formatCpuVal(long cpuVal, String prefix, long unavailable) { 475 if (cpuVal >= 0) { 476 return prefix + cpuVal + "us"; 477 } else if (cpuVal == unavailable) { 478 return prefix + "N/A"; 479 } else { 480 return prefix + cpuVal; 481 } 482 } 483 484 private enum SizePrefix { 485 486 KILO(1024, "K"), 487 MEGA(1024 * 1024, "M"), 488 GIGA(1024 * 1024 * 1024, "G"), 489 TERA(1024L * 1024L * 1024L * 1024L, "T"); 490 long size; 491 String abbrev; 492 493 SizePrefix(long size, String abbrev) { 494 this.size = size; 495 this.abbrev = abbrev; 496 } 497 498 private static String scale(long v, SizePrefix prefix) { 499 return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size), 500 2, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev; 501 } 502 /* 503 * scale the incoming values to a human readable form, represented as 504 * K, M, G and T, see java.c parse_size for the scaled values and 505 * suffixes. The lowest possible scaled value is Kilo. 506 */ 507 static String scaleValue(long v) { 508 if (v < MEGA.size) { 509 return scale(v, KILO); 510 } else if (v < GIGA.size) { 511 return scale(v, MEGA); 512 } else if (v < TERA.size) { 513 return scale(v, GIGA); 514 } else { 515 return scale(v, TERA); 516 } 517 } 518 } 519 520 /** 521 * A private helper method to get a localized message and also 522 * apply any arguments that we might pass. 523 */ 524 private static String getLocalizedMessage(String key, Object... args) { 525 String msg = ResourceBundleHolder.RB.getString(key); 526 return (args != null) ? MessageFormat.format(msg, args) : msg; 527 } 528 529 /** 530 * The java -help message is split into 3 parts, an invariant, followed 531 * by a set of platform dependent variant messages, finally an invariant 532 * set of lines. 533 * This method initializes the help message for the first time, and also 534 * assembles the invariant header part of the message. 535 */ 536 static void initHelpMessage(String progname) { 537 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header", 538 (progname == null) ? "java" : progname )); 539 } 540 541 /** 542 * Appends the vm selection messages to the header, already created. 543 * initHelpSystem must already be called. 544 */ 545 static void appendVmSelectMessage(String vm1, String vm2) { 546 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect", 547 vm1, vm2)); 548 } 549 550 /** 551 * Appends the vm synonym message to the header, already created. 552 * initHelpSystem must be called before using this method. 553 */ 554 static void appendVmSynonymMessage(String vm1, String vm2) { 555 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot", 556 vm1, vm2)); 557 } 558 559 /** 560 * Appends the last invariant part to the previously created messages, 561 * and finishes up the printing to the desired output stream. 562 * initHelpSystem must be called before using this method. 563 */ 564 static void printHelpMessage(boolean printToStderr) { 565 initOutput(printToStderr); 566 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer", 567 File.pathSeparator)); 568 ostream.println(outBuf.toString()); 569 } 570 571 /** 572 * Prints the Xusage text to the desired output stream. 573 */ 574 static void printXUsageMessage(boolean printToStderr) { 575 initOutput(printToStderr); 576 ostream.println(getLocalizedMessage("java.launcher.X.usage", 577 File.pathSeparator)); 578 if (OperatingSystem.isMacOS()) { 579 ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage", 580 File.pathSeparator)); 581 } 582 } 583 584 static void initOutput(boolean printToStderr) { 585 ostream = (printToStderr) ? System.err : System.out; 586 } 587 588 static void initOutput(PrintStream ps) { 589 ostream = ps; 590 } 591 592 static String getMainClassFromJar(String jarname) { 593 String mainValue; 594 try (JarFile jarFile = new JarFile(jarname)) { 595 Manifest manifest = jarFile.getManifest(); 596 if (manifest == null) { 597 abort(null, "java.launcher.jar.error2", jarname); 598 } 599 Attributes mainAttrs = manifest.getMainAttributes(); 600 if (mainAttrs == null) { 601 abort(null, "java.launcher.jar.error3", jarname); 602 } 603 604 // Main-Class 605 mainValue = mainAttrs.getValue(MAIN_CLASS); 606 if (mainValue == null) { 607 abort(null, "java.launcher.jar.error3", jarname); 608 } 609 610 // Launcher-Agent-Class (only check for this when Main-Class present) 611 String agentClass = mainAttrs.getValue(LAUNCHER_AGENT_CLASS); 612 if (agentClass != null) { 613 ModuleLayer.boot().findModule("java.instrument").ifPresent(m -> { 614 try { 615 String cn = "sun.instrument.InstrumentationImpl"; 616 Class<?> clazz = Class.forName(cn, false, null); 617 Method loadAgent = clazz.getMethod("loadAgent", String.class); 618 loadAgent.invoke(null, jarname); 619 } catch (Throwable e) { 620 if (e instanceof InvocationTargetException) e = e.getCause(); 621 abort(e, "java.launcher.jar.error4", jarname); 622 } 623 }); 624 } 625 626 // Add-Exports and Add-Opens 627 String exports = mainAttrs.getValue(ADD_EXPORTS); 628 if (exports != null) { 629 addExportsOrOpens(exports, false); 630 } 631 String opens = mainAttrs.getValue(ADD_OPENS); 632 if (opens != null) { 633 addExportsOrOpens(opens, true); 634 } 635 636 /* 637 * Hand off to FXHelper if it detects a JavaFX application 638 * This must be done after ensuring a Main-Class entry 639 * exists to enforce compliance with the jar specification 640 */ 641 if (mainAttrs.containsKey( 642 new Attributes.Name(JAVAFX_APPLICATION_MARKER))) { 643 FXHelper.setFXLaunchParameters(jarname, LM_JAR); 644 return FXHelper.class.getName(); 645 } 646 647 return mainValue.trim(); 648 } catch (IOException ioe) { 649 abort(ioe, "java.launcher.jar.error1", jarname); 650 } 651 return null; 652 } 653 654 /** 655 * Process the Add-Exports or Add-Opens value. The value is 656 * {@code <module>/<package> ( <module>/<package>)*}. 657 */ 658 static void addExportsOrOpens(String value, boolean open) { 659 for (String moduleAndPackage : value.split(" ")) { 660 String[] s = moduleAndPackage.trim().split("/"); 661 if (s.length == 2) { 662 String mn = s[0]; 663 String pn = s[1]; 664 ModuleLayer.boot() 665 .findModule(mn) 666 .filter(m -> m.getDescriptor().packages().contains(pn)) 667 .ifPresent(m -> { 668 if (open) { 669 Modules.addOpensToAllUnnamed(m, pn); 670 } else { 671 Modules.addExportsToAllUnnamed(m, pn); 672 } 673 }); 674 } 675 } 676 } 677 678 // From src/share/bin/java.c: 679 // enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE } 680 681 private static final int LM_UNKNOWN = 0; 682 private static final int LM_CLASS = 1; 683 private static final int LM_JAR = 2; 684 private static final int LM_MODULE = 3; 685 private static final int LM_SOURCE = 4; 686 687 static void abort(Throwable t, String msgKey, Object... args) { 688 if (msgKey != null) { 689 ostream.println(getLocalizedMessage(msgKey, args)); 690 } 691 if (trace) { 692 if (t != null) { 693 t.printStackTrace(); 694 } else { 695 Thread.dumpStack(); 696 } 697 } 698 System.exit(1); 699 } 700 701 /** 702 * This method: 703 * 1. Loads the main class from the module or class path 704 * 2. Checks the public static void main method. 705 * 3. If the main class extends FX Application then call on FXHelper to 706 * perform the launch. 707 * 708 * @param printToStderr if set, all output will be routed to stderr 709 * @param mode LaunchMode as determined by the arguments passed on the 710 * command line 711 * @param what the module name[/class], JAR file, or the main class 712 * depending on the mode 713 * 714 * @return the application's main class 715 */ 716 @SuppressWarnings("fallthrough") 717 public static Class<?> checkAndLoadMain(boolean printToStderr, 718 int mode, 719 String what) { 720 initOutput(printToStderr); 721 722 Class<?> mainClass = null; 723 switch (mode) { 724 case LM_MODULE: case LM_SOURCE: 725 mainClass = loadModuleMainClass(what); 726 break; 727 default: 728 mainClass = loadMainClass(mode, what); 729 break; 730 } 731 732 // record the real main class for UI purposes 733 // neither method above can return null, they will abort() 734 appClass = mainClass; 735 736 /* 737 * Check if FXHelper can launch it using the FX launcher. In an FX app, 738 * the main class may or may not have a main method, so do this before 739 * validating the main class. 740 */ 741 if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) || 742 doesExtendFXApplication(mainClass)) { 743 // Will abort() if there are problems with FX runtime 744 FXHelper.setFXLaunchParameters(what, mode); 745 mainClass = FXHelper.class; 746 } 747 748 validateMainClass(mainClass); 749 return mainClass; 750 } 751 752 /** 753 * Returns the main class for a module. The query is either a module name 754 * or module-name/main-class. For the former then the module's main class 755 * is obtained from the module descriptor (MainClass attribute). 756 */ 757 private static Class<?> loadModuleMainClass(String what) { 758 int i = what.indexOf('/'); 759 String mainModule; 760 String mainClass; 761 if (i == -1) { 762 mainModule = what; 763 mainClass = null; 764 } else { 765 mainModule = what.substring(0, i); 766 mainClass = what.substring(i+1); 767 } 768 769 // main module is in the boot layer 770 ModuleLayer layer = ModuleLayer.boot(); 771 Optional<Module> om = layer.findModule(mainModule); 772 if (om.isEmpty()) { 773 // should not happen 774 throw new InternalError("Module " + mainModule + " not in boot Layer"); 775 } 776 Module m = om.get(); 777 778 // get main class 779 if (mainClass == null) { 780 Optional<String> omc = m.getDescriptor().mainClass(); 781 if (omc.isEmpty()) { 782 abort(null, "java.launcher.module.error1", mainModule); 783 } 784 mainClass = omc.get(); 785 } 786 787 // load the class from the module 788 Class<?> c = null; 789 try { 790 c = Class.forName(m, mainClass); 791 if (c == null && OperatingSystem.isMacOS() 792 && Normalizer.isNormalized(mainClass, Normalizer.Form.NFD)) { 793 794 String cn = Normalizer.normalize(mainClass, Normalizer.Form.NFC); 795 c = Class.forName(m, cn); 796 } 797 } catch (LinkageError le) { 798 abort(null, "java.launcher.module.error3", mainClass, m.getName(), 799 le.getClass().getName() + ": " + le.getLocalizedMessage()); 800 } 801 if (c == null) { 802 abort(null, "java.launcher.module.error2", mainClass, mainModule); 803 } 804 805 System.setProperty("jdk.module.main.class", c.getName()); 806 return c; 807 } 808 809 /** 810 * Loads the main class from the class path (LM_CLASS or LM_JAR). 811 */ 812 private static Class<?> loadMainClass(int mode, String what) { 813 // get the class name 814 String cn; 815 switch (mode) { 816 case LM_CLASS: 817 cn = what; 818 break; 819 case LM_JAR: 820 cn = getMainClassFromJar(what); 821 break; 822 default: 823 // should never happen 824 throw new InternalError("" + mode + ": Unknown launch mode"); 825 } 826 827 // load the main class 828 cn = cn.replace('/', '.'); 829 Class<?> mainClass = null; 830 ClassLoader scl = ClassLoader.getSystemClassLoader(); 831 try { 832 try { 833 mainClass = Class.forName(cn, false, scl); 834 } catch (NoClassDefFoundError | ClassNotFoundException cnfe) { 835 if (OperatingSystem.isMacOS() 836 && Normalizer.isNormalized(cn, Normalizer.Form.NFD)) { 837 try { 838 // On Mac OS X since all names with diacritical marks are 839 // given as decomposed it is possible that main class name 840 // comes incorrectly from the command line and we have 841 // to re-compose it 842 String ncn = Normalizer.normalize(cn, Normalizer.Form.NFC); 843 mainClass = Class.forName(ncn, false, scl); 844 } catch (NoClassDefFoundError | ClassNotFoundException cnfe1) { 845 abort(cnfe1, "java.launcher.cls.error1", cn, 846 cnfe1.getClass().getCanonicalName(), cnfe1.getMessage()); 847 } 848 } else { 849 abort(cnfe, "java.launcher.cls.error1", cn, 850 cnfe.getClass().getCanonicalName(), cnfe.getMessage()); 851 } 852 } 853 } catch (LinkageError le) { 854 abort(le, "java.launcher.cls.error6", cn, 855 le.getClass().getName() + ": " + le.getLocalizedMessage()); 856 } 857 return mainClass; 858 } 859 860 /* 861 * Accessor method called by the launcher after getting the main class via 862 * checkAndLoadMain(). The "application class" is the class that is finally 863 * executed to start the application and in this case is used to report 864 * the correct application name, typically for UI purposes. 865 */ 866 public static Class<?> getApplicationClass() { 867 return appClass; 868 } 869 870 /* 871 * Check if the given class is a JavaFX Application class. This is done 872 * in a way that does not cause the Application class to load or throw 873 * ClassNotFoundException if the JavaFX runtime is not available. 874 */ 875 private static boolean doesExtendFXApplication(Class<?> mainClass) { 876 for (Class<?> sc = mainClass.getSuperclass(); sc != null; 877 sc = sc.getSuperclass()) { 878 if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) { 879 return true; 880 } 881 } 882 return false; 883 } 884 885 /* 886 * main type flags 887 */ 888 private static final int MAIN_WITHOUT_ARGS = 1; 889 private static final int MAIN_NONSTATIC = 2; 890 private static int mainType = 0; 891 892 /* 893 * Return type so that launcher invokes the correct main 894 */ 895 public static int getMainType() { 896 return mainType; 897 } 898 899 private static void setMainType(Method mainMethod) { 900 int mods = mainMethod.getModifiers(); 901 boolean isStatic = Modifier.isStatic(mods); 902 boolean noArgs = mainMethod.getParameterCount() == 0; 903 mainType = (isStatic ? 0 : MAIN_NONSTATIC) | (noArgs ? MAIN_WITHOUT_ARGS : 0); 904 } 905 906 // Check the existence and signature of main and abort if incorrect 907 static void validateMainClass(Class<?> mainClass) { 908 Method mainMethod = null; 909 try { 910 mainMethod = MainMethodFinder.findMainMethod(mainClass); 911 } catch (NoSuchMethodException nsme) { 912 // invalid main or not FX application, abort with an error 913 abort(null, "java.launcher.cls.error4", mainClass.getName(), 914 JAVAFX_APPLICATION_CLASS_NAME); 915 } catch (Throwable e) { 916 if (mainClass.getModule().isNamed()) { 917 abort(e, "java.launcher.module.error5", 918 mainClass.getName(), mainClass.getModule().getName(), 919 e.getClass().getName(), e.getLocalizedMessage()); 920 } else { 921 abort(e, "java.launcher.cls.error7", mainClass.getName(), 922 e.getClass().getName(), e.getLocalizedMessage()); 923 } 924 } 925 926 setMainType(mainMethod); 927 928 /* 929 * findMainMethod (above) will choose the correct method, based 930 * on its name and parameter type, however, we still have to 931 * ensure that the method is static (non-preview) and returns a void. 932 */ 933 int mods = mainMethod.getModifiers(); 934 boolean isStatic = Modifier.isStatic(mods); 935 boolean isPublic = Modifier.isPublic(mods); 936 boolean noArgs = mainMethod.getParameterCount() == 0; 937 938 if (!PreviewFeatures.isEnabled()) { 939 if (!isStatic || !isPublic || noArgs) { 940 abort(null, "java.launcher.cls.error2", "static", 941 mainMethod.getDeclaringClass().getName()); 942 } 943 } 944 945 if (!isStatic) { 946 if (mainClass.isMemberClass() && !Modifier.isStatic(mainClass.getModifiers())) { 947 abort(null, "java.launcher.cls.error9", 948 mainMethod.getDeclaringClass().getName()); 949 } 950 try { 951 Constructor<?> constructor = mainClass.getDeclaredConstructor(); 952 if (Modifier.isPrivate(constructor.getModifiers())) { 953 abort(null, "java.launcher.cls.error8", 954 mainMethod.getDeclaringClass().getName()); 955 } 956 } catch (Throwable ex) { 957 abort(null, "java.launcher.cls.error8", 958 mainMethod.getDeclaringClass().getName()); 959 } 960 } 961 962 if (mainMethod.getReturnType() != java.lang.Void.TYPE) { 963 abort(null, "java.launcher.cls.error3", 964 mainMethod.getDeclaringClass().getName()); 965 } 966 } 967 968 private static final String encprop = "sun.jnu.encoding"; 969 private static String encoding = null; 970 private static boolean isCharsetSupported = false; 971 972 /* 973 * converts a c or a byte array to a platform specific string, 974 * previously implemented as a native method in the launcher. 975 */ 976 static String makePlatformString(boolean printToStderr, byte[] inArray) { 977 initOutput(printToStderr); 978 if (encoding == null) { 979 encoding = System.getProperty(encprop); 980 isCharsetSupported = Charset.isSupported(encoding); 981 } 982 try { 983 String out = isCharsetSupported 984 ? new String(inArray, encoding) 985 : new String(inArray); 986 return out; 987 } catch (UnsupportedEncodingException uee) { 988 abort(uee, null); 989 } 990 return null; // keep the compiler happy 991 } 992 993 static String[] expandArgs(String[] argArray) { 994 List<StdArg> aList = new ArrayList<>(); 995 for (String x : argArray) { 996 aList.add(new StdArg(x)); 997 } 998 return expandArgs(aList); 999 } 1000 1001 static String[] expandArgs(List<StdArg> argList) { 1002 ArrayList<String> out = new ArrayList<>(); 1003 if (trace) { 1004 System.err.println("Incoming arguments:"); 1005 } 1006 for (StdArg a : argList) { 1007 if (trace) { 1008 System.err.println(a); 1009 } 1010 if (a.needsExpansion) { 1011 File x = new File(a.arg); 1012 File parent = x.getParentFile(); 1013 String glob = x.getName(); 1014 if (parent == null) { 1015 parent = new File("."); 1016 } 1017 try (DirectoryStream<Path> dstream = 1018 Files.newDirectoryStream(parent.toPath(), glob)) { 1019 int entries = 0; 1020 for (Path p : dstream) { 1021 out.add(p.normalize().toString()); 1022 entries++; 1023 } 1024 if (entries == 0) { 1025 out.add(a.arg); 1026 } 1027 } catch (Exception e) { 1028 out.add(a.arg); 1029 if (trace) { 1030 System.err.println("Warning: passing argument as-is " + a); 1031 System.err.print(e); 1032 } 1033 } 1034 } else { 1035 out.add(a.arg); 1036 } 1037 } 1038 String[] oarray = new String[out.size()]; 1039 out.toArray(oarray); 1040 1041 if (trace) { 1042 System.err.println("Expanded arguments:"); 1043 for (String x : oarray) { 1044 System.err.println(x); 1045 } 1046 } 1047 return oarray; 1048 } 1049 1050 /* duplicate of the native StdArg struct */ 1051 private static class StdArg { 1052 final String arg; 1053 final boolean needsExpansion; 1054 StdArg(String arg, boolean expand) { 1055 this.arg = arg; 1056 this.needsExpansion = expand; 1057 } 1058 // protocol: first char indicates whether expansion is required 1059 // 'T' = true ; needs expansion 1060 // 'F' = false; needs no expansion 1061 StdArg(String in) { 1062 this.arg = in.substring(1); 1063 needsExpansion = in.charAt(0) == 'T'; 1064 } 1065 public String toString() { 1066 return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}'; 1067 } 1068 } 1069 1070 static final class FXHelper { 1071 1072 private static final String JAVAFX_GRAPHICS_MODULE_NAME = 1073 "javafx.graphics"; 1074 1075 private static final String JAVAFX_LAUNCHER_CLASS_NAME = 1076 "com.sun.javafx.application.LauncherImpl"; 1077 1078 /* 1079 * The launch method used to invoke the JavaFX launcher. These must 1080 * match the strings used in the launchApplication method. 1081 * 1082 * Command line JavaFX-App-Class Launch mode FX Launch mode 1083 * java -cp fxapp.jar FXClass N/A LM_CLASS "LM_CLASS" 1084 * java -cp somedir FXClass N/A LM_CLASS "LM_CLASS" 1085 * java -jar fxapp.jar Present LM_JAR "LM_JAR" 1086 * java -jar fxapp.jar Not Present LM_JAR "LM_JAR" 1087 * java -m module/class [1] N/A LM_MODULE "LM_MODULE" 1088 * java -m module N/A LM_MODULE "LM_MODULE" 1089 * 1090 * [1] - JavaFX-Application-Class is ignored when modular args are used, even 1091 * if present in a modular jar 1092 */ 1093 private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS"; 1094 private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR"; 1095 private static final String JAVAFX_LAUNCH_MODE_MODULE = "LM_MODULE"; 1096 1097 /* 1098 * FX application launcher and launch method, so we can launch 1099 * applications with no main method. 1100 */ 1101 private static String fxLaunchName = null; 1102 private static String fxLaunchMode = null; 1103 1104 private static Class<?> fxLauncherClass = null; 1105 private static Method fxLauncherMethod = null; 1106 1107 /* 1108 * Set the launch params according to what was passed to LauncherHelper 1109 * so we can use the same launch mode for FX. Abort if there is any 1110 * issue with loading the FX runtime or with the launcher method. 1111 */ 1112 private static void setFXLaunchParameters(String what, int mode) { 1113 1114 // find the module with the FX launcher 1115 Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME); 1116 if (om.isEmpty()) { 1117 abort(null, "java.launcher.cls.error5"); 1118 } 1119 1120 // load the FX launcher class 1121 fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME); 1122 if (fxLauncherClass == null) { 1123 abort(null, "java.launcher.cls.error5"); 1124 } 1125 1126 try { 1127 /* 1128 * signature must be: 1129 * public static void launchApplication(String launchName, 1130 * String launchMode, String[] args); 1131 */ 1132 fxLauncherMethod = fxLauncherClass.getMethod("launchApplication", 1133 String.class, String.class, String[].class); 1134 1135 // verify launcher signature as we do when validating the main method 1136 int mod = fxLauncherMethod.getModifiers(); 1137 if (!Modifier.isStatic(mod)) { 1138 abort(null, "java.launcher.javafx.error1"); 1139 } 1140 if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) { 1141 abort(null, "java.launcher.javafx.error1"); 1142 } 1143 } catch (NoSuchMethodException ex) { 1144 abort(ex, "java.launcher.cls.error5", ex); 1145 } 1146 1147 fxLaunchName = what; 1148 switch (mode) { 1149 case LM_CLASS: 1150 fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS; 1151 break; 1152 case LM_JAR: 1153 fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR; 1154 break; 1155 case LM_MODULE: 1156 fxLaunchMode = JAVAFX_LAUNCH_MODE_MODULE; 1157 break; 1158 default: 1159 // should not have gotten this far... 1160 throw new InternalError(mode + ": Unknown launch mode"); 1161 } 1162 } 1163 1164 public static void main(String... args) throws Exception { 1165 if (fxLauncherMethod == null 1166 || fxLaunchMode == null 1167 || fxLaunchName == null) { 1168 throw new RuntimeException("Invalid JavaFX launch parameters"); 1169 } 1170 // launch appClass via fxLauncherMethod 1171 fxLauncherMethod.invoke(null, 1172 new Object[] {fxLaunchName, fxLaunchMode, args}); 1173 } 1174 } 1175 1176 /** 1177 * Called by the launcher to list the observable modules. 1178 */ 1179 static void listModules() { 1180 initOutput(System.out); 1181 1182 ModuleBootstrap.limitedFinder().findAll().stream() 1183 .sorted(new JrtFirstComparator()) 1184 .forEach(LauncherHelper::showModule); 1185 } 1186 1187 /** 1188 * Called by the launcher to show the resolved modules 1189 */ 1190 static void showResolvedModules() { 1191 initOutput(System.out); 1192 1193 ModuleLayer bootLayer = ModuleLayer.boot(); 1194 Configuration cf = bootLayer.configuration(); 1195 1196 cf.modules().stream() 1197 .map(ResolvedModule::reference) 1198 .sorted(new JrtFirstComparator()) 1199 .forEach(LauncherHelper::showModule); 1200 } 1201 1202 /** 1203 * Called by the launcher to describe a module 1204 */ 1205 static void describeModule(String moduleName) { 1206 initOutput(System.out); 1207 1208 ModuleFinder finder = ModuleBootstrap.limitedFinder(); 1209 ModuleReference mref = finder.find(moduleName).orElse(null); 1210 if (mref == null) { 1211 abort(null, "java.launcher.module.error4", moduleName); 1212 } 1213 ModuleDescriptor md = mref.descriptor(); 1214 1215 // one-line summary 1216 showModule(mref); 1217 1218 // unqualified exports (sorted by package) 1219 md.exports().stream() 1220 .filter(e -> !e.isQualified()) 1221 .sorted(Comparator.comparing(Exports::source)) 1222 .map(e -> Stream.concat(Stream.of(e.source()), 1223 toStringStream(e.modifiers())) 1224 .collect(Collectors.joining(" "))) 1225 .forEach(sourceAndMods -> ostream.format("exports %s%n", sourceAndMods)); 1226 1227 // dependences 1228 for (Requires r : md.requires()) { 1229 String nameAndMods = Stream.concat(Stream.of(r.name()), 1230 toStringStream(r.modifiers())) 1231 .collect(Collectors.joining(" ")); 1232 ostream.format("requires %s", nameAndMods); 1233 finder.find(r.name()) 1234 .map(ModuleReference::descriptor) 1235 .filter(ModuleDescriptor::isAutomatic) 1236 .ifPresent(any -> ostream.print(" automatic")); 1237 ostream.println(); 1238 } 1239 1240 // service use and provides 1241 for (String s : md.uses()) { 1242 ostream.format("uses %s%n", s); 1243 } 1244 for (Provides ps : md.provides()) { 1245 String names = ps.providers().stream().collect(Collectors.joining(" ")); 1246 ostream.format("provides %s with %s%n", ps.service(), names); 1247 1248 } 1249 1250 // qualified exports 1251 for (Exports e : md.exports()) { 1252 if (e.isQualified()) { 1253 String who = e.targets().stream().collect(Collectors.joining(" ")); 1254 ostream.format("qualified exports %s to %s%n", e.source(), who); 1255 } 1256 } 1257 1258 // open packages 1259 for (Opens opens: md.opens()) { 1260 if (opens.isQualified()) 1261 ostream.print("qualified "); 1262 String sourceAndMods = Stream.concat(Stream.of(opens.source()), 1263 toStringStream(opens.modifiers())) 1264 .collect(Collectors.joining(" ")); 1265 ostream.format("opens %s", sourceAndMods); 1266 if (opens.isQualified()) { 1267 String who = opens.targets().stream().collect(Collectors.joining(" ")); 1268 ostream.format(" to %s", who); 1269 } 1270 ostream.println(); 1271 } 1272 1273 // non-exported/non-open packages 1274 Set<String> concealed = new TreeSet<>(md.packages()); 1275 md.exports().stream().map(Exports::source).forEach(concealed::remove); 1276 md.opens().stream().map(Opens::source).forEach(concealed::remove); 1277 concealed.forEach(p -> ostream.format("contains %s%n", p)); 1278 } 1279 1280 /** 1281 * Prints a single line with the module name, version and modifiers 1282 */ 1283 private static void showModule(ModuleReference mref) { 1284 ModuleDescriptor md = mref.descriptor(); 1285 ostream.print(md.toNameAndVersion()); 1286 mref.location() 1287 .filter(uri -> !isJrt(uri)) 1288 .ifPresent(uri -> ostream.format(" %s", uri)); 1289 if (md.isOpen()) 1290 ostream.print(" open"); 1291 if (md.isAutomatic()) 1292 ostream.print(" automatic"); 1293 ostream.println(); 1294 } 1295 1296 /** 1297 * A ModuleReference comparator that considers modules in the run-time 1298 * image to be less than modules than not in the run-time image. 1299 */ 1300 private static class JrtFirstComparator implements Comparator<ModuleReference> { 1301 private final Comparator<ModuleReference> real; 1302 1303 JrtFirstComparator() { 1304 this.real = Comparator.comparing(ModuleReference::descriptor); 1305 } 1306 1307 @Override 1308 public int compare(ModuleReference a, ModuleReference b) { 1309 if (isJrt(a)) { 1310 return isJrt(b) ? real.compare(a, b) : -1; 1311 } else { 1312 return isJrt(b) ? 1 : real.compare(a, b); 1313 } 1314 } 1315 } 1316 1317 private static <T> Stream<String> toStringStream(Set<T> s) { 1318 return s.stream().map(e -> e.toString().toLowerCase(Locale.ROOT)); 1319 } 1320 1321 private static boolean isJrt(ModuleReference mref) { 1322 return isJrt(mref.location().orElse(null)); 1323 } 1324 1325 private static boolean isJrt(URI uri) { 1326 return (uri != null && uri.getScheme().equalsIgnoreCase("jrt")); 1327 } 1328 1329 }