Custom Objects and Threads

Recall that the last applet we were working on paints a rectangle in a canvas, and offers control buttons to manipulate the color, shape, and location of that rectangle.

Click here for MoveBoxWithCanvas.java source code Click here for DrawingCanvas.java source code

When looking at our applet, the one thing that really sticks out is the method of color selection: first, it looks 'non-standard', and second it involves a fair amount of code. Therefore, we want to design a new object specifically made for selecting a color, and then replace the code in our applet using the new ColorSelector object.

I have designed a new ColorSelector object that you can use in any of your classes. Here is a description of our new ColorSelector object. This description and the corresponding class file is everything you need to use the object. You do not need to know how it actually works (but of course the source code is included for your reference).

public class ColorSelector extends Panel
/* 
A class to use for selecting colors. It will bring up the sixteen standard 
Windows colors in small colored boxes. When you click on one of the boxes, 
the corresponding color will become the selected color and is specially
marked. You can retrieve the current color with the 'getColor' method.
Note: Clicking on one of the colored boxes will generate an action event that
can be used to determine when the current color has been changed.
*/
{ // Constructors
public ColorSelector()
/* Creates the default ColorSelector with 16 colored boxes horizontally in 
   the default size */
public ColorSelector(String _dir)
/* Creates a ColorSelector that will layout the 16 colored boxes horizontally 
   or vertically:
   if _dir = "horizontal" the boxes will appear horizontally
   if _dir = "vertical" the boxes will appear vertically */
public ColorSelector(int _width, int _height, String _dir)
/* Creates a ColorSelector that will layout the 16 colored boxes either
   horizontally or vertically, and will give each box the specified size:
   _width = width of each box
   _height = height of each box
   _dir = direction of layout of the boxes ("vertical" or "horizontal") */
   // Methods
public Color getColor()
/* Returns the current color. The default initial color is black, and each 
   click on a colored box will change the current color that will be 
   returned using this method. */

In our original code we do not need to touch the DrawingCanvas at all. Since we only want to modify the user interface, we only need to modify the main applet code. And, in fact, we can considerably simply that code. Here is the old and the new code, where I only list the methods that have changed from the previous "MoveBoxWithCanvas" and "DrawingCanvas" classes:
 

Without using ColorSelector 

Using the ColorSelector Object 

private Button up = 
         new Button("Up");
private Button down = 
         new Button("Down");
private Button left = 
         new Button("Left");
private Button right = 
         new Button("Right");
private Button increase = 
         new Button("[+]");
private Button 
         decrease = new Button("[-]");
private Choice objects = new Choice();
private CheckboxGroup colors = 
         new CheckboxGroup();
private Checkbox red    = 
       new Checkbox("red", colors, true),
                    black  = 
       new Checkbox("black", colors, false),
                    blue   = 
       new Checkbox("blue", colors, false),
                    yellow = 
       new Checkbox("yellow", colors, false),
                   green  = 
       new Checkbox("green", colors, false),
                    pink   = 
       new Checkbox("magenta", colors, false);
private DrawingCanvas drawing = 
       new DrawingCanvas();
private Button up = 
         new Button("Up");
private Button down = 
         new Button("Down");
private Button left = 
         new Button("Left");
private Button right = 
         new Button("Right");
private Button increase = 
         new Button("[+]");
private Button 
         decrease = new Button("[-]");
private Choice objects = new Choice();
private ColorSelector colors = new
                 ColorSelector("vertical");
 
private DrawingCanvas drawing = 
         new DrawingCanvas();
 
public void init()
{ 
   objects.addItem("Rectangle");
   objects.addItem("Circle");
   objects.addItem("Oval");

   Panel head = new Panel();  
   head.setLayout(new FlowLayout());
   head.add(new Label("Choose an object: "));
   head.add(objects)

   Panel colorCol = new Panel();
   colorCol.setLayout(new GridLayout(6,1));
   colorCol.add(black);      colorCol.add(blue);
   colorCol.add(green);      colorCol.add(pink);
   colorCol.add(red);        colorCol.add(yellow);

   Panel buttons = new Panel();
   buttons.setLayout(new FlowLayout());
   buttons.add(up);
   buttons.add(down);
   buttons.add(left);
   buttons.add(right);
   buttons.add(increase);
   buttons.add(decrease);

   setBackground(Color.blue);
   setLayout(new BorderLayout());
   add("South", buttons);
   add("North", head);
   add("West", colorCol);
   add("Center", drawing);
}
public void init()
{
   objects.addItem("Rectangle");
   objects.addItem("Circle");
   objects.addItem("Oval");

   Panel head = new Panel();
   head.setLayout(new FlowLayout());
   head.add(new Label("Choose an object: "));
   head.add(objects);

   Panel buttons = new Panel();
   buttons.setLayout(new FlowLayout());
   buttons.add(up);
   buttons.add(down);
   buttons.add(left);
   buttons.add(right);
   buttons.add(increase);
   buttons.add(decrease);

   setBackground(Color.blue);
   setLayout(new BorderLayout());
   add("South", buttons);
   add("North", head);
   add("West", colors);
   add("South", buttons);
}
public boolean action(Event e, Object o)
{
   if (e.target == up)
      return drawing.handleUp();
   else if (e.target == down)
      return drawing.handleDown();
   else if (e.target == left)
      return drawing.handleLeft();
   else if (e.target == right)
      return drawing.handleRight();
   else if (e.target == increase)
      return drawing.handleIncrease();
   else if (e.target == decrease)
      return drawing.handleDecrease();
   else if (e.target == black)
      return drawing.handleColorChange(Color.black);
   else if (e.target == black)
      return drawing.handleColorChange(Color.black);
   else if (e.target == blue)
      return drawing.handleColorChange(Color.blue);
   else if (e.target == green)
      return drawing.handleColorChange(Color.green);
   else if (e.target == pink)
      return drawing.handleColorChange(Color.pink);
   else if (e.target == red)
      return drawing.handleColorChange(Color.red);
   else if (e.target == yellow)
      return drawing.handleColorChange(Color.yellow);
   else if (e.target instanceof Choice)
      return drawing.handleChangeObject(
             objects.getSelectedItem());
   else
      return super.action(e, o);
}
public boolean action(Event e, Object o)
{
   if (e.target == up)
      return drawing.handleUp();
   else if (e.target == down)
      return drawing.handleDown();
   else if (e.target == left)
      return drawing.handleLeft();
   else if (e.target == right)
      return drawing.handleRight();
   else if (e.target == increase)
      return drawing.handleIncrease();
   else if (e.target == decrease)
      return drawing.handleDecrease();
   else if (e.target == colors)
      drawing.handleChangeColor(colors.getColor());
   else if (e.target instanceof Choice)
      return drawing.handleChangeObject(objects.getSelectedItem());
   else
      return super.action(e, o);
}

Of course, our code has not gotten shorter - even though at first glance it has. But, we are now using the ColorSelector's code as well, so that in total the number of lines has gone up. However, the code for the actual program doing our specific task is shorter, more concise, and easier to understand. As long as the ColorSelector is doing what it promises, we don't have to care how it works at all, and therefore it does not add to the complexity of our program. Moreover, we can now use the ColorSelector in other programs as well, or use it repeatedly in the same program, for example to choose foreground and background colors. Recall, however, that at our current state of knowledge the file ColorSelector.class must reside in the same directory as the rest of the class file in order to run correctly. Here is our new applet in action:

JAVA APPLET

Click here for the code of the ColorSelector object
Click here with the right mouse button to download the ColorSelector.class file

Threads and the Thread Interface

Most modern computers systems can handle multiple programs at the same time. Actually, that is somewhat misleading: computers with one central processor can only handle a single program at any given time, but it can switch between a variety of programs fast enough so that the programs appear to run simultaneously.

To carry this multitasking concept even further, why not have one program execute several strands of execution at the same time (again, not really at the same time but appearently simultaneously). In fact, many programs that you are using are employing this concept, called 'threads', all the time. For example:

Netscape can download a file and begin displaying the "top" of the file while continuing to download the rest - a thread handles the download independently of the display
Microsoft Word 7 and 8 check your spelling as you type, and underlines any mistakes it finds while you make the mistake - a thread does this ongoing spellchecking
The Java virtual machine automatically cleans up objects that are no longer in use and reallocates memory - a thread checks for objects pointing to null and disposes those while the rest of the program runs normally
Animations run while allowing you to press buttons to control the way the animation runs - a thread checks for user input and modifies the running animation whenever needed

So, we also want start learning about this concept by creating our own threads of execution in java programs. Java provides a Thread class in the java.lang package, so we could use that. However, if we want to make applets with threads, we have a basic problem: our applet extends Applet, and since Java does not allow multiple inheritance, it can not at the same time extend Thread also. The problem is solved using an Interface. First, let's look at the Runnable interface that allows classes that already extend other classes to use threads as well. Here's what the Java API has to say about the Runnable interface:

public  interface  java.lang.Runnable
{
        // Methods
    public abstract void run();
}

So the Runnable interface has only one method, and that is an abstract method. That, of course, means that if your class implements Runnable, it must define the run method (otherwise it would become an abstract class itself). Here is a simple example of a threaded applet that will simply count from 0 up.

import java.applet.Applet;
import java.awt.*;

public class Test extends Applet implements Runnable
{
   TextField output = new TextField(10);
   Thread    thread = null;
   int       count  = 0;

   public void init()
   {
      setLayout(new FlowLayout());
      add(output);
      thread = new Thread(this);
      thread.start();
   }
   
   public void run()
   {
      while (thread != null)
      {
         count++;  output.setText(String.valueOf(count));
         try {
            thread.sleep(200);
         }
         catch(InterruptedException e)
         { }
      }
   }
}

First, our classes extends Applet. Second, it implements Runnable and hence must define the run method. Next, a new field thread of type Thread is added to the applet (in addition to the TextField and the integer). When the applet loads, its init method executes, setting up the proper layout, instantiating a new Thread, and executing the thread's start method. Note that when the thread is instantiated, it is 'linked' to the current instance of our applet, because the reference 'this' is passed as a parameter to the thread. It means that when the thread's start method is called, it will in turn call upon the applet's run method. In other word, the thread basically controls what is going on and when, while the run method specifies exactly what the applet should do. Very often the run method is an infinite loop whose exectution is governed by the thread's start(), stop(), and sleep(#) methods. In particular, the sleep method will make the thread wait for the specified number of microseconds. Since the sleep method can throw an exception, it need to be embedded in a standard try-catch block. During the time a thread sleeps, other threads or programs will get the processor and the processor can execute them. When the thread needs to run again, it will request processor time from the CPU, which in turn will execute the thread as soon as possible.Here's the above applet in action:

JAVA APPLET


But now we have a big problem: to see what I mean, take a look at the last number you see, then go back to the previous page. Wait a while, then return to this place. You will see that the number has increased even when you were not looking at the page. That does illustrate the concepts of independently exeecuting threads nicely, but it means that once you look at this page, the thread will run indefinitely even if you don't look at this page any longer. It would, however, quit executing when the JVM shuts down - i.e. when Netscape quits. That, of course, is a waste of resources, and we need to improve on the above applet, as in the following code:

public class Test extends Applet implements Runnable
{
   TextField output = new TextField(10);
   Thread    thread = null;
   int       count  = 0;

   public void init()
   {
      setLayout(new FlowLayout());
      add(output);
      thread = new Thread(this);  
      thread.start();
   }
   public void start()
   {
      if (thread == null)
      {
         thread = new Thread(this);  thread.start();
      }
   }
   public void stop()
   {
      if (thread != null)
      {
         thread.stop();  thread = null;
      }
   }
   public void run()   // as before, no change
}

Let's review carefully what happens here:

the applet is loaded as soon as the user 'hits' this page and therefore the applet's init method executes. It instantiates, among other things, a new thread which is now not pointing to null. Then the thread's start method is executed - different from the applets own start method - which automatically executes the applet's run method. Since thread does not currently point to null, the loop is indefinite and the thread will start running (counting up the numbers).
Everytime the user leaves your page, the 'stop' method of your applet will execute. That in turn stops the thread (killing the run loop) and sets the thread to null, unless it already set to null (the counter will stop counting).
When a user revisits your page, the applet's 'start' method will execute. Since the thread has been set to null (by the stop method), a new thread is instantiated and started (which in turn will get into the infinite loop of the run method again and the counter will continue counting).
The previously running thread that now points to null will be cleaned up automatically by the Java Virtual Machine's garbage collection mechanism (which is, to be sure, another thread).

Note that the first time the applet loads, first the init method executes, and immediately after the start method. However,  since the thread was instantiated in the init method, it is not null, and therefore the start method does not do anything (it still executes, though).

Here is the applet in action. Check the following: notice the number of the counter, then go to the previous page. Wait a few seconds, then return to this page. You should see that the counter was not increased when you were not looking at the page, but when you return to the page the counter starts increasing from wherever it left off. In other words, initially both counters are approximately equal (since the threads started at about the same time). When you flip pages and return, however, the second counter stops when you are not looking at the page, while the first one continues to run.

JAVA APPLET

Note and Warning: When using threads in an applet, be sure to use the appropriate start and stop methods. If you don't, a thread may continue executing even when you are not looking at the page, and that would waste precious resources.


There may be occasions when you do want the thread to continue running, but not when the run loop consists of an otherwise infinite loop.

As a case in point, whenever you look at this page, the first counter thread will run, and t

Next, let's modify our previous MiniPaint applet. Instead of having buttons to move the rectangle around, we now want the rectangle to continuously move around, while the buttons can be used to change the direction. The other control elements should work as before.

Here is what we will have to do:

We do not want to modify the actual applet
The canvas is responsible for the drawing, and therefore we need to implement the Runnable interface for the drawing canvas, not for the original applet.
Modeled after  the above simple examples, we need to add appropriate  start, stop, and run methods to the canvas
we also need to add another thread as a field, and since we now need to store the direction somewhere, we finally need an additional field to contain the current direction of the moving rectangle.

This time, let's first take a look at the working applet, then let's come up with the source code we need to accomplish this applet.

JAVA APPLET

The code for the class MiniPaint is exactly as before, with no changes at all - well, actually we did rename the class, so we now need to have field of type ThreadedCanvas instead of DrawingCanvas. But no change other than that.. We of course have made use of convenient our ColorSelector object, as outlined at the beginning of this lecture. Here is the code for the modified drawing canvas object, with the new code in bold and methods that have not changed removed.

import java.awt.*;

public class ThreadedCanvas extends Canvas implements Runnable
{
   private int x = 20, y = 20;
   private int width = 10, height = 10;
   private int inc = 5;
   private Color myColor = Color.red;
   private String object = "Rectangle";
   private String direction = "right";
   private Thread thread = null;

   public ThreadedCanvas()
   {
      setBackground(Color.green);
      start();
   }
   public void start()
   {
      if (thread != null)
      {
         thread = new Thread(this);
         thread.start();
      }
   }
   public void stop()
   {
      if (thread != null)
      {
         thread.stop();
         thread = null;
      }
   }
   public void run()
   {
      boolean dummy;
      while (thread != null)
      {
         if (direction.equals("left"))
            dummy = handleLeft();
         if (direction.equals("right"))
            dummy = handleRight();
         if (direction.equals("up"))
            dummy = handleUp();
         if (direction.equals("down"))
            dummy = handleDown();
         try
         {
            thread.sleep(200);
         }
         catch(InterruptedException e)
         {
           System.out.println("THREAD ERROR !");
         }
      }
   }
   public void paint(Graphics g)
   {  // no change }
   public boolean handleUp()
   {
      if (y > 0)   y = y - inc;
      else         y = size().height;
      direction = "up";
      repaint();
      return true;
   }
   public boolean handleDown()
   {
      if (y < size().height) y = y + inc;
      else                   y = 0;
      direction = "down";
      repaint();
      return true;
   }
   public boolean handleRight()
   {
      if (x < size().width) x = x + inc;
      else                  x = 0;
      direction = "right";
      repaint();
      return true;
   }
   public boolean handleLeft()
   {
      if (x > 0)  x = x - inc;
      else        x = size().width;
      direction = "left";
      repaint();
      return true;
   
   public boolean handleIncrease()
   { // no change }
   public boolean handleDecrease()
   { // no change }
   public boolean handleChangeObject(String s)
   { // no change }
   public boolean handleColorChange(Color color)
   {  // no change }
   }            
}

Note that our start and stop methods imply that the current object moving in the given direction will actually stop as soon as you leave this page, and resume its movement as soon as you revisit this page. That, in fact, would be the appropriate behaviour.

To conclude the discussion about threads, here is a selection from the Java API on Threads. Keep in mind that an applet using threads needs to have a thread object as a field - with all the methods listed below - and in needs to have a start, stop, and run method as well.

public class Thread extends Object implements Runnable
{   // Fields
    public final static int MAX_PRIORITY; 
    public final static int MIN_PRIORITY;
    public final static int NORM_PRIORITY;
    // Constructors
    public Thread();
    public Thread(Runnable  target); 
    public Thread(Runnable  target, String  name);
    public Thread(String  name);    
    public Thread(ThreadGroup  group, Runnable  target);
    public Thread(ThreadGroup  group,
                      Runnable  target, String  name);
    public Thread(ThreadGroup  group, String  name);
    // Methods
    public void destroy();
    public final String getName();
    public final int getPriority();
    public final boolean isAlive();
    public final boolean isDaemon();
    public final void resume();
    public void run();
    public final void setDaemon(boolean  on);
    public final void setName(String  name); 
    public final void setPriority(int  newPriority); 
    public static void sleep(long  millis);    
    public void start();     
    public final void stop();
    public final void suspend();
}

We will go into some more details on threads when we discuss bigger projects we are going to undertake next. Don't forget to quit and restart Netscape - the first counter thread is still running, and can not be stopped otherwise !