|
|
@@ -836,8 +836,8 @@ public class LangUtil { |
|
|
|
|
|
|
|
/** check if input contains any packages to elide. */ |
|
|
|
public static class StringChecker { |
|
|
|
static StringChecker TEST_PACKAGES = new StringChecker( new String[] |
|
|
|
{ "org.aspectj.testing.", |
|
|
|
static StringChecker TEST_PACKAGES = new StringChecker(new String[] |
|
|
|
{ "org.aspectj.testing", |
|
|
|
"org.eclipse.jdt.internal.junit", |
|
|
|
"junit.framework.", |
|
|
|
"org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" |
|
|
@@ -860,5 +860,465 @@ public class LangUtil { |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Gen classpath. |
|
|
|
* @param bootclasspath |
|
|
|
* @param classpath |
|
|
|
* @param classesDir |
|
|
|
* @param outputJar |
|
|
|
* @return String combining classpath elements |
|
|
|
*/ |
|
|
|
public static String makeClasspath( // XXX dumb implementation |
|
|
|
String bootclasspath, |
|
|
|
String classpath, |
|
|
|
String classesDir, |
|
|
|
String outputJar) { |
|
|
|
StringBuffer sb = new StringBuffer(); |
|
|
|
addIfNotEmpty(bootclasspath, sb, File.pathSeparator); |
|
|
|
addIfNotEmpty(classpath, sb, File.pathSeparator); |
|
|
|
if (!addIfNotEmpty(classesDir, sb, File.pathSeparator)) { |
|
|
|
addIfNotEmpty(outputJar, sb, File.pathSeparator); |
|
|
|
} |
|
|
|
return sb.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* @param input ignored if null |
|
|
|
* @param sink the StringBuffer to add input to - return false if null |
|
|
|
* @param delimiter the String to append to input when added - ignored if empty |
|
|
|
* @return true if input + delimiter added to sink |
|
|
|
*/ |
|
|
|
private static boolean addIfNotEmpty(String input, StringBuffer sink, String delimiter) { |
|
|
|
if (LangUtil.isEmpty(input) || (null == sink)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
sink.append(input); |
|
|
|
if (!LangUtil.isEmpty(delimiter)) { |
|
|
|
sink.append(delimiter); |
|
|
|
} |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Create or initialize a process controller to run |
|
|
|
* a process in another VM asynchronously. |
|
|
|
* @param controller the ProcessController to initialize, if not null |
|
|
|
* @param classpath |
|
|
|
* @param mainClass |
|
|
|
* @param args |
|
|
|
* @return initialized ProcessController |
|
|
|
*/ |
|
|
|
public static ProcessController makeProcess( |
|
|
|
ProcessController controller, |
|
|
|
String classpath, |
|
|
|
String mainClass, |
|
|
|
String[] args) { |
|
|
|
File java = LangUtil.getJavaExecutable(classpath); |
|
|
|
ArrayList cmd = new ArrayList(); |
|
|
|
cmd.add(java.getAbsolutePath()); |
|
|
|
cmd.add("-classpath"); |
|
|
|
cmd.add(classpath); |
|
|
|
cmd.add(mainClass); |
|
|
|
if (!LangUtil.isEmpty(args)) { |
|
|
|
cmd.addAll(Arrays.asList(args)); |
|
|
|
} |
|
|
|
String[] command = (String[]) cmd.toArray(new String[0]); |
|
|
|
if (null == controller) { |
|
|
|
controller = new ProcessController(); |
|
|
|
} |
|
|
|
controller.init(command, mainClass); |
|
|
|
return controller; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a process to run asynchronously. |
|
|
|
* @param controller if not null, initialize this one |
|
|
|
* @param command the String[] command to run |
|
|
|
* @param controller the ProcessControl for streams and results |
|
|
|
*/ |
|
|
|
public static ProcessController makeProcess( // not needed? |
|
|
|
ProcessController controller, |
|
|
|
String[] command, |
|
|
|
String label) { |
|
|
|
if (null == controller) { |
|
|
|
controller = new ProcessController(); |
|
|
|
} |
|
|
|
controller.init(command, label); |
|
|
|
return controller; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Find java executable File path from java.home system property. |
|
|
|
* @param classpath ignored for now |
|
|
|
* @return File associated with the java command, or null if not found. |
|
|
|
*/ |
|
|
|
public static File getJavaExecutable(String classpath) { // XXX weak |
|
|
|
String javaHome = null; |
|
|
|
File result = null; |
|
|
|
//java.home |
|
|
|
// java.class.path |
|
|
|
// java.ext.dirs |
|
|
|
try { |
|
|
|
javaHome = System.getProperty("java.home"); |
|
|
|
} catch (Throwable t) { |
|
|
|
// ignore |
|
|
|
} |
|
|
|
if (null != javaHome) { |
|
|
|
File binDir = new File(javaHome, "bin"); |
|
|
|
if (binDir.isDirectory() && binDir.canRead()) { |
|
|
|
String[] execs = new String[] { "java", "java.exe" }; |
|
|
|
for (int i = 0; i < execs.length; i++) { |
|
|
|
result = new File(binDir, execs[i]); |
|
|
|
if (result.canRead()) { |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Handle an external process asynchrously. |
|
|
|
* <code>start()</code> launches a main thread to wait for the process |
|
|
|
* and pipes streams (in child threads) through to the corresponding |
|
|
|
* streams (e.g., the process System.err to this System.err). |
|
|
|
* This can complete normally, by exception, or on demand by a client. |
|
|
|
* Clients can implement <code>doCompleting(..)</code> to get notice |
|
|
|
* when the process completes. |
|
|
|
* <p>The following sample code creates a process with a completion |
|
|
|
* callback starts it, and some time later retries the process. |
|
|
|
* <pre> |
|
|
|
* LangUtil.ProcessController controller |
|
|
|
* = new LangUtil.ProcessController() { |
|
|
|
* protected void doCompleting(LangUtil.ProcessController.Thrown thrown, |
|
|
|
* int result) { |
|
|
|
* // signal result |
|
|
|
* } |
|
|
|
* }; |
|
|
|
* controller.init(new String[] { "java", "-version" }, "java version"); |
|
|
|
* controller.start(); |
|
|
|
* // some time later... |
|
|
|
* // retry... |
|
|
|
* if (!controller.completed()) { |
|
|
|
* controller.stop(); |
|
|
|
* controller.reinit(); |
|
|
|
* controller.start(); |
|
|
|
* } |
|
|
|
* </pre> |
|
|
|
* <u>warning</u>: Currently this does not close the input or output |
|
|
|
* streams, since doing so prevents their use later. |
|
|
|
*/ |
|
|
|
public static class ProcessController { |
|
|
|
/* |
|
|
|
* XXX not verified thread-safe, but should be. Known problems: |
|
|
|
* - user stops (completed = true) then exception thrown |
|
|
|
* from destroying process (stop() expects !completed) |
|
|
|
* ... |
|
|
|
*/ |
|
|
|
private String[] command; |
|
|
|
private String label; |
|
|
|
|
|
|
|
private boolean init; |
|
|
|
private boolean started; |
|
|
|
private boolean completed; |
|
|
|
/** if true, stopped by user when not completed */ |
|
|
|
private boolean userStopped; |
|
|
|
|
|
|
|
private Process process; |
|
|
|
private FileUtil.Pipe errStream; |
|
|
|
private FileUtil.Pipe outStream; |
|
|
|
private FileUtil.Pipe inStream; |
|
|
|
|
|
|
|
private int result; |
|
|
|
private Thrown thrown; |
|
|
|
|
|
|
|
public ProcessController() { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Permit re-running using the same command |
|
|
|
* if this is not started or if completed. |
|
|
|
* Can also call this when done with results to release |
|
|
|
* references associated with results |
|
|
|
* (e.g., stack traces). |
|
|
|
*/ |
|
|
|
public final void reinit() { |
|
|
|
if (!init) { |
|
|
|
throw new IllegalStateException("must init(..) before reinit()"); |
|
|
|
} |
|
|
|
if (started && !completed) { |
|
|
|
throw new IllegalStateException("not completed - do stop()"); |
|
|
|
} |
|
|
|
// init everything but command and label |
|
|
|
started = false; |
|
|
|
completed = false; |
|
|
|
result = Integer.MIN_VALUE; |
|
|
|
thrown = null; |
|
|
|
process = null; |
|
|
|
errStream = null; |
|
|
|
outStream = null; |
|
|
|
inStream = null; |
|
|
|
} |
|
|
|
|
|
|
|
public final void init( |
|
|
|
String classpath, |
|
|
|
String mainClass, |
|
|
|
String[] args) { |
|
|
|
init(LangUtil.getJavaExecutable(classpath), |
|
|
|
classpath, mainClass, args); |
|
|
|
} |
|
|
|
|
|
|
|
public final void init(File java, |
|
|
|
String classpath, |
|
|
|
String mainClass, |
|
|
|
String[] args) { |
|
|
|
LangUtil.throwIaxIfNull(java, "java"); |
|
|
|
LangUtil.throwIaxIfNull(mainClass, "mainClass"); |
|
|
|
LangUtil.throwIaxIfNull(args, "args"); |
|
|
|
ArrayList cmd = new ArrayList(); |
|
|
|
cmd.add(java.getAbsolutePath()); |
|
|
|
cmd.add("-classpath"); |
|
|
|
cmd.add(classpath); |
|
|
|
cmd.add(mainClass); |
|
|
|
if (!LangUtil.isEmpty(args)) { |
|
|
|
cmd.addAll(Arrays.asList(args)); |
|
|
|
} |
|
|
|
init((String[]) cmd.toArray(new String[0]), mainClass); |
|
|
|
} |
|
|
|
|
|
|
|
public final void init(String[] command, String label) { |
|
|
|
LangUtil.throwIaxIfNotAssignable(command, String.class, "command"); |
|
|
|
if (1 > command.length) { |
|
|
|
throw new IllegalArgumentException("empty command"); |
|
|
|
} |
|
|
|
this.command = new String[command.length]; |
|
|
|
System.arraycopy(command, 0, this.command, 0, command.length); |
|
|
|
this.label = LangUtil.isEmpty(label) ? command[0] : label; |
|
|
|
this.init = true; |
|
|
|
reinit(); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Start running the process and pipes asynchronously. |
|
|
|
* @return Thread started or null if unable to start thread |
|
|
|
* (results available via <code>getThrown()</code>, etc.) |
|
|
|
*/ |
|
|
|
public final Thread start() { |
|
|
|
if (!init) { |
|
|
|
throw new IllegalStateException("not initialized"); |
|
|
|
} |
|
|
|
synchronized (this) { |
|
|
|
if (started) { |
|
|
|
throw new IllegalStateException("already started"); |
|
|
|
} |
|
|
|
started = true; |
|
|
|
} |
|
|
|
try { |
|
|
|
process = Runtime.getRuntime().exec(command); |
|
|
|
} catch (IOException e) { |
|
|
|
stop(e, Integer.MIN_VALUE); |
|
|
|
return null; |
|
|
|
} |
|
|
|
errStream = new FileUtil.Pipe(process.getErrorStream(), System.err); |
|
|
|
outStream = new FileUtil.Pipe(process.getInputStream(), System.out); |
|
|
|
inStream = new FileUtil.Pipe(System.in, process.getOutputStream()); |
|
|
|
// start 4 threads, process & pipes for in, err, out |
|
|
|
Runnable processRunner = new Runnable() { |
|
|
|
public void run() { |
|
|
|
Throwable thrown = null; |
|
|
|
int result = Integer.MIN_VALUE; |
|
|
|
try { |
|
|
|
// pipe threads are children |
|
|
|
new Thread(errStream).start(); |
|
|
|
new Thread(outStream).start(); |
|
|
|
new Thread(inStream).start(); |
|
|
|
process.waitFor(); |
|
|
|
result = process.exitValue(); |
|
|
|
} catch (Throwable e) { |
|
|
|
thrown = e; |
|
|
|
} finally { |
|
|
|
stop(thrown, result); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
Thread result = new Thread(processRunner, label); |
|
|
|
result.start(); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Destroy any process, stop any pipes. |
|
|
|
* This waits for the pipes to clear (reading until |
|
|
|
* no more input is available), but does not wait |
|
|
|
* for the input stream for the pipe to close |
|
|
|
* (i.e., not waiting for end-of-file on input stream). |
|
|
|
*/ |
|
|
|
public final synchronized void stop() { |
|
|
|
if (completed) { |
|
|
|
return; |
|
|
|
} |
|
|
|
userStopped = true; |
|
|
|
stop(null, Integer.MIN_VALUE); |
|
|
|
} |
|
|
|
|
|
|
|
public final String[] getCommand() { |
|
|
|
String[] toCopy = command; |
|
|
|
if (LangUtil.isEmpty(toCopy)) { |
|
|
|
return new String[0]; |
|
|
|
} |
|
|
|
String[] result = new String[toCopy.length]; |
|
|
|
System.arraycopy(toCopy, 0, result, 0, result.length); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
public final boolean completed() { |
|
|
|
return completed; |
|
|
|
} |
|
|
|
|
|
|
|
public final boolean started() { |
|
|
|
return started; |
|
|
|
} |
|
|
|
|
|
|
|
public final boolean userStopped() { |
|
|
|
return userStopped; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Get any Throwable thrown. |
|
|
|
* Note that the process can complete normally (with a valid return |
|
|
|
* value), at the same time the pipes throw exceptions, |
|
|
|
* and that this may return some exceptions even if the process |
|
|
|
* is not complete. |
|
|
|
* @return null if not complete or |
|
|
|
* Thrown containing exceptions thrown by the process and streams. |
|
|
|
*/ |
|
|
|
public final Thrown getThrown() { // cache this |
|
|
|
return makeThrown(null); |
|
|
|
} |
|
|
|
|
|
|
|
public final int getResult() { |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Subclasses implement this to get synchronous notice of completion. |
|
|
|
* All pipes and processes should be complete at this time. |
|
|
|
* To get the exceptions thrown for the pipes, use |
|
|
|
* <code>getThrown()</code>. |
|
|
|
* If there is an exception, the process completed abruptly |
|
|
|
* (including side-effects of the user halting the process). |
|
|
|
* If <code>userStopped()</code> is true, then some client asked |
|
|
|
* that the process be destroyed using <code>stop()</code>. |
|
|
|
* Otherwise, the result code should be the result value |
|
|
|
* returned by the process. |
|
|
|
* @param thrown same as <code>getThrown().fromProcess</code>. |
|
|
|
* @param result same as <code>getResult()</code> |
|
|
|
* @see getThrown() |
|
|
|
* @see getResult() |
|
|
|
* @see stop() |
|
|
|
*/ |
|
|
|
protected void doCompleting(Thrown thrown, int result) { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Handle termination (on-demand, abrupt, or normal) |
|
|
|
* by destroying and/or halting process and pipes. |
|
|
|
* @param thrown ignored if null |
|
|
|
* @param result ignored if Integer.MIN_VALUE |
|
|
|
*/ |
|
|
|
private final synchronized void stop(Throwable thrown, int result) { |
|
|
|
if (completed) { |
|
|
|
throw new IllegalStateException("already completed"); |
|
|
|
} else if (null != this.thrown) { |
|
|
|
throw new IllegalStateException("already set thrown: " + thrown); |
|
|
|
} |
|
|
|
// assert null == this.thrown |
|
|
|
this.thrown = makeThrown(thrown); |
|
|
|
if (null != process) { |
|
|
|
process.destroy(); |
|
|
|
} |
|
|
|
if (null != inStream) { |
|
|
|
inStream.halt(true,true); |
|
|
|
inStream = null; |
|
|
|
} |
|
|
|
if (null != outStream) { |
|
|
|
outStream.halt(true, true); |
|
|
|
outStream = null; |
|
|
|
} |
|
|
|
if (null != errStream) { |
|
|
|
errStream.halt(true, true); |
|
|
|
errStream = null; |
|
|
|
} |
|
|
|
if (Integer.MIN_VALUE != result) { |
|
|
|
this.result = result; |
|
|
|
} |
|
|
|
completed = true; |
|
|
|
doCompleting(this.thrown, result); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Create snapshot of Throwable's thrown. |
|
|
|
* @param thrown ignored if null or if this.thrown is not null |
|
|
|
*/ |
|
|
|
private final synchronized Thrown makeThrown(Throwable processThrown) { |
|
|
|
if (null != thrown) { |
|
|
|
return thrown; |
|
|
|
} |
|
|
|
return new Thrown( |
|
|
|
processThrown, |
|
|
|
(null == outStream ? null : outStream.getThrown()), |
|
|
|
(null == errStream ? null : errStream.getThrown()), |
|
|
|
(null == inStream ? null : inStream.getThrown()) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
public static class Thrown { |
|
|
|
public final Throwable fromProcess; |
|
|
|
public final Throwable fromErrPipe; |
|
|
|
public final Throwable fromOutPipe; |
|
|
|
public final Throwable fromInPipe; |
|
|
|
/** true only if some Throwable is not null */ |
|
|
|
public final boolean thrown; |
|
|
|
private Thrown( |
|
|
|
Throwable fromProcess, |
|
|
|
Throwable fromOutPipe, |
|
|
|
Throwable fromErrPipe, |
|
|
|
Throwable fromInPipe) { |
|
|
|
this.fromProcess = fromProcess; |
|
|
|
this.fromErrPipe = fromErrPipe; |
|
|
|
this.fromOutPipe = fromOutPipe; |
|
|
|
this.fromInPipe = fromInPipe; |
|
|
|
thrown = ((null != fromProcess) |
|
|
|
|| (null != fromInPipe) |
|
|
|
|| (null != fromOutPipe) |
|
|
|
|| (null != fromErrPipe)); |
|
|
|
} |
|
|
|
|
|
|
|
public String toString() { |
|
|
|
StringBuffer sb = new StringBuffer(); |
|
|
|
append(sb, fromProcess, "process"); |
|
|
|
append(sb, fromOutPipe, " stdout"); |
|
|
|
append(sb, fromErrPipe, " stderr"); |
|
|
|
append(sb, fromInPipe, " stdin"); |
|
|
|
if (0 == sb.length()) { |
|
|
|
return "Thrown (none)"; |
|
|
|
} else { |
|
|
|
return sb.toString(); |
|
|
|
} |
|
|
|
} |
|
|
|
private void append(StringBuffer sb, Throwable thrown, String label) { |
|
|
|
if (null != thrown) { |
|
|
|
sb.append("from " + label + ": "); |
|
|
|
sb.append(LangUtil.renderExceptionShort(thrown)); |
|
|
|
sb.append(LangUtil.EOL); |
|
|
|
} |
|
|
|
} |
|
|
|
} // class Thrown |
|
|
|
} // class ProcessController |
|
|
|
} |
|
|
|
|
|
|
|
|