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