Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Launcher |
|
| 5.809523809523809;5.81 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | ||
18 | package org.apache.commons.launcher; | |
19 | ||
20 | import java.io.File; | |
21 | import java.io.IOException; | |
22 | import java.io.PrintStream; | |
23 | import java.net.URL; | |
24 | import java.net.URLClassLoader; | |
25 | import java.net.URLDecoder; | |
26 | import java.util.ResourceBundle; | |
27 | ||
28 | import org.apache.commons.launcher.types.ArgumentSet; | |
29 | import org.apache.commons.launcher.types.JVMArgumentSet; | |
30 | import org.apache.commons.launcher.types.SysPropertySet; | |
31 | import org.apache.tools.ant.Main; | |
32 | import org.apache.tools.ant.Project; | |
33 | import org.apache.tools.ant.ProjectHelper; | |
34 | import org.apache.tools.ant.taskdefs.Ant; | |
35 | import org.apache.tools.ant.taskdefs.Available; | |
36 | import org.apache.tools.ant.taskdefs.CallTarget; | |
37 | import org.apache.tools.ant.taskdefs.ConditionTask; | |
38 | import org.apache.tools.ant.taskdefs.ExecTask; | |
39 | import org.apache.tools.ant.taskdefs.Exit; | |
40 | import org.apache.tools.ant.taskdefs.Property; | |
41 | import org.apache.tools.ant.taskdefs.Mkdir; | |
42 | import org.apache.tools.ant.taskdefs.Copy; | |
43 | import org.apache.tools.ant.taskdefs.Delete; | |
44 | import org.apache.tools.ant.taskdefs.Taskdef; | |
45 | import org.apache.tools.ant.taskdefs.WaitFor; | |
46 | import org.apache.tools.ant.types.Description; | |
47 | import org.apache.tools.ant.types.FileList; | |
48 | import org.apache.tools.ant.types.FileSet; | |
49 | import org.apache.tools.ant.types.Path; | |
50 | import org.apache.tools.ant.types.PatternSet; | |
51 | ||
52 | /** | |
53 | * A class that is used to launch a Java process. The primary purpose of this | |
54 | * class is to eliminate the need for a batch or shell script to launch a Java | |
55 | * process. Some situations where elimination of a batch or shell script may be | |
56 | * desirable are: | |
57 | * <ul> | |
58 | * <li>You want to avoid having to determining where certain application paths | |
59 | * are e.g. your application's home directory, etc. Determining this | |
60 | * dynamically in a Windows batch scripts is very tricky on some versions of | |
61 | * Windows or when softlinks are used on Unix platforms. | |
62 | * <li>You need to enforce certain properties e.g. java.endorsed.dirs when | |
63 | * running with JDK 1.4. | |
64 | * <li>You want to allow users to pass in custom JVM arguments or system | |
65 | * properties without having to parse and reorder arguments in your script. | |
66 | * This can be tricky and/or messy in batch and shell scripts. | |
67 | * <li>You want to bootstrap Java properties from a configuration file instead | |
68 | * hard-coding them in your batch and shell scripts. | |
69 | * <li>You want to provide localized error messages which is very tricky to do | |
70 | * in batch and shell scripts. | |
71 | * </ul> | |
72 | * | |
73 | * @author Patrick Luby | |
74 | */ | |
75 | 0 | public class Launcher implements Runnable { |
76 | ||
77 | //----------------------------------------------------------- Static Fields | |
78 | ||
79 | ||
80 | /** | |
81 | * Cached bootstrap file. | |
82 | */ | |
83 | 0 | private static File bootstrapFile = null; |
84 | ||
85 | /** | |
86 | * Cached java command | |
87 | */ | |
88 | 0 | private static String javaCmd = null; |
89 | ||
90 | /** | |
91 | * Cached JDB command | |
92 | */ | |
93 | 0 | private static String jdbCmd = null; |
94 | ||
95 | /** | |
96 | * Default XML file name | |
97 | */ | |
98 | private final static String DEFAULT_XML_FILE_NAME = "launcher.xml"; | |
99 | ||
100 | /** | |
101 | * Shared lock. | |
102 | */ | |
103 | 0 | private static Object lock = new Object(); |
104 | ||
105 | /** | |
106 | * Cached log | |
107 | */ | |
108 | 0 | private static PrintStream log = System.err; |
109 | ||
110 | /** | |
111 | * Cached resourceBundle | |
112 | */ | |
113 | 0 | private static ResourceBundle resourceBundle = null; |
114 | ||
115 | /** | |
116 | * The started status flag. | |
117 | */ | |
118 | 0 | private static boolean started = false; |
119 | ||
120 | /** | |
121 | * The stopped status flag. | |
122 | */ | |
123 | 0 | private static boolean stopped = false; |
124 | ||
125 | /** | |
126 | * List of supported Ant tasks. | |
127 | */ | |
128 | 0 | public final static Object[] SUPPORTED_ANT_TASKS = new Object[] { |
129 | 0 | LaunchTask.TASK_NAME, LaunchTask.class, |
130 | "ant", Ant.class, | |
131 | "antcall", CallTarget.class, | |
132 | "available", Available.class, | |
133 | "condition", ConditionTask.class, | |
134 | "fail", Exit.class, | |
135 | "property", Property.class, | |
136 | "mkdir", Mkdir.class, | |
137 | "delete", Delete.class, | |
138 | "copy", Copy.class, | |
139 | "exec", ExecTask.class, | |
140 | "waitfor", WaitFor.class, | |
141 | "taskdef", Taskdef.class | |
142 | }; | |
143 | ||
144 | /** | |
145 | * List of supported Ant types. | |
146 | */ | |
147 | 0 | public final static Object[] SUPPORTED_ANT_TYPES = new Object[] { |
148 | ArgumentSet.TYPE_NAME, ArgumentSet.class, | |
149 | JVMArgumentSet.TYPE_NAME, JVMArgumentSet.class, | |
150 | SysPropertySet.TYPE_NAME, SysPropertySet.class, | |
151 | "description", Description.class, | |
152 | "fileset", FileSet.class, | |
153 | "filelist", FileList.class, | |
154 | "path", Path.class, | |
155 | "patternset", PatternSet.class | |
156 | }; | |
157 | ||
158 | /** | |
159 | * Cached tools classpath. | |
160 | */ | |
161 | 0 | private static String toolsClasspath = null; |
162 | ||
163 | /** | |
164 | * The verbose flag | |
165 | */ | |
166 | 0 | private static boolean verbose = false; |
167 | ||
168 | //---------------------------------------------------------- Static Methods | |
169 | ||
170 | ||
171 | /** | |
172 | * Get the started flag. | |
173 | * | |
174 | * @return the value of the started flag | |
175 | */ | |
176 | public static synchronized boolean isStarted() { | |
177 | ||
178 | 0 | return Launcher.started; |
179 | ||
180 | } | |
181 | ||
182 | /** | |
183 | * Get the stopped flag. | |
184 | * | |
185 | * @return the value of the stopped flag | |
186 | */ | |
187 | public static synchronized boolean isStopped() { | |
188 | ||
189 | 0 | return Launcher.stopped; |
190 | ||
191 | } | |
192 | ||
193 | /** | |
194 | * Start the launching process. This method is essential the | |
195 | * <code>main(String[])<code> method for this class except that this method | |
196 | * never invokes {@link System#exit(int)}. This method is designed for | |
197 | * applications that wish to invoke this class directly from within their | |
198 | * application's code. | |
199 | * | |
200 | * @param args command line arguments | |
201 | * @return the exit value of the last synchronous child JVM that was | |
202 | * launched or 1 if any other error occurs | |
203 | * @throws IllegalArgumentException if any error parsing the args parameter | |
204 | * occurs | |
205 | */ | |
206 | public static int start(String[] args) throws IllegalArgumentException { | |
207 | ||
208 | // Check make sure that neither this method or the stop() method is | |
209 | // already running since we do not support concurrency | |
210 | 0 | synchronized (Launcher.lock) { |
211 | 0 | if (Launcher.isStarted() || Launcher.isStopped()) |
212 | 0 | return 1; |
213 | 0 | Launcher.setStarted(true); |
214 | 0 | } |
215 | ||
216 | 0 | int returnValue = 0; |
217 | 0 | ClassLoader parentLoader = null; |
218 | 0 | Thread shutdownHook = new Thread(new Launcher()); |
219 | 0 | Runtime runtime = Runtime.getRuntime(); |
220 | ||
221 | try { | |
222 | ||
223 | // Cache the current class loader for this thread and set the class | |
224 | // loader before running Ant. Note that we only set the class loader | |
225 | // if we are running a Java version earlier than 1.4 as on 1.4 this | |
226 | // causes unnecessary loading of the XML parser classes. | |
227 | 0 | parentLoader = Thread.currentThread().getContextClassLoader(); |
228 | 0 | boolean lessThan14 = true; |
229 | try { | |
230 | 0 | Class.forName("java.lang.CharSequence"); |
231 | 0 | lessThan14 = false; |
232 | 0 | } catch (ClassNotFoundException cnfe) { |
233 | // If this class does not exist, then we are not running Java 1.4 | |
234 | 0 | } |
235 | 0 | if (lessThan14) |
236 | 0 | Thread.currentThread().setContextClassLoader(Launcher.class.getClassLoader()); |
237 | ||
238 | 0 | Project project = new Project(); |
239 | ||
240 | // Set the project's class loader | |
241 | 0 | project.setCoreLoader(Launcher.class.getClassLoader()); |
242 | ||
243 | // Initialize the project. Note that we don't invoke the | |
244 | // Project.init() method directly as this will cause all of | |
245 | // the myriad of Task subclasses to load which is a big | |
246 | // performance hit. Instead, we load only the | |
247 | // Launcher.SUPPORTED_ANT_TASKS and Launcher.SUPPORTED_ANT_TYPES | |
248 | // into the project that the Launcher supports. | |
249 | 0 | for (int i = 0; i < Launcher.SUPPORTED_ANT_TASKS.length; i++) { |
250 | // The even numbered elements should be the task name | |
251 | 0 | String taskName = (String)Launcher.SUPPORTED_ANT_TASKS[i]; |
252 | // The odd numbered elements should be the task class | |
253 | 0 | Class taskClass = (Class)Launcher.SUPPORTED_ANT_TASKS[++i]; |
254 | 0 | project.addTaskDefinition(taskName, taskClass); |
255 | } | |
256 | 0 | for (int i = 0; i < Launcher.SUPPORTED_ANT_TYPES.length; i++) { |
257 | // The even numbered elements should be the type name | |
258 | 0 | String typeName = (String)Launcher.SUPPORTED_ANT_TYPES[i]; |
259 | // The odd numbered elements should be the type class | |
260 | 0 | Class typeClass = (Class)Launcher.SUPPORTED_ANT_TYPES[++i]; |
261 | 0 | project.addDataTypeDefinition(typeName, typeClass); |
262 | } | |
263 | ||
264 | // Add all system properties as project properties | |
265 | 0 | project.setSystemProperties(); |
266 | ||
267 | // Parse the arguments | |
268 | 0 | int currentArg = 0; |
269 | ||
270 | // Set default XML file | |
271 | 0 | File launchFile = new File(Launcher.getBootstrapDir(), Launcher.DEFAULT_XML_FILE_NAME); |
272 | ||
273 | // Get standard launcher arguments | |
274 | 0 | for ( ; currentArg < args.length; currentArg++) { |
275 | // If we find a "-" argument or an argument without a | |
276 | // leading "-", there are no more standard launcher arguments | |
277 | 0 | if ("-".equals(args[currentArg])) { |
278 | 0 | currentArg++; |
279 | 0 | break; |
280 | 0 | } else if (args[currentArg].length() > 0 && !"-".equals(args[currentArg].substring(0, 1))) { |
281 | 0 | break; |
282 | 0 | } else if ("-help".equals(args[currentArg])) { |
283 | 0 | throw new IllegalArgumentException(); |
284 | 0 | } else if ("-launchfile".equals(args[currentArg])) { |
285 | 0 | if (currentArg + 1 < args.length){ |
286 | 0 | String fileArg = args[++currentArg]; |
287 | 0 | launchFile = new File(fileArg); |
288 | 0 | if (!launchFile.isAbsolute()) |
289 | 0 | launchFile = new File(Launcher.getBootstrapDir(), fileArg); |
290 | 0 | } else { |
291 | 0 | throw new IllegalArgumentException(args[currentArg] + " " + Launcher.getLocalizedString("missing.arg")); |
292 | } | |
293 | 0 | } else if ("-executablename".equals(args[currentArg])) { |
294 | 0 | if (currentArg + 1 < args.length) |
295 | 0 | System.setProperty(ChildMain.EXECUTABLE_PROP_NAME, args[++currentArg]); |
296 | else | |
297 | 0 | throw new IllegalArgumentException(args[currentArg] + " " + Launcher.getLocalizedString("missing.arg")); |
298 | 0 | } else if ("-verbose".equals(args[currentArg])) { |
299 | 0 | Launcher.setVerbose(true); |
300 | } else { | |
301 | 0 | throw new IllegalArgumentException(args[currentArg] + " " + Launcher.getLocalizedString("invalid.arg")); |
302 | } | |
303 | } | |
304 | ||
305 | // Get target | |
306 | 0 | String target = null; |
307 | 0 | if (currentArg < args.length) |
308 | 0 | target = args[currentArg++]; |
309 | else | |
310 | 0 | throw new IllegalArgumentException(Launcher.getLocalizedString("missing.target")); |
311 | ||
312 | // Get user properties | |
313 | 0 | for ( ; currentArg < args.length; currentArg++) { |
314 | // If we don't find any more "-" or "-D" arguments, there are no | |
315 | // more user properties | |
316 | 0 | if ("-".equals(args[currentArg])) { |
317 | 0 | currentArg++; |
318 | 0 | break; |
319 | 0 | } else if (args[currentArg].length() <= 2 || !"-D".equals(args[currentArg].substring(0, 2))) { |
320 | 0 | break; |
321 | } | |
322 | 0 | int delimiter = args[currentArg].indexOf('=', 2); |
323 | 0 | String key = null; |
324 | 0 | String value = null; |
325 | 0 | if (delimiter >= 2) { |
326 | 0 | key = args[currentArg].substring(2, delimiter); |
327 | 0 | value = args[currentArg].substring(delimiter + 1); |
328 | } else { | |
329 | // Unfortunately, MS-DOS batch scripts will split an | |
330 | // "-Dname=value" argument into "-Dname" and "value" | |
331 | // arguments. So, we need to assume that the next | |
332 | // argument is the property value unless it appears | |
333 | // to be a different type of argument. | |
334 | 0 | key = args[currentArg].substring(2); |
335 | 0 | if (currentArg + 1 < args.length && |
336 | !"-D".equals(args[currentArg + 1].substring(0, 2))) | |
337 | { | |
338 | 0 | value = args[++currentArg]; |
339 | } else { | |
340 | 0 | value = ""; |
341 | } | |
342 | } | |
343 | 0 | project.setUserProperty(key, value); |
344 | } | |
345 | ||
346 | // Treat all remaining arguments as application arguments | |
347 | 0 | String[] appArgs = new String[args.length - currentArg]; |
348 | 0 | for (int i = 0; i < appArgs.length; i++) { |
349 | 0 | appArgs[i] = args[i + currentArg]; |
350 | 0 | project.setUserProperty(LaunchTask.ARG_PROP_NAME + Integer.toString(i), appArgs[i]); |
351 | } | |
352 | ||
353 | // Set standard Ant user properties | |
354 | 0 | project.setUserProperty("ant.version", Main.getAntVersion()); |
355 | 0 | project.setUserProperty("ant.file", launchFile.getCanonicalPath()); |
356 | 0 | project.setUserProperty("ant.java.version", System.getProperty("java.specification.version")); |
357 | ||
358 | // Set the buildfile | |
359 | 0 | ProjectHelper.configureProject(project, launchFile); |
360 | ||
361 | // Check that the target exists | |
362 | 0 | if (!project.getTargets().containsKey(target)) |
363 | 0 | throw new IllegalArgumentException(target + " " + Launcher.getLocalizedString("invalid.target")); |
364 | ||
365 | // Execute the target | |
366 | try { | |
367 | 0 | runtime.addShutdownHook(shutdownHook); |
368 | 0 | } catch (NoSuchMethodError nsme) { |
369 | // Early JVMs do not support this method | |
370 | 0 | } |
371 | 0 | project.executeTarget(target); |
372 | ||
373 | 0 | } catch (Throwable t) { |
374 | // Log any errors | |
375 | 0 | returnValue = 1; |
376 | 0 | String message = t.getMessage(); |
377 | 0 | if (t instanceof IllegalArgumentException) { |
378 | 0 | Launcher.error(message, true); |
379 | } else { | |
380 | 0 | if (Launcher.verbose) |
381 | 0 | Launcher.error(t); |
382 | else | |
383 | 0 | Launcher.error(message, false); |
384 | } | |
385 | } finally { | |
386 | 0 | synchronized (Launcher.lock) { |
387 | // Remove the shutdown hook | |
388 | try { | |
389 | 0 | runtime.removeShutdownHook(shutdownHook); |
390 | 0 | } catch (NoSuchMethodError nsme) { |
391 | // Early JVMs do not support this method | |
392 | 0 | } |
393 | // Reset the class loader after running Ant | |
394 | 0 | Thread.currentThread().setContextClassLoader(parentLoader); |
395 | // Reset stopped flag | |
396 | 0 | Launcher.setStarted(false); |
397 | // Notify the stop() method that we have set the class loader | |
398 | 0 | Launcher.lock.notifyAll(); |
399 | 0 | } |
400 | 0 | } |
401 | ||
402 | // Override return value with exit value of last synchronous child JVM | |
403 | 0 | Process[] childProcesses = LaunchTask.getChildProcesses(); |
404 | 0 | if (childProcesses.length > 0) |
405 | 0 | returnValue = childProcesses[childProcesses.length - 1].exitValue(); |
406 | ||
407 | 0 | return returnValue; |
408 | ||
409 | } | |
410 | ||
411 | /** | |
412 | * Interrupt the {@link #start(String[])} method. This is done | |
413 | * by forcing the current or next scheduled invocation of the | |
414 | * {@link LaunchTask#execute()} method to throw an exception. In addition, | |
415 | * this method will terminate any synchronous child processes that any | |
416 | * instances of the {@link LaunchTask} class have launched. Note, however, | |
417 | * that this method will <b>not</b> terminate any asynchronous child | |
418 | * processes that have been launched. Accordingly, applications that use | |
419 | * this method are encouraged to always set the LaunchTask.TASK_NAME task's | |
420 | * "waitForChild" attribute to "true" to ensure that the | |
421 | * application that you want to control can be terminated via this method. | |
422 | * After this method has been executed, it will not return until is safe to | |
423 | * execute the {@link #start(String[])} method. | |
424 | * | |
425 | * @return true if this method completed without error and false if an | |
426 | * error occurred or the launch process is already stopped | |
427 | */ | |
428 | public static boolean stop() { | |
429 | ||
430 | 0 | synchronized (Launcher.lock) { |
431 | // Check the stopped flag to avoid concurrent execution of this | |
432 | // method | |
433 | 0 | if (Launcher.isStopped()) |
434 | 0 | return false; |
435 | ||
436 | // Make sure that the start() method is running. If not, just | |
437 | // return as there is nothing to do. | |
438 | 0 | if (Launcher.isStarted()) |
439 | 0 | Launcher.setStopped(true); |
440 | else | |
441 | 0 | return false; |
442 | 0 | } |
443 | ||
444 | 0 | boolean returnValue = true; |
445 | ||
446 | try { | |
447 | ||
448 | // Kill all of the synchronous child processes | |
449 | 0 | killChildProcesses(); |
450 | ||
451 | // Wait for the start() method to reset the start flag | |
452 | 0 | synchronized (Launcher.lock) { |
453 | 0 | if (Launcher.isStarted()) |
454 | 0 | Launcher.lock.wait(); |
455 | 0 | } |
456 | ||
457 | // Make sure that the start() method has really finished | |
458 | 0 | if (Launcher.isStarted()) |
459 | 0 | returnValue = true; |
460 | ||
461 | 0 | } catch (Throwable t) { |
462 | // Log any errors | |
463 | 0 | returnValue = false; |
464 | 0 | String message = t.getMessage(); |
465 | 0 | if (Launcher.verbose) |
466 | 0 | Launcher.error(t); |
467 | else | |
468 | 0 | Launcher.error(message, false); |
469 | } finally { | |
470 | // Reset stopped flag | |
471 | 0 | Launcher.setStopped(false); |
472 | 0 | } |
473 | ||
474 | 0 | return returnValue; |
475 | ||
476 | } | |
477 | ||
478 | /** | |
479 | * Print a detailed error message and exit. | |
480 | * | |
481 | * @param message the message to be printed | |
482 | * @param usage if true, print a usage statement after the message | |
483 | */ | |
484 | public static void error(String message, boolean usage) { | |
485 | ||
486 | 0 | if (message != null) |
487 | 0 | Launcher.getLog().println(Launcher.getLocalizedString("error") + ": " + message); |
488 | 0 | if (usage) |
489 | 0 | Launcher.getLog().println(Launcher.getLocalizedString("usage")); |
490 | ||
491 | 0 | } |
492 | ||
493 | /** | |
494 | * Print a detailed error message and exit. | |
495 | * | |
496 | * @param t the exception whose stack trace is to be printed. | |
497 | */ | |
498 | public static void error(Throwable t) { | |
499 | ||
500 | 0 | String message = t.getMessage(); |
501 | 0 | if (!Launcher.verbose && message != null) |
502 | 0 | Launcher.getLog().println(Launcher.getLocalizedString("error") + ": " + message); |
503 | else | |
504 | 0 | t.printStackTrace(Launcher.getLog()); |
505 | ||
506 | 0 | } |
507 | ||
508 | /** | |
509 | * Get the canonical directory of the class or jar file that this class was | |
510 | * loaded. This method can be used to calculate the root directory of an | |
511 | * installation. | |
512 | * | |
513 | * @return the canonical directory of the class or jar file that this class | |
514 | * file was loaded from | |
515 | * @throws IOException if the canonical directory or jar file | |
516 | * cannot be found | |
517 | */ | |
518 | public static File getBootstrapDir() throws IOException { | |
519 | ||
520 | 0 | File file = Launcher.getBootstrapFile(); |
521 | 0 | if (file.isDirectory()) |
522 | 0 | return file; |
523 | else | |
524 | 0 | return file.getParentFile(); |
525 | ||
526 | } | |
527 | ||
528 | /** | |
529 | * Get the canonical directory or jar file that this class was loaded | |
530 | * from. | |
531 | * | |
532 | * @return the canonical directory or jar file that this class | |
533 | * file was loaded from | |
534 | * @throws IOException if the canonical directory or jar file | |
535 | * cannot be found | |
536 | */ | |
537 | public static File getBootstrapFile() throws IOException { | |
538 | ||
539 | 0 | if (bootstrapFile == null) { |
540 | ||
541 | // Get a URL for where this class was loaded from | |
542 | 0 | String classResourceName = "/" + Launcher.class.getName().replace('.', '/') + ".class"; |
543 | 0 | URL resource = Launcher.class.getResource(classResourceName); |
544 | 0 | if (resource == null) |
545 | 0 | throw new IOException(Launcher.getLocalizedString("bootstrap.file.not.found") + ": " + Launcher.class.getName()); |
546 | 0 | String resourcePath = null; |
547 | 0 | String embeddedClassName = null; |
548 | 0 | boolean isJar = false; |
549 | 0 | String protocol = resource.getProtocol(); |
550 | 0 | if ((protocol != null) && |
551 | (protocol.indexOf("jar") >= 0)) { | |
552 | 0 | isJar = true; |
553 | } | |
554 | 0 | if (isJar) { |
555 | 0 | resourcePath = URLDecoder.decode(resource.getFile()); |
556 | 0 | embeddedClassName = "!" + classResourceName; |
557 | } else { | |
558 | 0 | resourcePath = URLDecoder.decode(resource.toExternalForm()); |
559 | 0 | embeddedClassName = classResourceName; |
560 | } | |
561 | 0 | int sep = resourcePath.lastIndexOf(embeddedClassName); |
562 | 0 | if (sep >= 0) |
563 | 0 | resourcePath = resourcePath.substring(0, sep); |
564 | ||
565 | // Now that we have a URL, make sure that it is a "file" URL | |
566 | // as we need to coerce the URL into a File object | |
567 | 0 | if (resourcePath.indexOf("file:") == 0) |
568 | 0 | resourcePath = resourcePath.substring(5); |
569 | else | |
570 | 0 | throw new IOException(Launcher.getLocalizedString("bootstrap.file.not.found") + ": " + Launcher.class.getName()); |
571 | ||
572 | // Coerce the URL into a file and check that it exists. Note that | |
573 | // the JVM <code>File(String)</code> constructor automatically | |
574 | // flips all '/' characters to '\' on Windows and there are no | |
575 | // valid escape characters so we sould not have to worry about | |
576 | // URL encoded slashes. | |
577 | 0 | File file = new File(resourcePath); |
578 | 0 | if (!file.exists() || !file.canRead()) |
579 | 0 | throw new IOException(Launcher.getLocalizedString("bootstrap.file.not.found") + ": " + Launcher.class.getName()); |
580 | 0 | bootstrapFile = file.getCanonicalFile(); |
581 | ||
582 | } | |
583 | ||
584 | 0 | return bootstrapFile; |
585 | ||
586 | } | |
587 | ||
588 | /** | |
589 | * Get the full path of the Java command to execute. | |
590 | * | |
591 | * @return a string suitable for executing a child JVM | |
592 | */ | |
593 | public static synchronized String getJavaCommand() { | |
594 | ||
595 | 0 | if (javaCmd == null) { |
596 | ||
597 | 0 | String osname = System.getProperty("os.name").toLowerCase(); |
598 | 0 | String commandName = null; |
599 | 0 | if (osname.indexOf("windows") >= 0) { |
600 | // Always use javaw.exe on Windows so that we aren't bound to an | |
601 | // MS-DOS window | |
602 | 0 | commandName = "javaw.exe"; |
603 | } else { | |
604 | 0 | commandName = "java"; |
605 | } | |
606 | 0 | javaCmd = System.getProperty("java.home") + File.separator + "bin" + File.separator + commandName; |
607 | ||
608 | } | |
609 | ||
610 | 0 | return javaCmd; |
611 | ||
612 | } | |
613 | ||
614 | /** | |
615 | * Get the full path of the JDB command to execute. | |
616 | * | |
617 | * @return a string suitable for executing a child JDB debugger | |
618 | */ | |
619 | public static synchronized String getJDBCommand() { | |
620 | ||
621 | 0 | if (jdbCmd == null) { |
622 | ||
623 | 0 | String osname = System.getProperty("os.name").toLowerCase(); |
624 | 0 | String commandName = null; |
625 | 0 | if (osname.indexOf("windows") >= 0) |
626 | 0 | commandName = "jdb.exe"; |
627 | else | |
628 | 0 | commandName = "jdb"; |
629 | 0 | jdbCmd = new File(System.getProperty("java.home")).getParent() + File.separator + "bin" + File.separator + commandName; |
630 | ||
631 | } | |
632 | ||
633 | 0 | return jdbCmd; |
634 | ||
635 | } | |
636 | ||
637 | /** | |
638 | * Get the PrintStream that all output should printed to. The default | |
639 | * PrintStream returned in System.err. | |
640 | * | |
641 | * @return the PrintStream instance to print output to | |
642 | */ | |
643 | public static synchronized PrintStream getLog() { | |
644 | ||
645 | 0 | return Launcher.log; |
646 | ||
647 | } | |
648 | ||
649 | /** | |
650 | * Set the classpath to the current JVM's tools classes. | |
651 | * | |
652 | * @return a string suitable for use as a JVM's -classpath argument | |
653 | * @throws IOException if the tools classes cannot be found | |
654 | */ | |
655 | public static synchronized String getToolsClasspath() throws IOException { | |
656 | ||
657 | 0 | if (toolsClasspath == null) { |
658 | ||
659 | 0 | File javaHome = null; |
660 | 0 | javaHome = new File(System.getProperty("java.home")).getCanonicalFile(); |
661 | 0 | Class clazz = null; |
662 | 0 | String[] toolsPaths = new String[2]; |
663 | 0 | toolsPaths[0] = javaHome.getParent() + File.separator + |
664 | "lib" + File.separator + "tools.jar"; | |
665 | 0 | toolsPaths[1] = javaHome.getPath() + File.separator + |
666 | "lib" + File.separator + "tools.jar"; | |
667 | 0 | File toolsFile = null; |
668 | 0 | for (int i = 0; i < toolsPaths.length; i++) { |
669 | 0 | ClassLoader loader = ClassLoader.getSystemClassLoader(); |
670 | 0 | toolsFile = new File(toolsPaths[i]); |
671 | // Check if the jar file exists and is readable | |
672 | 0 | if (!toolsFile.isFile() || !toolsFile.canRead()) |
673 | 0 | toolsFile = null; |
674 | 0 | if (toolsFile != null) { |
675 | try { | |
676 | 0 | URL toolsURL = toolsFile.toURL(); |
677 | 0 | loader = new URLClassLoader(new URL[]{toolsURL}, loader); |
678 | 0 | } catch (Exception e) { |
679 | 0 | toolsFile = null; |
680 | 0 | } |
681 | } | |
682 | // Try to load the javac class just to be sure. Note that we | |
683 | // use the system class loader if the file does not exist to | |
684 | // handle cases like Mac OS X where the tools.jar classes are | |
685 | // loaded by the bootstrap class loader. | |
686 | try { | |
687 | 0 | clazz = loader.loadClass("sun.tools.javac.Main"); |
688 | 0 | if (clazz != null) |
689 | 0 | break; |
690 | 0 | } catch (Exception e) {} |
691 | } | |
692 | ||
693 | 0 | if (clazz == null) |
694 | 0 | throw new IOException(Launcher.getLocalizedString("sdk.tools.not.found")); |
695 | ||
696 | // Save classpath. | |
697 | 0 | if (toolsFile != null) |
698 | 0 | toolsClasspath = toolsFile.getPath(); |
699 | else | |
700 | 0 | toolsClasspath = ""; |
701 | ||
702 | } | |
703 | ||
704 | 0 | return toolsClasspath; |
705 | ||
706 | } | |
707 | ||
708 | /** | |
709 | * Get a localized property. This method will search for localized | |
710 | * properties and will resolve ${...} style macros in the localized string. | |
711 | * | |
712 | * @param key the localized property to retrieve | |
713 | * @return the localized and resolved property value | |
714 | */ | |
715 | public static String getLocalizedString(String key) { | |
716 | ||
717 | 0 | return Launcher.getLocalizedString(key, Launcher.class.getName()); |
718 | ||
719 | } | |
720 | ||
721 | /** | |
722 | * Get a localized property. This method will search for localized | |
723 | * properties and will resolve ${...} style macros in the localized string. | |
724 | * | |
725 | * @param key the localized property to retrieve | |
726 | * @param className the name of the class to retrieve the property for | |
727 | * @return the localized and resolved property value | |
728 | */ | |
729 | public static String getLocalizedString(String key, String className) { | |
730 | ||
731 | try { | |
732 | 0 | ResourceBundle resourceBundle = ResourceBundle.getBundle(className); |
733 | 0 | return Launcher.resolveString(resourceBundle.getString(key)); |
734 | 0 | } catch (Exception e) { |
735 | // We should at least make it clear that the property is not | |
736 | // defined in the properties file | |
737 | 0 | return "<" + key + " property>"; |
738 | } | |
739 | ||
740 | } | |
741 | ||
742 | /** | |
743 | * Resolve ${...} style macros in strings. This method will replace any | |
744 | * embedded ${...} strings in the specified unresolved parameter with the | |
745 | * value of the system property in the enclosed braces. Note that any '$' | |
746 | * characters can be escaped by putting '$$' in the specified parameter. | |
747 | * In additional, the following special macros will be resolved: | |
748 | * <ul> | |
749 | * <li><code>${launcher.executable.name}</code> will be substituted with the | |
750 | * value of the "org.apache.commons.launcher.executableName" system | |
751 | * property, the "-executablename" command line argument, or, if both of | |
752 | * those are undefined, with the absolute path to the Java executable plus | |
753 | * its classpath and main class name arguments | |
754 | * <li><code>${launcher.bootstrap.file}</code> will get substituted with | |
755 | * the value returned by {@link #getBootstrapFile()} | |
756 | * <li><code>${launcher.bootstrap.dir}</code> will get substituted with | |
757 | * the value returned by {@link #getBootstrapDir()} | |
758 | * | |
759 | * @param unresolved the string to be resolved | |
760 | * @return the resolved String | |
761 | * @throws IOException if any error occurs | |
762 | */ | |
763 | private static String resolveString(String unresolved) throws IOException { | |
764 | ||
765 | 0 | if (unresolved == null) |
766 | 0 | return null; |
767 | ||
768 | // Substitute system property strings | |
769 | 0 | StringBuffer buf = new StringBuffer(); |
770 | 0 | int tokenEnd = 0; |
771 | 0 | int tokenStart = 0; |
772 | 0 | char token = '$'; |
773 | 0 | boolean escapeChar = false; |
774 | 0 | boolean firstToken = true; |
775 | 0 | boolean lastToken = false; |
776 | ||
777 | 0 | while (!lastToken) { |
778 | ||
779 | 0 | tokenEnd = unresolved.indexOf(token, tokenStart); |
780 | ||
781 | // Determine if this is the first token | |
782 | 0 | if (firstToken) { |
783 | 0 | firstToken = false; |
784 | // Skip if first token is zero length | |
785 | 0 | if (tokenEnd - tokenStart == 0) { |
786 | 0 | tokenStart = ++tokenEnd; |
787 | 0 | continue; |
788 | } | |
789 | } | |
790 | // Determine if this is the last token | |
791 | 0 | if (tokenEnd < 0) { |
792 | 0 | lastToken = true; |
793 | 0 | tokenEnd = unresolved.length(); |
794 | } | |
795 | ||
796 | 0 | if (escapeChar) { |
797 | ||
798 | // Don't parse the string | |
799 | 0 | buf.append(token + unresolved.substring(tokenStart, tokenEnd)); |
800 | 0 | escapeChar = !escapeChar; |
801 | ||
802 | } else { | |
803 | ||
804 | // Parse the string | |
805 | 0 | int openProp = unresolved.indexOf('{', tokenStart); |
806 | 0 | int closeProp = unresolved.indexOf('}', tokenStart + 1); |
807 | 0 | String prop = null; |
808 | ||
809 | // We must have a '{' in the first character and a closing | |
810 | // '}' after that | |
811 | 0 | if (openProp != tokenStart || |
812 | closeProp < tokenStart + 1 || | |
813 | closeProp >= tokenEnd) | |
814 | { | |
815 | 0 | buf.append(unresolved.substring(tokenStart, tokenEnd)); |
816 | } else { | |
817 | // Property found | |
818 | 0 | String propName = unresolved.substring(tokenStart + 1, closeProp); |
819 | 0 | if ("launcher.executable.name".equals(propName)) { |
820 | 0 | prop = System.getProperty(ChildMain.EXECUTABLE_PROP_NAME); |
821 | 0 | if (prop != null) { |
822 | // Quote the property | |
823 | 0 | prop = "\"" + prop + "\""; |
824 | } else { | |
825 | // Set property to fully quoted Java command line | |
826 | 0 | String classpath = Launcher.getBootstrapFile().getPath(); |
827 | 0 | prop = "\"" + System.getProperty("java.home") + File.separator + "bin" + File.separator + "java\" -classpath \"" + classpath + "\" LauncherBootstrap"; |
828 | 0 | } |
829 | 0 | } else if ("launcher.bootstrap.file".equals(propName)) { |
830 | 0 | prop = Launcher.getBootstrapFile().getPath(); |
831 | 0 | } else if ("launcher.bootstrap.dir".equals(propName)) { |
832 | 0 | prop = Launcher.getBootstrapDir().getPath(); |
833 | } else { | |
834 | 0 | prop = System.getProperty(unresolved.substring(tokenStart + 1, closeProp)); |
835 | } | |
836 | 0 | if (prop == null) |
837 | 0 | prop = ""; |
838 | 0 | buf.append(prop + unresolved.substring(++closeProp, tokenEnd)); |
839 | } | |
840 | ||
841 | } | |
842 | ||
843 | // If this is a blank token, then the next starts with the | |
844 | // token character. So, treat this token as an escape | |
845 | // character for the next token. | |
846 | 0 | if (tokenEnd - tokenStart == 0) |
847 | 0 | escapeChar = !escapeChar; |
848 | ||
849 | 0 | tokenStart = ++tokenEnd; |
850 | ||
851 | } | |
852 | ||
853 | 0 | return buf.toString(); |
854 | ||
855 | } | |
856 | ||
857 | /** | |
858 | * Set the PrintStream that all output should printed to. | |
859 | * | |
860 | * @param log PrintStream instance to print output to | |
861 | */ | |
862 | public static synchronized void setLog(PrintStream log) { | |
863 | ||
864 | 0 | if (log != null) |
865 | 0 | Launcher.log = log; |
866 | else | |
867 | 0 | Launcher.log = System.err; |
868 | ||
869 | 0 | } |
870 | ||
871 | /** | |
872 | * Set the started flag. | |
873 | * | |
874 | * @param started the value of the started flag | |
875 | */ | |
876 | private static synchronized void setStarted(boolean started) { | |
877 | ||
878 | 0 | Launcher.started = started; |
879 | ||
880 | 0 | } |
881 | ||
882 | /** | |
883 | * Set the stopped flag. | |
884 | * | |
885 | * @param stopped the value of the stopped flag | |
886 | */ | |
887 | private static synchronized void setStopped(boolean stopped) { | |
888 | ||
889 | 0 | Launcher.stopped = stopped; |
890 | ||
891 | 0 | } |
892 | ||
893 | /** | |
894 | * Set the verbose flag. | |
895 | * | |
896 | * @param verbose the value of the verbose flag | |
897 | */ | |
898 | public static synchronized void setVerbose(boolean verbose) { | |
899 | ||
900 | 0 | Launcher.verbose = verbose; |
901 | ||
902 | 0 | } |
903 | ||
904 | /** | |
905 | * Iterate through the list of synchronous child process launched by | |
906 | * all of the {@link LaunchTask} instances. | |
907 | */ | |
908 | public static void killChildProcesses() { | |
909 | ||
910 | 0 | Process[] procs = LaunchTask.getChildProcesses(); |
911 | 0 | for (int i = 0; i < procs.length; i++) |
912 | 0 | procs[i].destroy(); |
913 | ||
914 | 0 | } |
915 | ||
916 | //----------------------------------------------------------------- Methods | |
917 | ||
918 | /** | |
919 | * Wrapper to allow the {@link #killChildProcesses()} method to be | |
920 | * invoked in a shutdown hook. | |
921 | */ | |
922 | public void run() { | |
923 | ||
924 | 0 | Launcher.killChildProcesses(); |
925 | ||
926 | 0 | } |
927 | ||
928 | } |