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