/*::.
==================================================================================================================================
=================================================¦ Copyright © 2007 Allen Baker ¦=================================================
                                                 +------------------------------+
File:       StreamGobbler.java
Originator: Allen Baker (2007.09.09 21:19)
LayoutRev:  5
================================================================================================================================== */



/*.
==========================================================================================
Package
------------------------------------------------------------------------------------------ */
package cosmicabyss.com.lib;



/*.
==========================================================================================
Imports
------------------------------------------------------------------------------------------ */
import java.io.*;
import java.util.*;



/*::
======================================================================================================================== *//**
Instances of this class run as independent threads, intercept an InputStream, and redirect it to cOut and optionally to
another OutputStream.<P>

I took the idea for this class, information about how to use it, and the term "StreamGobbler" from an article called
"When Runtime.exec() won't", which I found at:
      <A href="http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html">
         When Runtime.exec() won't
      </A>

StreamGobblers are particularly useful for intercepting and emptying stdout and stderr when an external program is
executed from inside a Java program. (From the external program's perspective, stdout and stderr are OutputStreams. But
from the perspective of the Java program that is running the external program, the external program's stdout and stderr
are InputStreams.) The reason StreamGobbler is useful in this scenario is because some native platforms only provide a
limited buffer size for the standard input and output streams, and failure to promptly write the input stream or read
the output stream of the external program may cause the external program to block, and even deadlock. I can personally
testify that this happens and that without the information found in the article cited above, these bugs are very hard to
figure out.<P>

Here is an example of how to use StreamGobbler when executing external programs from Java:
   <BLOCKQUOTE>
      <PRE id="unindent">
         public static <Type> int runExternalProgram(Type pCommand) throws Exception
            {
            XString  cmd  = XString.toXString(pCommand);
            Process  proc = null;
            Runtime  rt   = Runtime.getRuntime();

            =======================================================================================
            The commandline to run the program is dependent on the os.
            ---------------------------------------------------------------------------------------
            switch (OS())
               {
               case Util.OS_WINDOWS9X:
                  proc = rt.exec("command.com /c " + cmd);
                  break;
               case Util.OS_WINDOWS:
                  proc = rt.exec("cmd.exe /c " + cmd);
                  break;
               case Util.OS_UNIX:
                  proc = rt.exec(cmd.string());
                  break;
               }

            =======================================================================================
            Create an FileOutputStream to send error messages to
            ---------------------------------------------------------------------------------------
            OutputStream errorFile = new FileOutputStream("/data/ERRORS.txt");

            =======================================================================================
            Send stderr messages to cOut and to errorFile
            ---------------------------------------------------------------------------------------
            StreamGobbler stderrGobbler = new StreamGobbler(proc.getErrorStream(), "  stderr", errorFile);
            stderrGobbler.start();

            =======================================================================================
            Send stdout messages to cOut
            ---------------------------------------------------------------------------------------
            StreamGobbler stdoutGobbler = new StreamGobbler(proc.getInputStream(), "  stdout");
            stdoutGobbler.start();

            ======================================================================================
            Wait until all streams have been gobbled up
            --------------------------------------------------------------------------------------
            StreamGobbler.waitForGobbling();

            ======================================================================================
            Wait for the process to exit and then return its exit code.
            --------------------------------------------------------------------------------------
            return proc.waitFor();
            }
      </PRE>
   </BLOCKQUOTE>

<P>
   <DL>
      <DT>
         <B>
            Example usage:
         </B>
         <DD>
            <BLOCKQUOTE>
               <PRE id="unindent">
                  no example provided
               </PRE>
            </BLOCKQUOTE>
         </DD>
      </DT>
      <DT>
         <B>
            View Source:
         </B>
         <DD>
            <A href="StreamGobbler.java.html">
               StreamGobbler.java
            </A>
         </DD>
      </DT>
      <DT>
         <B>
            Author:
         </B>
         <DD>
            <A href="mailto:sourcecode.v01@cosmicabyss.com">
               Allen Baker
            </A>
         </DD>
      </DT>
   </DL>
*//*
======================================================================================================================== */
public class StreamGobbler extends Thread
   {



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates a StreamGobbler class object that gobbles the input from a specified
   InputStream, gives a specified name to the InputStream being gobbled, and does not redirect the
   InputStream to an OutputStream.

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#000">View source</A>

   @param
      pInputStream is the InputStream to intercept and gobble input data from.
   @param
      pInputStreamName is the name of the InputStream that is being gobbled. This name is attached to
      any input data that is echoed to cOut.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public StreamGobbler(InputStream pInputStream, String pInputStreamName)
      {
      this(pInputStream, pInputStreamName, null);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method creates a StreamGobbler class object that gobbles the input from a specified
   InputStream, gives a specified name to the InputStream being gobbled, and redirects the InputStream
   to a specified OutputStream.

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#001">View source</A>

   @param
      pInputStream is the InputStream to intercept and gobble input data from.
   @param
      pInputStreamName is the name of the InputStream that is being gobbled. This name is attached to
      any input data that is echoed to cOut.
   @param
      pOutputStream is the OutputStream to which all data intercepted from the InputStream is sent.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public StreamGobbler(InputStream pInputStream, String pInputStreamName, OutputStream pOutputStream)
      {
      this.iInputStream     = pInputStream;
      this.iInputStreamName = pInputStreamName;
      this.iOutputStream    = pOutputStream;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method is the action component of a StreamGobbler instance and it runs on its own thread.
   (Every StreamGobbler has its own thread.) It is the code that does the actual gobbling of
   InputStream data. If an OutputStream was assigned to this StreamGobbler instance, then this method
   sends the data it intercepts from the InputStream to that OutputStream. It also tags all intercepted
   data with the InputStream name and sends it to cOut. It runs until there is no more data to be read
   from the InputStream and then exits which kills the thread it is running on.

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#002">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public void run()
      {
      inc();
      try
         {
         PrintWriter  pw = null;
         if (iOutputStream != null) pw = new PrintWriter(iOutputStream);

         InputStreamReader  isr  = new InputStreamReader(iInputStream);
         BufferedReader     br   = new BufferedReader(isr);
         String             line = null;
         while ( (line = br.readLine()) != null)
            {
            if (pw != null)
               {
               pw.println(line);
               }
//            cOut.println(iInputStreamName + ": " + line);
            System.out.println(line);
            }
         if (pw != null)
            {
            pw.flush();
            }
         dec();
         }
      catch (IOException ioe)
         {
         dec();
         ioe.printStackTrace();
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method sets the ConsoleStream for this object.

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#003">View source</A>

   @return
      A reference to this object

   @param
      pConsole is a ConsoleStream through which this objects sends its output.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public StreamGobbler setOut(ConsoleStream pConsole)
      {
      cOut = pConsole;
      return this;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This boilerplate method is used for testing an instantiated object of this class and may include any
   code the developer chooses.

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#004">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public StreamGobbler test() throws Exception
      {
      cOut.titledPrintf
         (
         "\"HELLO WORLD!\"",
         "%s  %s  %s",
         "I'm an object of the", CLASS_NAME, "class, and I approved this message."
         );
      return this;
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Protected  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Private  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*.
   ==========================================================================================
   Class Constants
      CLASS_NAME:
         The name of this class
      DFLT_LINE_LEN:
         The default line length for word wrapping
   ------------------------------------------------------------------------------------------ */
   private static final XString  CLASS_NAME    = new XString(StreamGobbler.class.getName());
   private static final int      DFLT_LINE_LEN = ConsoleMessage.defaultLineLength();
   /*.
   ==========================================================================================
   Class variables
      cOut:
         Console output.
      cCount:
         The number of StreamGobblers currently running.
      cStarted:
         A flag indicating if any StreamGobblers have been started or not.
   ------------------------------------------------------------------------------------------ */
   private static ConsoleStream  cOut     = ConsoleStream.getSingleton();
   private static int            cCount   = 0;
   private static boolean        cStarted = false;
   /*.
   ==========================================================================================
   Instance variables
      iInputStream:
         The stream to be gobbled up
      iInputStreamName:
         The name of the stream to be gobbled up. This is just used for identifying the
         source stream when the data being gobbled is sent to cOut.
      iOutputStream:
         The stream to which gobbled data is sent (redirected). This stream is in addition to
         cOut. While all gobbled data is always sent to cOut, iOutputStream may be set to
         either an actual OutputStream or to null.
   ------------------------------------------------------------------------------------------ */
   private InputStream   iInputStream     = null;
   private String        iInputStreamName = null;
   private OutputStream  iOutputStream    = null;



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method increments the count of threads that are gobbling

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#005">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private static synchronized void inc()
      {
      cCount++;
      cStarted = true;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method decrements the count of threads that are gobbling

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#006">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private static synchronized void dec()
      {
      cCount--;
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method says if any threads are currently gobbling.

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#007">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private static synchronized boolean stillGobbling()
      {
      return (cCount > 0);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method says how many threads are gobbling

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#008">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private static synchronized int count()
      {
      return (cCount);
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method says if any threads have started gobbling yet

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#009">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   private static boolean started()
      {
      return (cStarted);
      }



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Inner Classes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Public Static Methods  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method doesn't return until all gobbling has stopped

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#010">View source</A>

   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static void waitForGobbling()
      {
      while ( ! StreamGobbler.started())
         {
         Util.sleep(1);
         }
      while (StreamGobbler.stillGobbling())
         {
         Util.sleep(1);
         }
      }



   /*:                                    
   ====================================================================================================
   [][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][][]
   ==================================================================================================== *//**
   This method allows this class file to be unit tested as a standalone application. It's the method
   that's called when the class is invoked from the command line by using the java application launcher
   - "java". Main() is not a required method, but the practice of putting one in each class and
   wrapping class test code within it allows easy unit testing of the class; and main does not need to
   be removed when testing is complete.

   <P>
      <DL>
         <DT>
            <B>
               Command line usage:
            </B>
            <DD>
               Java cosmicabyss.com.lib.StreamGobbler
            </DD>
         </DT>
      </DL>

   <P><B>Implementation: </B><A HREF="StreamGobbler.java.html#011">View source</A>

   @param
      pArgs contains the command line arguments with which this class was invoked as an application.
   *//*
   ---------------------------------------------------------------------------------------------------- */
   public static void main(String[] pArgs) throws Exception
      {
      /*.
      ==========================================================================================
      Greetings !
      ------------------------------------------------------------------------------------------ */
      cOut.banner(CLASS_NAME);
      /*.
      ==========================================================================================
      Create an object and send its output to the ConsoleStream
      ------------------------------------------------------------------------------------------ */
      XString  cmd   = XString.toXString("dir *.java");
      Process  proc  = null;
      Runtime  rt    = Runtime.getRuntime();
      /*.
      ==========================================================================================
      The commandline to run the program is dependent on the os.
      ------------------------------------------------------------------------------------------ */
      switch (Util.OS())
         {
         case Util.OS_WINDOWS9X:
            proc = rt.exec("command.com /c " + cmd);
            break;
         case Util.OS_WINDOWS:
            proc = rt.exec("cmd.exe /c " + cmd);
            break;
         case Util.OS_UNIX:
            proc = rt.exec(cmd.string());
            break;
         }
      /*.
      ==========================================================================================
      Send stderr messages to cOut
      ------------------------------------------------------------------------------------------ */
      StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "  stderr");
      errorGobbler.start();
      /*.
      ==========================================================================================
      Send stdout messages to cOut
      ------------------------------------------------------------------------------------------ */
      StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "  stdout");
      outputGobbler.start();
      /*.
      ==========================================================================================
      Wait until all streams have been gobbled up
      ------------------------------------------------------------------------------------------ */
      StreamGobbler.waitForGobbling();
      /*.
      ==========================================================================================
      Wait for the process to exit and then return its exit code.
      ------------------------------------------------------------------------------------------ */
      cOut.println("Process exited with return code: " + proc.waitFor());
      }



   }  // class StreamGobbler



   /*:.
   ==============================================================================================================
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@[  Notes  ]@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
   ============================================================================================================== */