Path: blob/master/src/java.base/share/classes/sun/launcher/LauncherHelper.java
67760 views
/*1* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.launcher;2627/*28*29* <p><b>This is NOT part of any API supported by Sun Microsystems.30* If you write code that depends on this, you do so at your own31* risk. This code and its internal interfaces are subject to change32* or deletion without notice.</b>33*34*/3536/**37* A utility package for the java(1), javaw(1) launchers.38* The following are helper methods that the native launcher uses39* to perform checks etc. using JNI, see src/share/bin/java.c40*/41import java.io.File;42import java.io.IOException;43import java.io.PrintStream;44import java.io.UnsupportedEncodingException;45import java.lang.module.Configuration;46import java.lang.module.ModuleDescriptor;47import java.lang.module.ModuleDescriptor.Exports;48import java.lang.module.ModuleDescriptor.Opens;49import java.lang.module.ModuleDescriptor.Provides;50import java.lang.module.ModuleDescriptor.Requires;51import java.lang.module.ModuleFinder;52import java.lang.module.ModuleReference;53import java.lang.module.ResolvedModule;54import java.lang.reflect.InvocationTargetException;55import java.lang.reflect.Method;56import java.lang.reflect.Modifier;57import java.math.BigDecimal;58import java.math.RoundingMode;59import java.net.URI;60import java.nio.charset.Charset;61import java.nio.file.DirectoryStream;62import java.nio.file.Files;63import java.nio.file.Path;64import java.text.MessageFormat;65import java.text.Normalizer;66import java.util.ArrayList;67import java.util.Collections;68import java.util.Comparator;69import java.util.Iterator;70import java.util.List;71import java.util.Locale;72import java.util.Locale.Category;73import java.util.Optional;74import java.util.Properties;75import java.util.ResourceBundle;76import java.util.Set;77import java.util.TreeSet;78import java.util.jar.Attributes;79import java.util.jar.JarFile;80import java.util.jar.Manifest;81import java.util.stream.Collectors;82import java.util.stream.Stream;8384import jdk.internal.misc.VM;85import jdk.internal.module.ModuleBootstrap;86import jdk.internal.module.Modules;87import jdk.internal.platform.Container;88import jdk.internal.platform.Metrics;899091public final class LauncherHelper {9293// No instantiation94private LauncherHelper() {}9596// used to identify JavaFX applications97private static final String JAVAFX_APPLICATION_MARKER =98"JavaFX-Application-Class";99private static final String JAVAFX_APPLICATION_CLASS_NAME =100"javafx.application.Application";101private static final String JAVAFX_FXHELPER_CLASS_NAME_SUFFIX =102"sun.launcher.LauncherHelper$FXHelper";103private static final String LAUNCHER_AGENT_CLASS = "Launcher-Agent-Class";104private static final String MAIN_CLASS = "Main-Class";105private static final String ADD_EXPORTS = "Add-Exports";106private static final String ADD_OPENS = "Add-Opens";107108private static StringBuilder outBuf = new StringBuilder();109110private static final String INDENT = " ";111private static final String VM_SETTINGS = "VM settings:";112private static final String PROP_SETTINGS = "Property settings:";113private static final String LOCALE_SETTINGS = "Locale settings:";114115// sync with java.c and jdk.internal.misc.VM116private static final String diagprop = "sun.java.launcher.diag";117static final boolean trace = VM.getSavedProperty(diagprop) != null;118119private static final String defaultBundleName =120"sun.launcher.resources.launcher";121122private static class ResourceBundleHolder {123private static final ResourceBundle RB =124ResourceBundle.getBundle(defaultBundleName);125}126private static PrintStream ostream;127private static Class<?> appClass; // application class, for GUI/reporting purposes128129/*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 possible133* values vm, properties and locale.134*135* printToStderr: choose between stdout and stderr136*137* optionFlag: specifies which options to print default is all other138* possible values are vm, properties, locale.139*140* initialHeapSize: in bytes, as set by the launcher, a zero-value indicates141* this code should determine this value, using a suitable method or142* the line could be omitted.143*144* maxHeapSize: in bytes, as set by the launcher, a zero-value indicates145* this code should determine this value, using a suitable method.146*147* stackSize: in bytes, as set by the launcher, a zero-value indicates148* this code determine this value, using a suitable method or omit the149* line entirely.150*/151@SuppressWarnings("fallthrough")152static void showSettings(boolean printToStderr, String optionFlag,153long initialHeapSize, long maxHeapSize, long stackSize) {154155initOutput(printToStderr);156String opts[] = optionFlag.split(":");157String optStr = (opts.length > 1 && opts[1] != null)158? opts[1].trim()159: "all";160switch (optStr) {161case "vm":162printVmSettings(initialHeapSize, maxHeapSize, stackSize);163break;164case "properties":165printProperties();166break;167case "locale":168printLocale();169break;170case "system":171if (System.getProperty("os.name").contains("Linux")) {172printSystemMetrics();173break;174}175default:176printVmSettings(initialHeapSize, maxHeapSize, stackSize);177printProperties();178printLocale();179if (System.getProperty("os.name").contains("Linux")) {180printSystemMetrics();181}182break;183}184}185186/*187* prints the main vm settings subopt/section188*/189private static void printVmSettings(190long initialHeapSize, long maxHeapSize,191long stackSize) {192193ostream.println(VM_SETTINGS);194if (stackSize != 0L) {195ostream.println(INDENT + "Stack Size: " +196SizePrefix.scaleValue(stackSize));197}198if (initialHeapSize != 0L) {199ostream.println(INDENT + "Min. Heap Size: " +200SizePrefix.scaleValue(initialHeapSize));201}202if (maxHeapSize != 0L) {203ostream.println(INDENT + "Max. Heap Size: " +204SizePrefix.scaleValue(maxHeapSize));205} else {206ostream.println(INDENT + "Max. Heap Size (Estimated): "207+ SizePrefix.scaleValue(Runtime.getRuntime().maxMemory()));208}209ostream.println(INDENT + "Using VM: "210+ System.getProperty("java.vm.name"));211ostream.println();212}213214/*215* prints the properties subopt/section216*/217private static void printProperties() {218Properties p = System.getProperties();219ostream.println(PROP_SETTINGS);220List<String> sortedPropertyKeys = new ArrayList<>();221sortedPropertyKeys.addAll(p.stringPropertyNames());222Collections.sort(sortedPropertyKeys);223for (String x : sortedPropertyKeys) {224printPropertyValue(x, p.getProperty(x));225}226ostream.println();227}228229private static boolean isPath(String key) {230return key.endsWith(".dirs") || key.endsWith(".path");231}232233private static void printPropertyValue(String key, String value) {234ostream.print(INDENT + key + " = ");235if (key.equals("line.separator")) {236for (byte b : value.getBytes()) {237switch (b) {238case 0xd:239ostream.print("\\r ");240break;241case 0xa:242ostream.print("\\n ");243break;244default:245// print any bizzare line separators in hex, but really246// shouldn't happen.247ostream.printf("0x%02X", b & 0xff);248break;249}250}251ostream.println();252return;253}254if (!isPath(key)) {255ostream.println(value);256return;257}258String[] values = value.split(System.getProperty("path.separator"));259boolean first = true;260for (String s : values) {261if (first) { // first line treated specially262ostream.println(s);263first = false;264} else { // following lines prefix with indents265ostream.println(INDENT + INDENT + s);266}267}268}269270/*271* prints the locale subopt/section272*/273private static void printLocale() {274Locale locale = Locale.getDefault();275ostream.println(LOCALE_SETTINGS);276ostream.println(INDENT + "default locale = " +277locale.getDisplayName());278ostream.println(INDENT + "default display locale = " +279Locale.getDefault(Category.DISPLAY).getDisplayName());280ostream.println(INDENT + "default format locale = " +281Locale.getDefault(Category.FORMAT).getDisplayName());282printLocales();283ostream.println();284}285286private static void printLocales() {287Locale[] tlocales = Locale.getAvailableLocales();288final int len = tlocales == null ? 0 : tlocales.length;289if (len < 1 ) {290return;291}292// Locale does not implement Comparable so we convert it to String293// and sort it for pretty printing.294Set<String> sortedSet = new TreeSet<>();295for (Locale l : tlocales) {296sortedSet.add(l.toString());297}298299ostream.print(INDENT + "available locales = ");300Iterator<String> iter = sortedSet.iterator();301final int last = len - 1;302for (int i = 0 ; iter.hasNext() ; i++) {303String s = iter.next();304ostream.print(s);305if (i != last) {306ostream.print(", ");307}308// print columns of 8309if ((i + 1) % 8 == 0) {310ostream.println();311ostream.print(INDENT + INDENT);312}313}314}315316public static void printSystemMetrics() {317Metrics c = Container.metrics();318319ostream.println("Operating System Metrics:");320321if (c == null) {322ostream.println(INDENT + "No metrics available for this platform");323return;324}325326final long longRetvalNotSupported = -2;327328ostream.println(INDENT + "Provider: " + c.getProvider());329ostream.println(INDENT + "Effective CPU Count: " + c.getEffectiveCpuCount());330ostream.println(formatCpuVal(c.getCpuPeriod(), INDENT + "CPU Period: ", longRetvalNotSupported));331ostream.println(formatCpuVal(c.getCpuQuota(), INDENT + "CPU Quota: ", longRetvalNotSupported));332ostream.println(formatCpuVal(c.getCpuShares(), INDENT + "CPU Shares: ", longRetvalNotSupported));333334int cpus[] = c.getCpuSetCpus();335if (cpus != null) {336ostream.println(INDENT + "List of Processors, "337+ cpus.length + " total: ");338339ostream.print(INDENT);340for (int i = 0; i < cpus.length; i++) {341ostream.print(cpus[i] + " ");342}343if (cpus.length > 0) {344ostream.println("");345}346} else {347ostream.println(INDENT + "List of Processors: N/A");348}349350cpus = c.getEffectiveCpuSetCpus();351if (cpus != null) {352ostream.println(INDENT + "List of Effective Processors, "353+ cpus.length + " total: ");354355ostream.print(INDENT);356for (int i = 0; i < cpus.length; i++) {357ostream.print(cpus[i] + " ");358}359if (cpus.length > 0) {360ostream.println("");361}362} else {363ostream.println(INDENT + "List of Effective Processors: N/A");364}365366int mems[] = c.getCpuSetMems();367if (mems != null) {368ostream.println(INDENT + "List of Memory Nodes, "369+ mems.length + " total: ");370371ostream.print(INDENT);372for (int i = 0; i < mems.length; i++) {373ostream.print(mems[i] + " ");374}375if (mems.length > 0) {376ostream.println("");377}378} else {379ostream.println(INDENT + "List of Memory Nodes: N/A");380}381382mems = c.getEffectiveCpuSetMems();383if (mems != null) {384ostream.println(INDENT + "List of Available Memory Nodes, "385+ mems.length + " total: ");386387ostream.print(INDENT);388for (int i = 0; i < mems.length; i++) {389ostream.print(mems[i] + " ");390}391if (mems.length > 0) {392ostream.println("");393}394} else {395ostream.println(INDENT + "List of Available Memory Nodes: N/A");396}397398long limit = c.getMemoryLimit();399ostream.println(formatLimitString(limit, INDENT + "Memory Limit: ", longRetvalNotSupported));400401limit = c.getMemorySoftLimit();402ostream.println(formatLimitString(limit, INDENT + "Memory Soft Limit: ", longRetvalNotSupported));403404limit = c.getMemoryAndSwapLimit();405ostream.println(formatLimitString(limit, INDENT + "Memory & Swap Limit: ", longRetvalNotSupported));406407limit = c.getPidsMax();408ostream.println(formatLimitString(limit, INDENT + "Maximum Processes Limit: ",409longRetvalNotSupported, false));410ostream.println("");411}412413private static String formatLimitString(long limit, String prefix, long unavailable) {414return formatLimitString(limit, prefix, unavailable, true);415}416417private static String formatLimitString(long limit, String prefix, long unavailable, boolean scale) {418if (limit >= 0) {419if (scale) {420return prefix + SizePrefix.scaleValue(limit);421} else {422return prefix + limit;423}424} else if (limit == unavailable) {425return prefix + "N/A";426} else {427return prefix + "Unlimited";428}429}430431private static String formatCpuVal(long cpuVal, String prefix, long unavailable) {432if (cpuVal >= 0) {433return prefix + cpuVal + "us";434} else if (cpuVal == unavailable) {435return prefix + "N/A";436} else {437return prefix + cpuVal;438}439}440441private enum SizePrefix {442443KILO(1024, "K"),444MEGA(1024 * 1024, "M"),445GIGA(1024 * 1024 * 1024, "G"),446TERA(1024L * 1024L * 1024L * 1024L, "T");447long size;448String abbrev;449450SizePrefix(long size, String abbrev) {451this.size = size;452this.abbrev = abbrev;453}454455private static String scale(long v, SizePrefix prefix) {456return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size),4572, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev;458}459/*460* scale the incoming values to a human readable form, represented as461* K, M, G and T, see java.c parse_size for the scaled values and462* suffixes. The lowest possible scaled value is Kilo.463*/464static String scaleValue(long v) {465if (v < MEGA.size) {466return scale(v, KILO);467} else if (v < GIGA.size) {468return scale(v, MEGA);469} else if (v < TERA.size) {470return scale(v, GIGA);471} else {472return scale(v, TERA);473}474}475}476477/**478* A private helper method to get a localized message and also479* apply any arguments that we might pass.480*/481private static String getLocalizedMessage(String key, Object... args) {482String msg = ResourceBundleHolder.RB.getString(key);483return (args != null) ? MessageFormat.format(msg, args) : msg;484}485486/**487* The java -help message is split into 3 parts, an invariant, followed488* by a set of platform dependent variant messages, finally an invariant489* set of lines.490* This method initializes the help message for the first time, and also491* assembles the invariant header part of the message.492*/493static void initHelpMessage(String progname) {494outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header",495(progname == null) ? "java" : progname ));496}497498/**499* Appends the vm selection messages to the header, already created.500* initHelpSystem must already be called.501*/502static void appendVmSelectMessage(String vm1, String vm2) {503outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect",504vm1, vm2));505}506507/**508* Appends the vm synoym message to the header, already created.509* initHelpSystem must be called before using this method.510*/511static void appendVmSynonymMessage(String vm1, String vm2) {512outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot",513vm1, vm2));514}515516/**517* Appends the last invariant part to the previously created messages,518* and finishes up the printing to the desired output stream.519* initHelpSystem must be called before using this method.520*/521static void printHelpMessage(boolean printToStderr) {522initOutput(printToStderr);523outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",524File.pathSeparator));525ostream.println(outBuf.toString());526}527528/**529* Prints the Xusage text to the desired output stream.530*/531static void printXUsageMessage(boolean printToStderr) {532initOutput(printToStderr);533ostream.println(getLocalizedMessage("java.launcher.X.usage",534File.pathSeparator));535if (System.getProperty("os.name").contains("OS X")) {536ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage",537File.pathSeparator));538}539}540541static void initOutput(boolean printToStderr) {542ostream = (printToStderr) ? System.err : System.out;543}544545static void initOutput(PrintStream ps) {546ostream = ps;547}548549static String getMainClassFromJar(String jarname) {550String mainValue;551try (JarFile jarFile = new JarFile(jarname)) {552Manifest manifest = jarFile.getManifest();553if (manifest == null) {554abort(null, "java.launcher.jar.error2", jarname);555}556Attributes mainAttrs = manifest.getMainAttributes();557if (mainAttrs == null) {558abort(null, "java.launcher.jar.error3", jarname);559}560561// Main-Class562mainValue = mainAttrs.getValue(MAIN_CLASS);563if (mainValue == null) {564abort(null, "java.launcher.jar.error3", jarname);565}566567// Launcher-Agent-Class (only check for this when Main-Class present)568String agentClass = mainAttrs.getValue(LAUNCHER_AGENT_CLASS);569if (agentClass != null) {570ModuleLayer.boot().findModule("java.instrument").ifPresent(m -> {571try {572String cn = "sun.instrument.InstrumentationImpl";573Class<?> clazz = Class.forName(cn, false, null);574Method loadAgent = clazz.getMethod("loadAgent", String.class);575loadAgent.invoke(null, jarname);576} catch (Throwable e) {577if (e instanceof InvocationTargetException) e = e.getCause();578abort(e, "java.launcher.jar.error4", jarname);579}580});581}582583// Add-Exports and Add-Opens584String exports = mainAttrs.getValue(ADD_EXPORTS);585if (exports != null) {586addExportsOrOpens(exports, false);587}588String opens = mainAttrs.getValue(ADD_OPENS);589if (opens != null) {590addExportsOrOpens(opens, true);591}592593/*594* Hand off to FXHelper if it detects a JavaFX application595* This must be done after ensuring a Main-Class entry596* exists to enforce compliance with the jar specification597*/598if (mainAttrs.containsKey(599new Attributes.Name(JAVAFX_APPLICATION_MARKER))) {600FXHelper.setFXLaunchParameters(jarname, LM_JAR);601return FXHelper.class.getName();602}603604return mainValue.trim();605} catch (IOException ioe) {606abort(ioe, "java.launcher.jar.error1", jarname);607}608return null;609}610611/**612* Process the Add-Exports or Add-Opens value. The value is613* {@code <module>/<package> ( <module>/<package>)*}.614*/615static void addExportsOrOpens(String value, boolean open) {616for (String moduleAndPackage : value.split(" ")) {617String[] s = moduleAndPackage.trim().split("/");618if (s.length == 2) {619String mn = s[0];620String pn = s[1];621ModuleLayer.boot()622.findModule(mn)623.filter(m -> m.getDescriptor().packages().contains(pn))624.ifPresent(m -> {625if (open) {626Modules.addOpensToAllUnnamed(m, pn);627} else {628Modules.addExportsToAllUnnamed(m, pn);629}630});631}632}633}634635// From src/share/bin/java.c:636// enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }637638private static final int LM_UNKNOWN = 0;639private static final int LM_CLASS = 1;640private static final int LM_JAR = 2;641private static final int LM_MODULE = 3;642private static final int LM_SOURCE = 4;643644static void abort(Throwable t, String msgKey, Object... args) {645if (msgKey != null) {646ostream.println(getLocalizedMessage(msgKey, args));647}648if (trace) {649if (t != null) {650t.printStackTrace();651} else {652Thread.dumpStack();653}654}655System.exit(1);656}657658/**659* This method:660* 1. Loads the main class from the module or class path661* 2. Checks the public static void main method.662* 3. If the main class extends FX Application then call on FXHelper to663* perform the launch.664*665* @param printToStderr if set, all output will be routed to stderr666* @param mode LaunchMode as determined by the arguments passed on the667* command line668* @param what the module name[/class], JAR file, or the main class669* depending on the mode670*671* @return the application's main class672*/673@SuppressWarnings("fallthrough")674public static Class<?> checkAndLoadMain(boolean printToStderr,675int mode,676String what) {677initOutput(printToStderr);678679Class<?> mainClass = null;680switch (mode) {681case LM_MODULE: case LM_SOURCE:682mainClass = loadModuleMainClass(what);683break;684default:685mainClass = loadMainClass(mode, what);686break;687}688689// record the real main class for UI purposes690// neither method above can return null, they will abort()691appClass = mainClass;692693/*694* Check if FXHelper can launch it using the FX launcher. In an FX app,695* the main class may or may not have a main method, so do this before696* validating the main class.697*/698if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) ||699doesExtendFXApplication(mainClass)) {700// Will abort() if there are problems with FX runtime701FXHelper.setFXLaunchParameters(what, mode);702mainClass = FXHelper.class;703}704705validateMainClass(mainClass);706return mainClass;707}708709/**710* Returns the main class for a module. The query is either a module name711* or module-name/main-class. For the former then the module's main class712* is obtained from the module descriptor (MainClass attribute).713*/714private static Class<?> loadModuleMainClass(String what) {715int i = what.indexOf('/');716String mainModule;717String mainClass;718if (i == -1) {719mainModule = what;720mainClass = null;721} else {722mainModule = what.substring(0, i);723mainClass = what.substring(i+1);724}725726// main module is in the boot layer727ModuleLayer layer = ModuleLayer.boot();728Optional<Module> om = layer.findModule(mainModule);729if (!om.isPresent()) {730// should not happen731throw new InternalError("Module " + mainModule + " not in boot Layer");732}733Module m = om.get();734735// get main class736if (mainClass == null) {737Optional<String> omc = m.getDescriptor().mainClass();738if (!omc.isPresent()) {739abort(null, "java.launcher.module.error1", mainModule);740}741mainClass = omc.get();742}743744// load the class from the module745Class<?> c = null;746try {747c = Class.forName(m, mainClass);748if (c == null && System.getProperty("os.name", "").contains("OS X")749&& Normalizer.isNormalized(mainClass, Normalizer.Form.NFD)) {750751String cn = Normalizer.normalize(mainClass, Normalizer.Form.NFC);752c = Class.forName(m, cn);753}754} catch (LinkageError le) {755abort(null, "java.launcher.module.error3", mainClass, m.getName(),756le.getClass().getName() + ": " + le.getLocalizedMessage());757}758if (c == null) {759abort(null, "java.launcher.module.error2", mainClass, mainModule);760}761762System.setProperty("jdk.module.main.class", c.getName());763return c;764}765766/**767* Loads the main class from the class path (LM_CLASS or LM_JAR).768*/769private static Class<?> loadMainClass(int mode, String what) {770// get the class name771String cn;772switch (mode) {773case LM_CLASS:774cn = what;775break;776case LM_JAR:777cn = getMainClassFromJar(what);778break;779default:780// should never happen781throw new InternalError("" + mode + ": Unknown launch mode");782}783784// load the main class785cn = cn.replace('/', '.');786Class<?> mainClass = null;787ClassLoader scl = ClassLoader.getSystemClassLoader();788try {789try {790mainClass = Class.forName(cn, false, scl);791} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {792if (System.getProperty("os.name", "").contains("OS X")793&& Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {794try {795// On Mac OS X since all names with diacritical marks are796// given as decomposed it is possible that main class name797// comes incorrectly from the command line and we have798// to re-compose it799String ncn = Normalizer.normalize(cn, Normalizer.Form.NFC);800mainClass = Class.forName(ncn, false, scl);801} catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {802abort(cnfe1, "java.launcher.cls.error1", cn,803cnfe1.getClass().getCanonicalName(), cnfe1.getMessage());804}805} else {806abort(cnfe, "java.launcher.cls.error1", cn,807cnfe.getClass().getCanonicalName(), cnfe.getMessage());808}809}810} catch (LinkageError le) {811abort(le, "java.launcher.cls.error6", cn,812le.getClass().getName() + ": " + le.getLocalizedMessage());813}814return mainClass;815}816817/*818* Accessor method called by the launcher after getting the main class via819* checkAndLoadMain(). The "application class" is the class that is finally820* executed to start the application and in this case is used to report821* the correct application name, typically for UI purposes.822*/823public static Class<?> getApplicationClass() {824return appClass;825}826827/*828* Check if the given class is a JavaFX Application class. This is done829* in a way that does not cause the Application class to load or throw830* ClassNotFoundException if the JavaFX runtime is not available.831*/832private static boolean doesExtendFXApplication(Class<?> mainClass) {833for (Class<?> sc = mainClass.getSuperclass(); sc != null;834sc = sc.getSuperclass()) {835if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {836return true;837}838}839return false;840}841842// Check the existence and signature of main and abort if incorrect843static void validateMainClass(Class<?> mainClass) {844Method mainMethod = null;845try {846mainMethod = mainClass.getMethod("main", String[].class);847} catch (NoSuchMethodException nsme) {848// invalid main or not FX application, abort with an error849abort(null, "java.launcher.cls.error4", mainClass.getName(),850JAVAFX_APPLICATION_CLASS_NAME);851} catch (Throwable e) {852if (mainClass.getModule().isNamed()) {853abort(e, "java.launcher.module.error5",854mainClass.getName(), mainClass.getModule().getName(),855e.getClass().getName(), e.getLocalizedMessage());856} else {857abort(e, "java.launcher.cls.error7", mainClass.getName(),858e.getClass().getName(), e.getLocalizedMessage());859}860}861862/*863* getMethod (above) will choose the correct method, based864* on its name and parameter type, however, we still have to865* ensure that the method is static and returns a void.866*/867int mod = mainMethod.getModifiers();868if (!Modifier.isStatic(mod)) {869abort(null, "java.launcher.cls.error2", "static",870mainMethod.getDeclaringClass().getName());871}872if (mainMethod.getReturnType() != java.lang.Void.TYPE) {873abort(null, "java.launcher.cls.error3",874mainMethod.getDeclaringClass().getName());875}876}877878private static final String encprop = "sun.jnu.encoding";879private static String encoding = null;880private static boolean isCharsetSupported = false;881882/*883* converts a c or a byte array to a platform specific string,884* previously implemented as a native method in the launcher.885*/886static String makePlatformString(boolean printToStderr, byte[] inArray) {887initOutput(printToStderr);888if (encoding == null) {889encoding = System.getProperty(encprop);890isCharsetSupported = Charset.isSupported(encoding);891}892try {893String out = isCharsetSupported894? new String(inArray, encoding)895: new String(inArray);896return out;897} catch (UnsupportedEncodingException uee) {898abort(uee, null);899}900return null; // keep the compiler happy901}902903static String[] expandArgs(String[] argArray) {904List<StdArg> aList = new ArrayList<>();905for (String x : argArray) {906aList.add(new StdArg(x));907}908return expandArgs(aList);909}910911static String[] expandArgs(List<StdArg> argList) {912ArrayList<String> out = new ArrayList<>();913if (trace) {914System.err.println("Incoming arguments:");915}916for (StdArg a : argList) {917if (trace) {918System.err.println(a);919}920if (a.needsExpansion) {921File x = new File(a.arg);922File parent = x.getParentFile();923String glob = x.getName();924if (parent == null) {925parent = new File(".");926}927try (DirectoryStream<Path> dstream =928Files.newDirectoryStream(parent.toPath(), glob)) {929int entries = 0;930for (Path p : dstream) {931out.add(p.normalize().toString());932entries++;933}934if (entries == 0) {935out.add(a.arg);936}937} catch (Exception e) {938out.add(a.arg);939if (trace) {940System.err.println("Warning: passing argument as-is " + a);941System.err.print(e);942}943}944} else {945out.add(a.arg);946}947}948String[] oarray = new String[out.size()];949out.toArray(oarray);950951if (trace) {952System.err.println("Expanded arguments:");953for (String x : oarray) {954System.err.println(x);955}956}957return oarray;958}959960/* duplicate of the native StdArg struct */961private static class StdArg {962final String arg;963final boolean needsExpansion;964StdArg(String arg, boolean expand) {965this.arg = arg;966this.needsExpansion = expand;967}968// protocol: first char indicates whether expansion is required969// 'T' = true ; needs expansion970// 'F' = false; needs no expansion971StdArg(String in) {972this.arg = in.substring(1);973needsExpansion = in.charAt(0) == 'T';974}975public String toString() {976return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';977}978}979980static final class FXHelper {981982private static final String JAVAFX_GRAPHICS_MODULE_NAME =983"javafx.graphics";984985private static final String JAVAFX_LAUNCHER_CLASS_NAME =986"com.sun.javafx.application.LauncherImpl";987988/*989* The launch method used to invoke the JavaFX launcher. These must990* match the strings used in the launchApplication method.991*992* Command line JavaFX-App-Class Launch mode FX Launch mode993* java -cp fxapp.jar FXClass N/A LM_CLASS "LM_CLASS"994* java -cp somedir FXClass N/A LM_CLASS "LM_CLASS"995* java -jar fxapp.jar Present LM_JAR "LM_JAR"996* java -jar fxapp.jar Not Present LM_JAR "LM_JAR"997* java -m module/class [1] N/A LM_MODULE "LM_MODULE"998* java -m module N/A LM_MODULE "LM_MODULE"999*1000* [1] - JavaFX-Application-Class is ignored when modular args are used, even1001* if present in a modular jar1002*/1003private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";1004private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";1005private static final String JAVAFX_LAUNCH_MODE_MODULE = "LM_MODULE";10061007/*1008* FX application launcher and launch method, so we can launch1009* applications with no main method.1010*/1011private static String fxLaunchName = null;1012private static String fxLaunchMode = null;10131014private static Class<?> fxLauncherClass = null;1015private static Method fxLauncherMethod = null;10161017/*1018* Set the launch params according to what was passed to LauncherHelper1019* so we can use the same launch mode for FX. Abort if there is any1020* issue with loading the FX runtime or with the launcher method.1021*/1022private static void setFXLaunchParameters(String what, int mode) {10231024// find the module with the FX launcher1025Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);1026if (!om.isPresent()) {1027abort(null, "java.launcher.cls.error5");1028}10291030// load the FX launcher class1031fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME);1032if (fxLauncherClass == null) {1033abort(null, "java.launcher.cls.error5");1034}10351036try {1037/*1038* signature must be:1039* public static void launchApplication(String launchName,1040* String launchMode, String[] args);1041*/1042fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",1043String.class, String.class, String[].class);10441045// verify launcher signature as we do when validating the main method1046int mod = fxLauncherMethod.getModifiers();1047if (!Modifier.isStatic(mod)) {1048abort(null, "java.launcher.javafx.error1");1049}1050if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {1051abort(null, "java.launcher.javafx.error1");1052}1053} catch (NoSuchMethodException ex) {1054abort(ex, "java.launcher.cls.error5", ex);1055}10561057fxLaunchName = what;1058switch (mode) {1059case LM_CLASS:1060fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;1061break;1062case LM_JAR:1063fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;1064break;1065case LM_MODULE:1066fxLaunchMode = JAVAFX_LAUNCH_MODE_MODULE;1067break;1068default:1069// should not have gotten this far...1070throw new InternalError(mode + ": Unknown launch mode");1071}1072}10731074public static void main(String... args) throws Exception {1075if (fxLauncherMethod == null1076|| fxLaunchMode == null1077|| fxLaunchName == null) {1078throw new RuntimeException("Invalid JavaFX launch parameters");1079}1080// launch appClass via fxLauncherMethod1081fxLauncherMethod.invoke(null,1082new Object[] {fxLaunchName, fxLaunchMode, args});1083}1084}10851086/**1087* Called by the launcher to list the observable modules.1088*/1089static void listModules() {1090initOutput(System.out);10911092ModuleBootstrap.limitedFinder().findAll().stream()1093.sorted(new JrtFirstComparator())1094.forEach(LauncherHelper::showModule);1095}10961097/**1098* Called by the launcher to show the resolved modules1099*/1100static void showResolvedModules() {1101initOutput(System.out);11021103ModuleLayer bootLayer = ModuleLayer.boot();1104Configuration cf = bootLayer.configuration();11051106cf.modules().stream()1107.map(ResolvedModule::reference)1108.sorted(new JrtFirstComparator())1109.forEach(LauncherHelper::showModule);1110}11111112/**1113* Called by the launcher to describe a module1114*/1115static void describeModule(String moduleName) {1116initOutput(System.out);11171118ModuleFinder finder = ModuleBootstrap.limitedFinder();1119ModuleReference mref = finder.find(moduleName).orElse(null);1120if (mref == null) {1121abort(null, "java.launcher.module.error4", moduleName);1122}1123ModuleDescriptor md = mref.descriptor();11241125// one-line summary1126showModule(mref);11271128// unqualified exports (sorted by package)1129md.exports().stream()1130.filter(e -> !e.isQualified())1131.sorted(Comparator.comparing(Exports::source))1132.map(e -> Stream.concat(Stream.of(e.source()),1133toStringStream(e.modifiers()))1134.collect(Collectors.joining(" ")))1135.forEach(sourceAndMods -> ostream.format("exports %s%n", sourceAndMods));11361137// dependences1138for (Requires r : md.requires()) {1139String nameAndMods = Stream.concat(Stream.of(r.name()),1140toStringStream(r.modifiers()))1141.collect(Collectors.joining(" "));1142ostream.format("requires %s", nameAndMods);1143finder.find(r.name())1144.map(ModuleReference::descriptor)1145.filter(ModuleDescriptor::isAutomatic)1146.ifPresent(any -> ostream.print(" automatic"));1147ostream.println();1148}11491150// service use and provides1151for (String s : md.uses()) {1152ostream.format("uses %s%n", s);1153}1154for (Provides ps : md.provides()) {1155String names = ps.providers().stream().collect(Collectors.joining(" "));1156ostream.format("provides %s with %s%n", ps.service(), names);11571158}11591160// qualified exports1161for (Exports e : md.exports()) {1162if (e.isQualified()) {1163String who = e.targets().stream().collect(Collectors.joining(" "));1164ostream.format("qualified exports %s to %s%n", e.source(), who);1165}1166}11671168// open packages1169for (Opens opens: md.opens()) {1170if (opens.isQualified())1171ostream.print("qualified ");1172String sourceAndMods = Stream.concat(Stream.of(opens.source()),1173toStringStream(opens.modifiers()))1174.collect(Collectors.joining(" "));1175ostream.format("opens %s", sourceAndMods);1176if (opens.isQualified()) {1177String who = opens.targets().stream().collect(Collectors.joining(" "));1178ostream.format(" to %s", who);1179}1180ostream.println();1181}11821183// non-exported/non-open packages1184Set<String> concealed = new TreeSet<>(md.packages());1185md.exports().stream().map(Exports::source).forEach(concealed::remove);1186md.opens().stream().map(Opens::source).forEach(concealed::remove);1187concealed.forEach(p -> ostream.format("contains %s%n", p));1188}11891190/**1191* Prints a single line with the module name, version and modifiers1192*/1193private static void showModule(ModuleReference mref) {1194ModuleDescriptor md = mref.descriptor();1195ostream.print(md.toNameAndVersion());1196mref.location()1197.filter(uri -> !isJrt(uri))1198.ifPresent(uri -> ostream.format(" %s", uri));1199if (md.isOpen())1200ostream.print(" open");1201if (md.isAutomatic())1202ostream.print(" automatic");1203ostream.println();1204}12051206/**1207* A ModuleReference comparator that considers modules in the run-time1208* image to be less than modules than not in the run-time image.1209*/1210private static class JrtFirstComparator implements Comparator<ModuleReference> {1211private final Comparator<ModuleReference> real;12121213JrtFirstComparator() {1214this.real = Comparator.comparing(ModuleReference::descriptor);1215}12161217@Override1218public int compare(ModuleReference a, ModuleReference b) {1219if (isJrt(a)) {1220return isJrt(b) ? real.compare(a, b) : -1;1221} else {1222return isJrt(b) ? 1 : real.compare(a, b);1223}1224}1225}12261227private static <T> Stream<String> toStringStream(Set<T> s) {1228return s.stream().map(e -> e.toString().toLowerCase());1229}12301231private static boolean isJrt(ModuleReference mref) {1232return isJrt(mref.location().orElse(null));1233}12341235private static boolean isJrt(URI uri) {1236return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));1237}12381239}124012411242