Posts Tagged java

How to properly handle the output of the ProcessBuilder

It’s time for a rewrite of my old post https://thilosdevblog.wordpress.com/2011/11/21/proper-handling-of-the-processbuilder, because since Java 9 there are some new nice features, which make things easier.

There are many topics around the ProcessBuilder. In this post the focus is how to handle the output of executed commands correctly!

The following two things are important:

  • Consuming STDOUT and STDERR is necessary to avoid freezing!
  • If the process writes a large amount of output, it must be consumed.
    This can be done by calling #redirectErrorStream, which redirects STDERR to STDOUT!

Have look at my ProcessBuilderWrapper.

/**
 * GPL
 */
public class ProcessBuilderWrapper {
	private String[] command;
	private File workingDirectory;
	
	// exit value of the process
	private int status;

	private boolean redirectToStdout;
	private boolean redirectErrorStream;

	private ByteArrayOutputStream outStd = new ByteArrayOutputStream();
	private ByteArrayOutputStream outError = new ByteArrayOutputStream();

	// environment variables, that are visible to the process
	private Map<String, String> environment = null;

	public ProcessBuilderWrapper(String... command) {
		this.command = command;
	}

	public void run() throws IOException, InterruptedException {
		ProcessBuilder pb = new ProcessBuilder(command);
		if(workingDirectory != null)
			pb.directory(workingDirectory);
		if(environment != null && environment.size() > 0)
			pb.environment().putAll(environment);

		pb.redirectErrorStream(redirectErrorStream);

		Process process = pb.start();
		try (var infoStream = process.getInputStream(); var errorStream = process.getErrorStream()) {
			if(redirectToStdout) {
				infoStream.transferTo(System.out);
				errorStream.transferTo(System.out);
			} else {
				infoStream.transferTo(this.outStd);
				errorStream.transferTo(this.outError);
			}
		}
		status = process.waitFor();
	}

	public void redirectToStdOut() {
		this.redirectToStdout = true;
	}

	public void redirectErrorStream() {
		this.redirectErrorStream = true;
	}

	public void setWorkingDirectory(String dir) {
		this.workingDirectory = Paths.get(dir).toFile();
	}

	public void setEnvironment(Map<String, String> environment) {
		this.environment = environment;
	}

	public int getStatus() {
		return status;
	}

	public String getOutput() {
		return (redirectToStdout) ? "n/a" : outStd.toString();
	}

	public String getError() {
		return (redirectToStdout) ? "n/a" : outError.toString();
	}

	public boolean hasErrors() {
		return getStatus() != 0;
	}
}

Usage for regular commands

To run a regular command, you just have to do something like this:

ProcessBuilderWrapper pbw = new ProcessBuilderWrapper("ls", "-al");
pbw.setWorkingDirectory("/tmp");
pbw.redirectErrorStream();
pbw.run();
if(!pbw.hasErrors())
	System.out.println(pbw.getOutput());

Time consuming commands

For time consuming commands it could be important to see some progress information or output of the command. In this case, the inputstreams of the Process can be redirected to standard out.

To test my ProcessBuilderWrapper, I used the following time consuming bash script (sleep.sh):

#!/bin/bash
for i in {1..10} ; do
   echo "Step: ${i}"
   sleep 3s
done

The call of the ProcessBuilderWrapper:

ProcessBuilderWrapper pbw = new ProcessBuilderWrapper("bash", "sleep.sh");
pbw.setWorkingDirectory([path_to_the_script]);
pbw.redirectToStdOut();
pbw.run();
if(!pbw.hasErrors())
	System.out.println(pbw.getOutput());

Error case

To test the error case, I used the following bash script (error.sh):

#!/bin/bash

echo "Text on Stdtout"
echo "Text on StdtErr1" >/dev/stderr
echo "Text on StdtErr2" >&2
exit 1

And the call of the ProcessBuilderWrapper:

ProcessBuilderWrapper pbw = new ProcessBuilderWrapper("bash", "error.sh");
pbw.setWorkingDirectory([path_to_the_script]);
pbw.redirectToStdOut();
pbw.run();
if(!pbw.hasErrors())
	System.out.println(pbw.getOutput());

It’s tested with mascOS and Linux.

,

Leave a comment

JII (Java Image Info) Version 1.2.0

Version 1.2.0 of JII has been released.

It provides a clean interface to a collection of Java libraries and source code to read basic properties of images.

Changes:

  • updated java to 11
  • rewritten the ProcessBuilderWrapper
  • fixed call of identify (imagemagick)
  • changed repo

Since this version JII gets it’s own maven repository.

The following entries are required in the pom.xml:

<repositories>
	<repository>
		<id>jiirepo</id>
		<url>https://myrepo.thischwa.codes/repository/jii/</url>
	</repository>
</repositories>

<dependency>
	<groupId>codes.thischwa.jii</groupId>
	<artifactId>java-image-info</artifactId>
	<version>1.2.0</version>
</dependency>

, ,

Leave a comment

JII (Java Image Info) Version 1.1.0

Version 1.1.0 of JII has been released.

It provides a clean interface to a collection of Java libraries and source code to read basic properties of images.

Changes:

  • issue #5 Include Apache-Commons-Imaging
  • issue #8 Update platform to Java 8
  • updated slf4j to 1.7.25
  • changed to maven 3.5

, ,

Leave a comment

How to initialize a static final object

Sometimes I need to intitialize a complex static final object, but I don’t like static blocks for initialization. IMHO it isn’t nice to read!
An alternative way is using an static method:

private static final YourObject defaultYourObject = initYourObject();

private static YourObject initYourObject() {
	try {
		return new YourObject(param1, param2);
	} catch (Exception e) {
		// should never happened
		throw new Error("Could not initialize YourObject", e);
	}
}

And of course, the error-handling is an own special topic in this case!

,

Leave a comment

C5Connector.Java Version 0.11

Version 0.11 of the C5Connector.Java has been released.

It’s the Java backend for the filemanager of corefive.

The outstanding feature of this release is that the jar of C5Connector.Java contains the source code of the filemanager. This insures that there aren’t any compatibility issues between C5Connector.Java and the filemanager. We strongly recommend using this feature! The default implementations of the basic interfaces are adapted accordingly.

The changes in detail:

  • issue #39: Adapt FilemanagerConfig to Filemanager 2.5.
  • issue #40: Integrate the filemanager source into the jar file
  • issue #41: New directory with name equals to Parent directory
  • issue #43: The default implementation of FilemanagerCapability should respect if a file is protected
  • issue #44: updated filemanager to 2.5.0
  • issue #45: wrong path in getinfo for folders

 

This could be one of the last releases of this project. The Filemanager project is deprecated. This is why the future of C5Connector.Java is written in the stars.

,

Leave a comment