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 } |
|