001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.util.processes; 031 032import java.io.File; 033 034import org.apache.tools.ant.BuildEvent; 035import org.apache.tools.ant.BuildException; 036import org.apache.tools.ant.BuildListener; 037import org.apache.tools.ant.Project; 038import org.apache.tools.ant.taskdefs.Java; 039import org.apache.tools.ant.types.Commandline.Argument; 040import org.apache.tools.ant.types.Path; 041import org.openimaj.util.array.ArrayUtils; 042 043/** 044 * Helper methods for launching a new JVM and running code within it. Advanced 045 * configuration can be achieved by constructing a {@link ProcessOptions} and 046 * calling {@link #runProcess(ProcessOptions)}. For most other use-cases the 047 * static methods provide a convenient shortcut. 048 * 049 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 050 */ 051public class JavaProcess { 052 /** 053 * Options describing a JVM environment. 054 * 055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 056 * 057 */ 058 public static class ProcessOptions { 059 File workingDir; 060 String classPath; 061 String mainClass; 062 String jvmArgs; 063 File jarFile; 064 String args; 065 boolean cloneVM; 066 boolean outputErr = true; 067 boolean outputOut = true; 068 069 /** 070 * Construct a new {@link ProcessOptions} that inherits the classpath of 071 * the current JVM and will runs the main method of the given class. 072 * 073 * @param clz 074 * the main class 075 */ 076 public ProcessOptions(Class<?> clz) { 077 this.workingDir = new File(System.getProperty("user.dir", new File(".").getAbsolutePath())); 078 this.mainClass = clz.getName(); 079 cloneVM = true; 080 } 081 082 /** 083 * Construct a new {@link ProcessOptions} that will run the given jar 084 * file. 085 * 086 * @param jarFile 087 * the jar file 088 */ 089 public ProcessOptions(File jarFile) { 090 this.workingDir = new File(System.getProperty("user.dir", new File(".").getAbsolutePath())); 091 this.jarFile = jarFile; 092 cloneVM = false; 093 } 094 095 /** 096 * Construct a new {@link ProcessOptions} with the given classpath and 097 * main class. 098 * 099 * @param classPath 100 * the classpath the JVM can search 101 * @param mainClass 102 * the main class to run 103 */ 104 public ProcessOptions(String classPath, String mainClass) { 105 this.workingDir = new File(System.getProperty("user.dir", new File(".").getAbsolutePath())); 106 this.classPath = classPath; 107 this.mainClass = mainClass; 108 cloneVM = false; 109 } 110 111 /** 112 * Set the working directory for the JVM. By default this is set to the 113 * working directory of the current JVM. Passing a null value resets to 114 * the default. 115 * 116 * @param workingDir 117 * the working directory 118 */ 119 public void setWorkingDirectory(File workingDir) { 120 if (workingDir == null) 121 workingDir = new File(System.getProperty("user.dir", new File(".").getAbsolutePath())); 122 123 this.workingDir = workingDir; 124 } 125 126 /** 127 * Set the argument string to pass to the program 128 * 129 * @param args 130 * the argument string 131 */ 132 public void setArgs(String args) { 133 this.args = args; 134 } 135 136 /** 137 * Set the arguments to pass to the program 138 * 139 * @param args 140 * the arguments 141 */ 142 public void setArgs(String[] args) { 143 this.args = ArrayUtils.toString(args, " "); 144 } 145 146 /** 147 * Set the JVM arguments to use when launching the program. 148 * 149 * @param jvmArgs 150 * the JVM arguments. 151 */ 152 public void setJvmArgs(String jvmArgs) { 153 this.jvmArgs = jvmArgs; 154 } 155 156 /** 157 * Set the JVM arguments to use when launching the program. 158 * 159 * @param jvmArgs 160 * the JVM arguments. 161 */ 162 public void setJvmArgs(String[] jvmArgs) { 163 this.jvmArgs = ArrayUtils.toString(jvmArgs, " "); 164 } 165 166 protected void setup(Java java) { 167 if (jarFile != null) { 168 java.setJar(jarFile); 169 } else { 170 if (classPath == null) { 171 java.setClasspath(Path.systemClasspath); 172 } else { 173 java.setClasspath(new Path(java.getProject(), classPath)); 174 } 175 java.setClassname(mainClass); 176 } 177 178 if (jvmArgs != null) { 179 final Argument jvmArg = java.createJvmarg(); 180 jvmArg.setLine(jvmArgs); 181 } 182 183 if (args != null) { 184 final Argument taskArgs = java.createArg(); 185 taskArgs.setLine(args); 186 } 187 188 java.getProject().addBuildListener(new SimpleListener(java, outputOut, outputErr)); 189 190 java.setCloneVm(cloneVM); 191 } 192 193 /** 194 * Echo the {@link System#err} stream from the running JVM 195 * 196 * @param outputErr 197 * true to echo; false to hide 198 */ 199 public void outputErr(boolean outputErr) { 200 this.outputErr = outputErr; 201 } 202 203 /** 204 * Echo the {@link System#out} stream from the running JVM 205 * 206 * @param outputOut 207 * true to echo; false to hide 208 */ 209 public void outputOut(boolean outputOut) { 210 this.outputOut = outputOut; 211 } 212 } 213 214 /** 215 * Run the <code>main</code> main method of the given class in a new JVM. 216 * The new JVM has the same classpath as the invoking JVM. No JVM arguments 217 * are set. 218 * 219 * @param clz 220 * the class to run 221 * @param args 222 * the arguments to pass to the main method 223 * 224 * @throws ProcessException 225 * if an error occurs during execution or the return code is 226 * non-zero. 227 */ 228 public static void runProcess(Class<?> clz, String[] args) throws ProcessException { 229 final ProcessOptions opts = new ProcessOptions(clz); 230 opts.setArgs(args); 231 232 runProcess(opts); 233 } 234 235 /** 236 * Run the <code>main</code> main method of the given class in a new JVM. 237 * The new JVM has the same classpath as the invoking JVM. No JVM arguments 238 * are set. 239 * 240 * @param clz 241 * the class to run 242 * @param argString 243 * the arguments to pass to the main method 244 * 245 * @throws ProcessException 246 * if an error occurs during execution or the return code is 247 * non-zero. 248 */ 249 public static void runProcess(Class<?> clz, String argString) throws ProcessException { 250 final ProcessOptions opts = new ProcessOptions(clz); 251 opts.setArgs(argString); 252 253 runProcess(opts); 254 } 255 256 /** 257 * Run the <code>main</code> main method of the given class in a new JVM. 258 * The new JVM has the same classpath as the invoking JVM. 259 * 260 * @param clz 261 * the class to run 262 * @param jvmArgs 263 * arguments to pass to the JVM 264 * @param args 265 * the arguments to pass to the main method 266 * 267 * @throws ProcessException 268 * if an error occurs during execution or the return code is 269 * non-zero. 270 */ 271 public static void runProcess(Class<?> clz, String[] jvmArgs, String[] args) throws ProcessException { 272 final ProcessOptions opts = new ProcessOptions(clz); 273 opts.setArgs(args); 274 opts.setJvmArgs(jvmArgs); 275 276 runProcess(opts); 277 } 278 279 /** 280 * Run the <code>main</code> main method of the given class in a new JVM. 281 * The new JVM has the same classpath as the invoking JVM. 282 * 283 * @param clz 284 * the class to run 285 * @param jvmArgs 286 * arguments to pass to the JVM 287 * @param argString 288 * the arguments to pass to the main method 289 * 290 * @throws ProcessException 291 * if an error occurs during execution or the return code is 292 * non-zero. 293 */ 294 public static void runProcess(Class<?> clz, String jvmArgs, String argString) throws ProcessException { 295 final ProcessOptions opts = new ProcessOptions(clz); 296 opts.setArgs(argString); 297 opts.setJvmArgs(jvmArgs); 298 299 runProcess(opts); 300 } 301 302 /** 303 * Run the process described by the given {@link ProcessOptions} in a 304 * separate JVM and wait for it to exit. 305 * 306 * @param op 307 * the {@link ProcessOptions}. 308 * @throws ProcessException 309 * if an error occurs during execution or the return code is 310 * non-zero. 311 */ 312 public static void runProcess(ProcessOptions op) throws ProcessException { 313 final Project project = new Project(); 314 project.setBaseDir(op.workingDir); 315 project.init(); 316 project.fireBuildStarted(); 317 318 BuildException caught = null; 319 320 try { 321 final Java java = new Java(); 322 java.setNewenvironment(true); 323 java.setTaskName("runjava"); 324 java.setProject(project); 325 java.setFork(true); 326 java.setFailonerror(true); 327 328 op.setup(java); 329 330 java.init(); 331 java.execute(); 332 } catch (final BuildException e) { 333 caught = e; 334 throw new ProcessException(caught); 335 } finally { 336 project.log("finished"); 337 project.fireBuildFinished(caught); 338 } 339 } 340 341 private static class SimpleListener implements BuildListener { 342 Java proc; 343 boolean outputOut; 344 boolean outputErr; 345 346 SimpleListener(Java proc, boolean outputOut, boolean outputErr) { 347 this.proc = proc; 348 this.outputOut = outputOut; 349 this.outputErr = outputErr; 350 } 351 352 @Override 353 public void taskStarted(BuildEvent event) { 354 } 355 356 @Override 357 public void taskFinished(BuildEvent event) { 358 } 359 360 @Override 361 public void targetStarted(BuildEvent event) { 362 } 363 364 @Override 365 public void targetFinished(BuildEvent event) { 366 } 367 368 @Override 369 public void messageLogged(BuildEvent event) { 370 if (proc != event.getSource()) 371 return; 372 373 if (outputOut && event.getPriority() == Project.MSG_INFO) 374 System.out.println(event.getMessage()); 375 else if (outputErr && event.getPriority() == Project.MSG_ERR) 376 System.err.println(event.getMessage()); 377 } 378 379 @Override 380 public void buildStarted(BuildEvent event) { 381 } 382 383 @Override 384 public void buildFinished(BuildEvent event) { 385 } 386 } 387}