{Kurung Kurawal}

Java, External Process Execution

Pernahkah anda dalam suatu kebutuhan menjalan aplikasi lain dari dalam aplikasi Java anda? Misalnya, ketika anda harus membuka aplikasi Microsoft Word dari dalam aplikasi anda? Atau mungkin menjalankan sebuah perintah seperti seolah-olah berada dalam Command Line ?

Java menyediakan sebuah class Runtime untuk keperluan ini. Runtime adalah class yang memungkinkan terjadinya hubungan/komunikasi antara aplikasi (Kita batasi disini Java) dengan Environment tempat aplikasi tersebut berjalan. Lebih mendetail mengenai teknis dan JavaDoc bisa dilihat disini.

Saya akan memberikan contoh aplikasi yang sangat sederhana, yaitu PING. Anda pasti sudah tahu, atau bahkan mungkin sering menggunakan tool yang satu ini. Saya membuat sebuah aplikasi sederhana, yang bertujuan mendemokan penggunakan Runtime.exec() milik Java, dan bagaimana berinteraksi dengan instance Process yang dihasilkan exec() tersebut. Saya namakan, JPinger.

JPinger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package lee.gaismamedia;
 
import java.io.*;
/**
 * Created with IntelliJ IDEA.
 * User: lee ( lee@konglie.web.id )
 * Date: 7/10/14
 * Time: 12:56 PM
 */
public class JPinger {
        // external command which to be executed
    private final String PING_CMD;
 
    private Process pinger;
    private JPingerListener listener;
    private Boolean isPinging;
    public JPinger(JPingerListener l){
        String OS = System.getProperty("os.name");
        System.out.println(OS);
        if(OS.toLowerCase().indexOf("win") >= 0){
            // -t for ping until stopped
            // -a for resolve ip to hostname
            PING_CMD = "ping -t -a";
        } else {
            // on linux, this is the default behaviour
            PING_CMD = "ping";
        }
 
        this.listener = l;
        this.isPinging = false;
    }
 
    private Thread processReader;
 
    public void ping(String target){
        // only take the first-before-space destination
        // this actually affect nothing, as we are passing everything to PING
        String pingCommand = String.format("%s %s",
                PING_CMD, target.trim().split(" ")[0]);
 
        stopPing();
        try{
            listener.PingOutput("Execute: " + pingCommand + "\n");
 
            // create the Process instance
            pinger = Runtime.getRuntime().exec(pingCommand);
 
            // to make it easier,
            // we will pipe the InputStream and Error Stream to one single channel
            // so we will only focus accepting whatever the process are printing out
            PipedInputStream in = new PipedInputStream(1024 * 1024);
            PipedOutputStream out = new PipedOutputStream(in);
            final BufferedReader io = new BufferedReader(new InputStreamReader(in));
 
            // pipe the inputStream
            new StreamReader(pinger.getInputStream(), out).start();
 
            // pipe the errorStrem
            new StreamReader(pinger.getErrorStream(), out).start();
 
            // what about the OutputStream ??
            // well, in this case, PING does not accept any input from us once it is running
            // so, we don't care about the OutputStream
 
            // read and pass the output, line by line, to our listener
            processReader = new Thread(new Runnable(){
                @Override
                public void run() {
                    String line;
                    try{
                        while( (line = io.readLine()) != null && !Thread.interrupted()){
                            listener.PingOutput(line);
                        }
                    } catch (Exception e){
 
                    }
                }
            });
            processReader.start();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
 
    public void stopPing(){
        try {
            // destroy everything
            pinger.destroy();
            processReader.interrupt();
        } catch (Exception e){
            return;
        }
    }
}
 
class StreamReader extends Thread {
    private InputStream in;
    private OutputStream out;
 
    public StreamReader(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
    }
 
    public void run()
    {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            PrintStream printStream = new PrintStream(out);
            String line;
 
            // get line from the stream, and write it to the destination pipe.
            while ( (line = reader.readLine()) != null) {
                printStream.println(line);
            }
 
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
 
}

Dan untuk memudahkan saya dalam berkomunikasi dengan class JPinger ini, saya membuat sebuah Interface, sederhana saja, yang menjadi tempat bagi JPinger memberikan output dari perintah PING yang dijalankan.

JPingerListener

1
2
3
4
5
6
7
8
9
10
11
package lee.gaismamedia;
 
/**
 * Created with IntelliJ IDEA.
 * User: lee ( lee@konglie.web.id )
 * Date: 7/10/14
 * Time: 1:02 PM
 */
public interface JPingerListener {
    public void PingOutput(String line);
}

Kunci utama dari aplikasi ini adalah di baris
pinger = Runtime.getRuntime().exec(pingCommand);
dimana kita membuat sebuah instance class Process (ditampung di variable) pinger.

Sebuah Process akan memiliki 3 buah Stream, yaitu InputStream, OutputStream, dan ErrorStream ( CMIIW ). InputStream adalah stream tempat Process mengeluarkan seluruh output ke StdOut, ErrorStream untuk mengeluarkan Error ke StdErr, dan OutputStream adalah tempat Process menerima input dari StdIn.

Lho, kok terbalik? InputStream ke StdOut dan OutputStream ke StdIn ?

Iya benar. Yang dimaksud Stream disini adalah dari sudut pandang si instance Process yang kita miliki, bukan dari instance command yang berjalan misalnya dalam kasus kita adalah si PING.

Sehingga apabila kita mengeluarkan sesuatu (Output) dari Process, itu akan menjadi masukan (Input) ke PING, dan sebaliknya, masukan (Input) ke Process adalah hasil dari keluaran (Output) dari PING. Are you still with me?


ping out -> process in
ping err -> process in
ping in  

Tapi dalam kasus ping, memang saya tidak memproses input nya, kenapa? Karena setau saya PING tidak menerima input apapun setelah dijalankan.

So, demo aplikasinya? Download disini.

Sebagai catatan, untuk menghindari penggunaan memori terlalu besar, saya batasi output yang dikeluarkan hanya 100 baris terakhir saja. Dan core aplikasi ini adalah menggunakan class JPinger di atas. Selebihnya hanya standar desain Swing saja.

Semoga bisa berguna. Enjoy.