Path: blob/master/src/java.base/share/classes/sun/launcher/LauncherHelper.java
41152 views
/*1* Copyright (c) 2007, 2020, 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));406407ostream.println("");408}409410private static String formatLimitString(long limit, String prefix, long unavailable) {411if (limit >= 0) {412return prefix + SizePrefix.scaleValue(limit);413} else if (limit == unavailable) {414return prefix + "N/A";415} else {416return prefix + "Unlimited";417}418}419420private static String formatCpuVal(long cpuVal, String prefix, long unavailable) {421if (cpuVal >= 0) {422return prefix + cpuVal + "us";423} else if (cpuVal == unavailable) {424return prefix + "N/A";425} else {426return prefix + cpuVal;427}428}429430private enum SizePrefix {431432KILO(1024, "K"),433MEGA(1024 * 1024, "M"),434GIGA(1024 * 1024 * 1024, "G"),435TERA(1024L * 1024L * 1024L * 1024L, "T");436long size;437String abbrev;438439SizePrefix(long size, String abbrev) {440this.size = size;441this.abbrev = abbrev;442}443444private static String scale(long v, SizePrefix prefix) {445return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size),4462, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev;447}448/*449* scale the incoming values to a human readable form, represented as450* K, M, G and T, see java.c parse_size for the scaled values and451* suffixes. The lowest possible scaled value is Kilo.452*/453static String scaleValue(long v) {454if (v < MEGA.size) {455return scale(v, KILO);456} else if (v < GIGA.size) {457return scale(v, MEGA);458} else if (v < TERA.size) {459return scale(v, GIGA);460} else {461return scale(v, TERA);462}463}464}465466/**467* A private helper method to get a localized message and also468* apply any arguments that we might pass.469*/470private static String getLocalizedMessage(String key, Object... args) {471String msg = ResourceBundleHolder.RB.getString(key);472return (args != null) ? MessageFormat.format(msg, args) : msg;473}474475/**476* The java -help message is split into 3 parts, an invariant, followed477* by a set of platform dependent variant messages, finally an invariant478* set of lines.479* This method initializes the help message for the first time, and also480* assembles the invariant header part of the message.481*/482static void initHelpMessage(String progname) {483outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header",484(progname == null) ? "java" : progname ));485}486487/**488* Appends the vm selection messages to the header, already created.489* initHelpSystem must already be called.490*/491static void appendVmSelectMessage(String vm1, String vm2) {492outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect",493vm1, vm2));494}495496/**497* Appends the vm synoym message to the header, already created.498* initHelpSystem must be called before using this method.499*/500static void appendVmSynonymMessage(String vm1, String vm2) {501outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot",502vm1, vm2));503}504505/**506* Appends the last invariant part to the previously created messages,507* and finishes up the printing to the desired output stream.508* initHelpSystem must be called before using this method.509*/510static void printHelpMessage(boolean printToStderr) {511initOutput(printToStderr);512outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",513File.pathSeparator));514ostream.println(outBuf.toString());515}516517/**518* Prints the Xusage text to the desired output stream.519*/520static void printXUsageMessage(boolean printToStderr) {521initOutput(printToStderr);522ostream.println(getLocalizedMessage("java.launcher.X.usage",523File.pathSeparator));524if (System.getProperty("os.name").contains("OS X")) {525ostream.println(getLocalizedMessage("java.launcher.X.macosx.usage",526File.pathSeparator));527}528}529530static void initOutput(boolean printToStderr) {531ostream = (printToStderr) ? System.err : System.out;532}533534static void initOutput(PrintStream ps) {535ostream = ps;536}537538static String getMainClassFromJar(String jarname) {539String mainValue;540try (JarFile jarFile = new JarFile(jarname)) {541Manifest manifest = jarFile.getManifest();542if (manifest == null) {543abort(null, "java.launcher.jar.error2", jarname);544}545Attributes mainAttrs = manifest.getMainAttributes();546if (mainAttrs == null) {547abort(null, "java.launcher.jar.error3", jarname);548}549550// Main-Class551mainValue = mainAttrs.getValue(MAIN_CLASS);552if (mainValue == null) {553abort(null, "java.launcher.jar.error3", jarname);554}555556// Launcher-Agent-Class (only check for this when Main-Class present)557String agentClass = mainAttrs.getValue(LAUNCHER_AGENT_CLASS);558if (agentClass != null) {559ModuleLayer.boot().findModule("java.instrument").ifPresent(m -> {560try {561String cn = "sun.instrument.InstrumentationImpl";562Class<?> clazz = Class.forName(cn, false, null);563Method loadAgent = clazz.getMethod("loadAgent", String.class);564loadAgent.invoke(null, jarname);565} catch (Throwable e) {566if (e instanceof InvocationTargetException) e = e.getCause();567abort(e, "java.launcher.jar.error4", jarname);568}569});570}571572// Add-Exports and Add-Opens573String exports = mainAttrs.getValue(ADD_EXPORTS);574if (exports != null) {575addExportsOrOpens(exports, false);576}577String opens = mainAttrs.getValue(ADD_OPENS);578if (opens != null) {579addExportsOrOpens(opens, true);580}581582/*583* Hand off to FXHelper if it detects a JavaFX application584* This must be done after ensuring a Main-Class entry585* exists to enforce compliance with the jar specification586*/587if (mainAttrs.containsKey(588new Attributes.Name(JAVAFX_APPLICATION_MARKER))) {589FXHelper.setFXLaunchParameters(jarname, LM_JAR);590return FXHelper.class.getName();591}592593return mainValue.trim();594} catch (IOException ioe) {595abort(ioe, "java.launcher.jar.error1", jarname);596}597return null;598}599600/**601* Process the Add-Exports or Add-Opens value. The value is602* {@code <module>/<package> ( <module>/<package>)*}.603*/604static void addExportsOrOpens(String value, boolean open) {605for (String moduleAndPackage : value.split(" ")) {606String[] s = moduleAndPackage.trim().split("/");607if (s.length == 2) {608String mn = s[0];609String pn = s[1];610ModuleLayer.boot()611.findModule(mn)612.filter(m -> m.getDescriptor().packages().contains(pn))613.ifPresent(m -> {614if (open) {615Modules.addOpensToAllUnnamed(m, pn);616} else {617Modules.addExportsToAllUnnamed(m, pn);618}619});620}621}622}623624// From src/share/bin/java.c:625// enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }626627private static final int LM_UNKNOWN = 0;628private static final int LM_CLASS = 1;629private static final int LM_JAR = 2;630private static final int LM_MODULE = 3;631private static final int LM_SOURCE = 4;632633static void abort(Throwable t, String msgKey, Object... args) {634if (msgKey != null) {635ostream.println(getLocalizedMessage(msgKey, args));636}637if (trace) {638if (t != null) {639t.printStackTrace();640} else {641Thread.dumpStack();642}643}644System.exit(1);645}646647/**648* This method:649* 1. Loads the main class from the module or class path650* 2. Checks the public static void main method.651* 3. If the main class extends FX Application then call on FXHelper to652* perform the launch.653*654* @param printToStderr if set, all output will be routed to stderr655* @param mode LaunchMode as determined by the arguments passed on the656* command line657* @param what the module name[/class], JAR file, or the main class658* depending on the mode659*660* @return the application's main class661*/662@SuppressWarnings("fallthrough")663public static Class<?> checkAndLoadMain(boolean printToStderr,664int mode,665String what) {666initOutput(printToStderr);667668Class<?> mainClass = null;669switch (mode) {670case LM_MODULE: case LM_SOURCE:671mainClass = loadModuleMainClass(what);672break;673default:674mainClass = loadMainClass(mode, what);675break;676}677678// record the real main class for UI purposes679// neither method above can return null, they will abort()680appClass = mainClass;681682/*683* Check if FXHelper can launch it using the FX launcher. In an FX app,684* the main class may or may not have a main method, so do this before685* validating the main class.686*/687if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) ||688doesExtendFXApplication(mainClass)) {689// Will abort() if there are problems with FX runtime690FXHelper.setFXLaunchParameters(what, mode);691mainClass = FXHelper.class;692}693694validateMainClass(mainClass);695return mainClass;696}697698/**699* Returns the main class for a module. The query is either a module name700* or module-name/main-class. For the former then the module's main class701* is obtained from the module descriptor (MainClass attribute).702*/703private static Class<?> loadModuleMainClass(String what) {704int i = what.indexOf('/');705String mainModule;706String mainClass;707if (i == -1) {708mainModule = what;709mainClass = null;710} else {711mainModule = what.substring(0, i);712mainClass = what.substring(i+1);713}714715// main module is in the boot layer716ModuleLayer layer = ModuleLayer.boot();717Optional<Module> om = layer.findModule(mainModule);718if (!om.isPresent()) {719// should not happen720throw new InternalError("Module " + mainModule + " not in boot Layer");721}722Module m = om.get();723724// get main class725if (mainClass == null) {726Optional<String> omc = m.getDescriptor().mainClass();727if (!omc.isPresent()) {728abort(null, "java.launcher.module.error1", mainModule);729}730mainClass = omc.get();731}732733// load the class from the module734Class<?> c = null;735try {736c = Class.forName(m, mainClass);737if (c == null && System.getProperty("os.name", "").contains("OS X")738&& Normalizer.isNormalized(mainClass, Normalizer.Form.NFD)) {739740String cn = Normalizer.normalize(mainClass, Normalizer.Form.NFC);741c = Class.forName(m, cn);742}743} catch (LinkageError le) {744abort(null, "java.launcher.module.error3", mainClass, m.getName(),745le.getClass().getName() + ": " + le.getLocalizedMessage());746}747if (c == null) {748abort(null, "java.launcher.module.error2", mainClass, mainModule);749}750751System.setProperty("jdk.module.main.class", c.getName());752return c;753}754755/**756* Loads the main class from the class path (LM_CLASS or LM_JAR).757*/758private static Class<?> loadMainClass(int mode, String what) {759// get the class name760String cn;761switch (mode) {762case LM_CLASS:763cn = what;764break;765case LM_JAR:766cn = getMainClassFromJar(what);767break;768default:769// should never happen770throw new InternalError("" + mode + ": Unknown launch mode");771}772773// load the main class774cn = cn.replace('/', '.');775Class<?> mainClass = null;776ClassLoader scl = ClassLoader.getSystemClassLoader();777try {778try {779mainClass = Class.forName(cn, false, scl);780} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {781if (System.getProperty("os.name", "").contains("OS X")782&& Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {783try {784// On Mac OS X since all names with diacritical marks are785// given as decomposed it is possible that main class name786// comes incorrectly from the command line and we have787// to re-compose it788String ncn = Normalizer.normalize(cn, Normalizer.Form.NFC);789mainClass = Class.forName(ncn, false, scl);790} catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {791abort(cnfe1, "java.launcher.cls.error1", cn,792cnfe1.getClass().getCanonicalName(), cnfe1.getMessage());793}794} else {795abort(cnfe, "java.launcher.cls.error1", cn,796cnfe.getClass().getCanonicalName(), cnfe.getMessage());797}798}799} catch (LinkageError le) {800abort(le, "java.launcher.cls.error6", cn,801le.getClass().getName() + ": " + le.getLocalizedMessage());802}803return mainClass;804}805806/*807* Accessor method called by the launcher after getting the main class via808* checkAndLoadMain(). The "application class" is the class that is finally809* executed to start the application and in this case is used to report810* the correct application name, typically for UI purposes.811*/812public static Class<?> getApplicationClass() {813return appClass;814}815816/*817* Check if the given class is a JavaFX Application class. This is done818* in a way that does not cause the Application class to load or throw819* ClassNotFoundException if the JavaFX runtime is not available.820*/821private static boolean doesExtendFXApplication(Class<?> mainClass) {822for (Class<?> sc = mainClass.getSuperclass(); sc != null;823sc = sc.getSuperclass()) {824if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {825return true;826}827}828return false;829}830831// Check the existence and signature of main and abort if incorrect832static void validateMainClass(Class<?> mainClass) {833Method mainMethod = null;834try {835mainMethod = mainClass.getMethod("main", String[].class);836} catch (NoSuchMethodException nsme) {837// invalid main or not FX application, abort with an error838abort(null, "java.launcher.cls.error4", mainClass.getName(),839JAVAFX_APPLICATION_CLASS_NAME);840} catch (Throwable e) {841if (mainClass.getModule().isNamed()) {842abort(e, "java.launcher.module.error5",843mainClass.getName(), mainClass.getModule().getName(),844e.getClass().getName(), e.getLocalizedMessage());845} else {846abort(e, "java.launcher.cls.error7", mainClass.getName(),847e.getClass().getName(), e.getLocalizedMessage());848}849}850851/*852* getMethod (above) will choose the correct method, based853* on its name and parameter type, however, we still have to854* ensure that the method is static and returns a void.855*/856int mod = mainMethod.getModifiers();857if (!Modifier.isStatic(mod)) {858abort(null, "java.launcher.cls.error2", "static",859mainMethod.getDeclaringClass().getName());860}861if (mainMethod.getReturnType() != java.lang.Void.TYPE) {862abort(null, "java.launcher.cls.error3",863mainMethod.getDeclaringClass().getName());864}865}866867private static final String encprop = "sun.jnu.encoding";868private static String encoding = null;869private static boolean isCharsetSupported = false;870871/*872* converts a c or a byte array to a platform specific string,873* previously implemented as a native method in the launcher.874*/875static String makePlatformString(boolean printToStderr, byte[] inArray) {876initOutput(printToStderr);877if (encoding == null) {878encoding = System.getProperty(encprop);879isCharsetSupported = Charset.isSupported(encoding);880}881try {882String out = isCharsetSupported883? new String(inArray, encoding)884: new String(inArray);885return out;886} catch (UnsupportedEncodingException uee) {887abort(uee, null);888}889return null; // keep the compiler happy890}891892static String[] expandArgs(String[] argArray) {893List<StdArg> aList = new ArrayList<>();894for (String x : argArray) {895aList.add(new StdArg(x));896}897return expandArgs(aList);898}899900static String[] expandArgs(List<StdArg> argList) {901ArrayList<String> out = new ArrayList<>();902if (trace) {903System.err.println("Incoming arguments:");904}905for (StdArg a : argList) {906if (trace) {907System.err.println(a);908}909if (a.needsExpansion) {910File x = new File(a.arg);911File parent = x.getParentFile();912String glob = x.getName();913if (parent == null) {914parent = new File(".");915}916try (DirectoryStream<Path> dstream =917Files.newDirectoryStream(parent.toPath(), glob)) {918int entries = 0;919for (Path p : dstream) {920out.add(p.normalize().toString());921entries++;922}923if (entries == 0) {924out.add(a.arg);925}926} catch (Exception e) {927out.add(a.arg);928if (trace) {929System.err.println("Warning: passing argument as-is " + a);930System.err.print(e);931}932}933} else {934out.add(a.arg);935}936}937String[] oarray = new String[out.size()];938out.toArray(oarray);939940if (trace) {941System.err.println("Expanded arguments:");942for (String x : oarray) {943System.err.println(x);944}945}946return oarray;947}948949/* duplicate of the native StdArg struct */950private static class StdArg {951final String arg;952final boolean needsExpansion;953StdArg(String arg, boolean expand) {954this.arg = arg;955this.needsExpansion = expand;956}957// protocol: first char indicates whether expansion is required958// 'T' = true ; needs expansion959// 'F' = false; needs no expansion960StdArg(String in) {961this.arg = in.substring(1);962needsExpansion = in.charAt(0) == 'T';963}964public String toString() {965return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';966}967}968969static final class FXHelper {970971private static final String JAVAFX_GRAPHICS_MODULE_NAME =972"javafx.graphics";973974private static final String JAVAFX_LAUNCHER_CLASS_NAME =975"com.sun.javafx.application.LauncherImpl";976977/*978* The launch method used to invoke the JavaFX launcher. These must979* match the strings used in the launchApplication method.980*981* Command line JavaFX-App-Class Launch mode FX Launch mode982* java -cp fxapp.jar FXClass N/A LM_CLASS "LM_CLASS"983* java -cp somedir FXClass N/A LM_CLASS "LM_CLASS"984* java -jar fxapp.jar Present LM_JAR "LM_JAR"985* java -jar fxapp.jar Not Present LM_JAR "LM_JAR"986* java -m module/class [1] N/A LM_MODULE "LM_MODULE"987* java -m module N/A LM_MODULE "LM_MODULE"988*989* [1] - JavaFX-Application-Class is ignored when modular args are used, even990* if present in a modular jar991*/992private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";993private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";994private static final String JAVAFX_LAUNCH_MODE_MODULE = "LM_MODULE";995996/*997* FX application launcher and launch method, so we can launch998* applications with no main method.999*/1000private static String fxLaunchName = null;1001private static String fxLaunchMode = null;10021003private static Class<?> fxLauncherClass = null;1004private static Method fxLauncherMethod = null;10051006/*1007* Set the launch params according to what was passed to LauncherHelper1008* so we can use the same launch mode for FX. Abort if there is any1009* issue with loading the FX runtime or with the launcher method.1010*/1011private static void setFXLaunchParameters(String what, int mode) {10121013// find the module with the FX launcher1014Optional<Module> om = ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME);1015if (!om.isPresent()) {1016abort(null, "java.launcher.cls.error5");1017}10181019// load the FX launcher class1020fxLauncherClass = Class.forName(om.get(), JAVAFX_LAUNCHER_CLASS_NAME);1021if (fxLauncherClass == null) {1022abort(null, "java.launcher.cls.error5");1023}10241025try {1026/*1027* signature must be:1028* public static void launchApplication(String launchName,1029* String launchMode, String[] args);1030*/1031fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",1032String.class, String.class, String[].class);10331034// verify launcher signature as we do when validating the main method1035int mod = fxLauncherMethod.getModifiers();1036if (!Modifier.isStatic(mod)) {1037abort(null, "java.launcher.javafx.error1");1038}1039if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {1040abort(null, "java.launcher.javafx.error1");1041}1042} catch (NoSuchMethodException ex) {1043abort(ex, "java.launcher.cls.error5", ex);1044}10451046fxLaunchName = what;1047switch (mode) {1048case LM_CLASS:1049fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;1050break;1051case LM_JAR:1052fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;1053break;1054case LM_MODULE:1055fxLaunchMode = JAVAFX_LAUNCH_MODE_MODULE;1056break;1057default:1058// should not have gotten this far...1059throw new InternalError(mode + ": Unknown launch mode");1060}1061}10621063public static void main(String... args) throws Exception {1064if (fxLauncherMethod == null1065|| fxLaunchMode == null1066|| fxLaunchName == null) {1067throw new RuntimeException("Invalid JavaFX launch parameters");1068}1069// launch appClass via fxLauncherMethod1070fxLauncherMethod.invoke(null,1071new Object[] {fxLaunchName, fxLaunchMode, args});1072}1073}10741075/**1076* Called by the launcher to list the observable modules.1077*/1078static void listModules() {1079initOutput(System.out);10801081ModuleBootstrap.limitedFinder().findAll().stream()1082.sorted(new JrtFirstComparator())1083.forEach(LauncherHelper::showModule);1084}10851086/**1087* Called by the launcher to show the resolved modules1088*/1089static void showResolvedModules() {1090initOutput(System.out);10911092ModuleLayer bootLayer = ModuleLayer.boot();1093Configuration cf = bootLayer.configuration();10941095cf.modules().stream()1096.map(ResolvedModule::reference)1097.sorted(new JrtFirstComparator())1098.forEach(LauncherHelper::showModule);1099}11001101/**1102* Called by the launcher to describe a module1103*/1104static void describeModule(String moduleName) {1105initOutput(System.out);11061107ModuleFinder finder = ModuleBootstrap.limitedFinder();1108ModuleReference mref = finder.find(moduleName).orElse(null);1109if (mref == null) {1110abort(null, "java.launcher.module.error4", moduleName);1111}1112ModuleDescriptor md = mref.descriptor();11131114// one-line summary1115showModule(mref);11161117// unqualified exports (sorted by package)1118md.exports().stream()1119.filter(e -> !e.isQualified())1120.sorted(Comparator.comparing(Exports::source))1121.map(e -> Stream.concat(Stream.of(e.source()),1122toStringStream(e.modifiers()))1123.collect(Collectors.joining(" ")))1124.forEach(sourceAndMods -> ostream.format("exports %s%n", sourceAndMods));11251126// dependences1127for (Requires r : md.requires()) {1128String nameAndMods = Stream.concat(Stream.of(r.name()),1129toStringStream(r.modifiers()))1130.collect(Collectors.joining(" "));1131ostream.format("requires %s", nameAndMods);1132finder.find(r.name())1133.map(ModuleReference::descriptor)1134.filter(ModuleDescriptor::isAutomatic)1135.ifPresent(any -> ostream.print(" automatic"));1136ostream.println();1137}11381139// service use and provides1140for (String s : md.uses()) {1141ostream.format("uses %s%n", s);1142}1143for (Provides ps : md.provides()) {1144String names = ps.providers().stream().collect(Collectors.joining(" "));1145ostream.format("provides %s with %s%n", ps.service(), names);11461147}11481149// qualified exports1150for (Exports e : md.exports()) {1151if (e.isQualified()) {1152String who = e.targets().stream().collect(Collectors.joining(" "));1153ostream.format("qualified exports %s to %s%n", e.source(), who);1154}1155}11561157// open packages1158for (Opens opens: md.opens()) {1159if (opens.isQualified())1160ostream.print("qualified ");1161String sourceAndMods = Stream.concat(Stream.of(opens.source()),1162toStringStream(opens.modifiers()))1163.collect(Collectors.joining(" "));1164ostream.format("opens %s", sourceAndMods);1165if (opens.isQualified()) {1166String who = opens.targets().stream().collect(Collectors.joining(" "));1167ostream.format(" to %s", who);1168}1169ostream.println();1170}11711172// non-exported/non-open packages1173Set<String> concealed = new TreeSet<>(md.packages());1174md.exports().stream().map(Exports::source).forEach(concealed::remove);1175md.opens().stream().map(Opens::source).forEach(concealed::remove);1176concealed.forEach(p -> ostream.format("contains %s%n", p));1177}11781179/**1180* Prints a single line with the module name, version and modifiers1181*/1182private static void showModule(ModuleReference mref) {1183ModuleDescriptor md = mref.descriptor();1184ostream.print(md.toNameAndVersion());1185mref.location()1186.filter(uri -> !isJrt(uri))1187.ifPresent(uri -> ostream.format(" %s", uri));1188if (md.isOpen())1189ostream.print(" open");1190if (md.isAutomatic())1191ostream.print(" automatic");1192ostream.println();1193}11941195/**1196* A ModuleReference comparator that considers modules in the run-time1197* image to be less than modules than not in the run-time image.1198*/1199private static class JrtFirstComparator implements Comparator<ModuleReference> {1200private final Comparator<ModuleReference> real;12011202JrtFirstComparator() {1203this.real = Comparator.comparing(ModuleReference::descriptor);1204}12051206@Override1207public int compare(ModuleReference a, ModuleReference b) {1208if (isJrt(a)) {1209return isJrt(b) ? real.compare(a, b) : -1;1210} else {1211return isJrt(b) ? 1 : real.compare(a, b);1212}1213}1214}12151216private static <T> Stream<String> toStringStream(Set<T> s) {1217return s.stream().map(e -> e.toString().toLowerCase());1218}12191220private static boolean isJrt(ModuleReference mref) {1221return isJrt(mref.location().orElse(null));1222}12231224private static boolean isJrt(URI uri) {1225return (uri != null && uri.getScheme().equalsIgnoreCase("jrt"));1226}12271228}122912301231