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