1 /* 2 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package apple.launcher; 27 28 import java.io.*; 29 import java.lang.reflect.*; 30 import java.text.MessageFormat; 31 import java.util.*; 32 import java.util.jar.*; 33 34 import javax.swing.*; 35 36 class JavaAppLauncher implements Runnable { 37 static { 38 java.security.AccessController.doPrivileged( 39 new java.security.PrivilegedAction<Void>() { 40 public Void run() { 41 System.loadLibrary("osx"); 42 return null; 43 } 44 }); 45 } 46 47 private static native <T> T nativeConvertAndRelease(final long ptr); 48 private static native void nativeInvokeNonPublic(Class<? extends Method> cls, Method m, String[] args); 49 50 // entry point from native 51 static void launch(final long javaDictionaryPtr, final boolean verbose) { 52 final Map<String, ?> javaDictionary = nativeConvertAndRelease(javaDictionaryPtr); 53 (new JavaAppLauncher(javaDictionary, verbose)).run(); 54 } 55 56 // these are the values for the enumeration JavaFailureMode 57 static final String kJavaFailureMainClassNotSpecified = "MainClassNotSpecified"; 58 static final String kJavaFailureMainClassNotFound = "CannotLoadMainClass"; 59 static final String kJavaFailureMainClassHasNoMain = "NoMainMethod"; 60 static final String kJavaFailureMainClassMainNotStatic = "MainNotStatic"; 61 static final String kJavaFailureMainThrewException = "MainThrewException"; 62 static final String kJavaFailureMainInitializerException = "MainInitializerException"; 63 64 final boolean verbose; // Normally set by environment variable JAVA_LAUNCHER_VERBOSE. 65 final Map<String, ?> javaDictionary; 66 67 JavaAppLauncher(final Map<String, ?> javaDictionary, final boolean verbose) { 68 this.verbose = verbose; 69 this.javaDictionary = javaDictionary; 70 } 71 72 @Override 73 public void run() { 74 final Method m = loadMainMethod(getMainMethod()); 75 final String methodName = m.getDeclaringClass().getName() + ".main(String[])"; 76 try { 77 log("Calling " + methodName + " method"); 78 m.invoke(null, new Object[] { getArguments() }); 79 log(methodName + " has returned"); 80 } catch (final IllegalAccessException x) { 81 try { 82 nativeInvokeNonPublic(m.getClass(), m, getArguments()); 83 } catch (final Throwable excpt) { 84 logError(methodName + " threw an exception:"); 85 if ((excpt instanceof UnsatisfiedLinkError) && excpt.getMessage().equals("nativeInvokeNonPublic")) { 86 showFailureAlertAndKill(kJavaFailureMainThrewException, "nativeInvokeNonPublic not registered"); 87 } else { 88 excpt.printStackTrace(); 89 showFailureAlertAndKill(kJavaFailureMainThrewException, excpt.toString()); 90 } 91 } 92 } catch (final InvocationTargetException invokeExcpt) { 93 logError(methodName + " threw an exception:"); 94 invokeExcpt.getTargetException().printStackTrace(); 95 showFailureAlertAndKill(kJavaFailureMainThrewException, invokeExcpt.getTargetException().toString()); 96 } 97 } 98 99 Method loadMainMethod(final String mainClassName) { 100 try { 101 final Class<?> mainClass = Class.forName(mainClassName, true, sun.misc.Launcher.getLauncher().getClassLoader()); 102 final Method mainMethod = mainClass.getDeclaredMethod("main", new Class[] { String[].class }); 103 if ((mainMethod.getModifiers() & Modifier.STATIC) == 0) { 104 logError("The main(String[]) method of class " + mainClassName + " is not static!"); 105 showFailureAlertAndKill(kJavaFailureMainClassMainNotStatic, mainClassName); 106 } 107 return mainMethod; 108 } catch (final ExceptionInInitializerError x) { 109 logError("The main class \"" + mainClassName + "\" had a static initializer throw an exception."); 110 x.getException().printStackTrace(); 111 showFailureAlertAndKill(kJavaFailureMainInitializerException, x.getException().toString()); 112 } catch (final ClassNotFoundException x) { 113 logError("The main class \"" + mainClassName + "\" could not be found."); 114 showFailureAlertAndKill(kJavaFailureMainClassNotFound, mainClassName); 115 } catch (final NoSuchMethodException x) { 116 logError("The main class \"" + mainClassName + "\" has no static main(String[]) method."); 117 showFailureAlertAndKill(kJavaFailureMainClassHasNoMain, mainClassName); 118 } catch (final NullPointerException x) { 119 logError("No main class specified"); 120 showFailureAlertAndKill(kJavaFailureMainClassNotSpecified, null); 121 } 122 123 return null; 124 } 125 126 // get main class name from 'Jar' key, or 'MainClass' key 127 String getMainMethod() { 128 final Object javaJar = javaDictionary.get("Jar"); 129 if (javaJar != null) { 130 if (!(javaJar instanceof String)) { 131 logError("'Jar' key in 'Java' sub-dictionary of Info.plist requires a string value"); 132 return null; 133 } 134 135 final String jarPath = (String)javaJar; 136 if (jarPath.length() == 0) { 137 log("'Jar' key of sub-dictionary 'Java' of Info.plist key is empty"); 138 } else { 139 // extract main class from manifest of this jar 140 final String main = getMainFromManifest(jarPath); 141 if (main == null) { 142 logError("jar file '" + jarPath + "' does not have Main-Class: attribute in its manifest"); 143 return null; 144 } 145 146 log("Main class " + main + " found in jar manifest"); 147 return main; 148 } 149 } 150 151 final Object javaMain = javaDictionary.get("MainClass"); 152 if (!(javaMain instanceof String)) { 153 logError("'MainClass' key in 'Java' sub-dictionary of Info.plist requires a string value"); 154 return null; 155 } 156 157 final String main = (String)javaMain; 158 if (main.length() == 0) { 159 log("'MainClass' key of sub-dictionary 'Java' of Info.plist key is empty"); 160 return null; 161 } 162 163 log("Main class " + (String)javaMain + " found via 'MainClass' key of sub-dictionary 'Java' of Info.plist key"); 164 return (String)javaMain; 165 } 166 167 // get arguments for main(String[]) out of Info.plist and command line 168 String[] getArguments() { 169 // check for 'Arguments' key, which contains the main() args if not defined in Info.plist 170 final Object javaArguments = javaDictionary.get("Arguments"); 171 if (javaArguments == null) { 172 // no arguments 173 log("No arguments for main(String[]) specified"); 174 return new String[0]; 175 } 176 177 if (javaArguments instanceof List) { 178 final List<?> args = (List<?>)javaArguments; 179 final int count = args.size(); 180 log("Arguments to main(String[" + count + "]):"); 181 182 final String[] result = new String[count]; 183 for (int i = 0; i < count; ++i) { 184 final Object element = args.get(i); 185 if (element instanceof String) { 186 result[i] = (String)element; 187 } else { 188 logError("Found non-string in array"); 189 } 190 log(" arg[" + i + "]=" + result[i]); 191 } 192 return result; 193 } 194 195 logError("'Arguments' key in 'Java' sub-dictionary of Info.plist requires a string value or an array of strings"); 196 return new String[0]; 197 } 198 199 // returns name of main class, or null 200 String getMainFromManifest(final String jarpath) { 201 JarFile jar = null; 202 try { 203 jar = new JarFile(jarpath); 204 final Manifest man = jar.getManifest(); 205 final Attributes attr = man.getMainAttributes(); 206 return attr.getValue("Main-Class"); 207 } catch (final IOException x) { 208 // shrug 209 } finally { 210 if (jar != null) { 211 try { 212 jar.close(); 213 } catch (final IOException x) { } 214 } 215 } 216 return null; 217 } 218 219 void log(final String s) { 220 if (!verbose) return; 221 System.out.println("[LaunchRunner] " + s); 222 } 223 224 static void logError(final String s) { 225 System.err.println("[LaunchRunner Error] " + s); 226 } 227 228 // This kills the app and does not return! 229 static void showFailureAlertAndKill(final String msg, String arg) { 230 if (arg == null) arg = "<<null>>"; 231 JOptionPane.showMessageDialog(null, getMessage(msg, arg), "", JOptionPane.ERROR_MESSAGE); 232 System.exit(-1); 233 } 234 235 static String getMessage(final String msgKey, final Object ... args) { 236 final String msg = ResourceBundle.getBundle("appLauncherErrors").getString(msgKey); 237 return MessageFormat.format(msg, args); 238 } 239 }