8001533: java launcher must launch javafx applications

Reviewed-by: ksrini, mchung, kcr, alanb
This commit is contained in:
David Dehaven 2012-11-19 19:49:38 -08:00 committed by Kumar Srinivasan
parent 0bb98e7294
commit 5f0debc005
4 changed files with 214 additions and 76 deletions
jdk
src/share
bin
classes/sun/launcher
test/tools/launcher

@ -105,6 +105,7 @@ static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv,
InvocationFunctions *ifn); InvocationFunctions *ifn);
static jstring NewPlatformString(JNIEnv *env, char *s); static jstring NewPlatformString(JNIEnv *env, char *s);
static jclass LoadMainClass(JNIEnv *env, int mode, char *name); static jclass LoadMainClass(JNIEnv *env, int mode, char *name);
static jclass GetApplicationClass(JNIEnv *env);
static void TranslateApplicationArgs(int jargc, const char **jargv, int *pargc, char ***pargv); static void TranslateApplicationArgs(int jargc, const char **jargv, int *pargc, char ***pargv);
static jboolean AddApplicationOptions(int cpathc, const char **cpathv); static jboolean AddApplicationOptions(int cpathc, const char **cpathv);
@ -346,6 +347,7 @@ JavaMain(void * _args)
JavaVM *vm = 0; JavaVM *vm = 0;
JNIEnv *env = 0; JNIEnv *env = 0;
jclass mainClass = NULL; jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID; jmethodID mainID;
jobjectArray mainArgs; jobjectArray mainArgs;
int ret = 0; int ret = 0;
@ -419,10 +421,28 @@ JavaMain(void * _args)
* all environments, * all environments,
* 2) Remove the vestages of maintaining main_class through * 2) Remove the vestages of maintaining main_class through
* the environment (and remove these comments). * the environment (and remove these comments).
*
* This method also correctly handles launching existing JavaFX
* applications that may or may not have a Main-Class manifest entry.
*/ */
mainClass = LoadMainClass(env, mode, what); mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass); CHECK_EXCEPTION_NULL_LEAVE(mainClass);
PostJVMInit(env, mainClass, vm); /*
* In some cases when launching an application that needs a helper, e.g., a
* JavaFX application with no main method, the mainClass will not be the
* applications own main class but rather a helper class. To keep things
* consistent in the UI we need to track and report the application main class.
*/
appClass = GetApplicationClass(env);
NULL_CHECK(appClass);
/*
* PostJVMInit uses the class name as the application name for GUI purposes,
* for example, on OSX this sets the application name in the menu bar for
* both SWT and JavaFX. So we'll pass the actual application class here
* instead of mainClass as that may be a launcher or helper class instead
* of the application class.
*/
PostJVMInit(env, appClass, vm);
/* /*
* The LoadMainClass not only loads the main class, it will also ensure * The LoadMainClass not only loads the main class, it will also ensure
* that the main method's signature is correct, therefore further checking * that the main method's signature is correct, therefore further checking
@ -1215,6 +1235,20 @@ LoadMainClass(JNIEnv *env, int mode, char *name)
return (jclass)result; return (jclass)result;
} }
static jclass
GetApplicationClass(JNIEnv *env)
{
jmethodID mid;
jobject result;
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"getApplicationClass",
"()Ljava/lang/Class;"));
return (*env)->CallStaticObjectMethod(env, cls, mid);
}
/* /*
* For tools, convert command line args thus: * For tools, convert command line args thus:
* javac -cp foo:foo/"*" -J-ms32m ... * javac -cp foo:foo/"*" -J-ms32m ...

@ -69,7 +69,6 @@ import java.util.jar.Manifest;
public enum LauncherHelper { public enum LauncherHelper {
INSTANCE; INSTANCE;
private static final String MAIN_CLASS = "Main-Class"; private static final String MAIN_CLASS = "Main-Class";
private static StringBuilder outBuf = new StringBuilder(); private static StringBuilder outBuf = new StringBuilder();
private static final String INDENT = " "; private static final String INDENT = " ";
@ -87,6 +86,9 @@ public enum LauncherHelper {
private static final ResourceBundle RB = private static final ResourceBundle RB =
ResourceBundle.getBundle(defaultBundleName); ResourceBundle.getBundle(defaultBundleName);
} }
private static PrintStream ostream;
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
private static Class<?> appClass; // application class, for GUI/reporting purposes
/* /*
* A method called by the launcher to print out the standard settings, * A method called by the launcher to print out the standard settings,
@ -114,27 +116,27 @@ public enum LauncherHelper {
long initialHeapSize, long maxHeapSize, long stackSize, long initialHeapSize, long maxHeapSize, long stackSize,
boolean isServer) { boolean isServer) {
PrintStream ostream = (printToStderr) ? System.err : System.out; initOutput(printToStderr);
String opts[] = optionFlag.split(":"); String opts[] = optionFlag.split(":");
String optStr = (opts.length > 1 && opts[1] != null) String optStr = (opts.length > 1 && opts[1] != null)
? opts[1].trim() ? opts[1].trim()
: "all"; : "all";
switch (optStr) { switch (optStr) {
case "vm": case "vm":
printVmSettings(ostream, initialHeapSize, maxHeapSize, printVmSettings(initialHeapSize, maxHeapSize,
stackSize, isServer); stackSize, isServer);
break; break;
case "properties": case "properties":
printProperties(ostream); printProperties();
break; break;
case "locale": case "locale":
printLocale(ostream); printLocale();
break; break;
default: default:
printVmSettings(ostream, initialHeapSize, maxHeapSize, printVmSettings(initialHeapSize, maxHeapSize, stackSize,
stackSize, isServer); isServer);
printProperties(ostream); printProperties();
printLocale(ostream); printLocale();
break; break;
} }
} }
@ -142,7 +144,7 @@ public enum LauncherHelper {
/* /*
* prints the main vm settings subopt/section * prints the main vm settings subopt/section
*/ */
private static void printVmSettings(PrintStream ostream, private static void printVmSettings(
long initialHeapSize, long maxHeapSize, long initialHeapSize, long maxHeapSize,
long stackSize, boolean isServer) { long stackSize, boolean isServer) {
@ -172,14 +174,14 @@ public enum LauncherHelper {
/* /*
* prints the properties subopt/section * prints the properties subopt/section
*/ */
private static void printProperties(PrintStream ostream) { private static void printProperties() {
Properties p = System.getProperties(); Properties p = System.getProperties();
ostream.println(PROP_SETTINGS); ostream.println(PROP_SETTINGS);
List<String> sortedPropertyKeys = new ArrayList<>(); List<String> sortedPropertyKeys = new ArrayList<>();
sortedPropertyKeys.addAll(p.stringPropertyNames()); sortedPropertyKeys.addAll(p.stringPropertyNames());
Collections.sort(sortedPropertyKeys); Collections.sort(sortedPropertyKeys);
for (String x : sortedPropertyKeys) { for (String x : sortedPropertyKeys) {
printPropertyValue(ostream, x, p.getProperty(x)); printPropertyValue(x, p.getProperty(x));
} }
ostream.println(); ostream.println();
} }
@ -188,8 +190,7 @@ public enum LauncherHelper {
return key.endsWith(".dirs") || key.endsWith(".path"); return key.endsWith(".dirs") || key.endsWith(".path");
} }
private static void printPropertyValue(PrintStream ostream, private static void printPropertyValue(String key, String value) {
String key, String value) {
ostream.print(INDENT + key + " = "); ostream.print(INDENT + key + " = ");
if (key.equals("line.separator")) { if (key.equals("line.separator")) {
for (byte b : value.getBytes()) { for (byte b : value.getBytes()) {
@ -229,7 +230,7 @@ public enum LauncherHelper {
/* /*
* prints the locale subopt/section * prints the locale subopt/section
*/ */
private static void printLocale(PrintStream ostream) { private static void printLocale() {
Locale locale = Locale.getDefault(); Locale locale = Locale.getDefault();
ostream.println(LOCALE_SETTINGS); ostream.println(LOCALE_SETTINGS);
ostream.println(INDENT + "default locale = " + ostream.println(INDENT + "default locale = " +
@ -238,11 +239,11 @@ public enum LauncherHelper {
Locale.getDefault(Category.DISPLAY).getDisplayName()); Locale.getDefault(Category.DISPLAY).getDisplayName());
ostream.println(INDENT + "default format locale = " + ostream.println(INDENT + "default format locale = " +
Locale.getDefault(Category.FORMAT).getDisplayName()); Locale.getDefault(Category.FORMAT).getDisplayName());
printLocales(ostream); printLocales();
ostream.println(); ostream.println();
} }
private static void printLocales(PrintStream ostream) { private static void printLocales() {
Locale[] tlocales = Locale.getAvailableLocales(); Locale[] tlocales = Locale.getAvailableLocales();
final int len = tlocales == null ? 0 : tlocales.length; final int len = tlocales == null ? 0 : tlocales.length;
if (len < 1 ) { if (len < 1 ) {
@ -370,7 +371,7 @@ public enum LauncherHelper {
* initHelpSystem must be called before using this method. * initHelpSystem must be called before using this method.
*/ */
static void printHelpMessage(boolean printToStderr) { static void printHelpMessage(boolean printToStderr) {
PrintStream ostream = (printToStderr) ? System.err : System.out; initOutput(printToStderr);
outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer", outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer",
File.pathSeparator)); File.pathSeparator));
ostream.println(outBuf.toString()); ostream.println(outBuf.toString());
@ -380,7 +381,7 @@ public enum LauncherHelper {
* Prints the Xusage text to the desired output stream. * Prints the Xusage text to the desired output stream.
*/ */
static void printXUsageMessage(boolean printToStderr) { static void printXUsageMessage(boolean printToStderr) {
PrintStream ostream = (printToStderr) ? System.err : System.out; initOutput(printToStderr);
ostream.println(getLocalizedMessage("java.launcher.X.usage", ostream.println(getLocalizedMessage("java.launcher.X.usage",
File.pathSeparator)); File.pathSeparator));
if (System.getProperty("os.name").contains("OS X")) { if (System.getProperty("os.name").contains("OS X")) {
@ -389,36 +390,32 @@ public enum LauncherHelper {
} }
} }
static String getMainClassFromJar(PrintStream ostream, String jarname) { static void initOutput(boolean printToStderr) {
try { ostream = (printToStderr) ? System.err : System.out;
JarFile jarFile = null; }
try {
jarFile = new JarFile(jarname); static String getMainClassFromJar(String jarname) {
Manifest manifest = jarFile.getManifest(); String mainValue = null;
if (manifest == null) { try (JarFile jarFile = new JarFile(jarname)) {
abort(ostream, null, "java.launcher.jar.error2", jarname); Manifest manifest = jarFile.getManifest();
} if (manifest == null) {
Attributes mainAttrs = manifest.getMainAttributes(); abort(null, "java.launcher.jar.error2", jarname);
if (mainAttrs == null) {
abort(ostream, null, "java.launcher.jar.error3", jarname);
}
String mainValue = mainAttrs.getValue(MAIN_CLASS);
if (mainValue == null) {
abort(ostream, null, "java.launcher.jar.error3", jarname);
}
return mainValue.trim();
} finally {
if (jarFile != null) {
jarFile.close();
}
} }
Attributes mainAttrs = manifest.getMainAttributes();
if (mainAttrs == null) {
abort(null, "java.launcher.jar.error3", jarname);
}
mainValue = mainAttrs.getValue(MAIN_CLASS);
if (mainValue == null) {
abort(null, "java.launcher.jar.error3", jarname);
}
return mainValue.trim();
} catch (IOException ioe) { } catch (IOException ioe) {
abort(ostream, ioe, "java.launcher.jar.error1", jarname); abort(ioe, "java.launcher.jar.error1", jarname);
} }
return null; return null;
} }
// From src/share/bin/java.c: // From src/share/bin/java.c:
// enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR }; // enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR };
@ -426,7 +423,7 @@ public enum LauncherHelper {
private static final int LM_CLASS = 1; private static final int LM_CLASS = 1;
private static final int LM_JAR = 2; private static final int LM_JAR = 2;
static void abort(PrintStream ostream, Throwable t, String msgKey, Object... args) { static void abort(Throwable t, String msgKey, Object... args) {
if (msgKey != null) { if (msgKey != null) {
ostream.println(getLocalizedMessage(msgKey, args)); ostream.println(getLocalizedMessage(msgKey, args));
} }
@ -450,19 +447,22 @@ public enum LauncherHelper {
* b. is there a main * b. is there a main
* c. is the main public * c. is the main public
* d. is the main static * d. is the main static
* c. does the main take a String array for args * e. does the main take a String array for args
* 4. and off we go...... * 4. if no main method and if the class extends FX Application, then call
* on FXHelper to determine the main class to launch
* 5. and off we go......
* *
* @param printToStderr * @param printToStderr if set, all output will be routed to stderr
* @param isJar * @param mode LaunchMode as determined by the arguments passed on the
* @param name * command line
* @return * @param what either the jar file to launch or the main class when using
* LM_CLASS mode
* @return the application's main class
*/ */
public static Class<?> checkAndLoadMain(boolean printToStderr, public static Class<?> checkAndLoadMain(boolean printToStderr,
int mode, int mode,
String what) { String what) {
final PrintStream ostream = (printToStderr) ? System.err : System.out; initOutput(printToStderr);
final ClassLoader ld = ClassLoader.getSystemClassLoader();
// get the class name // get the class name
String cn = null; String cn = null;
switch (mode) { switch (mode) {
@ -470,44 +470,75 @@ public enum LauncherHelper {
cn = what; cn = what;
break; break;
case LM_JAR: case LM_JAR:
cn = getMainClassFromJar(ostream, what); cn = getMainClassFromJar(what);
break; break;
default: default:
// should never happen // should never happen
throw new InternalError("" + mode + ": Unknown launch mode"); throw new InternalError("" + mode + ": Unknown launch mode");
} }
cn = cn.replace('/', '.'); cn = cn.replace('/', '.');
Class<?> c = null; Class<?> mainClass = null;
try { try {
c = ld.loadClass(cn); mainClass = scloader.loadClass(cn);
} catch (ClassNotFoundException cnfe) { } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
abort(ostream, cnfe, "java.launcher.cls.error1", cn); abort(cnfe, "java.launcher.cls.error1", cn);
} }
getMainMethod(ostream, c); // set to mainClass, FXHelper may return something else
return c; appClass = mainClass;
Method m = getMainMethod(mainClass);
if (m != null) {
// this will abort if main method has the wrong signature
validateMainMethod(m);
return mainClass;
}
// Check if FXHelper can launch it using the FX launcher
Class<?> fxClass = FXHelper.getFXMainClass(mainClass);
if (fxClass != null) {
return fxClass;
}
// not an FX application either, abort with an error
abort(null, "java.launcher.cls.error4", mainClass.getName(),
FXHelper.JAVAFX_APPLICATION_CLASS_NAME);
return null; // avoid compiler error...
} }
static Method getMainMethod(PrintStream ostream, Class<?> clazz) { /*
String classname = clazz.getName(); * Accessor method called by the launcher after getting the main class via
Method method = null; * checkAndLoadMain(). The "application class" is the class that is finally
* executed to start the application and in this case is used to report
* the correct application name, typically for UI purposes.
*/
public static Class<?> getApplicationClass() {
return appClass;
}
// Check for main method or return null if not found
static Method getMainMethod(Class<?> clazz) {
try { try {
method = clazz.getMethod("main", String[].class); return clazz.getMethod("main", String[].class);
} catch (NoSuchMethodException nsme) { } catch (NoSuchMethodException nsme) {}
abort(ostream, null, "java.launcher.cls.error4", classname); return null;
} }
// Check the signature of main and abort if it's incorrect
static void validateMainMethod(Method mainMethod) {
/* /*
* getMethod (above) will choose the correct method, based * getMethod (above) will choose the correct method, based
* on its name and parameter type, however, we still have to * on its name and parameter type, however, we still have to
* ensure that the method is static and returns a void. * ensure that the method is static and returns a void.
*/ */
int mod = method.getModifiers(); int mod = mainMethod.getModifiers();
if (!Modifier.isStatic(mod)) { if (!Modifier.isStatic(mod)) {
abort(ostream, null, "java.launcher.cls.error2", "static", classname); abort(null, "java.launcher.cls.error2", "static",
mainMethod.getDeclaringClass().getName());
} }
if (method.getReturnType() != java.lang.Void.TYPE) { if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
abort(ostream, null, "java.launcher.cls.error3", classname); abort(null, "java.launcher.cls.error3",
mainMethod.getDeclaringClass().getName());
} }
return method;
} }
private static final String encprop = "sun.jnu.encoding"; private static final String encprop = "sun.jnu.encoding";
@ -519,7 +550,7 @@ public enum LauncherHelper {
* previously implemented as a native method in the launcher. * previously implemented as a native method in the launcher.
*/ */
static String makePlatformString(boolean printToStderr, byte[] inArray) { static String makePlatformString(boolean printToStderr, byte[] inArray) {
final PrintStream ostream = (printToStderr) ? System.err : System.out; initOutput(printToStderr);
if (encoding == null) { if (encoding == null) {
encoding = System.getProperty(encprop); encoding = System.getProperty(encprop);
isCharsetSupported = Charset.isSupported(encoding); isCharsetSupported = Charset.isSupported(encoding);
@ -530,7 +561,7 @@ public enum LauncherHelper {
: new String(inArray); : new String(inArray);
return out; return out;
} catch (UnsupportedEncodingException uee) { } catch (UnsupportedEncodingException uee) {
abort(ostream, uee, null); abort(uee, null);
} }
return null; // keep the compiler happy return null; // keep the compiler happy
} }
@ -611,5 +642,65 @@ public enum LauncherHelper {
return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}'; return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';
} }
} }
static final class FXHelper {
private static final String JAVAFX_APPLICATION_CLASS_NAME =
"javafx.application.Application";
private static final String JAVAFX_LAUNCHER_CLASS_NAME =
"com.sun.javafx.application.LauncherImpl";
/*
* FX application launcher and launch method, so we can launch
* applications with no main method.
*/
private static Class<?> fxLauncherClass = null;
private static Method fxLauncherMethod = null;
/*
* We can assume that the class does NOT have a main method or it would
* have been handled already. We do, however, need to check if the class
* extends Application and the launcher is available and abort with an
* error if it's not.
*/
private static Class<?> getFXMainClass(Class<?> mainClass) {
// Check if mainClass extends Application
if (!doesExtendFXApplication(mainClass)) {
return null;
}
// Check for the FX launcher classes
try {
fxLauncherClass = scloader.loadClass(JAVAFX_LAUNCHER_CLASS_NAME);
fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",
Class.class, String[].class);
} catch (ClassNotFoundException | NoSuchMethodException ex) {
abort(ex, "java.launcher.cls.error5", ex);
}
// That's all, return this class so we can launch later
return FXHelper.class;
}
/*
* Check if the given class is a JavaFX Application class. This is done
* in a way that does not cause the Application class to load or throw
* ClassNotFoundException if the JavaFX runtime is not available.
*/
private static boolean doesExtendFXApplication(Class<?> mainClass) {
for (Class<?> sc = mainClass.getSuperclass(); sc != null;
sc = sc.getSuperclass()) {
if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) {
return true;
}
}
return false;
}
// preloader ?
public static void main(String... args) throws Exception {
// launch appClass via fxLauncherMethod
fxLauncherMethod.invoke(null, new Object[] {appClass, args});
}
}
} }

@ -131,7 +131,10 @@ java.launcher.cls.error3=\
\ public static void main(String[] args) \ public static void main(String[] args)
java.launcher.cls.error4=\ java.launcher.cls.error4=\
Error: Main method not found in class {0}, please define the main method as:\n\ Error: Main method not found in class {0}, please define the main method as:\n\
\ public static void main(String[] args) \ public static void main(String[] args)\n\
or a JavaFX application class must extend {1}
java.launcher.cls.error5=\
Error: JavaFX runtime components are missing, and are required to run this application
java.launcher.jar.error1=\ java.launcher.jar.error1=\
Error: An unexpected error occurred while trying to open file {0} Error: An unexpected error occurred while trying to open file {0}
java.launcher.jar.error2=manifest not found in {0} java.launcher.jar.error2=manifest not found in {0}

@ -559,6 +559,16 @@ public class TestHelper {
return false; return false;
} }
boolean notContains(String str) {
for (String x : testOutput) {
if (x.contains(str)) {
appendError("string <" + str + "> found");
return false;
}
}
return true;
}
boolean matches(String stringToMatch) { boolean matches(String stringToMatch) {
for (String x : testOutput) { for (String x : testOutput) {
if (x.matches(stringToMatch)) { if (x.matches(stringToMatch)) {