aQute.libg.command.Command Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of biz.aQute.bnd Show documentation
Show all versions of biz.aQute.bnd Show documentation
This command line utility is the Swiss army knife of OSGi. It provides you with a breadth of tools to understand and manage OSGi based systems. This project basically uses bndlib.
package aQute.libg.command;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.lib.io.IO;
import aQute.libg.qtokens.QuotedTokenizer;
import aQute.service.reporter.Reporter;
public class Command {
private final static Logger logger = LoggerFactory.getLogger(Command.class);
private final static int TIMEDOUT = 126 - 3;
boolean trace;
Reporter reporter;
List arguments = new ArrayList<>();
Map variables = new LinkedHashMap<>();
long timeout = 0;
File cwd = new File("").getAbsoluteFile();
volatile Process process;
volatile boolean timedout;
private boolean useThreadForInput;
public Command(String fullCommand) {
this();
full(fullCommand);
}
public Command() {}
public int execute(Appendable stdout, Appendable stderr) throws Exception {
return execute((InputStream) null, stdout, stderr);
}
public int execute(String input, Appendable stdout, Appendable stderr) throws Exception {
InputStream in = input == null ? null : IO.stream(input, UTF_8);
return execute(in, stdout, stderr);
}
public static boolean needsWindowsQuoting(String s) {
int len = s.length();
if (len == 0) // empty string have to be quoted
return true;
for (int i = 0; i < len; i++) {
switch (s.charAt(i)) {
case ' ' :
case '\t' :
case '\\' :
case '"' :
return true;
}
}
return false;
}
private static final Pattern escapedDoubleQuote = Pattern.compile("([\\\\]*)\"");
private static final Pattern trailingBackslash = Pattern.compile("([\\\\]*)\\z");
public static String windowsQuote(String s) {
if (!needsWindowsQuoting(s))
return s;
s = escapedDoubleQuote.matcher(s)
.replaceAll("$1$1\\\\\"");
s = trailingBackslash.matcher(s)
.replaceAll("$1$1");
return "\"" + s + "\"";
}
public int execute(final InputStream in, Appendable stdout, Appendable stderr) throws Exception {
// Test System.in once since other threads can change during execution
final boolean systemIn = in == System.in;
logger.debug("executing cmd: {}", getArguments());
ProcessBuilder p = new ProcessBuilder(getArguments());
if (IO.isWindows()) {
// [cs] Arguments on windows aren't processed correctly.
// So we need to perform some escaping.
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6511002
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
p.command(p.command()
.stream()
.map(Command::windowsQuote)
.collect(toList()));
}
p.environment()
.putAll(variables);
p.directory(cwd);
if (systemIn) {
p.redirectInput(ProcessBuilder.Redirect.INHERIT);
}
Process process = this.process = p.start();
// Make sure the command will not linger when we go
Thread hook = new Thread(process::destroy, getArguments().toString());
Runtime.getRuntime()
.addShutdownHook(hook);
ScheduledExecutorService scheduler = null;
if (timeout != 0) {
scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> {
timedout = true;
process.destroy();
}, timeout, TimeUnit.MILLISECONDS);
}
final OutputStream stdin = process.getOutputStream();
Thread inThread = null;
final AtomicBoolean finished = new AtomicBoolean(false);
try (InputStream out = process.getInputStream(); InputStream err = process.getErrorStream()) {
Thread outThread = new Thread(collector(out, stdout), "Write Output Thread");
outThread.setDaemon(true);
Thread errThread = new Thread(collector(err, stderr), "Write Error Thread");
errThread.setDaemon(true);
outThread.start();
errThread.start();
if (in != null) {
if (systemIn || useThreadForInput) {
inThread = new Thread(() -> {
try {
while (!finished.get()) {
int n = in.available();
if (n == 0) {
Thread.sleep(100);
} else {
int c = in.read();
if (c < 0) {
break; // finally will close stdin
}
stdin.write(c);
if (c == '\n') {
stdin.flush();
}
}
}
} catch (InterruptedIOException | InterruptedException e) {
// Ignore here
} catch (Exception e) {
logger.debug("stdin copy exception in thread", e);
} finally {
IO.close(stdin);
}
}, "Read Input Thread");
inThread.setDaemon(true);
inThread.start();
} else {
try {
IO.copy(in, stdin);
} catch (Exception e) {
logger.debug("stdin copy exception", e);
} finally {
IO.close(stdin);
}
}
}
logger.debug("exited process");
errThread.join();
outThread.join();
logger.debug("stdout/stderr streams have finished");
} finally {
if (scheduler != null) {
scheduler.shutdownNow();
}
Runtime.getRuntime()
.removeShutdownHook(hook);
}
int exitValue = process.waitFor();
finished.set(true);
if (inThread != null) {
if (!systemIn) {
IO.close(in);
}
inThread.interrupt();
}
logger.debug("cmd {} executed with result={}, result: {}/{}, timedout={}", getArguments(), exitValue, stdout,
stderr, timedout);
if (timedout)
return TIMEDOUT;
return exitValue;
}
public void add(String arg) {
arguments.add(arg);
}
public void add(String... args) {
Collections.addAll(arguments, args);
}
public void addAll(Collection args) {
arguments.addAll(args);
}
public void setTimeout(long duration, TimeUnit unit) {
timeout = unit.toMillis(duration);
}
public void setTrace() {
this.trace = true;
}
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
public void setCwd(File dir) {
if (!dir.isDirectory())
throw new IllegalArgumentException("Working directory must be a directory: " + dir);
this.cwd = dir;
}
public void cancel() {
process.destroy();
}
private Runnable collector(InputStream in, Appendable sb) {
return () -> {
try {
for (int c; (c = in.read()) >= 0;) {
sb.append((char) c);
}
} catch (IOException e) {
// We assume the socket is closed
} catch (Exception e) {
try {
sb.append("\n**************************************\n");
sb.append(e.toString());
sb.append("\n**************************************\n");
} catch (IOException e1) {}
logger.debug("cmd exec", e);
}
};
}
public Command var(String name, String value) {
variables.put(name, value);
return this;
}
public Command arg(String arg) {
add(arg);
return this;
}
public Command arg(String... args) {
add(args);
return this;
}
public Command full(String full) {
arguments.clear();
new QuotedTokenizer(full, " \t", false, true).stream()
.filter(token -> !token.isEmpty())
.forEachOrdered(this::add);
return this;
}
public void inherit() {
ProcessBuilder pb = new ProcessBuilder();
var(pb.environment());
}
public String var(String name) {
return variables.get(name);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String del = "";
for (String argument : getArguments()) {
sb.append(del);
sb.append(argument);
del = " ";
}
return sb.toString();
}
public List getArguments() {
return arguments;
}
public void setUseThreadForInput(boolean useThreadForInput) {
this.useThreadForInput = useThreadForInput;
}
public void var(Map env) {
for (Map.Entry e : env.entrySet()) {
var(e.getKey(), e.getValue());
}
}
}