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