/*
 * [TestProcessBuilder.java]
 *
 * Summary: Program to demostrate use of communicating with a child process using three threads and ProcessBuilder.
 *
 * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2009-04-07 initial version
 */
package com.mindprod.example;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import static java.lang.System.*;

/**
 * Program to demostrate use of communicating with a child process using three threads and ProcessBuilder.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2009-04-07 initial version
 * @since 2009-04-07
 */
final public class TestProcessBuilder
    {
    /**
     * Demonstrate us of ProcessBuilder and communicating with a child process.
     *
     * @param args not used
     *
     * @throws java.io.IOException if trouble setting up reader/writer
     */
    public static void main( String[] args ) throws IOException
        {
        // Boomerang exchoes input to output.
        final ProcessBuilder pb = new ProcessBuilder( "E:/sys/boomerang.exe" );
        // merge child's error and normal output streams.
        // Note it is not called setRedirectErrorStream.
        pb.redirectErrorStream( true );
        final Process p = pb.start();
        OutputStream os = p.getOutputStream();
        InputStream is = p.getInputStream();
        // spawn two threads to handle I/O with child while we wait for it to complete.
        new Thread( new Receiver( is ) ).start();
        new Thread( new Sender( os ) ).start();
        try
            {
            p.waitFor();
            }
        catch ( InterruptedException e )
            {
            Thread.currentThread().interrupt();
            }
        out.println( "Child done" );
        // at this point the child is complete.  All of its output may or may not have been processed however.
        // The Receiver thread will continue until it has finished processing it.
        // You must close the streams even if you never use them!  In this case the threads close is and os.
        p.getErrorStream().close();
        }
    }

/**
 * thread to send output to the child.
 */
final class Sender implements Runnable
    {
    /**
     * e.g. \n \r\n or \r, whatever system uses to separate lines in a text file. Only used inside multiline fields. The
     * file itself should use Windows format \r \n, though \n by itself will alsolineSeparator work.
     */
    private static final String lineSeparator = System.getProperty( "line.separator" );

    /**
     * stream to send output to child on
     */
    private final OutputStream os;

    /**
     * constructor
     *
     * @param os stream to use to send data to child.
     */
    Sender( OutputStream os )
        {
        this.os = os;
        }

    /**
     * method invoked when Sender thread started.  Feeds dummy data to child.
     */
    public void run()
        {
        try
            {
            final BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( os ), 50 /* keep small for tests */ );
            for ( int i = 99; i >= 0; i-- )
                {
                bw.write( "There are " + i + " bottles of beer on the wall, " + i + " bottles of beer." );
                bw.write( lineSeparator );
                }
            bw.close();
            }
        catch ( IOException e )
            {
            throw new IllegalArgumentException( "IOException sending data to child process." );
            }
        }
    }

/**
 * thread to read output from child
 */
class Receiver implements Runnable
    {
    /**
     * stream to receive data from child
     */
    private final InputStream is;

    /**
     * contructor
     *
     * @param is stream to receive data from child
     */
    Receiver( InputStream is )
        {
        this.is = is;
        }

    /**
     * method invoked when Receiver thread started.  Reads data from child and displays in on out.
     */
    public void run()
        {
        try
            {
            final BufferedReader br = new BufferedReader( new InputStreamReader( is ),
                    50
                    /* keep small for testing */ );
            String line;
            while ( ( line = br.readLine() ) != null )
                {
                out.println( line );
                }
            br.close();
            }
        catch ( IOException e )
            {
            throw new IllegalArgumentException( "IOException receiving data from child process." );
            }
        }
    }