diff --git a/src/share/classes/sun/launcher/LauncherHelper.java b/src/share/classes/sun/launcher/LauncherHelper.java index 3b899bcca4d2cb3b02f77078835348407b0e3d7d..2fc42a6962b619c8b4412031f255e89b1f024204 100644 --- a/src/share/classes/sun/launcher/LauncherHelper.java +++ b/src/share/classes/sun/launcher/LauncherHelper.java @@ -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 @@ public enum LauncherHelper { 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 @@ public enum LauncherHelper { } 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 - Class fxClass = FXHelper.getFXMainClass(mainClass); - if (fxClass != null) { - return fxClass; + /* + * 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; } - // 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 @@ public enum LauncherHelper { 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 @@ public enum LauncherHelper { } 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 @@ public enum LauncherHelper { 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}); } } } - diff --git a/src/share/classes/sun/launcher/resources/launcher.properties b/src/share/classes/sun/launcher/resources/launcher.properties index bfa05181784b22faec62b605079c4ebf34684704..8fbefacf8da73e5350fdd97223654cdf83314a63 100644 --- a/src/share/classes/sun/launcher/resources/launcher.properties +++ b/src/share/classes/sun/launcher/resources/launcher.properties @@ -140,3 +140,6 @@ java.launcher.jar.error1=\ 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 diff --git a/test/tools/launcher/FXLauncherTest.java b/test/tools/launcher/FXLauncherTest.java index 9b58c0846c0a8ce57b3666dfc27ec27c46f9ead5..1ed251bad193d7c75daaef1f2507d72decd77823 100644 --- a/test/tools/launcher/FXLauncherTest.java +++ b/test/tools/launcher/FXLauncherTest.java @@ -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 @@ public class FXLauncherTest extends TestHelper { /* 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 @@ public class FXLauncherTest extends TestHelper { } /* - * 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 @@ public class FXLauncherTest extends TestHelper { 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 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 createManifestContents(String mainclassentry) { + static List createManifestContents(String mainClassEntry, String fxMainEntry) { List 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 @@ public class FXLauncherTest extends TestHelper { /* * 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 @@ public class FXLauncherTest extends TestHelper { */ @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 @@ public class FXLauncherTest extends TestHelper { } } } - 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 {