UNB/ CS/ David Bremner/ teaching/ java/ DebugConsole.java
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.JTextField;

/** 
        This class is designed to help debug console applications in a debugger.
        For some reason, many debuggers are unable to read console input. 
        
        To use this class, place the lines
        
        <PRE>
        static
   {    DebugConsole debug = new DebugConsole();
        debug.show();
   }
        </PRE>
        
        <I>immediately</I> after the <TT>{</TT> of the class containing <TT>main</TT>.
        
        The debug console is a frame with a text area in the center and a a text field
        on the bottom. You enter text into the text field and hit ENTER at the end of 
        each input line. 
        
        Now you can run the debugger in the normal way and run your console application.
        
        However, there is no provision for indicating end of input. If your program 
        waits for readLine() to return a null, you will instead have to rewrite your
        input loop to check for a sentinel. One could easily make such an enhancement, 
        but it would make the implementation more difficult to understand.
                
        Once you have read chapters 12 and 13, you will be able to understand most of
        the code in this class.
                
        The write method of the DebugOutputStream sends characters to the text area.
        It is slow to send the characters one at a time, but it works and I wanted to 
        keep it simple.
        
        The read method of the DebugInputStream reads one character from the string
        containing the user input. New input is given to the DebugInputStream whenever
        the user hits ENTER. 
        
        However, there is one important issue--when there is no input available, the
        read method needs to wait. In this course, you did not learn how to achieve that.
        The synchronized keyword and the wait and notify calls implement the waiting 
        behavior.
*/

class DebugConsole extends JFrame
{  public DebugConsole()
   {  final int DEFAULT_FRAME_WIDTH = 300;
      final int DEFAULT_FRAME_HEIGHT = 300;
      setSize(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT);
      
      outputArea = new JTextArea();
      outputArea.setEditable(false);

      inputField = new JTextField();
      TextFieldListener listener = new TextFieldListener();
      inputField.addActionListener(listener);

      Container contentPane = getContentPane();
      contentPane.add(inputField, "South");
      contentPane.add(outputArea, "Center");

      in = new DebugInputStream();
      out = new DebugOutputStream(outputArea);
      System.setIn(in);
      System.setOut(new PrintStream(out));

      addWindowListener(new WindowCloser());
   }
   
   private JTextField inputField;
   private JTextArea outputArea;
   private DebugInputStream in;
   private DebugOutputStream out;

   private class TextFieldListener implements ActionListener
   {  public void actionPerformed(ActionEvent event)
      {  // get user input
      
         String input = inputField.getText() + "\n";
         
         // process user input
         
         in.addInput(input);
         outputArea.append(input);
         
         // clear text field
         
         inputField.setText("");
      }
   }

   private class WindowCloser extends WindowAdapter
   {  public void windowClosing(WindowEvent event)
      {  System.exit(0);
      }
   }
}

class DebugOutputStream extends OutputStream
{  public DebugOutputStream(JTextArea area)
   {  textArea = area;
   }
   
   public void write(int b)
   {  char c = (char)b;
      textArea.append("" + c);
   }
   
   private JTextArea textArea;
}

class DebugInputStream extends InputStream
{  public DebugInputStream()
   {  input = "";
      position = 0;
   }
   
   public synchronized void addInput(String newInput)
   {  input = input + newInput;
      notify();
   }
   
   public synchronized int read()
   {  while (input.length() == position)
      {  input = "";
         position = 0;
        try
         {  wait();
         } catch(InterruptedException e) {}
      }
      char c = input.charAt(position);
      position++;
      return c;
   }
   
   public int read(byte[] b)
   {  b[0] = (byte)read();
      return 1;
   }

   private String input;
   private int position;
}