/**
* JLibs: Common Utilities for Java
* Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com>
*
* Modified by Shilad Sen to remove additional dependencies.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.wikibrain.utils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Similar to {@link ProcessBuilder}, this class makes the java process creation easier.
* <p>
* This class simply manages collection of java process attributes and help your to create command out of it.
* <pre class="prettyprint">
* JavaProcessBuilder jvm = new JavaProcessBuilder();
* </pre>
* {@code JavaProcessBuilder} is pre-configured with current java home and current working directory initially.<br>
* you can change them as below:
* <pre class="prettyprint">
* jvm.javaHome(new File("c:/jdk5")); // to configure java home
* jvm.workingDir(new File("c:/myProject")); // to configure working directory
* </pre>
* To configure various attributes:
* <pre class="prettyprint">
* // to configure classpath
* jvm.classpath("lib/jlibs-core.jar") // relative path from configured working dir
* .classpath(new File("c:/myproject/lib/jlibs-xml.jar");
*
* // to get configured classpath
* List<File> classpath = jvm.classpath();
*
* // to configure additional classpath
* jvm.endorsedDir("lib/endorsed")
* .extDir("lib/ext")
* .libraryPath("lib/native")
* .bootClasspath("lib/boot/xerces.jar")
* .appendBootClasspath("lib/boot/xalan.jar")
* .prependBootClasspath("lib/boot/dom.jar");
*
* // to configure System Properties
* jvm.systemProperty("myprop", "myvalue")
* .systemProperty("myflag");
*
* // to configure heap and vmtype
* jvm.initialHeap(512); // or jvm.initialHeap("512m");
* jvm.maxHeap(1024); // or jvm.maxHeap("1024m");
* jvm.client(); // to use -client
* jvm.server(); // to use -server
*
* // to configure remote debugging
* jvm.debugPort(7000)
* .debugSuspend(true);
*
* // to configure any additional jvm args
* jvm.jvmArg("-Xgc:somealgo");
*
* // to configure mainclass and its arguments
* jvm.mainClass("example.MyTest")
* .arg("-xvf")
* .arg("testDir");
* </pre>
* To get the created command:
* <pre class="prettyprint">
* String command[] = jvm.command();
* </pre>
* Any relative paths specified, will get resolved relative to working directory during command creation.
* <p>
* To launch it:
* <pre class="prettyprint">
* Process p = jvm.{@link #launch(java.io.OutputStream, java.io.OutputStream) launch}(System.out, System.err);
* </pre>
* the two arguments to {@link #launch(java.io.OutputStream, java.io.OutputStream) launch(...)} specify to which process output and error streams to be redirected.
* These arguments can be null, if you don't want them to be redirected.
*
* @author Santhosh Kumar T
*/
public class JavaProcessBuilder{
/*-------------------------------------------------[ java-home ]---------------------------------------------------*/
private File javaHome = new File(System.getProperty("java.home"));
public JavaProcessBuilder javaHome(File javaHome){
this.javaHome = javaHome;
return this;
}
public File javaHome(){
return javaHome;
}
/*-------------------------------------------------[ working-dir ]---------------------------------------------------*/
private File workingDir = new File(System.getProperty("user.dir"));
public JavaProcessBuilder workingDir(String dir){
return workingDir(new File(dir));
}
public JavaProcessBuilder workingDir(File dir){
workingDir = dir;
return this;
}
public File workingDir(){
return workingDir;
}
/*-------------------------------------------------[ classpath ]---------------------------------------------------*/
private List<File> classpath = new ArrayList<File>();
public JavaProcessBuilder classpath(String resource){
return classpath(new File(resource));
}
public JavaProcessBuilder classpath(File resource){
classpath.add(resource);
return this;
}
public List<File> classpath(){
return classpath;
}
/*-------------------------------------------------[ endorsed-dirs ]---------------------------------------------------*/
private List<File> endorsedDirs = new ArrayList<File>();
public JavaProcessBuilder endorsedDir(String dir){
return endorsedDir(new File(dir));
}
public JavaProcessBuilder endorsedDir(File dir){
endorsedDirs.add(dir);
return this;
}
public List<File> endorsedDirs(){
return endorsedDirs;
}
/*-------------------------------------------------[ ext-dirs ]---------------------------------------------------*/
private List<File> extDirs = new ArrayList<File>();
public JavaProcessBuilder extDir(String dir){
return extDir(new File(dir));
}
public JavaProcessBuilder extDir(File dir){
extDirs.add(dir);
return this;
}
public List<File> extDirs(){
return extDirs;
}
/*-------------------------------------------------[ library-path ]---------------------------------------------------*/
private List<File> libraryPath = new ArrayList<File>();
public JavaProcessBuilder libraryPath(String dir){
return libraryPath(new File(dir));
}
public JavaProcessBuilder libraryPath(File dir){
libraryPath.add(dir);
return this;
}
public List<File> libraryPath(){
return libraryPath;
}
/*-------------------------------------------------[ boot-classpath ]---------------------------------------------------*/
private List<File> bootClasspath = new ArrayList<File>();
public JavaProcessBuilder bootClasspath(String resource){
return bootClasspath(new File(resource));
}
public JavaProcessBuilder bootClasspath(File resource){
bootClasspath.add(resource);
return this;
}
public List<File> bootClasspath(){
return bootClasspath;
}
/*-------------------------------------------------[ append-boot-classpath ]---------------------------------------------------*/
private List<File> appendBootClasspath = new ArrayList<File>();
public JavaProcessBuilder appendBootClasspath(String resource){
return appendBootClasspath(new File(resource));
}
public JavaProcessBuilder appendBootClasspath(File resource){
appendBootClasspath.add(resource);
return this;
}
public List<File> appendBootClasspath(){
return appendBootClasspath;
}
/*-------------------------------------------------[ prepend-boot-classpath ]---------------------------------------------------*/
private List<File> prependBootClasspath = new ArrayList<File>();
public JavaProcessBuilder prependBootClasspath(String resource){
return prependBootClasspath(new File(resource));
}
public JavaProcessBuilder prependBootClasspath(File resource){
prependBootClasspath.add(resource);
return this;
}
public List<File> prependBootClasspath(){
return prependBootClasspath;
}
/*-------------------------------------------------[ system-properties ]---------------------------------------------------*/
private Map<String, String> systemProperties = new HashMap<String, String>();
public JavaProcessBuilder systemProperty(String name, String value){
systemProperties.put(name, value);
return this;
}
public JavaProcessBuilder systemProperty(String name){
return systemProperty(name, null);
}
public Map<String, String> systemProperties(){
return systemProperties;
}
/*-------------------------------------------------[ initial-heap ]---------------------------------------------------*/
private String initialHeap;
public JavaProcessBuilder initialHeap(int mb){
return initialHeap(mb+"m");
}
public JavaProcessBuilder initialHeap(String size){
initialHeap = size;
return this;
}
public String initialHeap(){
return initialHeap;
}
/*-------------------------------------------------[ max-heap ]---------------------------------------------------*/
private String maxHeap;
public JavaProcessBuilder maxHeap(int mb){
return maxHeap(mb+"m");
}
public JavaProcessBuilder maxHeap(String size){
maxHeap = size;
return this;
}
public String maxHeap(){
return maxHeap;
}
/*-------------------------------------------------[ vm-type ]---------------------------------------------------*/
private String vmType;
public JavaProcessBuilder client(){
vmType = "-client";
return this;
}
public JavaProcessBuilder server(){
vmType = "-server";
return this;
}
public String vmType(){
return vmType;
}
/*-------------------------------------------------[ jvm-args ]---------------------------------------------------*/
private List<String> jvmArgs = new ArrayList<String>();
public JavaProcessBuilder jvmArg(String arg){
jvmArgs.add(arg);
return this;
}
public List<String> jvmArgs(){
return jvmArgs;
}
/*-------------------------------------------------[ main-class ]---------------------------------------------------*/
private String mainClass;
public JavaProcessBuilder mainClass(String mainClass){
this.mainClass = mainClass;
return this;
}
public String mainClass(){
return mainClass;
}
/*-------------------------------------------------[ debug ]---------------------------------------------------*/
private boolean debugSuspend;
public JavaProcessBuilder debugSuspend(boolean suspend){
this.debugSuspend = suspend;
return this;
}
public boolean debugSuspend(){
return debugSuspend;
}
private int debugPort = -1;
public JavaProcessBuilder debugPort(int port){
this.debugPort = port;
return this;
}
public int debugPort(){
return debugPort;
}
/*-------------------------------------------------[ arguments ]---------------------------------------------------*/
private List<String> args = new ArrayList<String>();
public JavaProcessBuilder arg(String arg){
args.add(arg);
return this;
}
public List<String> args(){
return args;
}
/*-------------------------------------------------[ Command ]---------------------------------------------------*/
private static String toString(File fromDir, Iterable<File> files) throws IOException{
StringBuilder buff = new StringBuilder();
for(File file: files){
if(buff.length()>0)
buff.append(File.pathSeparator);
if(fromDir==null)
buff.append(file.getCanonicalPath());
else
buff.append(WpIOUtils.getRelativePath(fromDir, file));
}
return buff.toString();
}
/** Returns command with all its arguments */
public String[] command() throws IOException{
List<String> cmd = new ArrayList<String>();
String executable = javaHome.getCanonicalPath()+File.separator+"bin"+File.separator+"java";
if(OS.get().isWindows())
executable += ".exe";
cmd.add(executable);
String path = toString(workingDir, prependBootClasspath);
if(path.length()>0)
cmd.add("-Xbootclasspath/p:"+path);
path = toString(workingDir, bootClasspath);
if(path.length()>0)
cmd.add("-Xbootclasspath:"+path);
path = toString(workingDir, appendBootClasspath);
if(path.length()>0)
cmd.add("-Xbootclasspath/a:"+path);
path = toString(workingDir, classpath);
if(path.length()>0){
// Hack to prevent windows command line arg from being too long. Send this through environment.
// cmd.add("-classpath");
// cmd.add(path);
}
path = toString(workingDir, extDirs);
if(path.length()>0)
cmd.add("-Djava.ext.dirs="+path);
path = toString(workingDir, endorsedDirs);
if(path.length()>0)
cmd.add("-Djava.endorsed.dirs="+path);
path = toString(workingDir, libraryPath);
if(path.length()>0)
cmd.add("-Djava.library.path="+path);
for(Map.Entry<String, String> prop: systemProperties.entrySet()){
if(prop.getValue()==null)
cmd.add("-D"+prop.getKey());
else
cmd.add("-D"+prop.getKey()+"="+prop.getValue());
}
if(initialHeap!=null)
cmd.add("-Xms"+initialHeap);
if(maxHeap!=null)
cmd.add("-Xmx"+maxHeap);
if(vmType!=null)
cmd.add(vmType);
if(debugPort!=-1){
cmd.add("-Xdebug");
cmd.add("-Xnoagent");
cmd.add("-Xrunjdwp:transport=dt_socket,server=y,suspend="+(debugSuspend?'y':'n')+",address="+debugPort);
}
cmd.addAll(jvmArgs);
if(mainClass!=null){
cmd.add(mainClass);
cmd.addAll(args);
}
return cmd.toArray(new String[cmd.size()]);
}
/**
* launches jvm with current configuration.
*
* note that, the streams passed are not closed automatically.
*
* @param output outputstream to which process's input stream to be redirected.
* if null, it is not redirected
* @param error outputstream to which process's error stream to be redirected.
* if null, it is not redirected
* @return the process created
*
* @exception IOException if an I/O error occurs.
*/
public Process launch(final OutputStream output, final OutputStream error) throws IOException{
Map<String, String> envMap = System.getenv();
String env[] = new String[envMap.size()+1];
int i = 0;
boolean found = false;
for (String name : envMap.keySet()) {
String value = envMap.get(name);
if (name.equalsIgnoreCase("CLASSPATH")) {
value = toString(workingDir, classpath);
found = true;
}
env[i++] = name + "=" + value;
}
if (found) {
env = ArrayUtils.subarray(env, 0, env.length - 1);
} else {
env[i] = "CLASSPATH=" + toString(workingDir, classpath);
}
final Process process = Runtime.getRuntime().exec(command(), env, workingDir);
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(process.getInputStream(), output);
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} } ).start();
new Thread(new Runnable() {public void run() {
try {
IOUtils.copy(process.getErrorStream(), error);
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
} } ).start();
return process;
}
}