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 public class Launcher implements Runnable {
76
77 //----------------------------------------------------------- Static Fields
78
79
80 /**
81 * Cached bootstrap file.
82 */
83 private static File bootstrapFile = null;
84
85 /**
86 * Cached java command
87 */
88 private static String javaCmd = null;
89
90 /**
91 * Cached JDB command
92 */
93 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 private static Object lock = new Object();
104
105 /**
106 * Cached log
107 */
108 private static PrintStream log = System.err;
109
110 /**
111 * Cached resourceBundle
112 */
113 private static ResourceBundle resourceBundle = null;
114
115 /**
116 * The started status flag.
117 */
118 private static boolean started = false;
119
120 /**
121 * The stopped status flag.
122 */
123 private static boolean stopped = false;
124
125 /**
126 * List of supported Ant tasks.
127 */
128 public final static Object[] SUPPORTED_ANT_TASKS = new Object[] {
129 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 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 private static String toolsClasspath = null;
162
163 /**
164 * The verbose flag
165 */
166 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 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 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 synchronized (Launcher.lock) {
211 if (Launcher.isStarted() || Launcher.isStopped())
212 return 1;
213 Launcher.setStarted(true);
214 }
215
216 int returnValue = 0;
217 ClassLoader parentLoader = null;
218 Thread shutdownHook = new Thread(new Launcher());
219 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 parentLoader = Thread.currentThread().getContextClassLoader();
228 boolean lessThan14 = true;
229 try {
230 Class.forName("java.lang.CharSequence");
231 lessThan14 = false;
232 } catch (ClassNotFoundException cnfe) {
233 // If this class does not exist, then we are not running Java 1.4
234 }
235 if (lessThan14)
236 Thread.currentThread().setContextClassLoader(Launcher.class.getClassLoader());
237
238 Project project = new Project();
239
240 // Set the project's class loader
241 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 for (int i = 0; i < Launcher.SUPPORTED_ANT_TASKS.length; i++) {
250 // The even numbered elements should be the task name
251 String taskName = (String)Launcher.SUPPORTED_ANT_TASKS[i];
252 // The odd numbered elements should be the task class
253 Class taskClass = (Class)Launcher.SUPPORTED_ANT_TASKS[++i];
254 project.addTaskDefinition(taskName, taskClass);
255 }
256 for (int i = 0; i < Launcher.SUPPORTED_ANT_TYPES.length; i++) {
257 // The even numbered elements should be the type name
258 String typeName = (String)Launcher.SUPPORTED_ANT_TYPES[i];
259 // The odd numbered elements should be the type class
260 Class typeClass = (Class)Launcher.SUPPORTED_ANT_TYPES[++i];
261 project.addDataTypeDefinition(typeName, typeClass);
262 }
263
264 // Add all system properties as project properties
265 project.setSystemProperties();
266
267 // Parse the arguments
268 int currentArg = 0;
269
270 // Set default XML file
271 File launchFile = new File(Launcher.getBootstrapDir(), Launcher.DEFAULT_XML_FILE_NAME);
272
273 // Get standard launcher arguments
274 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 if ("-".equals(args[currentArg])) {
278 currentArg++;
279 break;
280 } else if (args[currentArg].length() > 0 && !"-".equals(args[currentArg].substring(0, 1))) {
281 break;
282 } else if ("-help".equals(args[currentArg])) {
283 throw new IllegalArgumentException();
284 } else if ("-launchfile".equals(args[currentArg])) {
285 if (currentArg + 1 < args.length){
286 String fileArg = args[++currentArg];
287 launchFile = new File(fileArg);
288 if (!launchFile.isAbsolute())
289 launchFile = new File(Launcher.getBootstrapDir(), fileArg);
290 } else {
291 throw new IllegalArgumentException(args[currentArg] + " " + Launcher.getLocalizedString("missing.arg"));
292 }
293 } else if ("-executablename".equals(args[currentArg])) {
294 if (currentArg + 1 < args.length)
295 System.setProperty(ChildMain.EXECUTABLE_PROP_NAME, args[++currentArg]);
296 else
297 throw new IllegalArgumentException(args[currentArg] + " " + Launcher.getLocalizedString("missing.arg"));
298 } else if ("-verbose".equals(args[currentArg])) {
299 Launcher.setVerbose(true);
300 } else {
301 throw new IllegalArgumentException(args[currentArg] + " " + Launcher.getLocalizedString("invalid.arg"));
302 }
303 }
304
305 // Get target
306 String target = null;
307 if (currentArg < args.length)
308 target = args[currentArg++];
309 else
310 throw new IllegalArgumentException(Launcher.getLocalizedString("missing.target"));
311
312 // Get user properties
313 for ( ; currentArg < args.length; currentArg++) {
314 // If we don't find any more "-" or "-D" arguments, there are no
315 // more user properties
316 if ("-".equals(args[currentArg])) {
317 currentArg++;
318 break;
319 } else if (args[currentArg].length() <= 2 || !"-D".equals(args[currentArg].substring(0, 2))) {
320 break;
321 }
322 int delimiter = args[currentArg].indexOf('=', 2);
323 String key = null;
324 String value = null;
325 if (delimiter >= 2) {
326 key = args[currentArg].substring(2, delimiter);
327 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 key = args[currentArg].substring(2);
335 if (currentArg + 1 < args.length &&
336 !"-D".equals(args[currentArg + 1].substring(0, 2)))
337 {
338 value = args[++currentArg];
339 } else {
340 value = "";
341 }
342 }
343 project.setUserProperty(key, value);
344 }
345
346 // Treat all remaining arguments as application arguments
347 String[] appArgs = new String[args.length - currentArg];
348 for (int i = 0; i < appArgs.length; i++) {
349 appArgs[i] = args[i + currentArg];
350 project.setUserProperty(LaunchTask.ARG_PROP_NAME + Integer.toString(i), appArgs[i]);
351 }
352
353 // Set standard Ant user properties
354 project.setUserProperty("ant.version", Main.getAntVersion());
355 project.setUserProperty("ant.file", launchFile.getCanonicalPath());
356 project.setUserProperty("ant.java.version", System.getProperty("java.specification.version"));
357
358 // Set the buildfile
359 ProjectHelper.configureProject(project, launchFile);
360
361 // Check that the target exists
362 if (!project.getTargets().containsKey(target))
363 throw new IllegalArgumentException(target + " " + Launcher.getLocalizedString("invalid.target"));
364
365 // Execute the target
366 try {
367 runtime.addShutdownHook(shutdownHook);
368 } catch (NoSuchMethodError nsme) {
369 // Early JVMs do not support this method
370 }
371 project.executeTarget(target);
372
373 } catch (Throwable t) {
374 // Log any errors
375 returnValue = 1;
376 String message = t.getMessage();
377 if (t instanceof IllegalArgumentException) {
378 Launcher.error(message, true);
379 } else {
380 if (Launcher.verbose)
381 Launcher.error(t);
382 else
383 Launcher.error(message, false);
384 }
385 } finally {
386 synchronized (Launcher.lock) {
387 // Remove the shutdown hook
388 try {
389 runtime.removeShutdownHook(shutdownHook);
390 } catch (NoSuchMethodError nsme) {
391 // Early JVMs do not support this method
392 }
393 // Reset the class loader after running Ant
394 Thread.currentThread().setContextClassLoader(parentLoader);
395 // Reset stopped flag
396 Launcher.setStarted(false);
397 // Notify the stop() method that we have set the class loader
398 Launcher.lock.notifyAll();
399 }
400 }
401
402 // Override return value with exit value of last synchronous child JVM
403 Process[] childProcesses = LaunchTask.getChildProcesses();
404 if (childProcesses.length > 0)
405 returnValue = childProcesses[childProcesses.length - 1].exitValue();
406
407 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 synchronized (Launcher.lock) {
431 // Check the stopped flag to avoid concurrent execution of this
432 // method
433 if (Launcher.isStopped())
434 return false;
435
436 // Make sure that the start() method is running. If not, just
437 // return as there is nothing to do.
438 if (Launcher.isStarted())
439 Launcher.setStopped(true);
440 else
441 return false;
442 }
443
444 boolean returnValue = true;
445
446 try {
447
448 // Kill all of the synchronous child processes
449 killChildProcesses();
450
451 // Wait for the start() method to reset the start flag
452 synchronized (Launcher.lock) {
453 if (Launcher.isStarted())
454 Launcher.lock.wait();
455 }
456
457 // Make sure that the start() method has really finished
458 if (Launcher.isStarted())
459 returnValue = true;
460
461 } catch (Throwable t) {
462 // Log any errors
463 returnValue = false;
464 String message = t.getMessage();
465 if (Launcher.verbose)
466 Launcher.error(t);
467 else
468 Launcher.error(message, false);
469 } finally {
470 // Reset stopped flag
471 Launcher.setStopped(false);
472 }
473
474 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 if (message != null)
487 Launcher.getLog().println(Launcher.getLocalizedString("error") + ": " + message);
488 if (usage)
489 Launcher.getLog().println(Launcher.getLocalizedString("usage"));
490
491 }
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 String message = t.getMessage();
501 if (!Launcher.verbose && message != null)
502 Launcher.getLog().println(Launcher.getLocalizedString("error") + ": " + message);
503 else
504 t.printStackTrace(Launcher.getLog());
505
506 }
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 File file = Launcher.getBootstrapFile();
521 if (file.isDirectory())
522 return file;
523 else
524 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 if (bootstrapFile == null) {
540
541 // Get a URL for where this class was loaded from
542 String classResourceName = "/" + Launcher.class.getName().replace('.', '/') + ".class";
543 URL resource = Launcher.class.getResource(classResourceName);
544 if (resource == null)
545 throw new IOException(Launcher.getLocalizedString("bootstrap.file.not.found") + ": " + Launcher.class.getName());
546 String resourcePath = null;
547 String embeddedClassName = null;
548 boolean isJar = false;
549 String protocol = resource.getProtocol();
550 if ((protocol != null) &&
551 (protocol.indexOf("jar") >= 0)) {
552 isJar = true;
553 }
554 if (isJar) {
555 resourcePath = URLDecoder.decode(resource.getFile());
556 embeddedClassName = "!" + classResourceName;
557 } else {
558 resourcePath = URLDecoder.decode(resource.toExternalForm());
559 embeddedClassName = classResourceName;
560 }
561 int sep = resourcePath.lastIndexOf(embeddedClassName);
562 if (sep >= 0)
563 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 if (resourcePath.indexOf("file:") == 0)
568 resourcePath = resourcePath.substring(5);
569 else
570 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 File file = new File(resourcePath);
578 if (!file.exists() || !file.canRead())
579 throw new IOException(Launcher.getLocalizedString("bootstrap.file.not.found") + ": " + Launcher.class.getName());
580 bootstrapFile = file.getCanonicalFile();
581
582 }
583
584 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 if (javaCmd == null) {
596
597 String osname = System.getProperty("os.name").toLowerCase();
598 String commandName = null;
599 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 commandName = "javaw.exe";
603 } else {
604 commandName = "java";
605 }
606 javaCmd = System.getProperty("java.home") + File.separator + "bin" + File.separator + commandName;
607
608 }
609
610 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 if (jdbCmd == null) {
622
623 String osname = System.getProperty("os.name").toLowerCase();
624 String commandName = null;
625 if (osname.indexOf("windows") >= 0)
626 commandName = "jdb.exe";
627 else
628 commandName = "jdb";
629 jdbCmd = new File(System.getProperty("java.home")).getParent() + File.separator + "bin" + File.separator + commandName;
630
631 }
632
633 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 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 if (toolsClasspath == null) {
658
659 File javaHome = null;
660 javaHome = new File(System.getProperty("java.home")).getCanonicalFile();
661 Class clazz = null;
662 String[] toolsPaths = new String[2];
663 toolsPaths[0] = javaHome.getParent() + File.separator +
664 "lib" + File.separator + "tools.jar";
665 toolsPaths[1] = javaHome.getPath() + File.separator +
666 "lib" + File.separator + "tools.jar";
667 File toolsFile = null;
668 for (int i = 0; i < toolsPaths.length; i++) {
669 ClassLoader loader = ClassLoader.getSystemClassLoader();
670 toolsFile = new File(toolsPaths[i]);
671 // Check if the jar file exists and is readable
672 if (!toolsFile.isFile() || !toolsFile.canRead())
673 toolsFile = null;
674 if (toolsFile != null) {
675 try {
676 URL toolsURL = toolsFile.toURL();
677 loader = new URLClassLoader(new URL[]{toolsURL}, loader);
678 } catch (Exception e) {
679 toolsFile = null;
680 }
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 clazz = loader.loadClass("sun.tools.javac.Main");
688 if (clazz != null)
689 break;
690 } catch (Exception e) {}
691 }
692
693 if (clazz == null)
694 throw new IOException(Launcher.getLocalizedString("sdk.tools.not.found"));
695
696 // Save classpath.
697 if (toolsFile != null)
698 toolsClasspath = toolsFile.getPath();
699 else
700 toolsClasspath = "";
701
702 }
703
704 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 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 ResourceBundle resourceBundle = ResourceBundle.getBundle(className);
733 return Launcher.resolveString(resourceBundle.getString(key));
734 } catch (Exception e) {
735 // We should at least make it clear that the property is not
736 // defined in the properties file
737 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 if (unresolved == null)
766 return null;
767
768 // Substitute system property strings
769 StringBuffer buf = new StringBuffer();
770 int tokenEnd = 0;
771 int tokenStart = 0;
772 char token = '$';
773 boolean escapeChar = false;
774 boolean firstToken = true;
775 boolean lastToken = false;
776
777 while (!lastToken) {
778
779 tokenEnd = unresolved.indexOf(token, tokenStart);
780
781 // Determine if this is the first token
782 if (firstToken) {
783 firstToken = false;
784 // Skip if first token is zero length
785 if (tokenEnd - tokenStart == 0) {
786 tokenStart = ++tokenEnd;
787 continue;
788 }
789 }
790 // Determine if this is the last token
791 if (tokenEnd < 0) {
792 lastToken = true;
793 tokenEnd = unresolved.length();
794 }
795
796 if (escapeChar) {
797
798 // Don't parse the string
799 buf.append(token + unresolved.substring(tokenStart, tokenEnd));
800 escapeChar = !escapeChar;
801
802 } else {
803
804 // Parse the string
805 int openProp = unresolved.indexOf('{', tokenStart);
806 int closeProp = unresolved.indexOf('}', tokenStart + 1);
807 String prop = null;
808
809 // We must have a '{' in the first character and a closing
810 // '}' after that
811 if (openProp != tokenStart ||
812 closeProp < tokenStart + 1 ||
813 closeProp >= tokenEnd)
814 {
815 buf.append(unresolved.substring(tokenStart, tokenEnd));
816 } else {
817 // Property found
818 String propName = unresolved.substring(tokenStart + 1, closeProp);
819 if ("launcher.executable.name".equals(propName)) {
820 prop = System.getProperty(ChildMain.EXECUTABLE_PROP_NAME);
821 if (prop != null) {
822 // Quote the property
823 prop = "\"" + prop + "\"";
824 } else {
825 // Set property to fully quoted Java command line
826 String classpath = Launcher.getBootstrapFile().getPath();
827 prop = "\"" + System.getProperty("java.home") + File.separator + "bin" + File.separator + "java\" -classpath \"" + classpath + "\" LauncherBootstrap";
828 }
829 } else if ("launcher.bootstrap.file".equals(propName)) {
830 prop = Launcher.getBootstrapFile().getPath();
831 } else if ("launcher.bootstrap.dir".equals(propName)) {
832 prop = Launcher.getBootstrapDir().getPath();
833 } else {
834 prop = System.getProperty(unresolved.substring(tokenStart + 1, closeProp));
835 }
836 if (prop == null)
837 prop = "";
838 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 if (tokenEnd - tokenStart == 0)
847 escapeChar = !escapeChar;
848
849 tokenStart = ++tokenEnd;
850
851 }
852
853 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 if (log != null)
865 Launcher.log = log;
866 else
867 Launcher.log = System.err;
868
869 }
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 Launcher.started = started;
879
880 }
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 Launcher.stopped = stopped;
890
891 }
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 Launcher.verbose = verbose;
901
902 }
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 Process[] procs = LaunchTask.getChildProcesses();
911 for (int i = 0; i < procs.length; i++)
912 procs[i].destroy();
913
914 }
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 Launcher.killChildProcesses();
925
926 }
927
928 }