9

Basically, I have a process which runs when I press a button on my java application. And this process executes a command to the terminal of the OS. But sometimes this command needs to have an interaction with the user. And I would like to know if this was possible to have the interaction from the process to the user when needed?

My code:

File marsSimulator = new File("resources/mars_simulator/Mars4_5.jar");
if(marsSimulator.exists() && temp.exists()){
   String res="";
   try {
     Process p = Runtime.getRuntime().exec(new String[]{"java","-jar",marsSimulator.getAbsolutePath(),tempAssembly.getAbsolutePath()});
     p.waitFor();

     InputStream is = p.getInputStream();

     byte b[] = new byte[is.available()];
     is.read(b, 0, b.length); // probably try b.length-1 or -2 to remove "new-line(s)"

     res = new String(b);

   } catch (Exception ex) {
     ex.printStackTrace();
   }
}

Also, I forgot to say that the application is made with SWING and that the output of the process is shown onto a TextArea... Should I change anything ?

Notice that the process blocks when there is an interaction with the user. If there isn't, the process doesn't block !

What do I need to do in this case (which I don't know how to do it ) ?

  1. When the process needs the interaction. I need to know when the process wants some interaction.
  2. I need to get the output generated of the process interactively (line by line).

P.S.: For people who wanna understand the process line, I am using the Mars Simulator (http://courses.missouristate.edu/KenVollmar/MARS/) and I am sending the jar application into a process with a mips assembly code associated.


This next pieces of code is working with my project

Hope it will help for the next adventurers!

And thank you to Nicolas Filotto for helping me.

My class ObservableStream:

class ObservableStream extends Observable {
  private final Queue<String> lines = new ConcurrentLinkedQueue<>();

  public void addLine(String line) {
    lines.add(line);
    setChanged();
    notifyObservers();
  }

  public String nextLine() {
    return lines.poll();
  }

  public String getLine(){return lines.peek();}
}

And the other part of the code:

Process p = Runtime.getRuntime().exec(new String[]{"java","-jar",marsSimulator.getAbsolutePath(),tempAssembly.getAbsolutePath()});

//This code does the interaction from the process with the GUI ! Implied, input interaction+output interaction from the process
ObservableStream out = new ObservableStream();
// Observer that simply sends to my external process line by line what we put in
// the variable output
PrintWriter writer = new PrintWriter(p.getOutputStream(), true);
out.addObserver(
  (o, arg) -> {
    ObservableStream stream = (ObservableStream) o;
    String line;
    while ((line = stream.nextLine()) != null) {
      writer.println(line);
    }
  }
);

ObservableStream input = new ObservableStream();
input.addObserver(
  (o, arg) -> {
    ObservableStream stream = (ObservableStream) o;
    String line;
    while ((line = stream.nextLine()) != null) {
      outputTextArea.appendText(line+"\n");
    }
  }
);

// The thread that reads the standard output stream of the external process
// and put the lines into my variable input
new Thread(
  () -> {
    try (BufferedReader reader = new BufferedReader(
      new InputStreamReader(p.getInputStream()))
    ) {
       String line;
       while ((line = reader.readLine()) != null) {
         input.addLine(line);
      }
    } catch (IOException e1) {
      e1.printStackTrace();
  }
 }
).start();


new Thread(
  ()->{
    while(p.isAlive()){
      String res = input.getLine();
      if(res!=null && res.equals("Enter integer value:")) {
        boolean integerIsRequested=true;
        Thread t=null;
        while(integerIsRequested){
          if(t==null) {
            t = new Thread(new Runnable() {
              public void run() {
                String test1 = JOptionPane.showInputDialog("Enter Integer value:");
                while(!test1.matches("^\\d+$")){
                  test1 = JOptionPane.showInputDialog("Error: Not a valid Integer.\nEnter a correct Integer value:");
                }
                Integer i = Integer.valueOf(test1);

                if (i != null) {
                  out.addLine(test1);
                }
              }
            });
            t.start();

          }
          if(!t.isAlive()){
            integerIsRequested=false;
          }
        }
      }
    }
    outputTextArea.appendText("Program executed\n");
  }
).start();

By the way, this post is unique Jarrod ;)

13
  • 2
    That's what InputStreams and OutputStreams are for. Commented May 14, 2016 at 0:39
  • And how could I know if the user needs to have some interaction ? @HovercraftFullOfEels Commented May 14, 2016 at 0:55
  • 1
    I'm no pro at this, but perhaps by 1) understanding the potential interaction need, 2) catching what comes back to you and parsing it, and 3) then responding. Commented May 14, 2016 at 0:59
  • both proccesses can share stdin and stdout. that would be the simplest, yet possibly unreliable aolution if both of your programs use stdin/stdout for something. if it's only the second process, it's pretty safe. Commented May 14, 2016 at 1:18
  • 1
    A process does not send any specific signals when it needs some input. It just reads it from it's inputstream or waits until an input is available in the inputstream. The only way to know if input is required is to read it's output and figure out if its expecting an input (just like a human would do). Commented May 17, 2016 at 16:33

4 Answers 4

5
+150

To implement such use case I would personally use:

  1. An Observable object to notify my UI when a new line has been provided by the external process
  2. An Observable object to which I add new lines provided by my UI
  3. An Observer of #1 that will refresh the data of my UI
  4. An Observer of #2 that will send the lines provided by my UI to my external process
  5. A Thread that will check if a new line has been provided by my external process and if so it will provide those lines to #1

So as I don't have your full env, I will show you how it will work with mock objects:

First my fake external application that only does an Echo of what he receives:

public class Echo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String line = scanner.nextLine();
            System.out.printf("echo > %s%n", line);
        }
    }
}

If this class receives foo, it will print into the standard output stream echo > foo

Then my Observable class

public class ObservableStream extends Observable {
    private final Queue<String> lines = new ConcurrentLinkedQueue<>();

    public void addLine(String line) {
        lines.add(line);
        setChanged();
        notifyObservers();
    }

    public String nextLine() {
        return lines.poll();
    }
}

NB: The class ObservableStream (as it is implemented so far) is meant to have only one Observer no more which is enough according to your needs. Indeed is only used to decouple your UI from how the data is retrieved or published

Then finally the main code:

Process p = Runtime.getRuntime().exec(
    new String[]{"java", "-cp", "/my/path/to/my/classes", "Echo"}
);
// The Observable object allowing to get the input lines from my external process
ObservableStream input = new ObservableStream();
// A mock observer that simply prints the lines provided by the external process
// but in your case you will update your text area instead
input.addObserver(
    (o, arg) -> {
        ObservableStream stream = (ObservableStream) o;
        String line;
        while ((line = stream.nextLine()) != null) {
            System.out.printf("Line Received from the external process: %s%n", line);
        }
    }
);
// The thread that reads the standard output stream of the external process 
// and put the lines into my variable input
new Thread(
    () -> {
        try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(p.getInputStream()))
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                input.addLine(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
).start();
// The Observable object allowing to send the input lines to my external process
ObservableStream output = new ObservableStream();
// Observer that simply sends to my external process line by line what we put in
// the variable output
PrintWriter writer = new PrintWriter(p.getOutputStream(), true);
output.addObserver(
    (o, arg) -> {
        ObservableStream stream = (ObservableStream) o;
        String line;
        while ((line = stream.nextLine()) != null) {
            writer.println(line);
        }
    }
);

// A simple scanner used to send new messages to my external process
Scanner scanner = new Scanner(System.in);
while (true) {
    output.addLine(scanner.nextLine());
}

If this code receives foo, it will print into the standard output stream Line Received from the external process: echo > foo

Sign up to request clarification or add additional context in comments.

18 Comments

Ok, so far the output stream is working well ! But when I implement the input stream it doesn't work ! It freezes my program. Probably I would put that on a thread ? And if yes, where do I need to put it ? If not and this should be working, then I don't understand...
I don't know your code so I don't know what's wrong but indeed using a Thread will help but you have the logic. You rely on the observer design pattern to decouple your UI from how the data is retrieved or published
what do you call output stream in this case?
Ok, so the "ObservableStream output = new ObservableStream();" doesn't work (but if I remove it, it works well). P.S.: everythings under ObservableStream output... makes my program freezing.
No, I don't believe so the only way is to make your external program print a message asking for an input that you will be able to get from the variable input. If you don't ask me a question, I cannot know that you expect an answer, this is the same idea
|
2

I hope it answers your question... subProcessStuff "emulates" that sub process. It can be anything - but this way we have all in place. It requires 2 params passed into console. String and Integer. Gobbler got Callback which is an interface, with anonymous implementation - and there are checks for params. To answer if subprocess waits we simply track what is says - just like if a user would operate with it.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Scanner;

class Test1 {
    public static void main(String[] args) {
        for (String arg : args)
            System.out.println("arg: " + arg);

        for (String arg : args)
            if (arg.equals("-test")) {
                subProcessStuff();
                return;
            }
        mainProcess();

    }

    public static void subProcessStuff() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            System.out.println("Enter String");
            String s = br.readLine();
            System.out.println("Enered String: " + s);
            System.out.println("Enter Integer:");
            int i = Integer.parseInt(br.readLine());
            System.out.println("Entered Integer: " + i);
        } catch (IOException e) {
            System.err.println("io error - " + e.getMessage());
        } catch (NumberFormatException nfe) {
            System.err.println("Invalid Format!");
        }
    }

    private static PrintStream out;

    public static void mainProcess() {
        String[] commands = { "ls", "-alt" };
        ProcessBuilder builder = new ProcessBuilder("java", "Test1", "-test");

        // builder.inheritIO(); // I avoid this. It was messing me up.

        try {
            Process proc = builder.start();
            InputStream errStream = proc.getErrorStream();
            InputStream inStream = proc.getInputStream();
            OutputStream outStream = proc.getOutputStream();

            new Thread(new StreamGobbler("err", out, errStream)).start();

            out = new PrintStream(new BufferedOutputStream(outStream));

            Callback cb = new Callback() {
                @Override
                public void onNextLine(String line) {
                    if (line.equals("Enter String")) {
                        out.println("aaaaa");
                        out.flush();
                    }
                    if (line.equals("Enter Integer:")) {
                        out.println("123");
                        out.flush();                    
                    }
                }
            };
            new Thread(new StreamGobbler("in", out, inStream, cb)).start();
            int errorCode = proc.waitFor();
            System.out.println("error code: " + errorCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
}

interface Callback {
    void onNextLine(String line);
}

class StreamGobbler implements Runnable {
    private PrintStream out;
    private Scanner inScanner;
    private String name;
    private Callback cb;

    public StreamGobbler(String name, PrintStream out, InputStream inStream) {
        this.name = name;
        this.out = out;
        inScanner = new Scanner(new BufferedInputStream(inStream));
    }

    public StreamGobbler(String name, PrintStream out, InputStream inStream, Callback cb) {
        this.name = name;
        this.out = out;
        inScanner = new Scanner(new BufferedInputStream(inStream));
        this.cb = cb;
    }

    @Override
    public void run() {
        while (inScanner.hasNextLine()) {
            String line = inScanner.nextLine();
            if (cb != null)
                cb.onNextLine(line);
            System.out.printf("%s: %s%n", name, line);
        }
    }
}

Comments

1

I don't think you can check the state of the process from the Java. However you can do it by using some Linux command. (Of course if you're using Linux)

If your Java process has access to the /proc directory then you can read the status file for the process.

For example for a process with process id 12280

/proc/12280/status

Here's the relevant output of the status file

Name:   java
State:  S (sleeping)
Tgid:   12280
Pid:    12280
PPid:   12279
...

Second line gives the state of the process. You'll need to run a thread to continuously poll this file to read the status.

1 Comment

The problem is that my application must be cross-platform.
0

Line by Line The Code i use to interract with a different jar which is a speechRecognizer.I think you want to achieve something like this.

Example:

The jar i am interracting(speechRecognizer) is executing different commands and run some other Threads.Every time it has to interract with the main jar it prints something that i need.For example (user said:How are you),so you can have a same logic and when external jar need interraction with user it prints something and you read it into the main app.So:

// About Process
private Process process;
private BufferedReader bufferedReader;
private boolean stopped = true;
Thread processChecker;

//Running it in a Thread so the app don't lags
new Thread(() -> {
        try {
            stopped = false;
            //Starting the external jar..
            ProcessBuilder builder = new ProcessBuilder("java", "-jar", System.getProperty("user.home")
                    + File.separator + "Desktop" + File.separator + "speechRecognizer.jar", "BITCH_PLEASE");
            //Redirecting the ErrorStream
            builder.redirectErrorStream(true);
            process = builder.start();
            bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;



            //Check continusly if the process is still alive
            //i case of crash i should do something..
            processChecker = new Thread(() -> {
                while (process.isAlive()) {
                    try {
                        Thread.sleep(1200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                stopSpeechReader(false);
            });
            processChecker.start();

            // Continuesly Read Output of external process
            while (!stopped) {
                while ((line = bufferedReader.readLine()) != null && !line.isEmpty()) {
                    System.out.println(line);
                    checkSpeechResult(line);
                }
            }

            // Interrupt the mf Thread if is Alive
            if (processChecker.isAlive())
                processChecker.interrupt();
            System.out.println("SpeechReader Stopped! Process is alive:" + process.isAlive() + " >Exit Value:"
                    + process.exitValue());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.