// utils.beh -- utility methods and variables for Demeter/Java front end. // $Id: utils.beh,v 1.52 2003/01/03 09:04:00 dougo Exp $ { Modules, Module, Project, ProjectName, OptionName, OptionValue } { // traversal toAll(UniversalVisitor) { to *; } // public String toString() = toAll (ToStringVisitor); public String toString() (@ ToStringVisitor v = new ToStringVisitor(); v.start(); universal_trv0(v); return v.get_return_val(); @) } ToStringVisitor { start (@ out = new PrintWriter(sw = new StringWriter()); @) return String (@ sw.toString() @) } Utils { /** Make sure file exists and is readable. */ public static InputStream makeInputStream(File file) throws IOException (@ if (!file.exists()) { Stream.err.println("Error: Input file " + file + " does not exist."); } else if (!file.canRead()) { Stream.err.println("Error: Input file " + file + " exists but is not readable."); } else if (!file.isFile()) { Stream.err.println("Error: Input file " + file + " is not a plain file."); } else try { FileInputStream in = new FileInputStream(file); return in; } catch (FileNotFoundException exc) { // This shouldn't happen... throw new RuntimeException(exc.toString()); } throw new IOException(); @) /** Make sure the directory is writable. Return null otherwise. */ public static PrintWriter makePrintWriter(File file) (@ String dirname = file.getParent(); if (dirname == null) dirname = System.getProperty("user.dir"); if (!new File(dirname).canWrite()) { Stream.err.println("Error: Can't create " + file + ", directory is not writable."); } else try { PrintWriter out = new PrintWriter(new FileWriter(file)); return out; } catch (IOException ex) { Stream.err.println("Error: Can't create " + file + ": " + ex); } return null; @) } Utils { // Unfortunately these don't actually change the process's current // directory. Sigh. (@ private static final Stack dirs = new Stack(); private static final Properties p = System.getProperties(); private static final String cwd_prop = "user.dir"; @) /** Push the current directory onto the stack, and change to dir. */ static void pushd(File dir) (@ dirs.push(p.getProperty(cwd_prop)); p.put(cwd_prop, dir.getPath()); @) /** Pop the top directory off the stack and change to it. */ static void popd() (@ p.put(cwd_prop, dirs.pop()); @) /** The current directory. */ static File getcwd() (@ return new File(p.getProperty(cwd_prop)); @) } Utils { /** Should threads use blocking I/O? (On some platforms, blocking I/O blocks the whole process, not just the thread.) */ (@ static public boolean block = true; @) /** Do we need to kill threads that are blocked after the exec'd process is finished? On some platforms, the read() will never return when the stream is terminated, either because EOF isn't being seen or it's not being generated. */ (@ static public boolean eofbug = false; @) /** Execute a process, forwarding its output and error to our output and error streams. Block until done; return its exit value. */ static int exec(String cmdarray[]) throws IOException, InterruptedException (@ return exec(cmdarray, false); @) /** Execute a process, forwarding its output and error to our output and error streams. If withInput is true, also forward our input stream to the process's input stream. Block until done; return its exit value. */ static int exec(String cmdarray[], boolean withInput) throws IOException, InterruptedException (@ Process p = execProcess(cmdarray); InputForwarder in = null; if (withInput) { in = new InputForwarder(System.in, p.getOutputStream(), "stdin", "process", block); } return waitForProcess(p, in); @) /** Execute a process, forwarding its output and error to our output and error streams and the given file input to its input stream. Block until done; return its exit value. */ static int exec(String cmdarray[], FileInputStream fin) throws IOException, InterruptedException (@ Process p = execProcess(cmdarray); InputForwarder in = new InputForwarder(fin, p.getOutputStream(), "file", "process", true); return waitForProcess(p, in); @) /** Exec a process and return the Process object. */ static Process execProcess(String cmdarray[]) throws IOException (@ Stream.debug.println("exec(" + makeVec(cmdarray) + ")"); return Runtime.getRuntime().exec(cmdarray); @) /** Wait for a process to die, forwarding its output and error to our output and error streams, and if in is not null, use it to forward input to the process as well. Return the process's exit value. */ static int waitForProcess(Process p, InputForwarder in) throws InterruptedException (@ if (in != null) in.start(); OutputForwarder out = new OutputForwarder(p.getInputStream(), System.out, "process", "stdout", block); OutputForwarder err = new OutputForwarder(p.getErrorStream(), System.err, "process", "stderr", block); out.start(); err.start(); Stream.debug.println("waiting..."); int ret = p.waitFor(); Stream.debug.println("returned " + ret); if (in != null) { /* in.die = true; Stream.debug.println("Waiting for " + in.getName() + "..."); in.join(); */ // If the process is dead, writing to it won't do any good, // so we can just kill the input forwarder rather than waiting for // it to finish. Stream.debug.println("Interrupting " + in.getName() + "..."); in.interrupt(); } if (!block || eofbug) { // If we're in noblock mode, we can't detect EOF directly // because it would block. If we're in eofbug mode, we can't // detect EOF because it's a bug. In either case, sleep for a // few seconds to make sure everything gets read before trying // to stop the threads. try { Thread.sleep(3000); } catch (InterruptedException e) { } out.die = true; err.die = true; } if (out.reading && eofbug) { // It's blocked waiting for input which it will never get. Stream.debug.println("Interrupting " + out.getName() + "..."); out.interrupt(); } else { // Wait for it to finish writing. Stream.debug.println("Waiting for " + out.getName() + "..."); out.join(); } if (err.reading && eofbug) { // It's blocked waiting for input which it will never get. Stream.debug.println("Interrupting " + err.getName() + "..."); err.interrupt(); } else { // Wait for it to finish writing. Stream.debug.println("Waiting for " + err.getName() + "..."); err.join(); } return ret; @) /** This is stupid: convert an array into a Vector. */ static Vector makeVec(Object array[]) (@ if (array == null) return new Vector(); Vector vector = new Vector(array.length); for (int i = 0; i < array.length; i++) vector.addElement(array[i]); return vector; @) } OutputForwarder { (@ boolean die = false, reading = false; @) /** Copy input to output, blocking if necessary. */ public void run() (@ setName(forwardFrom + " -> " + forwardTo); try { final int l = 1024; int a = 0, n = 0; byte b[] = new byte[l]; if (block) { reading = true; while ((n = in.read(b, 0, l)) != -1) { reading = false; Stream.debug.println("Read " + n + " bytes from " + forwardFrom); out.write(b, 0, n); out.flush(); Stream.debug.println("Wrote " + n + " bytes to " + forwardTo); if (die) break; reading = true; } reading = false; } else do { if ((a = waitForAvailable()) == 0) break; Stream.debug.println("Available: " + a); if ((n = in.read(b, 0, Math.min(a, l))) != -1) { Stream.debug.println("Read " + n + " bytes from " + forwardFrom); out.write(b, 0, n); out.flush(); Stream.debug.println("Wrote " + n + " bytes to " + forwardTo); } } while (n != -1); } catch (InterruptedIOException e) { // ignore-- we meant to kill the thread } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e.toString()); } @) /** Wait until there are bytes available and return the amount. Yield this thread instead of blocking. Return 0 if terminated prematurely (e.g. by "die" being set to true). */ int waitForAvailable() throws IOException (@ int a = 0; while ((a = in.available()) == 0) { if (die) { Stream.debug.println(getName() + " dying."); return 0; } yield(); } return a; @) } InputForwarder { /** Copy input to output, blocking if necessary. Close output when finished. */ public void run() (@ super.run(); try { out.close(); } catch (IOException e) { throw new RuntimeException(e.toString()); } @) } Stream { (@ static { out = new PrintWriter(System.out, true); err = new PrintWriter(System.err, true); verbose = new PrintWriter(new NullOutputStream()); debug = new PrintWriter(new NullOutputStream()); } @) } NullOutputStream { public void write(int b) (@ @) }