Java read process output unbuffered / realtime - java

I want to read the stdout of a process right as it is generated.
The process will send information for a progress indicator, so it doesn't make sense that I get the information all at once, which I do and which is the problem. I tried to use Scanner class as suggested in a post, but I still get the output only after the process has finished.
I realize this question has been asked before, but it hasn't been answered.
You will probably want to look at class StreamGobblerOutput first.
public List<String> executeCall(String fileName)
{
StringBuilder sbOutput = new StringBuilder();
StringBuilder sbError = new StringBuilder();
File file = new File(fileName);
try ( BufferedReader br = new BufferedReader(new FileReader(file)) ) {
String line;
while ((line = br.readLine()) != null) {
String [] parts = line.split("\\s");
if(parts.length<2) {
sbError.append("Command too short for call: " + parts[0]);
continue;
}
List<String> args = new ArrayList<String>();
args.add ("sfb.exe");
for(int i = 1; i <parts.length; ++i) {
args.add (parts[i]);
}
args.add (sfbPassword);
ProcessBuilder pb = new ProcessBuilder (args);
pb.directory(new File(Support.getJustThePathFromFile(file)));
Map<String, String> envs = pb.environment();
String path = envs.get("Path");
envs.put("Path", Paths.get(".").toAbsolutePath().normalize().toString() + ";" +path);
//pb.redirectOutput(new Redirect() {});
Process p = pb.start();
String outputPathPrefix = pb.directory().getCanonicalPath();
// any output?
StreamGobblerOutput outputGobbler = new StreamGobblerOutput(p.getInputStream(), outputPathPrefix);
outputGobbler.start();
// any errors?
StreamGobblerError errorGobbler = new StreamGobblerError(p.getErrorStream());
errorGobbler.start();
try
{
p.waitFor();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
sbOutput = outputGobbler.getOutput();
sbError = errorGobbler.getErrors();
String rootPath= Support.getJustThePathFromFile(new File(fileName));
File rootFile = new File(rootPath + "/..");
String rootFolder = rootFile.getCanonicalFile().getName();
System.err.println("rootFolder: " + rootFolder);
mainApp.addModifiedFiles(outputGobbler.getModifiedFileNames(), rootFolder);
}
} catch ( IOException ex) {
sbError.append(ex.getMessage());
}
mainApp.addOutput(sbOutput.toString());
mainApp.addError(sbError.toString());
return;
}
private class StreamGobblerOutput extends Thread {
private InputStream is;
private String outputPathPrefix;
private StringBuilder sbOutput;
private List<String> modifiedFileNames;
private Scanner scanner;
private StreamGobblerOutput(InputStream is, String outputPathPrefix) {
this.is = is;
this.outputPathPrefix = outputPathPrefix;
sbOutput = new StringBuilder();
modifiedFileNames = new ArrayList<String>();
scanner = new Scanner(is);
}
public StringBuilder getOutput() {
return sbOutput;
}
public List<String> getModifiedFileNames() {
return modifiedFileNames;
}
#Override
public void run() {
//create pattern
Pattern patternProgress = Pattern.compile("\\((\\d+)%\\)");
//InputStreamReader isr = new InputStreamReader(is);
//BufferedReader br = new BufferedReader(isr);
String ligne = null;
while (scanner.hasNextLine()) {
ligne = scanner.nextLine();
sbOutput.append(ligne);
sbOutput.append("\r\n");
//bw.write("\r\n");
Matcher mProgress = patternProgress.matcher(ligne);
if (mProgress.find()) {
int percentage = Integer.parseInt(mProgress.group(1));
System.err.println("percentage=" + percentage);
mainApp.mainWindowController.setProgressExecute(percentage/100.0);
}
}
mainApp.mainWindowController.setProgressExecute(1.0);
if (scanner != null) {
scanner.close();
}
}
}
private class StreamGobblerError extends Thread {
private InputStream is;
private StringBuilder sbError;
private Scanner scanner;
private StreamGobblerError(InputStream is) {
this.is = is;
sbError = new StringBuilder();
scanner = new Scanner(is);
}
public StringBuilder getErrors() {
return sbError;
}
#Override
public void run() {
//InputStreamReader isr = new InputStreamReader(is);
//BufferedReader br = new BufferedReader(isr);
String ligne = null;
while (scanner.hasNextLine()) {
ligne = scanner.nextLine();
sbError.append(ligne);
sbError.append("\r\n");
}
if (scanner != null) {
scanner.close();
}
}
}
Update: I tried redirecting the output to a file and reading from it, but it appears this runs into the same buffering problem as the previous implementation: I get only two data points.
As a workaround, I will have to ask the creator of the .exe to include 4100 extra characters in each line showing the progress.

If your external process is C/C++ (stdio) based, than this is most likely a block buffering issue:
stdio-based programs as a rule are line buffered if they are running interactively in a terminal and block buffered when their stdout is redirected to a pipe. In the latter case, you won't see new lines until the buffer overflows or flushed.
see this answer for more details, and some possible workarounds.
Please also note that according to this, line buffering is not an option on Win32:
_IOLBF
For some systems, this provides line buffering. However, for Win32, the behavior is the same as _IOFBF - Full Buffering.
so if you choose to modify the "exe" program to set a proper output mode with setvbuf, you would have to use:
_IONBF No buffer
instead.

From the Javadocs
Optionally, a PrintStream can be created so as to flush automatically;
this means that the flush method is automatically invoked after a byte
array is written, one of the println methods is invoked, or a newline
character or byte ('\n') is written.
Reference: http://docs.oracle.com/javase/8/docs/api/java/io/PrintStream.html
One way to do this is to have output stream flush() after every write.
System.out.flush()
You could also define your own flushable PrintStream and use that.

Related

Get the result of the method and save it to a variable

There is such code, I pass two parameters to the input and get the result of this method in the console, I need to save the result in a variable and pass it to another method how to do it right? Please do not rush tomatoes with a beginner in programming, I will be glad to any help. The result of the screen method.
enter image description here
public static String activation(String serialNumber, String keyName) throws IOException, InterruptedException, SQLException {
LocalDate futureDate = LocalDate.now().plusMonths(12);
String formattedDate = futureDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
String[] command =
{
"cmd",
};
Process p = Runtime.getRuntime().exec(command);
//new Thread(new SyncPipe(p.getErrorStream(), System.err)).start();
new Thread(new SyncPipe(p.getInputStream(), System.out)).start();
PrintWriter stdin = new PrintWriter(p.getOutputStream());
stdin.println("C:\\tdes_ecb.exe " + serialNumber + " " + keyName + " " + formattedDate);
stdin.close();
int returnCode = p.waitFor();
String code = Integer.toString(returnCode);
return code;
}
static class SyncPipe implements Runnable {
public SyncPipe(InputStream istrm, OutputStream ostrm) {
inputStream = istrm;
outputStream = ostrm;
}
public void run() {
try {
final byte[] buffer = new byte[1024];
for (int length = 0; (length = inputStream.read(buffer)) != -1; ) {
outputStream.write(buffer, 0, length);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < buffer.length; i++) {
char c = (char) buffer[i];
sb.append(c);
}
String convertedString = sb.toString();
key(convertedString);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private final OutputStream outputStream;
private final InputStream inputStream;
}
public void SyncPipe(InputStream inputStream, OutputStream outputStream) {
this.inputStream = inputStream;
this.outputStream = outputStream;
}
For you to get the result of Runtime.getRuntime().exec(command) into a variable, there is no need for a separate thread, you can simple read it right from your object Process and store in a String or StringBuilder, after this it is a matter of understanding the text and splitting it based on your rules.
To immediately read the result of a process:
final StringBuilder ret = new StringBuilder();
final Runtime rt = Runtime.getRuntime();
final String[] commands = { "cmd", "/c", "cd c:\\myuser" };
final Process proc = rt.exec(commands);
final BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
final BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String s = null;
while ((s = stdInput.readLine()) != null) {
ret.append(s).append("\n");
}
while ((s = stdError.readLine()) != null) {
ret.append(s).append("<br />");
}
String res = ret.toString();
After the code above you‘ll have all the text from the results in a String, now it is a matter of splitting it and/or removing unnecessary information. For this you can use the methods indexOf, split and removeAll combined, they are all methods inside the String class.
In you case, to make it simple we can divide the splitting in 3 stages.
1) Ignore the first empty line (\r\n)
2) Ignore the whole first line (command and arguments)
3) Use only the text starting from the position 0 until the next line break
String res = "\r\nC:\\User\\aaa\\bbb\\ccc\\tdex_ecb.exe 000000 111111 33333 44444 \r\n INFO I WANT\r\n C:\\\\User\\\\aaa\\\\bbb\\\\ccc\\\\";
res = res.substring(2);
res = res.substring(res.indexOf("\r\n")+2);
res = res.substring(0, res.indexOf("\r\n"));
System.out.println(res);
Now your variable res has only the text you want to pass to another method.
I am not clear of some parts of your requirement. If it is to get the output of the child process as input in the main Java process, then I don't think it is feasible directly through the Java APIs.
Indirect way of getting child process output in the main Java process
I am not sure if you tried this, but you can redirect the output of the child process into a file and read from that file once the process is over, within the same process. (Of course, if your output data in sensitive, you may not want to do this.)
String filePath = "C:/Temp/abc.txt";
/* Create the process and redirect its output to a new file. */
ProcessBuilder pb = new ProcessBuilder().command( "C:\\Temp\\echo.bat" ) //Replace this with your command
.redirectOutput( Redirect.to( new File( filePath ) ) );
Process p = pb.start();
p.waitFor(); //Now, wait for the process to get over...
/* Now, read from the generated file. */
List<String> lines = Files.readAllLines( Paths.get( filePath ) );
Linking the outputs and inputs, resp., of child and main process
However, if your need is to simply display the output of the child process in the console of the main Java process, you may do this.
Process p = new ProcessBuilder().command( "C:\\Temp\\echo.bat" ) //Replace this with your command
.inheritIO().start();
p.waitFor();

Java code optimization, replacing all chars in a file

I have tried doing it like this:
import java.io.*;
public class ConvertChar {
public static void main(String args[]) {
Long now = System.nanoTime();
String nomCompletFichier = "C:\\Users\\aahamed\\Desktop\\test\\test.xml";
Convert(nomCompletFichier);
Long inter = System.nanoTime() - now;
System.out.println(inter);
}
public static void Convert(String nomCompletFichier) {
FileWriter writer = null;
BufferedReader reader = null;
try {
File file = new File(nomCompletFichier);
reader = new BufferedReader(new FileReader(file));
String oldtext = "";
while (reader.ready()) {
oldtext += reader.readLine() + "\n";
}
reader.close();
// replace a word in a file
// String newtext = oldtext.replaceAll("drink", "Love");
// To replace a line in a file
String newtext = oldtext.replaceAll("&(?!amp;)", "&");
writer = new FileWriter(file);
writer.write(newtext);
writer.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
However the code above takes more time to execute than creating two different files:
import java.io.*;
public class ConvertChar {
public static void main(String args[]) {
Long now = System.nanoTime();
String nomCompletFichier = "C:\\Users\\aahamed\\Desktop\\test\\test.xml";
Convert(nomCompletFichier);
Long inter = System.nanoTime() - now;
System.out.println(inter);
}
private static void Convert(String nomCompletFichier) {
BufferedReader br = null;
BufferedWriter bw = null;
try {
File file = new File(nomCompletFichier);
File tempFile = File.createTempFile("buffer", ".tmp");
bw = new BufferedWriter(new FileWriter(tempFile, true));
br = new BufferedReader(new FileReader(file));
while (br.ready()) {
bw.write(br.readLine().replaceAll("&(?!amp;)", "&") + "\n");
}
bw.close();
br.close();
file.delete();
tempFile.renameTo(file);
} catch (IOException e) {
// writeLog("Erreur lors de la conversion des caractères : " + e.getMessage(), 0);
} finally {
try {
bw.close();
} catch (Exception ignore) {
}
try {
br.close();
} catch (Exception ignore) {
}
}
}
}
Is there any way to do the 2nd code without creating a temp file and reducing the execution time? I am doing a code optimization.
The main reason why your first program is slow is probably that it's building up the string oldtext incrementally. The problem with that is that each time you add another line to it it may need to make a copy of it. Since each copy takes time roughly proportional to the length of the string being copied, your execution time will scale like the square of the size of your input file.
You can check whether this is your problem by trying with files of different lengths and seeing how the runtime depends on the file size.
If so, one easy way to get around the problem is Java's StringBuilder class which is intended for exactly this task: building up a large string incrementally.
The main culprit in your first example is that you're building oldtext inefficiently using String concatenations, as explained here. This allocates a new string for every concatenation. Java provides you StringBuilder for building strings:
StringBuilder builder = new StringBuilder;
while(reader.ready()){
builder.append(reader.readLine());
builder.append("\n");
}
String oldtext = builder.toString();
You can also do the replacement when you're building your text in StringBuilder. Another problem with your code is that you shouldn't use ready() to check if there is some content left in the file - check the result of readLine(). Finally, closing the stream should be in a finally or try-with-resources block. The result could look like this:
StringBuilder builder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line = reader.readLine();
while (line != null) {
builder.append(line.replaceAll("&(?!amp;)", "&"));
builder.append('\n');
line = reader.readLine();
}
}
String newText = builder.toString();
Writing to a temporary file is a good solution too, though. The amount of I/O, which is the slowest to handle, is the same in both cases - read the full content once, write result once.

File convertion not working inside a for loop

I need to ftp download and convert a file to a string, this way:
public static boolean leArquivos(String inicioArquivo) {
try {
FTPClient mFtp = new FTPClient();
mFtp.connect(FTPHOST, PORTA);
mFtp.login(USUARIO, SENHA);
FTPFile[] ftpFiles = mFtp.listFiles();
int length = ftpFiles.length;
for (int i = 0; i < length; i++) {
String nome = ftpFiles[i].getName();
String[] itens = nome.split("_");
boolean isFile = ftpFiles[i].isFile();
String arquivo_id = itens[0];
if (isFile && (arquivo_id.equals(inicioArquivo))) {
// the follow lines work if outside the for loop
InputStream inStream = mFtp.retrieveFileStream(nome.toString());
String arquivoLido = convertStreamToString(inStream);
String[] arquivoLidoPartes = arquivoLido.split("#");
Retorno.adicionaRegistro(nome, arquivoLidoPartes[0], arquivoLidoPartes[1], false);
}
}
} catch(Exception e) {
e.printStackTrace();
return false;
}
return true;
}
This gonna read a 'inicioArquivo_anything.txt' and put into a string.
FTP and Registro.adicionaRegistro works fine.
If I move the 4 lines that are inside the 'if' to outside the 'for' loop, it works for a single file.
I need perform the action for several files.
Sorry about bad english (and bad Java too)...
EDIT
Worked this way
The convertion code:
private static String convertStreamToString(InputStream is, FTPClient mFtp) throws IOException { // added the client
BufferedReader r = new BufferedReader(new InputStreamReader(is));
StringBuilder total = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
total.append(line);
}
r.close(); // close stream
is.close(); // close stream
mFtp.completePendingCommand();
return total.toString();
}
And changed this:
String arquivoLido = convertStreamToString(inStream, mFtp);
inStream.close();
As written in the API doc, you have to close the stream (after the conversion) and call the completePendingCommand method to finalize and check the status of the transfer :
FTPClient.html#retrieveFileStream
And, in all you programs, the basics: don't forget to close the Streams !!

Read a particuliar line in txt file in Java

The file ListeMot.txt contain 336529 Line
How to catch a particular line.
This my code
int getNombre()
{
nbre = (int)(Math.random()*336529);
return nbre ;
}
public String FindWord () throws IOException{
String word = null;
int nbr= getNombre();
InputStreamReader reader = null;
LineNumberReader lnr = null;
reader = new InputStreamReader(new FileInputStream("../image/ListeMot.txt"));
lnr = new LineNumberReader(reader);
word = lnr.readLine(nbr);
}
Why I can't get word = lnr.readLine(nbr);??
Thanks
P.S I am new in java!
To get the Nth line you have to read all the lines before it.
If you do this more than once, the most efficient thing to do may be to load all the lines into memory first.
private final List<String> words = new ArrayList<String>();
private final Random random = new Random();
public String randomWord() throws IOException {
if (words.isEmpty()) {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("../image/ListeMot.txt")));
String line;
while ((line = br.readLine()) != null)
words.add(line);
br.close();
}
return words.get(random.nextInt(words.size()));
}
BTW: The the parameter theWord meant to be used?
There is no method like readLine(int lineNumber) in Java API. You should read all previous lines from a specific line number. I have manipulated your 2nd method, take a look at it:
public void FindWord () throws IOException
{
String word = "";
int nbr = getNombre();
InputStreamReader reader = null;
LineNumberReader lnr = null;
reader = new InputStreamReader( new FileInputStream( "src/a.txt" ) );
lnr = new LineNumberReader( reader );
while(lnr.getLineNumber() != nbr)
word = lnr.readLine();
System.out.println( word );
}
The above code is not error free since I assume you know the limit of the line number in the given text file, i.e. if we generate a random number which is greater than the actual line number, the code will go into an infinite loop, be careful.
Another issue, line numbers start from 1 so I suggest you to change your random line number generator method like this:
int getNombre()
{
nbre = (int)(Math.random()*336529) + 1;
return nbre ;
}
The LineNumberReader only keeps track of the number of lines read, it does not give random access to lines in the stream.

How to get android system log from a Java program

Does anyone know how to get the android device system log programatically using Java? This would be something similar to what is available on lower panel on the Dalvik Debug Monitor.
Thanks in advance.
Untested with 'adb shell logcat', but I've used this to get other things from via adb:
public static String[] getAdbLogCat() {
try {
Process p = Runtime.getRuntime().exec("/path/to/adb shell logcat");
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
final StringBuffer output = new StringBuffer();
String line;
ArrayList<String> arrList = new ArrayList<String>();
while ((line = br.readLine()) != null) {
System.out.println(line);
}
return (String[])arrList.toArray(new String[0]);
} catch (IOException e) {
System.err.println(e);
e.printStackTrace();
return new String[]{};
}
}
I started with Mathias Conradt's answer above. It didn't work for me, but after working with it for a long time, I found out what tweaks it needed to get it working right. This will work. It doesn't require root access, special permissions, or anything.
private static String getAdbLogCat()
{
String log = "";
String str;
try
{
String myStringArray[]= {"logcat", "-d"};
Process process = Runtime.getRuntime().exec(myStringArray);
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
str = br.readLine();
while (str != null)
{
log += str;
str = br.readLine();
}
}
catch (IOException e)
{
}
return log;
}

Categories