Source code: com/tuneology/avm/Native.java
1 /*
2 Native.java
3
4 Copyright (C) 2002 Fran Taylor
5
6 This file is part of java-avm.
7
8 java-avm is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
12
13 java-avm is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with java-avm; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA
22
23 $Id: Native.java,v 1.5 2002/11/06 09:22:57 xnarf Exp $
24
25 */
26
27 package com.tuneology.avm;
28
29 import java.util.*;
30 import java.io.*;
31
32 /**
33 * This class extends the System and Runtime classes to provide
34 * extra functionality.
35 *
36 * @author Fran Taylor
37 *
38 * @version $Id: Native.java,v 1.5 2002/11/06 09:22:57 xnarf Exp $
39 */
40 public class Native {
41 private Native() { }
42 /**
43 * This class gets called for each line of output generated by the program.
44 *
45 * @author Fran Taylor
46 */
47 public interface LineReader {
48 /**
49 * Called by runCommand when a line of output is generated by a process.
50 *
51 * @param str The line of output from the program.
52 * @param e True if the line is from stderr, false if from stdout.
53 * @return false to stop running the program, true otherwise.
54 * @throws IOException if an error occurs parsing the input.
55 */
56 public boolean processLine(String str, boolean e) throws Exception;
57 }
58 /**
59 * This class is called for each line of output generated by a shell program.
60 *
61 * @author Fran Taylor
62 */
63 public interface Logger {
64 /**
65 * Called for each line of output generated by the process.
66 *
67 * @param str the string to add to the log
68 */
69 public void addMessage(String str);
70 }
71 /**
72 * Returns the list of arguments quoted in a single string, for logging purposes.
73 *
74 * @param cmd the argument array.
75 * @return the quoted list of arguments as a single string.
76 */
77 public static String getArgString(String[] cmd) {
78 String retval = "cmd: ";
79 retval = "cmd: \"" + cmd[0] + "\"\n";
80 for(int i = 1; i < cmd.length; i++) {
81 retval += "arg: \"" + cmd[i] + "\"\n";
82 }
83 return retval;
84 }
85 /**
86 * Returns the list of arguments quoted in a single string, for logging purposes.
87 *
88 * @param cmd the String arguments.
89 * @return the quoted list of arguments as a single string.
90 */
91 public static String getArgString(Vector cmd) {
92 String retval = "cmd: ";
93 retval = "cmd: \"" + ((String) cmd.get(0)) + "\"\n";
94 for(int i = 1; i < cmd.size(); i++) {
95 retval += "arg: \"" + ((String) cmd.get(i)) + "\"\n";
96 }
97 return retval;
98 }
99 /**
100 * Parses the user input and returns it in the form of
101 * a vector of strings, ready to pass to runCommand.
102 * Values within double quotes are preserved as is
103 * otherwise, spaces delimit args.
104 *
105 * @param str The string to be parsed.
106 # @return a vector containing string arguments.
107 */
108 public static Vector parseUserInput(String str) {
109 // surely there's a better way...
110 // StringTokenizer isn't powerful enough
111 Vector v = new Vector();
112 int mx = str.length();
113 for(int ind = 0; ind < mx; ind++) {
114 int q1 = str.indexOf("\"", ind);
115 if (q1 != -1) {
116 int q2 = str.indexOf("\"", q1 + 1);
117 if (q2 != -1) {
118 String arg = str.substring(q1 + 1, q2 - q1);
119 v.add(arg);
120 ind = q2 + 1;
121 continue;
122 }
123 // only one double quote. ignore it.
124 }
125 // skip over whitespace
126 for( ; (ind < mx) && (str.charAt(ind) == ' '); ind++);
127 int p = ind;
128 // skip forward to a space
129 for( ; (p < mx) && (str.charAt(p) != ' '); p++);
130 String arg = str.substring(ind, p);
131 v.add(arg);
132 ind = p;
133 }
134 return v;
135 }
136 public static Process exec(String[] cmd) throws IOException {
137 logger.addMessage(logCmd(cmd, null));
138 return Runtime.getRuntime().exec(cmd);
139 }
140 /**
141 * Runs the specified external program. Blocks until the program exits.
142 *
143 * @param cmd the shell command to run
144 * @param dir the directory in which to run it, or null for the current directory.
145 * @param lrdr the line reader used to parse the user input (optional).
146 * @param logOutput true if the process's output is to be logged, false if its output is not to be logged.
147 * @return The value returned by the process.
148 * @throws Exception if an error occurs
149 */
150 public static int runCommand(String cmd, File dir, Native.LineReader lrdr, boolean logOutput) throws Exception {
151 String logStr = logCmd(cmd, dir);
152 int retval = -1;
153 long startTime = System.currentTimeMillis();
154 retval = runCommand(Runtime.getRuntime().exec(cmd, null, dir), logStr, lrdr, logOutput, startTime);
155 return retval;
156 }
157 /**
158 * Runs the specified external program. Blocks until the program exits.
159 *
160 * @param v The shell command to run, as a vector of strings.
161 * @param dir the directory in which to run it, or null for the current directory.
162 * @param lrdr the line reader used to parse the user input (optional).
163 * @param logOutput true if the process's output is to be logged, false if its output is not to be logged.
164 * @return The value returned by the process.
165 * @throws Exception if an error occurs
166 */
167 public static int runCommand(Vector v, File dir, Native.LineReader lrdr, boolean logOutput) throws Exception {
168 String[] cmd = new String[v.size()];
169 for(int i = 0; i < v.size(); i++) cmd[i] = (String) v.get(i);
170 return runCommand(cmd, dir, lrdr, logOutput);
171 }
172 /**
173 * Runs the specified external program. Blocks until the program exits.
174 *
175 * @param cmd The shell command to run, as an array of strings.
176 * @param dir the directory in which to run it, or null for the current directory.
177 * @param lrdr the line reader used to parse the user input (optional).
178 * @param logOutput true if the process's output is to be logged, false if its output is not to be logged.
179 * @return The value returned by the process.
180 * @throws Exception if an error occurs
181 */
182 public static int runCommand(String[] cmd, File dir, Native.LineReader lrdr, boolean logOutput) throws Exception {
183 String logStr = logCmd(cmd, dir);
184 int retval = -1;
185 long startTime = System.currentTimeMillis();
186 retval = runCommand(Runtime.getRuntime().exec(cmd, null, dir), logStr, lrdr, logOutput, startTime);
187 return retval;
188 }
189 /**
190 * Takes a vector of strings and creates an array of strings.
191 *
192 * @param v A vector containing strings.
193 * @return An array made of the strings in the vector.
194 */
195 public static String[] getStringArray(Vector v) {
196 String[] cmd = new String[v.size()];
197 for(int i = 0; i < v.size(); i++) cmd[i] = (String) v.get(i);
198 return cmd;
199 }
200 /**
201 * For Unix only: executes the mkfifo command.
202 *
203 * @param f the file pointing to the fifo to be created.
204 * @throws Exception If there was an error executing the mkfifo command.
205 */
206 static public void mkfifo(File f) throws Exception {
207 Vector cmd = getMkfifoCmd();
208 cmd.add(f.getCanonicalPath());
209 Native.runCommand(cmd, null, null, true);
210 }
211 /**
212 * Returns true if the pathname specifies an executable program.
213 *
214 * @param path A pathname.
215 * @return true if the path is an executable file.
216 */
217 public static boolean isExecutable(String path) {
218 if (isWindows) {
219 String lcpath = path.toLowerCase();
220 return lcpath.endsWith(".com") || lcpath.endsWith(".exe");
221 } else {
222 return i_isExecutable(path);
223 }
224 }
225 /**
226 * Sets the shell command used for mkfifo.
227 *
228 * @param s the mkfifo command.
229 */
230 static public void setMkfifoCmd(String s) { mkfifoCmd = s; }
231 /**
232 * All process I/O is logged to this interface.
233 *
234 * @param l the logger object.
235 */
236 static public void setLogger(Logger l) { logger = l; }
237 /**
238 * Returns a string from the resource bundle.
239 *
240 * @param str the identifier for the string.
241 * @return the resource string.
242 */
243 static public String getResourceString(String str) { return resources.getString(str); }
244 /**
245 * Returns the mkfifo command.
246 *
247 * @return the mkfifo command.
248 */
249 static Vector getMkfifoCmd() { return parseUserInput(mkfifoCmd); }
250 private static boolean processLine(Native.LineReader rdr, String str) throws Exception {
251 if (!rdr.processLine(str, true)) {
252 return false;
253 }
254 return true;
255 }
256 private static String logCmd(String cmd, File dir) {
257 String cmdStr = "";
258 if (dir != null) cmdStr += "dir: " + dir.getPath() + " ";
259 cmdStr += "cmd: " + cmd;
260 return cmdStr;
261 }
262 private static String logCmd(String[] cmd, File dir) {
263 String str = "";
264 if (dir != null) str += "dir: " + dir.getPath() + " ";
265 str += "cmd: " + cmd[0];
266 int firstArg = 1;
267 if (cmd[0].indexOf("sudo") != -1) {
268 str += " " + cmd[1];
269 firstArg++;
270 }
271 str += "\n";
272 if (cmd.length > firstArg) {
273 for(int i = firstArg; i < cmd.length; i++) {
274 str += "arg: \"" + cmd[i] + "\"\n";
275 }
276 }
277 str += "\n";
278 return str;
279 }
280 private static class ProcReader extends Thread {
281 ProcReader(BufferedReader errRdr, Native.LineReader rdr, Process proc) {
282 this.errRdr = errRdr;
283 lrdr = rdr;
284 this.proc = proc;
285 logBuf = new StringBuffer();
286 ex = null;
287 }
288 public void run() {
289 String str;
290 try {
291 while((str = errRdr.readLine()) != null) {
292 logBuf.append(str + "\n");
293 if (lrdr != null) {
294 try {
295 if (!processLine(lrdr, str)) {
296 proc.destroy();
297 break;
298 }
299 } catch (IOException ex) {
300 this.ex = ex;
301 }
302 }
303 }
304 } catch (Exception ex) {
305 this.ex = ex;
306 }
307 }
308 private BufferedReader errRdr;
309 private LineReader lrdr;
310 private Process proc;
311 StringBuffer logBuf;
312 Exception ex;
313 }
314 private static int runCommand(final Process proc, String logStr, final Native.LineReader lrdr, boolean logOutput, long startTime)
315 throws Exception {
316 final StringBuffer logBuf = new StringBuffer(logStr);
317 BufferedReader rdr = new BufferedReader(new InputStreamReader(proc.getInputStream()));
318 final BufferedReader errRdr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
319 ProcReader th = new ProcReader(errRdr, lrdr, proc);
320 int retval = 0;
321 long endTime = -1;
322 String str;
323 th.start();
324 try {
325 while((str = rdr.readLine()) != null) {
326 if (logOutput)
327 logBuf.append(str + "\n");
328 if (lrdr != null) {
329 if (!processLine(lrdr, str)) {
330 proc.destroy();
331 break;
332 }
333 }
334 }
335 retval = proc.waitFor();
336 } catch (Exception ex) {
337 proc.destroy();
338 try { retval = proc.waitFor(); } catch (InterruptedException exx) { }
339 }
340 endTime = System.currentTimeMillis();
341 th.join();
342 if (logger != null) {
343 logger.addMessage(logBuf.toString());
344 logger.addMessage("Execution time: " + (endTime - startTime) + " msec\n");
345 }
346 if (th.ex != null) {
347 logBuf.append(th.logBuf.toString());
348 throw th.ex;
349 }
350 return retval;
351 }
352 static private native boolean i_isExecutable(String path);
353
354 /** True if the jvm is running on some sort of Windows. */
355 static public final boolean isWindows;
356 /** True if the jvm is running on Linux. */
357 static public final boolean isLinux;
358 /** True if the jvm is running on Solaris. */
359 static public final boolean isSolaris;
360 /** For linux, contains the name and version of the distribution. */
361 static public final String distName;
362 /** Contains the version information for java-avm. */
363 static public final String version = "java-avm 0.1a1 - Copyright (C) 2002 Fran Taylor";
364
365 static private final String osName;
366 static private String mkfifoCmd;
367 private static Logger logger;
368 static private ResourceBundle resources;
369 static {
370 // what kind of OS are we on?
371 osName = System.getProperty("os.name");
372 isWindows = (osName.indexOf("indows") != -1);
373 isLinux = (osName.indexOf("inux") != -1);
374 isSolaris = (osName.indexOf("olaris") != -1);
375 String name = "";
376 try {
377 resources = ResourceBundle.getBundle("resources.Native");
378 } catch (MissingResourceException e) {
379 System.err.println("Can't find resource");
380 System.exit(1);
381 }
382 if (isLinux) {
383 try {
384 // which distribution? Get first non-blank
385 // line in /etc/issue
386 String str;
387 BufferedReader rdr = new BufferedReader(new FileReader("/etc/issue"));
388 while((str = rdr.readLine()) != null) {
389 if (str.trim().equals(""))
390 continue;
391 name = str;
392 break;
393 }
394 } catch (Exception ex) {
395 }
396 }
397 distName = name;
398 }
399 }
400
401 /*
402 Local Variables:
403 mode:java
404 indent-tabs-mode:nil
405 c-basic-offset:4
406 c-indent-level:4
407 c-continued-statement-offset:4
408 c-brace-offset:-4
409 c-brace-imaginary-offset:-4
410 c-argdecl-indent:0
411 c-label-offset:0
412 End:
413 */