001package arcommon;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.InputStream;
006import java.io.OutputStream;
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Iterator;
010import java.util.List;
011
012public class ProcessUtil {
013    public static interface ProcessOutput {
014        public void output(String message, boolean err);
015    }
016    
017    public static enum Priority {LOW, NORMAL, HIGH};
018    
019    public static class SimpleProcessOutput implements ProcessOutput {
020        public void output(String message, boolean err) {
021            message = message.replace("\r\r\n", "\r\n");
022            if (err) {
023                System.err.print(message);
024            } else {
025                System.out.print(message);
026            }
027        }
028    };
029    
030    public static class FullProcessOutput implements ProcessOutput {
031        private StringBuilder messageStringBuilder = new StringBuilder();
032        
033        public void output(String message, boolean err) {
034            messageStringBuilder.append(message);
035        }
036        
037        public String toString() {
038            return messageStringBuilder.toString();
039        }
040    }
041    
042    public static Process startCommand(String command, Priority priority) {
043        return startCommand(command, priority, (String[])null);
044    }
045
046    public static Process startCommand(String command, Priority priority, String ... parameters) {
047        if (!Util.isWindowsOS() && priority != Priority.NORMAL) {
048            throw new RuntimeException("Not supported");
049        }
050        
051        if (priority == Priority.NORMAL) {
052            return startCommand(command, parameters);
053        }
054        
055        ArrayList<String> parameterList = new ArrayList<String>();
056        parameterList.add("/c");
057        parameterList.add("start");
058        switch (priority) {
059            case LOW :
060                parameterList.add("/LOW");
061            break;
062            case NORMAL :
063                parameterList.add("/NORMAL");
064            break;
065            case HIGH :
066                parameterList.add("/HIGH");
067            break;
068            default :
069                throw new RuntimeException("Unknown priority " + priority);
070        }
071        parameterList.add(command);
072        if (parameters != null) {
073            for (int i = 0; i < parameters.length; i++) {
074                if (parameters[i] == null) {
075                    throw new NullPointerException("Parameter " + i + " is null. " + Arrays.toString(parameters));
076                }
077                parameterList.add(parameters[i]);
078            }
079        }
080        String[] newParameters = parameterList.toArray(new String[parameterList.size()]);
081        return startCommand("cmd.exe", newParameters);
082    }
083    
084    public static Process startCommand(String command) {
085        return startCommand(command, (String[])null);
086    }
087
088    public static Process startCommand(String command, String ... parameters) {
089        return startCommand(command, true, null, parameters);
090    }
091    
092    public static Process startCommand(String command, boolean throwOnFail, File processStartDirectory, String ... parameters) {
093        return startCommand(command, throwOnFail, null, processStartDirectory, parameters);
094    }
095    
096    public static Process startCommand(String command, boolean throwOnFail, String[] environmentVariables, File processStartDirectory, String ... parameters) {
097        try {
098            String[] parametersTemp = createParameters(command, parameters);
099            Process process = Runtime.getRuntime().exec(parametersTemp, environmentVariables, processStartDirectory);
100            return process;
101        } catch (Exception e) {
102            if (throwOnFail) {
103                throw new RuntimeException(e);
104            }
105            e.printStackTrace();
106            return null;
107        }
108    }
109    
110    private static String[] createParameters(String command, String ... parameters) {
111        ArrayList<String> parameterList = new ArrayList<String>();
112        parameterList.add(command);
113        if (parameters != null) {
114            for (int i = 0; i < parameters.length; i++) {
115                if (parameters[i] == null) {
116                    throw new NullPointerException("Parameter " + i + " is null. " + Arrays.toString(parameters));
117                }
118                parameterList.add(parameters[i]);
119            }
120        }
121        return parameterList.toArray(new String[parameterList.size()]);
122    }
123    
124    public static int executeCommandNoInputNoOutput(String command) {
125        return executeCommandNoInput(command, null, (String[])null);
126    }
127    
128    public static int executeCommandNoInputNoOutput(String command, String ... parameters) {
129        return executeCommandNoInput(command, null, parameters);
130    }
131    
132    public static int executeCommandNoInputNoOutput(String command, File processStartDirectory, String ... parameters) {
133        return executeCommandNoInput(command, null, processStartDirectory, parameters);
134    }
135
136    public static int executeCommandNoInputStdOutput(String command) {
137        return executeCommandNoInput(command, new SimpleProcessOutput(), (String[])null);
138    }
139    
140    public static int executeCommandNoInputStdOutput(String command, String ... parameters) {
141        return executeCommandNoInput(command, new SimpleProcessOutput(), parameters);
142    }
143    
144    public static int executeCommandNoInputStdOutput(String command, File processStartDirectory, String ... parameters) {
145        return executeCommand(command, false, new SimpleProcessOutput(), processStartDirectory, parameters);
146    }
147    
148    public static int executeCommandStdInputNoOutput(String command) {
149        return executeCommand(command, true, null, null, (String[])null);
150    }
151    
152    public static int executeCommandStdInputNoOutput(String command, String ... parameters) {
153        return executeCommand(command, true, null, null, parameters);
154    }
155    
156    public static int executeCommandStdInputNoOutput(String command, File processStartDirectory, String ... parameters) {
157        return executeCommand(command, true, null, processStartDirectory, parameters);
158    }
159
160    public static int executeCommandStdInputStdOutput(String command) {
161        return executeCommand(command, true, new SimpleProcessOutput(), null, (String[])null);
162    }
163    
164    public static int executeCommandStdInputStdOutput(String command, String ... parameters) {
165        return executeCommand(command, true, new SimpleProcessOutput(), null, parameters);
166    }
167    
168    public static int executeCommandStdInputStdOutput(String command, File processStartDirectory, String ... parameters) {
169        return executeCommand(command, true, new SimpleProcessOutput(), processStartDirectory, parameters);
170    }
171
172    public static int executeCommandStdInput(String command, ProcessOutput processOutput) {
173        return executeCommandStdInput(command, processOutput, (String[])null);
174    }
175    
176    public static int executeCommandStdInput(String command, ProcessOutput processOutput, String ... parameters) {
177        return executeCommand(command, true, processOutput, null, parameters);
178    }
179    
180    public static int executeCommandStdInput(String command, ProcessOutput processOutput, File processStartDirectory, String ... parameters) {
181        return executeCommand(command, true, processOutput, processStartDirectory, parameters);
182    }
183    
184    public static int executeCommandNoInput(String command, ProcessOutput processOutput) {
185        return executeCommandNoInput(command, processOutput, (String[])null);
186    }
187    
188    public static int executeCommandNoInput(String command, ProcessOutput processOutput, String ... parameters) {
189        return executeCommand(command, false, processOutput, null, parameters);
190    }
191    
192    public static int executeCommandNoInput(String command, ProcessOutput processOutput, File processStartDirectory, String ... parameters) {
193        return executeCommand(command, false, processOutput, processStartDirectory, parameters);
194    }
195    
196    private static int executeCommand(String command, boolean stdInput, ProcessOutput processOutput, File processStartDirectory, String ... parameters) {
197        Process process = startCommand(command, true, processStartDirectory, parameters);
198        
199        if (stdInput) {
200            attachStdInput(process);
201        }
202        
203        return attachOutputAndWaitForExit(process, processOutput);
204    }
205    
206    public static void attachStdInput(final Process process) {
207        try {
208            System.in.available();
209        } catch (Exception e) {
210            System.out.println("Unable to attach standard input (" + e.getMessage() + ")");
211            return ;
212        }
213        
214        MyThread thread = new MyThread("Standard input read thread") {
215            @Override
216            public void run() {
217                OutputStream outputStream = process.getOutputStream();
218                try {
219                    while (true) {
220                        String lineFromStdIn;
221                        synchronized (ProcessUtil.class) {
222                            while (System.in.available() == 0 && isProcessRunning(process)) {
223                                Util.sleep(100);
224                            }
225                            if (!isProcessRunning(process)) {
226                                break;
227                            }
228                            lineFromStdIn = Util.readLineFromStdIn();
229                        }
230                        if (!isProcessRunning(process)) {
231                            break;
232                        }
233                        outputStream.write(lineFromStdIn.getBytes());
234                        if (Util.isWindowsOS()) {
235                            outputStream.write(13);
236                        }
237                        outputStream.write(10);
238                        outputStream.flush();
239                    }
240                } catch (IOException e) {
241                    throw new RuntimeException(e);
242                }
243            }
244        };
245        thread.start();
246    }
247    
248    public static void attachStdOutput(Process process) {
249        attachOutput(process, new SimpleProcessOutput());
250    }
251    
252    public static void attachOutput(Process process, ProcessOutput processOutput) {
253        attachOutput(process, false, processOutput);
254    }
255    
256    public static void attachOutput(final Process process, boolean daemonThread, final ProcessOutput processOutput) {
257        MyThread thread = new MyThread("Standard output write thread") {
258            @Override
259            public void run() {
260                attachOutputAndWaitForExit(process, processOutput);
261            };
262        };
263        if (daemonThread) {
264            thread.setDaemon(daemonThread);
265        }
266        thread.start();
267    }
268    
269    public static int attachOutputAndWaitForExit(Process process, ProcessOutput processOutput) {
270        if (process == null) {
271            throw new IllegalArgumentException("process == null");
272        }
273        
274        String encoding = System.getProperty("file.encoding");
275        // Hack
276        if (Util.isWindowsOS()) {
277            encoding = "Cp1252";
278        } else {
279            encoding = "UTF-8";
280        }
281        
282        try {
283            InputStream inputStream = process.getInputStream();
284            InputStream errorStream = process.getErrorStream();
285            boolean running = true;
286            int exitCode = -1;
287            do {
288                readBytesFromStream(inputStream, encoding, processOutput, false);
289                readBytesFromStream(errorStream, encoding, processOutput, true);
290                try {
291                    exitCode = process.exitValue();
292                    running = false;
293                } catch (IllegalThreadStateException e) {
294                    Util.sleep(DateUtil.SECOND_IN_MILLISECONDS / 10);
295                }
296            } while (running || inputStream.available() > 0 || errorStream.available() > 0);
297            return exitCode;
298        } catch (IOException e) {
299            throw new RuntimeException(e);
300        }
301    }
302    
303    private static void readBytesFromStream(InputStream stream, String encoding, ProcessOutput processOutput, boolean err) {
304        try {
305            while (stream.available() > 0) {
306                byte[] buffer = new byte[1000];
307                int length = stream.read(buffer);
308                if (length == -1) {
309                    break;
310                }
311                if (length == 0) {
312                    continue;
313                }
314                String message = new String(buffer, 0, length, encoding);
315                if (processOutput != null) {
316                    processOutput.output(message, err);
317                }
318            }
319        } catch (IOException e) {
320            throw new RuntimeException(e);
321        }
322    }
323    
324    public static int numberOfProcessesRunning(String processName) {
325        List<ProcessData> listProcess = listProcess();
326        Iterator<ProcessData> i = listProcess.iterator();
327        int result = 0;
328        while (i.hasNext()) {
329            ProcessData processData = i.next();
330            if (processData.name.equals(processName)) {
331                result++;
332            }
333        }
334        return result;
335    }
336    
337    public static boolean killProcess(String processName) {
338        if (!Util.isWindowsOS()) {
339            throw new RuntimeException("Not supported");
340        }
341        int result = executeCommandNoInputNoOutput("taskkill", "/F", "/IM", processName);
342        return result == 0;
343    }
344    
345    public static boolean isProcessRunning(Process process) {
346        try {
347            process.exitValue();
348            return false;
349        } catch (IllegalThreadStateException e) {
350            return true;
351        }
352    }
353    
354    public static boolean isProcessRunning(String processName) {
355        List<ProcessData> listProcess = listProcess();
356        Iterator<ProcessData> i = listProcess.iterator();
357        while (i.hasNext()) {
358            ProcessData processData = i.next();
359            if (processData.name.startsWith(processName)) {
360                return true;
361            }
362        }
363        return false;
364    }
365    
366    public static class ProcessData {
367        public String name;
368        public int pid;
369        public String sessionName;
370        public int sessionNumber;
371        public int memory;
372
373        public String toString() {
374            return name;
375        }
376    }
377    
378    public static List<ProcessData> listProcess() {
379        switch (Util.getOSType()) {
380            case WINDOWS :
381                return listProcessWindows();
382//          break;
383            case LINUX :
384                return listProcessLinux();
385//          break;
386            case MAC :
387                throw new RuntimeException("Not supported");
388//          break;
389            default :
390                throw new RuntimeException("Unknown OS type: " + Util.getOSType());
391        }
392    }
393    
394    private static List<ProcessData> listProcessLinux() {
395        FullProcessOutput fullProcessOutput = new FullProcessOutput();
396        executeCommandNoInput("ps", fullProcessOutput, "-eo", "%c");
397        String output = fullProcessOutput.toString();
398        String[] split = output.split("\n");
399        List<ProcessData> result = new ArrayList<ProcessData>();
400        for (int i = 1; i < split.length; i++) {
401            String name = split[i];
402            ProcessData processData = new ProcessData();
403            processData.name = name;
404            result.add(processData);
405        }
406        return result;
407    }
408    
409    private static List<ProcessData> listProcessWindows() {
410        FullProcessOutput fullProcessOutput = new FullProcessOutput();
411        executeCommandNoInput("tasklist", fullProcessOutput);
412        String output = fullProcessOutput.toString();
413        String[] outputSplit = output.split("\r\n");
414        List<ProcessData> result = new ArrayList<ProcessData>();
415        for (int i = 4; i < outputSplit.length; i++) {
416            ProcessData processData = new ProcessData();
417            /*
418Windows XP
419========================= ====== ================ ======== ============
420
421Windows 10
422Image Name                     PID Session Name        Session#    Mem Usage
423========================= ======== ================ =========== ============
424         1         2         3         4         5         6         7         8
42512345678901234567890123456789012345678901234567890123456789012345678901234567890
426             */
427            if (Util.isWindowsXp()) {
428                // Windows XP
429                processData.name = outputSplit[i].substring(0, 25).trim();
430                String number = outputSplit[i].substring(26, 32).trim();
431                processData.pid = StringUtil.stringToInt(number);
432                processData.sessionName = outputSplit[i].substring(33, 49).trim();
433                number = outputSplit[i].substring(50, 58).trim();
434                processData.sessionNumber = StringUtil.stringToInt(number);
435                number = outputSplit[i].substring(59, 71 - 3).trim();
436                number = StringUtil.replace(number, ".", "");
437                processData.memory = StringUtil.stringToInt(number) * 1024;
438            } else {
439                // Windows 10
440                processData.name = outputSplit[i].substring(0, 25).trim();
441                String number = outputSplit[i].substring(26, 34).trim();
442                processData.pid = StringUtil.stringToInt(number);
443                processData.sessionName = outputSplit[i].substring(35, 51).trim();
444                number = outputSplit[i].substring(52, 63).trim();
445                processData.sessionNumber = StringUtil.stringToInt(number);
446                number = outputSplit[i].substring(65, 76 - 2).trim();
447                processData.memory = (int)(StringUtil.stringToDouble(number) * 1024);
448            }
449                
450            result.add(processData);
451        }
452        
453        return result;
454    }
455}