Coverage Report - org.apache.commons.launcher.Launcher
 
Classes in this File Line Coverage Branch Coverage Complexity
Launcher
0%
0/304
0%
0/174
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  
 }