For a school project I am trying to create a terminal in Java. The terminal works in the following way:
User types a command
Program grabs command and replaces <command> with the command in the string
/bin/bash -c "cd current/directory/; <command>; echo kjsfdjkadhlga; pwd
Program starts the process created via a ProcessBuilder object
Program spawns a thread that reads from stdout and stderr
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.
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());
}
}
Related
I have a VB script to which I need to pass username and password.
I want to run this VB script through Java code programmatically.
Is there a way that I can pass the Windows credentials to the VB script in Java programmatically?
You can have the credentials on the OS environment and read them from there:
String credentials = System.getenv().get("encrypted_credentials_or_something");
And then run your command from Java. However, Runtime.exec() won't work in some cases:
When the command is not on the System's PATH
When arguments are involved
When you want to have access to the process output
When you need to be able to kill the process
When you need to check if it terminated successfully or in error (status code != 0 - which is why you write System.exit(int) to terminate a Java application. The System.exit(1), for example, indicates abnormal termination)
That's why I created this utility class to execute external processes with arguments and everything. It works very well for me:
import java.io.*;
import java.util.*;
public class ExternalCommandHelper {
public static final void executeProcess(File directory, String command) throws Exception {
InputStreamReader in = null;
try {
//creates a ProcessBuilder with the command and its arguments
ProcessBuilder builder = new ProcessBuilder(extractCommandWithArguments(command));
//errors will be printed to the standard output
builder.redirectErrorStream(true);
//directory from where the command will be executed
builder.directory(directory);
//starts the process
Process pid = builder.start();
//gets the process output so you can print it if you want
in = new InputStreamReader(pid.getInputStream());
//simply prints the output while the process is being executed
BufferedReader reader = new BufferedReader(in);
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int status = 0;
//waits for the process to finish. Expects status 0 no error. Throws exception if the status code is anything but 0.
if ((status = pid.waitFor()) != 0) {
throw new IllegalStateException("Error executing " + command + " in " + directory.getAbsolutePath() + ". Error code: " + status);
}
} finally {
if (in != null) {
in.close();
}
}
}
//Splits the command and arguments. A bit more reliable than using String.split()
private static String[] extractCommandWithArguments(String command) {
StringTokenizer st = new StringTokenizer(command);
String[] cmdWithArgs = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++) {
cmdWithArgs[i] = st.nextToken();
}
return cmdWithArgs;
}
}
I have a project where program has to open notepad file and after entering text and saving that notepad file program should display number of words in that file and it should delete the entered content in the file.
iam getting this error Error not derjava.lang.NullPointerException after running the program.
though after entering some text in Mytext.txt and saving it?
my question is why BufferedReader is reading empty file even though iam saving the file with some content.
Appreciate the help..
public class Notepad_Example {
public static void main(String[] jfb) {
try {
ProcessBuilder proc = new ProcessBuilder("notepad.exe", "C:\\Java Projects\\Reverse String\\src\\Mytext.txt");
proc.start();
BufferedReader br;
String s;
br = new BufferedReader(new FileReader("C:\\Java Projects\\Reverse String\\src\\Mytext.txt"));
s = br.readLine();
char c[] = new char[s.length()];
int j = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) != ' ') {
c[i] = s.charAt(i);
} else {
j++;
}
}
System.out.println("number of words are " + (j + 1));
br.close();
} catch (Exception hj) {
System.out.println("Error not der" + hj);
}
try {
FileWriter fw = new FileWriter("C:\\Java Projects\\Reverse String\\src\\Mytext.txt");
fw.close();
} catch (Exception hj) {
System.out.println("Error not der" + hj);
}
}
}
The issue you are having is here:
ProcessBuilder proc=new ProcessBuilder("notepad.exe","C:\\Java Projects\\Reverse String\\src\\Mytext.txt");
proc.start();
proc.start() is returning the freshly started process. You'll have to give the user the chance to edit and save the file and close the editor before you can read from that file. That is you have to wait for that process to finish before you can start using the results (the saved file) of that process.
So do instead something like this:
Process process = proc.start();
int result = process.waitFor();
if (result == 0) {
// Do your rest here
} else {
// give error message as the process did not finish without error.
}
Some further remarks:
The rest of your code also appears to have some issues.
You are only reading one line of that file. What if the user is using new lines?
The exception handling is not very good, at the very least print the stack trace of the exception which will give you further hints of where an exception was occuring
If you are using Java 7, read on try with resources; if you are using Java 6, add finally blocks to make sure your resources (the streams) are getting closed.
When you run proc.start(); it is not going to block and waitfor the process to end, it will continue running.
You will need to call the proc.waitFor() method, to block until it has finished.
NOTE
we have had some weird behaviour when using the process builder...
we used to start the process with a
new ProcessBuilder("notepad.exe", "C:\\Java Projects\\Reverse String\\src\\Mytext.txt");
but that started to fail wen we upgraded to Win7 and Java7 - we we not sure where this problem really originated, but we changed out Code like this:
String[] cmd = new String[]{"notepad.exe", "C:\\Java Projects\\Reverse String\\src\\Mytext.txt"};
new ProcessBuilder(cmd);
and since then it worked correct!
I want to transfer the content of a String object from a Java program to a C program as input. To avoid the use of temporarly file I want to use the stdin with process.getOutputStream() as I'm executing the C program with ProcessBuilder via my Java program.
From the shell, my C program is able to work from input gave from the keyboard or with a file given by a redirector:
#myprog < myFile.txt
When I send data from my Java program process.getOutputStream().write() it is working while there is less than 16363bytes to send. If more, an IOException occur with message "Stream closed".
Here my java code:
ProcessBuilder processBuilder;
if (Double.isNaN(delta2))
{
processBuilder = new ProcessBuilder(CMD.getPath(), Integer.toString(algoType.ordinal() + 1));
}
else
{
processBuilder = new ProcessBuilder(CMD.getPath(), Integer.toString(algoType.ordinal() + 1), Double.toString(delta2));
}
try
{
Process process = processBuilder.start();
OutputStream writer = process.getOutputStream();
InputStream matrixToSend = new ByteArrayInputStream(shortestPathMatrix.getBytes());
int buf;
int i = 0;
if (matrixToSend != null)
{
while ((buf = matrixToSend.read()) != -1)
{
System.out.println(i++);
if (i == 8172)
{
writer.flush();
}
writer.write(buf);
}
System.out.println("plouf");
writer.close();
}
ReadThread stdoutThread = new ReadThread(process.getInputStream());
ReadThread stderrthread = new ReadThread(process.getErrorStream());
process.waitFor();
for (ReadThread t : new ReadThread[]
{ stdoutThread, stderrthread })
{
synchronized (t)
{
if (!t.hasCompleted)
{
t.wait();
}
}
}
ProcessOutput output = new ProcessOutput(process.exitValue(), stdoutThread.targetBuffer.toByteArray(), stderrthread.targetBuffer.toByteArray());
if (output.getReturnCode() == 0)
{
writer.close();
return parseOutoutToResult(new String(output.getStdout()));
}
else
{
writer.close();
byte[] out = output.getStdout();
byte[] err = output.getStderr();
String error = new String(err.length == 0 ? out : err);
throw new IllegalStateException("Command " + CMD.getPath() + " [" + Integer.toString(algoType.ordinal() + 1) + " " + Double.toString(delta2) + "] has failed (exit code=" + output.getReturnCode()
+ ")\n" + error);
}
}
catch (Exception e)
{
e.printStackTrace();
}
In my C code, I simply do:
FILE *f = stdin;
And then:
while (fgets(line, MAX_LINE_LENGTH, f))
{
//process
}
Thanks for any help.
Either fgets() does not get executed or
assuming MAX_LINE_LENGTHis larger then 16363: As the C source uses fgets() which only returns after having received (an EOF or) a newline (typicall a \n) the Java code shall send a \n before the transmitting pipe is full.
The size of a pipe is system dependened and seems to be 16k on your system.
Also the Java code seems to be sending binary data, where as the C code expects ascii data (as using fgets()). So the Java ought to convert the integers it sends to their ascii format and write() out the latter.
Thanks all for your help. In fact it was a stupid bug in the way I was formatting the data to be send. The C program was closing the pipe as it was not reconizing the data...
I'm trying to get input from the console of a .exe process started by a Java script. Nothing appears in the console window, and nothing is read by the program until the process is terminated.
blServ = new ProcessBuilder(blPath + "Blockland.exe", "ptlaaxobimwroe", "-dedicated", "-port " + port, "-profilepath " + blPath.substring(0, blPath.length() - 1)).start();
System.out.println("Attempting to start server...\n" + blPath);
consoleIn = new BufferedReader(new InputStreamReader(blServ.getInputStream()));
'blServ' is a Process object. And yes, the program is starting successfully.
public void blStreamConsole() //called once every 500 milliseconds
{
String lineStr = "";
String line = "";
int lines = 0;
try
{
if (consoleIn != null)
{
while ((line = consoleIn.readLine()) != null)
{
//if (!line.equals("%"));
//{
lineStr += line + wordSym;
lines++;
//}
}
}
}
catch (IOException e)
{
netOut.println("notify" + wordSym + "ERROR: An I/O exception occured when trying to get data from the remote console. Some lines may not be displayed.");
}
if (!lineStr.equals("") && !(lineStr == null))
netOut.println("streamconsole" + wordSym + lines + wordSym + lineStr);
}
Basically, this method sees if there is more input waiting in the consoleIn object, and if there is, it appends every line it has to another string, and that other string is sent to a client. Unfortunately, it is all sent in one big chunk right when Blockland.exe is closed. Sorry about the indenting issues. The Stackoverflow editor re-arranged all of the code.
It seems to me that there are two possibilities here:
readLine blocks, waiting for input (and doesn't return null as you expect). You may be able to fix it by not using BufferedReader and instead using the InputStream
The output stream doesn't flush until all the input has been written. Try putting a flush there:
Also note that if lineStr is null, you'll get a NullPointerException as your code currently is (you need to swap your conditions), but it can't even be null.
if (!lineStr.isEmpty())
{
netOut.println("streamconsole" + wordSym + lines + wordSym + lineStr);
netOut.flush();
}
while ((line = consoleIn.readLine()) != null){
lineStr += line + wordSym;
lines++;
}
The problem with this piece of code is that it will keep running until the program exits. It will append every single line to lineStr until the program exits (when console.readLine() is null). The whole lineStr is then printed afterwards, containing the whole console.
If you want to continuously print the output, you will need to print it immediatly:
while ((line = consoleIn.readLine()) != null){
netOut.println(line);
}
You can run this in one separate thread, and it will keep outputting the console to the output stream until the program exits.
I'm not sure why, but while I am trying to debug, I find this is very weird:
As you see in the image, the value of in.readLine() is null and in.readLine() == null is true. But why it skips the if (in.readLine() == null) { ... line? But when I tried to place the breakpoint in line 266 and 267, it's entering the code on that condition.
The code:
private void startSOfficeService() throws InterruptedException, IOException {
if (System.getProperty("os.name").matches(("(?i).*Windows.*"))) {
try {
//Check if the soffice process is running
Process process = Runtime.getRuntime().exec("tasklist /FI \"IMAGENAME eq soffice.exe\"");
//Need to wait for this command to execute
int code = process.waitFor();
//If we get anything back from readLine, then we know the process is running
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
if (in.readLine() == null) {
//Nothing back, then we should execute the process
String[] SOFFICE_CMD = { SOFFICE_SERVICE_PATH,
"-accept=socket,host=" + SOFFICE_SERVICE_HOST + ",port=" + SOFFICE_SERVICE_PORT + ";urp;",
"-invisible",
"-nologo"};
process = Runtime.getRuntime().exec(SOFFICE_CMD);
code = process.waitFor();
System.out.println("soffice script started");
} else {
System.out.println("soffice script is already running");
}
in.close();
in = null;
System.gc();
} catch(Exception e) {
e.printStackTrace();
}
}
}
When your debugger evaluates in.readLine(), it consumes from the reader. Therefore, if you were on the last line of whatever is being read, in.readLine() would be non-null, putting control in the else, but when you evaluate in.readLine() to display in the debugger, it reads again, finds that there are no more lines, and returns null as the value to show in the debugger.
To see the real picture, assign in.readLine() to a variable first, and watch the value of that variable, which won't change by simply reading it.