8004547: Extend JavaFX launcher support to allow full JavaFX launch feature set
authorksrini
Mon, 07 Jan 2013 09:58:48 -0800
changeset 15003 364408faddeb
parent 15002 8ab8c2b2eee6
child 15004 cf5d2d5094cc
8004547: Extend JavaFX launcher support to allow full JavaFX launch feature set Reviewed-by: mchung, kcr, ksrini Contributed-by: david.dehaven@oracle.com
jdk/src/share/classes/sun/launcher/LauncherHelper.java
jdk/src/share/classes/sun/launcher/resources/launcher.properties
jdk/test/tools/launcher/FXLauncherTest.java
--- a/jdk/src/share/classes/sun/launcher/LauncherHelper.java	Sat Jan 05 17:06:54 2013 +0000
+++ b/jdk/src/share/classes/sun/launcher/LauncherHelper.java	Mon Jan 07 09:58:48 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -409,6 +409,15 @@
             if (mainValue == null) {
                 abort(null, "java.launcher.jar.error3", jarname);
             }
+            /*
+             * Hand off to FXHelper if it detects a JavaFX application
+             * This must be done after ensuring a Main-Class entry
+             * exists to enforce compliance with the jar specification
+             */
+            if (mainAttrs.containsKey(
+                    new Attributes.Name(FXHelper.JAVAFX_APPLICATION_MARKER))) {
+                return FXHelper.class.getName();
+            }
             return mainValue.trim();
         } catch (IOException ioe) {
             abort(ioe, "java.launcher.jar.error1", jarname);
@@ -483,26 +492,23 @@
         } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
             abort(cnfe, "java.launcher.cls.error1", cn);
         }
-        // set to mainClass, FXHelper may return something else
+        // set to mainClass
         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. In an FX app,
+         * the main class may or may not have a main method, so do this before
+         * validating the main class.
+         */
+        if (mainClass.equals(FXHelper.class) ||
+                FXHelper.doesExtendFXApplication(mainClass)) {
+            // Will abort() if there are problems with the FX runtime
+            FXHelper.setFXLaunchParameters(what, mode);
+            return FXHelper.class;
         }
 
-        // 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...
+        validateMainClass(mainClass);
+        return mainClass;
     }
 
     /*
@@ -515,16 +521,18 @@
         return appClass;
     }
 
-    // Check for main method or return null if not found
-    static Method getMainMethod(Class<?> clazz) {
+    // Check the existence and signature of main and abort if incorrect
+    static void validateMainClass(Class<?> mainClass) {
+        Method mainMethod;
         try {
-            return clazz.getMethod("main", String[].class);
-        } catch (NoSuchMethodException nsme) {}
-        return null;
-    }
+            mainMethod = mainClass.getMethod("main", String[].class);
+        } catch (NoSuchMethodException nsme) {
+            // invalid main or not FX application, abort with an error
+            abort(null, "java.launcher.cls.error4", mainClass.getName(),
+                  FXHelper.JAVAFX_APPLICATION_CLASS_NAME);
+            return; // Avoid compiler issues
+        }
 
-    // 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
@@ -644,41 +652,78 @@
     }
 
     static final class FXHelper {
+        // Marker entry in jar manifest that designates a JavaFX application jar
+        private static final String JAVAFX_APPLICATION_MARKER =
+                "JavaFX-Application-Class";
         private static final String JAVAFX_APPLICATION_CLASS_NAME =
                 "javafx.application.Application";
         private static final String JAVAFX_LAUNCHER_CLASS_NAME =
                 "com.sun.javafx.application.LauncherImpl";
 
         /*
+         * The launch method used to invoke the JavaFX launcher. These must
+         * match the strings used in the launchApplication method.
+         *
+         * Command line                 JavaFX-App-Class  Launch mode  FX Launch mode
+         * java -cp fxapp.jar FXClass   N/A               LM_CLASS     "LM_CLASS"
+         * java -cp somedir FXClass     N/A               LM_CLASS     "LM_CLASS"
+         * java -jar fxapp.jar          Present           LM_JAR       "LM_JAR"
+         * java -jar fxapp.jar          Not Present       LM_JAR       "LM_JAR"
+         */
+        private static final String JAVAFX_LAUNCH_MODE_CLASS = "LM_CLASS";
+        private static final String JAVAFX_LAUNCH_MODE_JAR = "LM_JAR";
+
+        /*
          * FX application launcher and launch method, so we can launch
          * applications with no main method.
          */
+        private static String fxLaunchName = null;
+        private static String fxLaunchMode = null;
+
         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.
+         * Set the launch params according to what was passed to LauncherHelper
+         * so we can use the same launch mode for FX. Abort if there is any
+         * issue with loading the FX runtime or with the launcher method.
          */
-        private static Class<?> getFXMainClass(Class<?> mainClass) {
-            // Check if mainClass extends Application
-            if (!doesExtendFXApplication(mainClass)) {
-                return null;
-            }
-
+        private static void setFXLaunchParameters(String what, int mode) {
             // Check for the FX launcher classes
             try {
                 fxLauncherClass = scloader.loadClass(JAVAFX_LAUNCHER_CLASS_NAME);
+                /*
+                 * signature must be:
+                 * public static void launchApplication(String launchName,
+                 *     String launchMode, String[] args);
+                 */
                 fxLauncherMethod = fxLauncherClass.getMethod("launchApplication",
-                        Class.class, String[].class);
+                        String.class, String.class, String[].class);
+
+                // verify launcher signature as we do when validating the main method
+                int mod = fxLauncherMethod.getModifiers();
+                if (!Modifier.isStatic(mod)) {
+                    abort(null, "java.launcher.javafx.error1");
+                }
+                if (fxLauncherMethod.getReturnType() != java.lang.Void.TYPE) {
+                    abort(null, "java.launcher.javafx.error1");
+                }
             } 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;
+            fxLaunchName = what;
+            switch (mode) {
+                case LM_CLASS:
+                    fxLaunchMode = JAVAFX_LAUNCH_MODE_CLASS;
+                    break;
+                case LM_JAR:
+                    fxLaunchMode = JAVAFX_LAUNCH_MODE_JAR;
+                    break;
+                default:
+                    // should not have gotten this far...
+                    throw new InternalError(mode + ": Unknown launch mode");
+            }
         }
 
         /*
@@ -696,11 +741,15 @@
             return false;
         }
 
-        // preloader ?
         public static void main(String... args) throws Exception {
+            if (fxLauncherMethod == null
+                    || fxLaunchMode == null
+                    || fxLaunchName == null) {
+                throw new RuntimeException("Invalid JavaFX launch parameters");
+            }
             // launch appClass via fxLauncherMethod
-            fxLauncherMethod.invoke(null, new Object[] {appClass, args});
+            fxLauncherMethod.invoke(null,
+                    new Object[] {fxLaunchName, fxLaunchMode, args});
         }
     }
 }
-
--- a/jdk/src/share/classes/sun/launcher/resources/launcher.properties	Sat Jan 05 17:06:54 2013 +0000
+++ b/jdk/src/share/classes/sun/launcher/resources/launcher.properties	Mon Jan 07 09:58:48 2013 -0800
@@ -140,3 +140,6 @@
 java.launcher.jar.error2=manifest not found in {0}
 java.launcher.jar.error3=no main manifest attribute, in {0}
 java.launcher.init.error=initialization error
+java.launcher.javafx.error1=\
+    Error: The JavaFX launchApplication method has the wrong signature, it\n\
+    must be declared static and return a value of type void
--- a/jdk/test/tools/launcher/FXLauncherTest.java	Sat Jan 05 17:06:54 2013 +0000
+++ b/jdk/test/tools/launcher/FXLauncherTest.java	Mon Jan 07 09:58:48 2013 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8001533
+ * @bug 8001533 8004547
  * @summary Test launching FX application with java -jar
  * Test uses main method and blank main method, a jfx app class and an incorrest
  * jfx app class, a main-class for the manifest, a bogus one and none.
@@ -47,6 +47,8 @@
 
     /* standard main class can be used as java main for fx app class */
     static final String StdMainClass = "helloworld.HelloWorld";
+    static final String ExtMainClass = "helloworld.ExtHello";
+    static final String NonFXMainClass = "helloworld.HelloJava";
     static int testcount = 0;
 
     /* a main method and a blank. */
@@ -107,9 +109,7 @@
     }
 
     /*
-     * Create class to extend fx java file for test application
-     * TODO: make test to create java file and this extension of the java file
-     *      and jar them together an run app via this java class.
+     * Create class that extends HelloWorld instead of Application
      */
     static void createExtJavaFile(String mainmethod) {
         try {
@@ -125,16 +125,48 @@
             compile("-cp", ".", "-d", ".", mainClass + JAVA_FILE_EXT);
         } catch (java.io.IOException ioe) {
             ioe.printStackTrace();
-            throw new RuntimeException("Failed creating HelloWorld.");
+            throw new RuntimeException("Failed creating ExtHello.");
+        }
+    }
+
+    /*
+     * Create non-JavaFX class for testing if jfxrt.jar is being loaded
+     * when it shouldn't be
+     */
+    static void createNonFXJavaFile() {
+        try {
+            String mainClass = "HelloJava";
+            List<String> contents = new ArrayList<>();
+            contents.add("package helloworld;");
+            contents.add("public class HelloJava {");
+            contents.add("    public static void main(String[] args) {");
+            contents.add("        for(String aa : args)");
+            contents.add("            System.out.println(\"arg: \" + aa);" );
+            contents.add("    }");
+            contents.add("}");
+            // Create and compile java source.
+            MainJavaFile = new File(mainClass + JAVA_FILE_EXT);
+            createFile(MainJavaFile, contents);
+            compile("-cp", ".", "-d", ".", mainClass + JAVA_FILE_EXT);
+        } catch (java.io.IOException ioe) {
+            ioe.printStackTrace();
+            throw new RuntimeException("Failed creating HelloJava.");
         }
     }
 
     // Create manifest for test fx application
-    static List<String> createManifestContents(String mainclassentry) {
+    static List<String> createManifestContents(String mainClassEntry, String fxMainEntry) {
         List<String> mcontents = new ArrayList<>();
         mcontents.add("Manifest-Version: 1.0");
         mcontents.add("Created-By: FXLauncherTest");
-        mcontents.add("Main-Class: " + mainclassentry);
+        if (mainClassEntry != null) {
+            mcontents.add("Main-Class: " + mainClassEntry);
+            System.out.println("Main-Class: " + mainClassEntry);
+        }
+        if (fxMainEntry != null) {
+            mcontents.add("JavaFX-Application-Class: " + fxMainEntry);
+            System.out.println("JavaFX-Application-Class: " + fxMainEntry);
+        }
         return mcontents;
     }
 
@@ -175,31 +207,41 @@
 
     /*
      * Set Main-Class and iterate main_methods.
-     * Try launching with both -jar and -cp methods.
+     * Try launching with both -jar and -cp methods, with and without FX main
+     * class manifest entry.
      * All cases should run.
+     *
+     * See sun.launcher.LauncherHelper$FXHelper for more details on how JavaFX
+     * applications are launched.
      */
     @Test
     static void testBasicFXApp() throws Exception {
-        testBasicFXApp(true);
-        testBasicFXApp(false);
+        testBasicFXApp(true, false);    // -cp, no JAC
+        testBasicFXApp(false, true);    // -jar, with JAC
+        testBasicFXApp(false, false);   // -jar, no JAC
     }
 
-    static void testBasicFXApp(boolean useCP) throws Exception {
+    static void testBasicFXApp(boolean useCP, boolean setFXMainClass) throws Exception {
         String testname = "testBasicFXApp";
+        if (useCP) {
+            testname = testname.concat("_useCP");
+        }
+        String fxMC = StdMainClass;
+        if (!setFXMainClass) {
+            testname = testname.concat("_noJAC");
+            fxMC = null;
+        }
         for (String mm : MAIN_METHODS) {
             testcount++;
             line();
-            System.out.println("test# " + testcount +
-                "-  Main method: " + mm +
-                 ";  MF main class: " + StdMainClass);
+            System.out.println("test# " + testcount + "-  Main method: " + mm);
             createJavaFile(mm);
-            createFile(ManifestFile, createManifestContents(StdMainClass));
+            createFile(ManifestFile, createManifestContents(StdMainClass, fxMC));
             createJar(FXtestJar, ManifestFile);
             String sTestJar = FXtestJar.getAbsolutePath();
             TestResult tr;
             if (useCP) {
                 tr = doExec(javaCmd, "-cp", sTestJar, StdMainClass, APP_PARMS[0], APP_PARMS[1]);
-                testname = testname.concat("_useCP");
             } else {
                 tr = doExec(javaCmd, "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
             }
@@ -224,26 +266,33 @@
      */
     @Test
     static void testExtendFXApp() throws Exception {
-        testExtendFXApp(true);
-        testExtendFXApp(false);
+        testExtendFXApp(true, false);   // -cp, no JAC
+        testExtendFXApp(false, true);   // -jar, with JAC
+        testExtendFXApp(false, false);  // -jar, no JAC
     }
 
-    static void testExtendFXApp(boolean useCP) throws Exception {
+    static void testExtendFXApp(boolean useCP, boolean setFXMainClass) throws Exception {
         String testname = "testExtendFXApp";
+        if (useCP) {
+            testname = testname.concat("_useCP");
+        }
+        String fxMC = ExtMainClass;
+        if (!setFXMainClass) {
+            testname = testname.concat("_noJAC");
+            fxMC = null;
+        }
         for (String mm : MAIN_METHODS) {
             testcount++;
             line();
-            System.out.println("test# " + testcount +
-                "-  Main method: " + mm + ";  MF main class: " + StdMainClass);
+            System.out.println("test# " + testcount + "-  Main method: " + mm);
             createJavaFile(mm);
             createExtJavaFile(mm);
-            createFile(ManifestFile, createManifestContents(StdMainClass));
+            createFile(ManifestFile, createManifestContents(ExtMainClass, fxMC));
             createJar(FXtestJar, ManifestFile);
             String sTestJar = FXtestJar.getAbsolutePath();
             TestResult tr;
             if (useCP) {
-                tr = doExec(javaCmd, "-cp", sTestJar, StdMainClass, APP_PARMS[0], APP_PARMS[1]);
-                testname = testname.concat("_useCP");
+                tr = doExec(javaCmd, "-cp", sTestJar, ExtMainClass, APP_PARMS[0], APP_PARMS[1]);
             } else {
                 tr = doExec(javaCmd, "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
             }
@@ -256,27 +305,82 @@
                     }
                 }
             }
-            checkStatus(tr, testname, testcount, StdMainClass);
+            checkStatus(tr, testname, testcount, ExtMainClass);
         }
     }
 
     /*
+     * Ensure we can NOT launch a FX app jar with no Main-Class manifest entry
+     */
+    @Test
+    static void testMissingMC() throws Exception {
+        String testname = "testMissingMC";
+        testcount++;
+        line();
+        System.out.println("test# " + testcount + ": abort on missing Main-Class");
+        createJavaFile(" "); // no main() needed
+        createFile(ManifestFile, createManifestContents(null, StdMainClass)); // No MC, but supply JAC
+        createJar(FXtestJar, ManifestFile);
+        String sTestJar = FXtestJar.getAbsolutePath();
+        TestResult tr = doExec(javaCmd, "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
+        tr.checkNegative(); // should abort if no Main-Class
+        if (tr.testStatus) {
+            if (!tr.contains("no main manifest attribute")) {
+                System.err.println("ERROR: launcher did not abort properly");
+            }
+        } else {
+            System.err.println("ERROR: jar executed with no Main-Class!");
+        }
+        checkStatus(tr, testname, testcount, StdMainClass);
+    }
+
+    /*
      * test to ensure that we don't load any extraneous fx jars when
      * launching a standard java application
+     * Test both -cp and -jar methods since they use different code paths.
+     * Neither case should cause jfxrt.jar to be loaded.
      */
     @Test
-    static void testExtraneousJars()throws Exception {
+    static void testExtraneousJars() throws Exception {
+        testExtraneousJars(true);
+        testExtraneousJars(false);
+    }
+
+    static void testExtraneousJars(boolean useCP) throws Exception {
         String testname = "testExtraneousJars";
+        if (useCP) {
+            testname = testname.concat("_useCP");
+        }
         testcount++;
         line();
-        System.out.println("test# " + testcount);
-        TestResult tr = doExec(javacCmd, "-J-verbose:class", "-version");
-        if (!tr.notContains("jfxrt.jar")) {
-            System.out.println("testing for extraneous jfxrt jar");
-            System.out.println(tr);
-            throw new Exception("jfxrt.jar is being loaded by javac!!!");
+        System.out.println("test# " + testcount
+                + ": test for erroneous jfxrt.jar loading");
+        createNonFXJavaFile();
+        createFile(ManifestFile, createManifestContents(NonFXMainClass, null));
+        createJar(FXtestJar, ManifestFile);
+        String sTestJar = FXtestJar.getAbsolutePath();
+        TestResult tr;
+
+        if (useCP) {
+            tr = doExec(javaCmd, "-verbose:class", "-cp", sTestJar, NonFXMainClass, APP_PARMS[0], APP_PARMS[1]);
+        } else {
+            tr = doExec(javaCmd, "-verbose:class", "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
         }
-        checkStatus(tr, testname, testcount, StdMainClass);
+        tr.checkPositive();
+        if (tr.testStatus) {
+            if (!tr.notContains("jfxrt.jar")) {
+                System.out.println("testing for extraneous jfxrt jar");
+                System.out.println(tr);
+                throw new Exception("jfxrt.jar is being loaded, it should not be!");
+            }
+            for (String p : APP_PARMS) {
+                if (!tr.contains(p)) {
+                    System.err.println("ERROR: Did not find "
+                            + p + " in output!");
+                }
+            }
+        }
+        checkStatus(tr, testname, testcount, NonFXMainClass);
     }
 
     public static void main(String... args) throws Exception {