1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *
15 *  See the License for the specific language governing permissions and
16 *  limitations under the License.
17 */
18
19/**
20 * @author Vitaly A. Provodin
21 */
22
23package org.apache.harmony.jpda.tests.jdwp.share;
24
25import java.io.IOException;
26import java.util.Vector;
27
28import org.apache.harmony.jpda.tests.framework.LogWriter;
29import org.apache.harmony.jpda.tests.framework.StreamRedirector;
30import org.apache.harmony.jpda.tests.framework.TestErrorException;
31import org.apache.harmony.jpda.tests.framework.jdwp.JDWPDebuggeeWrapper;
32import org.apache.harmony.jpda.tests.share.JPDATestOptions;
33
34/**
35 * This class provides basic DebuggeeWrapper implementation based on JUnit framework,
36 * which can launch and control debuggee process.
37 */
38public class JDWPUnitDebuggeeProcessWrapper extends JDWPDebuggeeWrapper {
39
40    /**
41     * Target VM debuggee process.
42     */
43    public Process process;
44
45    protected StreamRedirector errRedir;
46    protected StreamRedirector outRedir;
47
48    /**
49     * Creates new instance with given data.
50     *
51     * @param settings
52     *            test run options
53     * @param logWriter
54     *            where to print log messages
55     */
56    public JDWPUnitDebuggeeProcessWrapper(JPDATestOptions settings, LogWriter logWriter) {
57        super(settings, logWriter);
58    }
59
60    /**
61     * Launches process and redirects output.
62     */
63    public void launchProcessAndRedirectors(String cmdLine) throws IOException {
64        logWriter.println("Launch process: " + cmdLine);
65        process = launchProcess(cmdLine);
66        logWriter.println("Launched process");
67        if (process != null) {
68            logWriter.println("Start redirectors");
69            errRedir = new StreamRedirector(process.getErrorStream(), logWriter, "STDERR");
70            errRedir.setDaemon(true);
71            errRedir.start();
72            outRedir = new StreamRedirector(process.getInputStream(), logWriter, "STDOUT");
73            outRedir.setDaemon(true);
74            outRedir.start();
75            logWriter.println("Started redirectors");
76        }
77    }
78
79    /**
80     * Waits for process to exit and closes uotput redirectors
81     */
82    public void finishProcessAndRedirectors() {
83        if (process != null) {
84            try {
85                logWriter.println("Waiting for process exit");
86                WaitForProcessExit(process);
87                logWriter.println("Finished process");
88            } catch (IOException e) {
89                throw new TestErrorException("IOException in waiting for process exit: ", e);
90            }
91
92            logWriter.println("Waiting for redirectors finish");
93            if (outRedir != null) {
94                outRedir.exit();
95                try {
96                    outRedir.join(settings.getTimeout());
97                } catch (InterruptedException e) {
98                    logWriter.println("InterruptedException in stopping outRedirector: " + e);
99                }
100                if (outRedir.isAlive()) {
101                    logWriter.println("WARNING: redirector not stopped: " + outRedir.getName());
102                }
103            }
104            if (errRedir != null) {
105                errRedir.exit();
106                try {
107                    errRedir.join(settings.getTimeout());
108                } catch (InterruptedException e) {
109                    logWriter.println("InterruptedException in stopping errRedirector: " + e);
110                }
111                if (errRedir.isAlive()) {
112                    logWriter.println("WARNING: redirector not stopped: " + errRedir.getName());
113                }
114            }
115            logWriter.println("Finished redirectors");
116        }
117    }
118
119    /**
120     * Launches process with given command line.
121     *
122     * @param cmdLine
123     *            command line
124     * @return associated Process object or null if not available
125     * @throws IOException
126     *             if error occurred in launching process
127     */
128    protected Process launchProcess(String cmdLine) throws IOException {
129
130    	// Runtime.exec(String) does not preserve quoted arguments
131    	// process = Runtime.getRuntime().exec(cmdLine);
132
133    	String args[] = splitCommandLine(cmdLine);
134    	process = Runtime.getRuntime().exec(args);
135        return process;
136    }
137
138    /**
139     * Splits command line into arguments preserving spaces in quoted arguments
140     * either with single and double quotes (not prefixed by '\').
141     *
142     * @param cmdLine
143     *            command line
144     * @return associated Process object or null if not available
145     * @throws IOException
146     *             if error occurred in launching process
147     */
148/*
149    public String[] splitCommandLine(String cmd) {
150
151        // allocate array for parsed arguments
152        int max_argc = 250;
153        Vector argv = new Vector();
154
155        // parse command line
156        int len = cmd.length();
157        if (len > 0) {
158            for (int arg = 0; arg < len;) {
159                // skip initial spaces
160                while (Character.isWhitespace(cmd.charAt(arg))) arg++;
161                // parse non-spaced or quoted argument
162                for (int p = arg; ; p++) {
163                    // check for ending separator
164                    if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
165                    	if (p > len) p = len;
166                    	String val = cmd.substring(arg, p);
167                        argv.add(val);
168                        arg = p + 1;
169                        break;
170                    }
171
172                    // check for starting quote
173                    if (cmd.charAt(p) == '\"') {
174                         char quote = cmd.charAt(p++);
175                         // skip all chars until terminating quote or end of line
176                         for (; p < len; p++) {
177                             // check for terminating quote
178                             if (cmd.charAt(p) == quote)
179                            	 break;
180                             // skip escaped quote
181                             if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
182                            	 p++;
183                         }
184                     }
185
186                    // skip escaped quote
187                    if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == '\"') {
188                    	p++;
189                    }
190                }
191            }
192        }
193
194    	logWriter.println("Splitted command line: " + argv);
195        int size = argv.size();
196        String args[] = new String[size];
197        return (String[])argv.toArray(args);
198	}
199*/
200    public String[] splitCommandLine(String cmd) {
201
202        int len = cmd.length();
203        char chars[] = new char[len];
204        Vector<String> argv = new Vector<String>();
205
206        if (len > 0) {
207            for (int arg = 0; arg < len;) {
208                // skip initial spaces
209                while (Character.isWhitespace(cmd.charAt(arg))) arg++;
210                // parse non-spaced or quoted argument
211                for (int p = arg, i = 0; ; p++) {
212                    // check for starting quote
213                    if (p < len && (cmd.charAt(p) == '\"' || cmd.charAt(p) == '\'')) {
214                         char quote = cmd.charAt(p++);
215                         // copy all chars until terminating quote or end of line
216                         for (; p < len; p++) {
217                             // check for terminating quote
218                             if (cmd.charAt(p) == quote) {
219                            	 p++;
220                            	 break;
221                             }
222                             // preserve escaped quote
223                             if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
224                            	 p++;
225                             chars[i++] = cmd.charAt(p);
226                         }
227                     }
228
229                    // check for ending separator
230                    if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
231                    	String val = new String(chars, 0, i);
232                        argv.add(val);
233                        arg = p + 1;
234                        break;
235                    }
236
237                    // preserve escaped quote
238                    if (cmd.charAt(p) == '\\' && (p+1) < len
239                    		&& (cmd.charAt(p+1) == '\"' || cmd.charAt(p+1) == '\'')) {
240                    	p++;
241                    }
242
243                    // copy current char
244                    chars[i++] = cmd.charAt(p);
245                }
246            }
247        }
248
249    	logWriter.println("Splitted command line: " + argv);
250        int size = argv.size();
251        String args[] = new String[size];
252        return (String[])argv.toArray((String[])args);
253	}
254
255    /**
256     * Waits for launched process to exit.
257     *
258     * @param process
259     *            associated Process object or null if not available
260     * @throws IOException
261     *             if any exception occurs in waiting
262     */
263    protected void WaitForProcessExit(Process process) throws IOException {
264        ProcessWaiter thrd = new ProcessWaiter();
265        thrd.setDaemon(true);
266        thrd.start();
267        try {
268            thrd.join(settings.getTimeout());
269        } catch (InterruptedException e) {
270            throw new TestErrorException(e);
271        }
272
273        if (thrd.isAlive()) {
274            thrd.interrupt();
275        }
276
277        try {
278            int exitCode = process.exitValue();
279            logWriter.println("Finished debuggee with exit code: " + exitCode);
280        } catch (IllegalThreadStateException e) {
281            logWriter.printError("Terminate debuggee process");
282            process.destroy();
283            throw new TestErrorException("Debuggee process did not finish during timeout", e);
284        }
285
286        // dispose any resources of the process
287        process.destroy();
288    }
289
290    /**
291     * Separate thread for waiting for process exit for specified timeout.
292     */
293    class ProcessWaiter extends Thread {
294        public void run() {
295            try {
296                process.waitFor();
297            } catch (InterruptedException e) {
298                logWriter.println("Ignoring exception in ProcessWaiter thread interrupted: " + e);
299            }
300        }
301    }
302
303    public void start() {
304    }
305
306    public void stop() {
307    }
308}
309