package net.thechunk.playpen.utils.process;

import lombok.extern.log4j.Log4j2;

import java.io.*;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Log4j2
public class XProcess {
    private List<String> command;
    private String workingDir;
    private Process process = null;
    private List<IProcessListener> listeners = new CopyOnWriteArrayList<>();
    private BufferedWriter inputWriter = null;

    public XProcess(List<String> command, String workingDir) {
        this.command = command;
        this.workingDir = workingDir;
    }

    public void addListener(IProcessListener listener) {
        listeners.add(listener);
        listener.onProcessAttach(this);
    }

    public void removeListener(IProcessListener listener) {
        listeners.remove(listener);
    }

    public void sendInput(String in) {
        try {
            inputWriter.write(in);
            inputWriter.flush();
        } catch (IOException e) {
            log.error("Error while writing to input stream", e);
        }

        for(IProcessListener listener : listeners) {
            listener.onProcessInput(this, in);
        }
    }

    public boolean run() {
        try {
            process = new ProcessBuilder(command)
                    .directory(new File(workingDir))
                    .start();
        } catch (IOException e) {
            log.error("Unable to start process", e);
            return false;
        }

        StreamThread outThread = new StreamThread(this, process.getInputStream());
        StreamThread errThread = new StreamThread(this, process.getErrorStream());
        outThread.start();
        errThread.start();

        try (OutputStreamWriter osw = new OutputStreamWriter(process.getOutputStream());
            BufferedWriter writer = new BufferedWriter(osw)) {
            inputWriter = writer;
            process.waitFor();
        }
        catch(IOException e) {
            log.error("Exception while waiting for process", e);
        }
        catch(InterruptedException e) {
            log.error("Interrupted while waiting for process", e);
        }
        finally {
            inputWriter = null;

            for(IProcessListener listener : listeners) {
                listener.onProcessEnd(this);
            }
        }

        return true;
    }

    public boolean isRunning() {
        return process.isAlive();
    }

    public void stop() {
        if (System.getProperty("os.name").toLowerCase().contains("win"))
            process.destroy();
        else {
            try {
                Field f = process.getClass().getDeclaredField("pid");
                f.setAccessible(true);
                int pid = (Integer)f.get(process);
                Runtime.getRuntime().exec("kill -9 " + String.valueOf(pid));
            } catch (NoSuchFieldException | IllegalAccessException | IOException e) {
                log.warn("Unable to use reflection for PID, using normal stop");
                process.destroy();
            }
        }
    }

    protected void receiveOutput(String out) {
        for(IProcessListener listener : listeners) {
            listener.onProcessOutput(this, out);
        }
    }

    @Log4j2
    private static class StreamThread extends Thread {
        private InputStream is = null;
        private XProcess proc = null;

        public StreamThread(XProcess proc, InputStream is) {
            this.is = is;
            this.proc = proc;
        }

        @Override
        public void run() {
            try(InputStreamReader isr = new InputStreamReader(is);
                BufferedReader reader = new BufferedReader(isr)) {
                String line = null;
                while((line = reader.readLine()) != null) {
                    proc.receiveOutput(line);
                }
            }
            catch(IOException e) {
                log.error("Error while reading input stream", e);
            }
        }
    }
}

@@ -2470,599 +2470,34 @@
     
-if (System.getProperty(%22os.name%22).toLowerCase().contains(%22win%22))%0A            process.destroy();%0A        else %7B%0A            try %7B%0A                Field f = process.getClass().getDeclaredField(%22pid%22);%0A                f.setAccessible(true);%0A                int pid = (Integer)f.get(process);%0A                Runtime.getRuntime().exec(%22kill -9 %22 + String.valueOf(pid));%0A            %7D catch (NoSuchFieldException %7C IllegalAccessException %7C IOException e) %7B%0A                log.warn(%22Unable to use reflection for PID, using normal stop%22);%0A                process.destroy();%0A            %7D%0A        %7D
+process.destroyForcibly();
 %0A   

Switch to destroyForcibly(). On *nix this will send SIGKILL, on Windows it will map to destroy().