# HG changeset patch # User ksrini # Date 1353383378 28800 # Node ID f4b1adde53b3de696f151729f4ac9aee1e57e657 # Parent e50acb436a8ddacc961fb61a6ee3fb55145f8dee 8001533: java launcher must launch javafx applications Reviewed-by: ksrini, mchung, kcr, alanb Contributed-by: david.dehaven@oracle.com diff -r e50acb436a8d -r f4b1adde53b3 jdk/src/share/bin/java.c --- a/jdk/src/share/bin/java.c Tue Nov 20 09:26:38 2012 +0000 +++ b/jdk/src/share/bin/java.c Mon Nov 19 19:49:38 2012 -0800 @@ -105,6 +105,7 @@ InvocationFunctions *ifn); static jstring NewPlatformString(JNIEnv *env, char *s); 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 jboolean AddApplicationOptions(int cpathc, const char **cpathv); @@ -346,6 +347,7 @@ JavaVM *vm = 0; JNIEnv *env = 0; jclass mainClass = NULL; + jclass appClass = NULL; // actual application class being launched jmethodID mainID; jobjectArray mainArgs; int ret = 0; @@ -419,10 +421,28 @@ * all environments, * 2) Remove the vestages of maintaining main_class through * 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); 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 * that the main method's signature is correct, therefore further checking @@ -1215,6 +1235,20 @@ 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: * javac -cp foo:foo/"*" -J-ms32m ... diff -r e50acb436a8d -r f4b1adde53b3 jdk/src/share/classes/sun/launcher/LauncherHelper.java --- a/jdk/src/share/classes/sun/launcher/LauncherHelper.java Tue Nov 20 09:26:38 2012 +0000 +++ b/jdk/src/share/classes/sun/launcher/LauncherHelper.java Mon Nov 19 19:49:38 2012 -0800 @@ -69,7 +69,6 @@ public enum LauncherHelper { INSTANCE; private static final String MAIN_CLASS = "Main-Class"; - private static StringBuilder outBuf = new StringBuilder(); private static final String INDENT = " "; @@ -87,6 +86,9 @@ private static final ResourceBundle RB = 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, @@ -114,27 +116,27 @@ long initialHeapSize, long maxHeapSize, long stackSize, boolean isServer) { - PrintStream ostream = (printToStderr) ? System.err : System.out; + initOutput(printToStderr); String opts[] = optionFlag.split(":"); String optStr = (opts.length > 1 && opts[1] != null) ? opts[1].trim() : "all"; switch (optStr) { case "vm": - printVmSettings(ostream, initialHeapSize, maxHeapSize, - stackSize, isServer); + printVmSettings(initialHeapSize, maxHeapSize, + stackSize, isServer); break; case "properties": - printProperties(ostream); + printProperties(); break; case "locale": - printLocale(ostream); + printLocale(); break; default: - printVmSettings(ostream, initialHeapSize, maxHeapSize, - stackSize, isServer); - printProperties(ostream); - printLocale(ostream); + printVmSettings(initialHeapSize, maxHeapSize, stackSize, + isServer); + printProperties(); + printLocale(); break; } } @@ -142,7 +144,7 @@ /* * prints the main vm settings subopt/section */ - private static void printVmSettings(PrintStream ostream, + private static void printVmSettings( long initialHeapSize, long maxHeapSize, long stackSize, boolean isServer) { @@ -172,14 +174,14 @@ /* * prints the properties subopt/section */ - private static void printProperties(PrintStream ostream) { + private static void printProperties() { Properties p = System.getProperties(); ostream.println(PROP_SETTINGS); List sortedPropertyKeys = new ArrayList<>(); sortedPropertyKeys.addAll(p.stringPropertyNames()); Collections.sort(sortedPropertyKeys); for (String x : sortedPropertyKeys) { - printPropertyValue(ostream, x, p.getProperty(x)); + printPropertyValue(x, p.getProperty(x)); } ostream.println(); } @@ -188,8 +190,7 @@ return key.endsWith(".dirs") || key.endsWith(".path"); } - private static void printPropertyValue(PrintStream ostream, - String key, String value) { + private static void printPropertyValue(String key, String value) { ostream.print(INDENT + key + " = "); if (key.equals("line.separator")) { for (byte b : value.getBytes()) { @@ -229,7 +230,7 @@ /* * prints the locale subopt/section */ - private static void printLocale(PrintStream ostream) { + private static void printLocale() { Locale locale = Locale.getDefault(); ostream.println(LOCALE_SETTINGS); ostream.println(INDENT + "default locale = " + @@ -238,11 +239,11 @@ Locale.getDefault(Category.DISPLAY).getDisplayName()); ostream.println(INDENT + "default format locale = " + Locale.getDefault(Category.FORMAT).getDisplayName()); - printLocales(ostream); + printLocales(); ostream.println(); } - private static void printLocales(PrintStream ostream) { + private static void printLocales() { Locale[] tlocales = Locale.getAvailableLocales(); final int len = tlocales == null ? 0 : tlocales.length; if (len < 1 ) { @@ -370,7 +371,7 @@ * initHelpSystem must be called before using this method. */ static void printHelpMessage(boolean printToStderr) { - PrintStream ostream = (printToStderr) ? System.err : System.out; + initOutput(printToStderr); outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer", File.pathSeparator)); ostream.println(outBuf.toString()); @@ -380,7 +381,7 @@ * Prints the Xusage text to the desired output stream. */ static void printXUsageMessage(boolean printToStderr) { - PrintStream ostream = (printToStderr) ? System.err : System.out; + initOutput(printToStderr); ostream.println(getLocalizedMessage("java.launcher.X.usage", File.pathSeparator)); if (System.getProperty("os.name").contains("OS X")) { @@ -389,36 +390,32 @@ } } - static String getMainClassFromJar(PrintStream ostream, String jarname) { - try { - JarFile jarFile = null; - try { - jarFile = new JarFile(jarname); - Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - abort(ostream, null, "java.launcher.jar.error2", jarname); - } - Attributes mainAttrs = manifest.getMainAttributes(); - 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(); - } + static void initOutput(boolean printToStderr) { + ostream = (printToStderr) ? System.err : System.out; + } + + static String getMainClassFromJar(String jarname) { + String mainValue = null; + try (JarFile jarFile = new JarFile(jarname)) { + Manifest manifest = jarFile.getManifest(); + if (manifest == null) { + abort(null, "java.launcher.jar.error2", jarname); } + 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) { - abort(ostream, ioe, "java.launcher.jar.error1", jarname); + abort(ioe, "java.launcher.jar.error1", jarname); } return null; } - // From src/share/bin/java.c: // enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR }; @@ -426,7 +423,7 @@ private static final int LM_CLASS = 1; 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) { ostream.println(getLocalizedMessage(msgKey, args)); } @@ -450,19 +447,22 @@ * b. is there a main * c. is the main public * d. is the main static - * c. does the main take a String array for args - * 4. and off we go...... + * e. does the main take a String array for args + * 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 isJar - * @param name - * @return + * @param printToStderr if set, all output will be routed to stderr + * @param mode LaunchMode as determined by the arguments passed on the + * command line + * @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, int mode, String what) { - final PrintStream ostream = (printToStderr) ? System.err : System.out; - final ClassLoader ld = ClassLoader.getSystemClassLoader(); + initOutput(printToStderr); // get the class name String cn = null; switch (mode) { @@ -470,44 +470,75 @@ cn = what; break; case LM_JAR: - cn = getMainClassFromJar(ostream, what); + cn = getMainClassFromJar(what); break; default: // should never happen throw new InternalError("" + mode + ": Unknown launch mode"); } cn = cn.replace('/', '.'); - Class c = null; + Class mainClass = null; try { - c = ld.loadClass(cn); - } catch (ClassNotFoundException cnfe) { - abort(ostream, cnfe, "java.launcher.cls.error1", cn); + mainClass = scloader.loadClass(cn); + } catch (NoClassDefFoundError | ClassNotFoundException cnfe) { + abort(cnfe, "java.launcher.cls.error1", cn); } - getMainMethod(ostream, c); - return c; + // set to mainClass, FXHelper may return something else + 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(); - Method method = null; + /* + * Accessor method called by the launcher after getting the main class via + * 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 { - method = clazz.getMethod("main", String[].class); - } catch (NoSuchMethodException nsme) { - abort(ostream, null, "java.launcher.cls.error4", classname); - } + return clazz.getMethod("main", String[].class); + } catch (NoSuchMethodException nsme) {} + 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 * on its name and parameter type, however, we still have to * ensure that the method is static and returns a void. */ - int mod = method.getModifiers(); + int mod = mainMethod.getModifiers(); 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) { - abort(ostream, null, "java.launcher.cls.error3", classname); + if (mainMethod.getReturnType() != java.lang.Void.TYPE) { + abort(null, "java.launcher.cls.error3", + mainMethod.getDeclaringClass().getName()); } - return method; } private static final String encprop = "sun.jnu.encoding"; @@ -519,7 +550,7 @@ * previously implemented as a native method in the launcher. */ static String makePlatformString(boolean printToStderr, byte[] inArray) { - final PrintStream ostream = (printToStderr) ? System.err : System.out; + initOutput(printToStderr); if (encoding == null) { encoding = System.getProperty(encprop); isCharsetSupported = Charset.isSupported(encoding); @@ -530,7 +561,7 @@ : new String(inArray); return out; } catch (UnsupportedEncodingException uee) { - abort(ostream, uee, null); + abort(uee, null); } return null; // keep the compiler happy } @@ -611,5 +642,65 @@ 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}); + } + } } diff -r e50acb436a8d -r f4b1adde53b3 jdk/src/share/classes/sun/launcher/resources/launcher.properties --- a/jdk/src/share/classes/sun/launcher/resources/launcher.properties Tue Nov 20 09:26:38 2012 +0000 +++ b/jdk/src/share/classes/sun/launcher/resources/launcher.properties Mon Nov 19 19:49:38 2012 -0800 @@ -131,7 +131,10 @@ \ public static void main(String[] args) java.launcher.cls.error4=\ 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=\ Error: An unexpected error occurred while trying to open file {0} java.launcher.jar.error2=manifest not found in {0} diff -r e50acb436a8d -r f4b1adde53b3 jdk/test/tools/launcher/TestHelper.java --- a/jdk/test/tools/launcher/TestHelper.java Tue Nov 20 09:26:38 2012 +0000 +++ b/jdk/test/tools/launcher/TestHelper.java Mon Nov 19 19:49:38 2012 -0800 @@ -559,6 +559,16 @@ 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) { for (String x : testOutput) { if (x.matches(stringToMatch)) {