Search code examples
javastringjava-8outputprocessbuilder

Process from ProcessBuilder not directing output to string


I am creating a process that uses a service broker to run multiple modules. These modules will, at the end of the process, return a string that the initial service broker call will return and print. However, my process from process builder isn't returning my string. I placed print statements to tell me when the different modules are running and it PRINTS the correct answer but doesn't RETURN it as a string to the service broker.

ServiceBroker

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
import java.util.stream.Collectors;

public class ServiceBrokerTest {
    public static String main(String[] args) throws FileNotFoundException {
        String serviceName = args[0];
        String parmList = args[1];
        try {
            Path filePath = Paths.get("Service.txt");
            String absPath = filePath.toFile().getAbsolutePath();
            File serviceText = new File(absPath);

            Scanner scan = new Scanner(serviceText);
            String[] currentLine = null;
            while (scan.hasNextLine()){
                currentLine = scan.nextLine().split(",");

                if (currentLine[0].equals(serviceName)){
                    ProcessBuilder processCompile = new ProcessBuilder();
                    processCompile.directory(new File(System.getProperty("user.dir")));
                    String[] parmsProcessCompile = {"javac", currentLine[1]};
                    processCompile.command(parmsProcessCompile);

                    Process c = processCompile.inheritIO().start();
                    c.waitFor();
                    c.destroy();

                    ProcessBuilder processRun = new ProcessBuilder();
                    processRun.directory(new File(System.getProperty("user.dir")));
                    String runName = currentLine[1].split("\\.")[0];
                    String[] parmsProcessRun = {"java", runName,parmList};
                    processRun.command(parmsProcessRun);
                    Process r = processRun.inheritIO().start();
                    r.waitFor();
                    BufferedReader stdOut = new BufferedReader(new InputStreamReader(r.getInputStream()));
                    String stdOutStr = stdOut.lines().collect(Collectors.joining(System.lineSeparator()));

                    return stdOutStr;
                }
            }
        }
        catch (Exception e){
            String[] temp = new String[]{"English", "703"};
            System.out.println(Error.error(temp));
        }

        return null;
    }


}

Translate

    public class Translate {
    public static void main(String[] args) throws IOException {
        translate(args);
    }
    /**
     *
     * @param args  string array. first index will be the filename, second will be the english word to translate from
     * @return  call to service broker with instructions for error call or text broker call
     */
    public static String translate(String[] args) throws IOException {

        ServiceBrokerTest serviceBroker = new ServiceBrokerTest();

        try {
            String[] argArray = args[0].split(",");
            String filename = argArray[0];
            String englishWord = argArray[1];
            String searchMethod = argArray[2];

            // check if the file exists, if not, call error module
            if (!searchFile(filename)) {
                // call error module and return
                return ServiceBrokerTest.main(new String[] {"Error", "English" + ", 805"});

            }
            //for testing
            System.out.println("process run Trans");
            //-------------------------
            return ServiceBrokerTest.main(new String[]{"TB", filename + "," + englishWord + "," + searchMethod});
        } catch (Exception e) {
            return ServiceBrokerTest.main(new String[] {"Error", "English" + ", 404"});
        }
    }

    /**
     *
     * @param filename  name of file you are searching for
     * @return true if file exists, false if otherwise
     */
    public static boolean searchFile(String filename) {
        File temp = new File(filename);
        return temp.isFile();
    }

}

TextBroker

public class TextBroker {

    public static void main(String[] args) throws FileNotFoundException {
        TB(args);
    }

    /**
     * Creates the file from the fileName that was passed down from the service broker
     */
    public static File fileCreation(String filename) throws FileNotFoundException {
        File input = new File(filename);
        return input;
    }


    /**------------------------------------------------------------------------------------------------
     * Search Method
     * ------------------------------------------------------------------------------------------------
     * Takes the passed parameters parm and searchMethod as well as the file created by fileCreation
     * and parses through the given file in search for the correct or corresponding output.
     * ------------------------------------------------------------------------------------------------
     */
    public static String fileSearch(File file, String parm, String searchMethod) throws FileNotFoundException {
        Scanner fileScan = new Scanner(file);
        String[] currentLine;

        /**
         * If there is a comparison that needs >= or <= then the value has to be converted from a
         * string to an int in order for proper comparison of the values.
         */
        int convertedString = 0;

        /**------------------------------------------------------------------------------------------------
         * Search Blocks
         * ------------------------------------------------------------------------------------------------
         * Each if block works by converting the passed parm if necessary, like for >= or <=,
         * then using a while loop it searches the file, splitting the current line and comparing the
         * first value of the line. If it follows the searchMethod parm then it is correct and is returned
         * by the method as a string.
         * ------------------------------------------------------------------------------------------------
         */
        if (searchMethod.equals("<=")){
            convertedString = Integer.parseInt(parm);
            while (fileScan.hasNextLine()){
                currentLine = fileScan.nextLine().split(",");

                if (convertedString <= Integer.parseInt(currentLine[0]) ){
                    System.out.println(currentLine[1]);
                    return currentLine[1];
                }
            }
        }
        else if (searchMethod.equals(">=")){
            convertedString = Integer.parseInt(parm);
            while (fileScan.hasNextLine()){
                currentLine = fileScan.nextLine().split(",");

                if (convertedString <= Integer.parseInt(currentLine[0]) ){
                    System.out.println(currentLine[1]);
                    return currentLine[1];
                }
            }
        }
        else if (searchMethod.equals("=")){
            while (fileScan.hasNextLine()){
                currentLine = fileScan.nextLine().split(",");

                if (currentLine[0].equals(parm)){
                    System.out.println(currentLine[1]);
                    return currentLine[1];
                }
            }
        }

        return null;
    }

    /**
     * The parameters are recieved via an array of strings as args which are assigned variables
     * and passed to the required methods fileCreation and fileSearch.
     * This is in a try catch block so that a fileNotFound is caught and triggers an error call
     * or any other error is caught.
     */
    public static String TB(String[] args) throws FileNotFoundException {
        System.out.println("Process run TB");
        try {
            String[] splitArgs = args[0].split(",");
            String fileName = splitArgs[0];
            String parm = splitArgs[1];
            String searchMethod = splitArgs[2];
            String returnString = fileSearch(fileCreation(fileName),parm, searchMethod);
            return returnString;
        } catch (Exception e) {
            ServiceBrokerTest serviceBroker = new ServiceBrokerTest();
            return serviceBroker.main(new String[] {"Error", "English" + ", 404"});
        }

    }
}

SB is given the parameters {"Trans, "german.txt,dog,="}, it passes the [1] string to a translate module, hence trans, which passes along the parms to a TextBroker who returns the correct translated word. I essentially need to get this word from TextBroker back to the initial service broker call and print. I cant print it from the Text Broker we need it to be passed back down the line. I have tried a whole thread of examples and cannot get my SB to return anything other than null even while my print statements indicate that the subprocesses are reaching the correct points for returning a string. Maybe I'm missing something simple but I cannot see what's wrong. I can provide more files for running if you need just let me know.

Thank you for your help.


Solution

  • You're making multiple mistakes in your process builder code; you need to fix them all before this works.

    inheritIO() literally means: forget .getInputStream() and redirect and all that jazz, just inherit the standard in/out/err of the java process itself. Which you specifically do not want, so get rid of that.

    But that's not enough.

    Then, you first call .waitFor(), and THEN you open the inputstream. That doesn't work - once the process has died there's no inputstream for you to get.

    You need to start the process, then read the data, and THEN you can waitFor if you must.

    Here is some example code from one of my projects:

    private static <E extends Throwable> String exec0(String description, Function<String, E> exceptionFactory, String... args) throws E {
    
        Process process; try {
            process = new ProcessBuilder(args).redirectErrorStream(true).start();
        } catch (IOException e) {
            throw rethrow(e, "Cannot execute " + description, exceptionFactory);
        }
        
        String output; try {
            @Cleanup var in = process.getInputStream();
            output = new String(in.readAllBytes(), StandardCharsets.US_ASCII);
        } catch (IOException e) {
            throw rethrow(e, "Cannot stream results of " + description, exceptionFactory);
        }
        
        int v; try {
            v = process.waitFor();
        } catch (InterruptedException e) {
            throw rethrow(e, "Executing " + description + " interrupted", exceptionFactory);
        }
        
        if (v != 0) throw exceptionFactory.apply(description + " returned with error code " + v + ": " + output);
        return output;
    }
    
    private static <E extends Throwable> E rethrow(Throwable source, String prefix, Function<String, E> exceptionFactory) {
    
        String msg = source.getMessage() == null ? prefix : (prefix + ": " + source.getMessage());
        E ex = exceptionFactory.apply(msg);
        if (source.getCause() != null) ex.initCause(source.getCause());
        ex.setStackTrace(source.getStackTrace());
        return ex;
    }
    

    It does some fancy footwork to let you automatically throw the appropriate exception for your API design (generally 'I run an app on the host OS' is supposed to be an implementation detail, therefore the specific exceptions that ProcessBuilder throws are not appropriate to just blindly pass on; they need to be restated in terms of your API.

    If you don't care about it, you can easily rip it out.

    The key parts are to open the input stream first (don't call waitFor until after), and reading all the bytes out. in.readAllBytes() is a rather convenient way to do that.

    NB: @Cleanup is lombok cleanup. If you don't use that, then use try (var in = x.getInputStream()) { ... } - you need to safe-close that thing.