0

I have a class that extends OutputStream and is used to make the output of said OutputStream into a Text or StyledText. The class works great, however it introduces a problem.

I am working on an SWT application that includes a fake console. I'm not using it to run sytem commands, but rather a few different Minecraft servers that I use for slightly different development environments/purposes.

From what I've read about ProcessBuilder, inheritIO() is supposed to make the resulting Process' IO streams the same as the Java process that created it:

From the Oracle Docs:

public ProcessBuilder inheritIO() Sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.

This is a convenience method. An invocation of the form

pb.inheritIO()

behaves in exactly the same way as the invocation

pb.redirectInput(Redirect.INHERIT)
  .redirectOutput(Redirect.INHERIT)
  .redirectError(Redirect.INHERIT)

What I've tried to do is redirect System.out (via System.setOut()), and then create the process:

private static Process start(PrintStream out, Server server, String version) {
    try {
        System.setOut(out);
        return new ProcessBuilder("java", "-jar", version + ".jar").inheritIO().directory(server.getDirectory()).start();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

If you are unfamilair with a Minecraft server structure, it looks a bit like this:

root/
├--plugins/ (if using Bukkit, as in my case)
   ├-- PluginName/
   └-- PluginName.jar
└-- minecraft-server.jar (or some version of craftbukkit, in my case)

I typically have more than one version of the server software within the server's root directory, in case I need to use a specific version. (That's my reasoning behind the variables in the method)

The Process runs fine; I can see the output. But the output is in the wrong place. It's almost as if my System.setOut() call was ignored, because I still see the output in the console, rather than in the GUI textbox. If I do a System.out.println call, it outputs as I expect it to: on the GUI. So I am unsure how the 'inheriting' of the IO streams work.

Is there a way to listen/redirect/pipe it so I can print it to the GUI?

Community
  • 1
  • 1
KILL3RTACO
  • 75
  • 9

2 Answers2

3

The I/O redirection of ProcessBuilder does not redirect streams but file descriptors. Even setting System.out to another stream does not change the file descriptor of the initial standard output. ProcessBuilder.inheritIO() inherits the parent process' standard output/input/error file descriptors.

The PrintStream instance is not passed to the started operating system process, but rather something like (it is pseudo code, I do not think it actually works):

((FileOutputStream) System.out.out).getFD()

Or, more correct:

FileDescriptor.out

which is static final indicating that it will not change, even if you set System.out to something different.

So if you want to have your child process's output appear in the fake console you mentioned, you have to use the StreamGobbler proposed here or find a different way of starting your child process (not via the operating system but maybe by dynamically loading your JAR at runtime).

Community
  • 1
  • 1
Lars Gendner
  • 1,816
  • 2
  • 14
  • 24
  • Okay, I'll amend my answer then :-) – Lars Gendner Sep 02 '15 at 00:50
  • I've done the dynamic loading of jars before, which is a possibility, and might even add more functionality to the application I'm writing. But suppose I load a server jar, run it, stop the server, choose a different server version, load _that_ jar, run it, etc... I would be adding to much to the classpath wouldn't I? I've dug around and searched but I haven't found a way to remove the jars that were loaded. – KILL3RTACO Sep 02 '15 at 01:32
  • Maybe http://stackoverflow.com/a/148707/5226711 depicts some possible approaches for you? – Lars Gendner Sep 02 '15 at 02:17
  • Ah, yes - I've seen this. The StreamGobbler approach doesn't seem to be working that well. The best I've gotten is that it displays the ouput but causes the window to freeze (probably until the server stops running). If I understand that answer correctly, the approach is to create a classloader and load the jar, however I don't see how it is relevant to the system classpath – KILL3RTACO Sep 02 '15 at 02:21
  • Maybe you could add more details to your question then. What exactly does not seem to be working in the StreamGobbler approach? How does your attempt using a classloader look like? What research have you done regarding classpath and class loaders? – Lars Gendner Sep 02 '15 at 02:33
0

Here's what I decided on doing

It's not really a workaround, but I've decided on the following process for running each server:

  • Choose server version
  • Load the required jar into the classpath:

    try {
        String path = "file:///path/to/jar.jar";
        URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class<?> sysclass = URLClassLoader.class;
        Class<?>[] parameters = new Class<?>[]{URL.class};
        Method method = sysclass.getDeclaredMethod("addURL", parameters);
        method.setAccessible(true);
        method.invoke(sysLoader, new URL(path));
        sysLoader.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
  • When the server stops running, only allow that server to be run again
  • If another server is desired, an application restart is required

The last step is to ensure needless class names aren't added. I say this because Bukkit tends to have the following structure for its classes:

org.bukkit/
├--craftbukkit.{SERVER_VERSION}/
├--(api classes and packages, most of which are Interfaces, the implementations are in the craftbukkit package)

I'm not really fond of the structure, and I'm not really fond of needing an app restart, but I doubt that specific situation will arise that often.

KILL3RTACO
  • 75
  • 9
  • 1. Are you sure that `ClassLoader.getSystemClassLoader()` always returns a `URLClassLoader`? 2. Why don't you just call `sysLoader.addURL(new URL(path))`? 3. Does closing the system class loader really work? – Lars Gendner Sep 02 '15 at 11:14