package com.claudecode.tool.util;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 进程执行器 —— 消除 BashTool、GrepTool、NotificationTool 中的重复 ProcessBuilder 模式。
*
* 封装进程创建、超时管理、输出捕获和资源清理。
*/
public final class ProcessExecutor {
private ProcessExecutor() {}
/**
* 进程执行结果。
*/
public record Result(int exitCode, String stdout, String stderr, boolean timedOut) {
public boolean isSuccess() {
return exitCode == 0 && !timedOut;
}
public String output() {
return stdout != null ? stdout : "";
}
public String combinedOutput() {
StringBuilder sb = new StringBuilder();
if (stdout != null && !stdout.isEmpty()) sb.append(stdout);
if (stderr != null && !stderr.isEmpty()) {
if (!sb.isEmpty()) sb.append("\n");
sb.append(stderr);
}
return sb.toString();
}
}
/**
* 在指定工作目录执行命令,带超时和资源清理。
*/
public static Result execute(List command, Path workDir, long timeoutMs) {
return execute(command, workDir, timeoutMs, null);
}
/**
* 在指定工作目录执行命令,带超时、环境变量和资源清理。
*/
public static Result execute(List command, Path workDir, long timeoutMs, Map env) {
ProcessBuilder pb = new ProcessBuilder(command);
if (workDir != null) pb.directory(workDir.toFile());
pb.redirectErrorStream(false);
if (env != null && !env.isEmpty()) {
pb.environment().putAll(env);
}
Process process = null;
try {
process = pb.start();
String stdout;
String stderr;
try (var stdoutStream = process.getInputStream();
var stderrStream = process.getErrorStream()) {
stdout = new String(stdoutStream.readAllBytes());
stderr = new String(stderrStream.readAllBytes());
}
boolean finished = process.waitFor(timeoutMs, TimeUnit.MILLISECONDS);
if (!finished) {
process.destroyForcibly();
process.waitFor(2, TimeUnit.SECONDS);
return new Result(-1, stdout, stderr, true);
}
return new Result(process.exitValue(), stdout, stderr, false);
} catch (IOException e) {
return new Result(-1, "", "IOException: " + e.getMessage(), false);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new Result(-1, "", "Interrupted", false);
} finally {
if (process != null && process.isAlive()) {
process.destroyForcibly();
}
}
}
/**
* 快速检查命令是否可用(which/where)。
*/
public static boolean isCommandAvailable(String command) {
boolean isWindows = System.getProperty("os.name", "").toLowerCase().contains("win");
String checker = isWindows ? "where" : "which";
Result result = execute(List.of(checker, command), null, 5000);
return result.isSuccess();
}
/**
* 执行 shell 命令(通过 sh -c 或 cmd /c)。
*/
public static Result executeShell(String shellCommand, Path workDir, long timeoutMs) {
boolean isWindows = System.getProperty("os.name", "").toLowerCase().contains("win");
List command;
if (isWindows) {
command = List.of("cmd.exe", "/c", shellCommand);
} else {
command = List.of("/bin/sh", "-c", shellCommand);
}
return execute(command, workDir, timeoutMs);
}
}