Click here to Skip to main content
Click here to Skip to main content

Tagged as

Java Console apps made easy

, 10 Feb 2012
Rate this:
Please Sign up or sign in to vote.
This article describes the creation of a Java console type application.

Demo screenshot

Contents

Introduction

The humble console app: I write them all the time creating one off tests to explore new coding ideas. I'll even string up quick, menu based, test applications for testing remote device communications and command sets, where the number of commands can grow during the product development cycle. The advantage of course, is that to grow the application I only have to add a menu item and an event handler in the code. That is far preferable to squeezing more fields onto an already crowded Windows Forms application. Sometimes you don't know what you have till it's gone.

I decided to tackle Java this year and get a feel for the language and tools. I usually take on a new (for me) language by translating an application I have written into the new language and stumbling through the thorny issues as I encounter them. I was surprised at how many of the things I took for granted in C# turned out to be rather hard to do in Java; case in point, the console app.

The application I chose to port to Java happened to be a console based menu driven application. Porting the menu code was fairly straightforward once I figured out how to substitute an interface and nested Java class for a C# delegate in order to obtain callback behavior. The real headache lay in the performance of the application's menu code in an actual Windows console instance.

Java applications utilize STDIN, STDOUT, and STDERR streams but do not own the console instance displaying the streams. Since Java aims at a broad range of Operating Systems, it doesn't include support for clearing the screen. I tried a number of work-a-rounds to get my application to function as intended without acceptable results.

I was about to give up when I took a different track and considered duplicating console functionality in a Java GUI widget. This turned out to be more promising. There were quite a few examples of redirecting STDOUT and STDERR to a JtextArea but I was unable to find one that also successfully copied keyboard input to STDIN. What I did find was quite a bit of chatter from other developers indicating a desire for such a component.

I began to test the various code solutions and snippets that I had found and settled on one particularly robust example[^]. I then added some code to pipe keyboard characters to STDIN, added a public clear() method, and some properties to customize the look and feel of the console. Finally I figured out how to create a custom blinking block cursor for JavaConsole to round things off.

How to use this Java Console

Minimally all that is necessary to use this Java Console in your project is to declare an instance of the JavaConsole class.

public static void main(String[] args) {

    new JavaConsole();
    scanner = new Scanner(System.in);

    System.out.println("Hello World!");
    scanner.nextLine();
    
    System.exit(0);
}

Here is an example of how to customize the look of the Java Console.

public static void main(String[] args){
    
    Scanner scanner = new Scanner(System.in);
    JavaConsole console = new JavaConsole();
    
    //Customize the console text field
    console.setBackground(Color.WHITE);
    console.setForeground(Color.BLACK);
    console.setFont(new Font ("Ariel", Font.BOLD, 12));
    
    //Customize the console frame
    console.setTitle("Hello World Program");
    
    Image im = Toolkit.getDefaultToolkit().getImage("../images/MyIcon.png");
    console.setIconImage(im);
    
    System.out.println("Hello World!");
    scanner.nextLine();
    
    System.exit(0);
}

Here is an example using the JavaConsole.clear() method to clear the screen between chunks of text.

public static void main(String[] args){
    
    Scanner scanner = new Scanner(System.in);
    JavaConsole console = new JavaConsole();

    System.out.println(
            "It was a dark and stormy night; the rain fell in torrents, except at " +
            "occasional intervals, when it was checked by a violent gust of wind " +
            "which swept up the streets (for it is in London that our scene lies), " +
            "rattling along the house-tops, and fiercely agitating the scanty flame " +
            "of the lamps that struggled against the darkness. Through one of the " +
            "obscurest quarters of London, and among haunts little loved by the " +
            "gentlemen of the police, a man, evidently of the lowest orders, was " +
            "wending his solitary way.");
    
    scanner.nextLine();
    console.clear();
    System.out.println(
            "He stopped twice or thrice at different shops and houses of a description " +
            "correspondent with the appearance of the quartier in which they were situated, " +
            "and tended inquiry for some article or another which did not seem easily " +
            "to be met with. All the answers he received were couched in the negative; " +
            "and as he turned from each door he muttered to himself, in no very elegant " +
            "phraseology, his disappointment and discontent. At length, at one house, " +
            "the landlord, a sturdy butcher, after rendering the same reply the inquirer " +
            "had hitherto received, added, \"But if this vill do as vell, Dummie, it is " +
            "quite at your sarvice!\"");
    
    scanner.nextLine();
    
    System.exit(0);
}

Finally, here's a comprehensive list of Java Console custom features.

console.getBackground();
console.getForeground();
console.getFont();

console.setBackground(Color);
console.setForeground(Color);
console.setFont(Font);

console.setTitle(title);
console.setIconImage(Image);

console.clear();

How to wire up a console

The first thing we need to do is to redirect STDOUT and STDERR to our JtextArea. The code class I started with achieved this in the following manner.

  1. Establish some Piped Input Streams.
  2. private final PipedInputStream pin=new PipedInputStream(); 
    private final PipedInputStream pin2=new PipedInputStream();
  3. Hook the input streams up to the associated output streams.
  4. PipedOutputStream pout=new PipedOutputStream(this.pin);
    PipedOutputStream pout2=new PipedOutputStream(this.pin2);
  5. Redirect System STDOUT and STDERR to these output streams.
  6. System.setOut(new PrintStream(pout,true)); 
    System.setErr(new PrintStream(pout2,true));
  7. Spin up some threads to read from the Piped Input Streams.
  8. //Declarations
    private Thread reader;
    private Thread reader2;
    
    //In the class constructor
    reader=new Thread(this);
    reader.setDaemon(true);
    reader.start();
    
    reader2=new Thread(this);
    reader2.setDaemon(true);
    reader2.start();
  9. Monitor STDOUT and STDERR appending the text to the JtextArea.
  10. public synchronized void run()
    {
        try
        {           
            while (Thread.currentThread()==reader)
            {
                try { this.wait(100);}catch(InterruptedException ie) {}
                if (pin.available()!=0)
                {
                    String input=this.readLine(pin);
                    textArea.append(input);
                    textArea.setCaretPosition(textArea.getDocument().getLength()); //DWM 02-07-2012
                }
                if (quit) return;
            }
        
            while (Thread.currentThread()==reader2)
            {
                try { this.wait(100);}catch(InterruptedException ie) {}
                if (pin2.available()!=0)
                {
                    String input=this.readLine(pin2);
                    textArea.append(input);
                    textArea.setCaretPosition(textArea.getDocument().getLength()); //DWM 02-07-2012
                }
                if (quit) return;
            }           
        } catch (Exception e)
        {
            textArea.append("\nConsole reports an Internal error.");
            textArea.append("The error is: "+e);
            textArea.setCaretPosition(textArea.getDocument().getLength()); //DWM 02-07-2012
        }
        
    }

Now that we have STDOUT and STDERR piped to the JtextArea, we need to monitor keyboard input to the JtextArea and copy the characters to STDIN. I modified the original code to do so as follows:

  1. Establish a Piped Output Stream for reading keyboard input.
  2. private final PipedOutputStream pout3=new PipedOutputStream();
  3. Hook the output stream to an associated input stream and redirect STDIN to this input stream.
  4. System.setIn(new PipedInputStream(this.pout3));
  5. Add a KeyListener and copy out keyboard input as it arrives.
  6. textArea.addKeyListener(new KeyListener() {
    
        public void keyPressed(KeyEvent e) {}
    
        public void keyReleased(KeyEvent e) {}
    
        public void keyTyped(KeyEvent e)  {
                try { pout3.write(e.getKeyChar()); } catch (IOException ex) {}
        }});

Making a custom block caret

The default caret of the JtextArea is a vertical bar but I wanted a prompt that was a bit more prominent. It turns out that this feature is customizable and I found some examples on how to do this. Refer to A custom caret class [^] and Fancier custom caret class [^] to get an idea of how it's done. Here is the code for my flashing block caret:

public class BlockCaret extends DefaultCaret {

    private static final long serialVersionUID = 1L;

    /**
     * @brief Class Constructor
     */
    public BlockCaret() {
        setBlinkRate(500); // half a second
    }

    /* (non-Javadoc)
     * @see javax.swing.text.DefaultCaret#damage(java.awt.Rectangle)
     */
    protected synchronized void damage(Rectangle r) {
        if (r == null)
            return;

        // give values to x,y,width,height (inherited from java.awt.Rectangle)
        x = r.x;
        y = r.y;
        height = r.height;
        // A value for width was probably set by paint(), which we leave alone.
        // But the first call to damage() precedes the first call to paint(), so
        // in this case we must be prepared to set a valid width, or else
        // paint()
        // will receive a bogus clip area and caret will not get drawn properly.
        if (width <= 0)
            width = getComponent().getWidth();
        
        repaint();  //Calls getComponent().repaint(x, y, width, height) to erase 
        repaint();  // previous location of caret. Sometimes one call isn't enough.
    }

    /* (non-Javadoc)
     * @see javax.swing.text.DefaultCaret#paint(java.awt.Graphics)
     */
    public void paint(Graphics g) {
        JTextComponent comp = getComponent();

        if (comp == null)
            return;

        int dot = getDot();
        Rectangle r = null;
        char dotChar;
        try {
            r = comp.modelToView(dot);
            if (r == null)
                return;
            dotChar = comp.getText(dot, 1).charAt(0);
        } catch (BadLocationException e) {
            return;
        }

        if(Character.isWhitespace(dotChar)) dotChar = '_';

        if ((x != r.x) || (y != r.y)) {
            // paint() has been called directly, without a previous call to
            // damage(), so do some cleanup. (This happens, for example, when
            // the text component is resized.)
            damage(r);
            return;
        }

        g.setColor(comp.getCaretColor());
        g.setXORMode(comp.getBackground()); // do this to draw in XOR mode

        width = g.getFontMetrics().charWidth(dotChar);
        if (isVisible())
            g.fillRect(r.x, r.y, width, r.height);
    }
}

Extra! Extra! The menu system

I have included the menu system, that I like to use so much, in this demo. It is a Java port of the easy to use console menu featured in this CodeProject article [^]. As I mentioned in the Introduction, working with callbacks in Java is a bit different than C# and the Menu class illustrates this well. Here's how it's done in C#.

  1. I declare a delegate (a function pointer):
  2. delegate void MenuCallback();
  3. Next I create some event handlers for menu picks:
  4. private static void HandleItem1
    {
         Console.WriteLine("You chose item 1.\n");
         Console.Read();
    }
    
    private static void HandleItem2
    {
         Console.WriteLine("You chose item 2.\n");
         Console.Read();
    }
  5. Then I add them to the Menu like so:
  6. menu = new Menu();
    menu.Add("Item 1", new MenuCallback(HandleItem1));
    menu.Add("Item 2", new MenuCallback(HandleItem2));
    menu.Show();

Java does not allow for the passing of function pointers and thus delegates. Instead this behavior is restricted to typed object pointers and thus the class interface becomes the Java equivalent of the delegate. Here's how it's done in Java.

  1. I declare an interface (a typed object pointer):
  2. public interface MenuCallback extends EventListener {
        public void Invoke(); 
    }
  3. Next I create some event handlers for menu picks:
  4. private static void HandleItem1 {
         System.out.println("You chose item 1.\n.");
         scanner.nextLine();
    }
    
    private static void HandleItem2 {
         System.out.println("You chose item 2.\n.");
         scanner.nextLine();
    }
  5. Then I add them to the Menu like so:
  6. Menu menu = new Menu(console);
    menu.add("Item 1", new MenuCallback() { public void Invoke() {
         HandleItem1(); } });
    menu.add("Item 2", new MenuCallback() { public void Invoke() {
         HandleItem2(); } });
    menu.show();

It's a bit more verbose since we are in effect, having to wrap our function pointer in a class.

Final comments

The menu system does not work well in the standard console when used in a Java application. The version of Menu class included here should only be used in this Java Console. This Java Console may be used anytime you want to have a console window that is owned by your application. While it features a clear() method for clearing the screen it does not, however handle DOS commands or provide a command prompt.

History

  • Feb 09, 2012: Version 1.0.0.0.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

David MacDermot

United States United States
No Biography provided

Comments and Discussions

 
QuestionBackspace returning as character PinmemberMember 1085663331-May-14 13:03 
GeneralRe: Backspace returning as character PinpremiumPIEBALDconsult31-May-14 14:20 
GeneralRe: Backspace returning as character PinmemberMember 1085663331-May-14 14:44 
GeneralRe: Backspace returning as character PinmemberMember 1085663331-May-14 15:20 
QuestionJavaConsole is Not Working In My Project PinmemberMember 1012719311-Sep-13 0:27 
AnswerRe: JavaConsole is Not Working In My Project PinmemberDavid MacDermot11-Sep-13 6:19 
GeneralMy vote of 5 PinmemberDarthillian29-Apr-13 22:27 
Generalgreat Pinmembersonnykwe21-Feb-13 5:04 
Generalgood tools Pinmemberpaladin198818-Jan-13 23:09 
GeneralMy vote of 5 Pinmemberpaladin198818-Jan-13 22:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140821.2 | Last Updated 10 Feb 2012
Article Copyright 2012 by David MacDermot
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid