3

For a school project I am trying to create a terminal in Java. The terminal works in the following way:

  1. User types a command
  2. Program grabs command and replaces <command> with the command in the string

    /bin/bash -c "cd current/directory/; <command>; echo kjsfdjkadhlga; pwd
    
  3. Program starts the process created via a ProcessBuilder object

  4. Program spawns a thread that reads from stdout and stderr
  5. Program continues looking for user input, and if the command is done running, then whatever the user entered is run as a command, otherwise it is fed to the currently running command as input.
  6. As output is generated, program looks through the output for the kjsfdjkadhlga string so it knows when the user's command is done being run, and then grabs the remaining output and stores it as the current path that the user is at.

How this works/reasons for everything:

In order to avoid myself having to implement my own input parser to handle things like multiple commands on a line, IO redirection, and whatnot to work with the ProcessBuilder, I just essentially convert the command to a bash script and let bash execute it.

Since every process executes only a single command (or whatever it was given at the time of creation, which is a single user command in this case) then terminates, no process specific information is stored, such as the current working directory. To transfer that information, I call pwd after the user's command and then in the process of the next command, but before the user's command is run, I cd to that directory, effectively allowing the value of $PWD to persist between processes.

The Problem:

It all works well, except for when user interaction is required. If the user just types cat, it is supposed to wait for a line of user input, then print it, then wait for a line of user input, then print it, and repeat forever (I don't handle Crtl+C yet...). However, what actually happens is that the terminal waits for a line of user input, then prints it, then terminates without waiting for more input.

What I have tried:

Currently, I provide input to the command being run with:

 BufferedWriter stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
 stdin.write(input);
 stdin.newLine();
 stdin.close();

If instead of calling close(), I call flush(), then cat ends up waiting for user input and not doing anything until I terminate my Terminal program, at which point it then prints everything the user had input.

It appears that the flush() function doesn't actually do anything. A Stack Overflow question mentioned using the raw OutputStream and calling write() instead of using a BufferedWriter. However, that has the same effect. In the OutputStream documentation for flush(), it states that "The flush method of OutputStream does nothing."

I have also tried using a BufferedOutputStream, but the documentation says that its flush function simply forces the buffered data to be written to the underlying OutputStream, which doesn't change the fact that the OutputStream is not flushing its stream.

This question seems to be the most promising, but I couldn't get it to work when implementing it. It may be because I am on Mac OS instead of Windows.

Does anybody know how to do this if keeping stdin open long enough to submit multiple lines of input is possible, or if I am going about it wrong?


Code

main()

Terminal terminal = new Terminal();
Scanner in = new Scanner(System.in);

while (in.hasNextLine())
{
    String line = in.nextLine();
    terminal.sendInput(line, terminal);
}

terminal.sendInput() called by main

// ProcessReaderDelegate implements functions called when receiving output on stdout, stderr, and when the process terminates.
public int sendInput(String text, ProcessReaderDelegate delegate)
{
    if (processes.size() > 0)
    {
        processes.get(0).sendInput(text);  // Is a ProcessReader object
        return 1;
    }

    run(text, delegate);   // runs the given text as the <command> text described above
    return 2;
}

ProcessReader's sendInput() called by terminal.sendInput()

public boolean sendInput(String input)
{
    try
    {
        // stdin and process are a instance fields
        // tried this and doesn't seem to work (with either flush or close)
        stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
        stdin.write(input);
        stdin.newLine();
        stdin.close();

        // tried this and doesn't seem to work (with either flush or close)
        //BufferedOutputStream os = new BufferedOutputStream(process.getOutputStream());
        //os.write(input.getBytes());
        //os.write("\n".getBytes());
        //os.flush();
        //os.close();

        return true;
    }
    catch (IOException e)
    {
        System.out.println("ERROR: this should never happen: " + e.getMessage());
        return false;
    }
}

terminal.run() called by terminal.sendInput()

public void run(String command, ProcessReaderDelegate delegate)
{
    // don't do anything with empty command since it screws up the command concatentaion later
    if (command.equals(""))
    {
        delegate.receivedOutput(null, prompt);
        return;
    }

    try
    {
        // create the command
        List<String> list = new ArrayList<String>();
        list.add(shellPath);
        list.add(UNIX_BASED ? "-c" : "Command : ");

        String cmd = (UNIX_BASED ? getUnixCommand(command) : getWindowsCommand(command));
        list.add(cmd);
        //System.out.println("command='" + list.get(0) + " " + list.get(1) + " " + list.get(2) + "'");

        // create the process and run it
        ProcessBuilder builder = new ProcessBuilder(list);
        Process p = builder.start();
        ProcessReader stdout = new ProcessReader(p, delegate, this);
        new Thread(stdout).start();

        processes.add(stdout);
    }
    catch (IOException e)
    {
        System.out.println(e.getMessage());
    }
}

ProcessReader.run() executed in thread and reads stdout and stderr

public void run()
{
    try
    {
        boolean hitend = false;
        String buffer = "";

        while (true)
        {
            int c;
            String text;

            // ======================================================
            // read from stdout
            // read the next character
            c = stdout.read();

            // build the string
            while (c != -1)     // while data available in the stream
            {
                buffer += (char)c;
                c = stdout.read();
            }

            // send the string to the delegate
            if ((!hitend) && (buffer.length() > 0))
            {
                // END_STRING is the "kjsfdjkadhlga" echoed after the command executes
                int index = buffer.indexOf(END_STRING);

                if (index >= 0)
                {
                    hitend = true;
                    text = buffer.substring(0, index);
                    buffer = buffer.substring(index + END_STRING.length());

                    if (outputDelegate != null)
                    {
                        outputDelegate.receivedOutput(process, text);
                    }
                }
                else
                {
                    for (int i = END_STRING.length() - 1; i >= 0; i--)
                    {
                        index = buffer.indexOf(END_STRING.substring(0, i));

                        if (i == 0)
                        {
                            index = buffer.length();
                        }

                        if (index >= 0)
                        {
                            text = buffer.substring(0, index);
                            buffer = buffer.substring(index + i);

                            if (outputDelegate != null)
                            {
                                outputDelegate.receivedOutput(process, text);
                            }
                        }
                    }
                }
            }

            // ======================================================
            // read from stderr
            // read the next character
            c = stderr.read();
            text = "";      // slow method; make faster with array

            // build the string
            while (c != -1)     // while data available in the stream
            {
                text += (char)c;
                c = stderr.read();
            }

            // send the string to the delegate
            if ((text.length() > 0) && (outputDelegate != null))
            {
                outputDelegate.receivedError(process, text);
            }

            // ======================================================
            // check if the process is done (and hence no more output)
            boolean done = false;

            try
            {
                int value = process.exitValue();
                done = true;        // if got to this point, then process is done

                // read the ending environment variables
                Map<String, String> env = new HashMap<String, String>();
                String[] words = buffer.split(" ");

                env.put(ENV_WORKING_DIR, words[0]);

                if (envDelegate != null)
                {
                    envDelegate.processTerminatedWithEnvironment(process, env);
                }

                // end the process
                outputDelegate.processEnded(process);
                stdout.close();
                stderr.close();

                break;
            }
            catch (Exception e) {System.out.println(e.getMessage());}   // no exit value --> process not done

            if (done)   // just on the off chance that closing the streams crashes everything
            {
                break;
            }
        }
    }
    catch (IOException e)
    {
        System.out.println("ERROR: ProcessReader: " + e.getMessage());
    }
}
Community
  • 1
  • 1
Adam Evans
  • 2,072
  • 1
  • 20
  • 29
  • 1
    `OutputStream.flush()` does nothing because there is no buffer to flush. `BufferedOutputStream.flush()` flushes its buffer. Your problem appears to have more to do with the behaviour of `cat` than of Java. You shouldn't close the output stream if you plan to write further to it. You are going to have to post some code. – user207421 Feb 25 '16 at 02:45
  • @EJP I agree with the idea that I shouldn't close the output stream if I want to continue writing. However, closing it seems to be the only way to get `cat` to process the current input and produce output. That is the question: how to leave the output stream open and get the process to consume the input. – Adam Evans Feb 25 '16 at 04:30

0 Answers0