Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

JDK 1.1, Lightweight Components, A Named Inner-Class Source/Listener Version of the Lightweight 3D Button Class

Java Programming, Lecture Notes # 180, Revised 03/31/98.

Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

The material in this lesson is extremely important. However, there is simply too much material to be covered in detail during lecture periods. Therefore, students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for studying this material on their own, and bringing any questions regarding the material to class for discussion.

This lesson was originally written on December 6, 1997 using the software and documentation in the JDK 1.1.3 download package.

Introduction

A previous lesson developed and discussed a lightweight 3D button class that mimics a heavyweight button in many respects. That class was developed outside the JDK 1.1 Source/Listener methodology by using the methods named enableEvents() and process...Event() where ... indicates the name of the event type.

It is also possible, to develop lightweight components which are in full compliance with the Source/Listener methodology. The availability of inner classes also makes this a neat and tidy process where the entire lightweight component can be developed inside a single outer class definition.

This lesson develops a lightweight 3D button class that replicates the functionality of the earlier one, but does so using the Source/Listener methodology with named inner classes.

Much of the class definition is still the same as the original. The following sections will discuss only those parts of the class definition which changed as a result of this change in methodology.

A test program will be provided at the end of the lesson that can be used to exercise this new version of the lightweight button class.

Please refer to the class named LWButton01 in a previous lesson for a complete functional description of the lightweight 3D button class.

Interesting Code Fragments

The first interesting code fragment consists of the declaration of two new instance variables. The instance variable named refToThis is a reference to the lightweight button object (usually referred to as this). This reference is needed so that code inside the inner classes can refer to the outer object for such purposes as invoking repaint() on the object.

The instance variable named mouseListener is a reference to the mouse listener object. This is needed so that code inside the key listener methods can invoke the mousePressed() method on the mouse listener object.

In both cases, the purpose is no different from before, but the mechanics are slightly different.
 
  LWButton02 refToThis; //ref to this object  
  MyMouseListenerClass mouseListener;
The next interesting code fragment appears inside the constructor where the call to the enableEvents() method has been replaced by a series of typical Source/Listener statements.

The only thing in this code that is in any way unusual is the last line where a reference to the this object is stored in an instance variable named refToThis to make it available to methods inside of the inner classes. The purpose is as described in the discussion of the new instance variables above.
 
    mouseListener = new MyMouseListenerClass();
    
    this.addMouseListener(mouseListener);
    this.addKeyListener(new MyKeyListenerClass());
    this.addFocusListener(new MyFocusListenerClass());
    
    refToThis = this;                 
The next interesting code fragment is the listener class for key events. This is a completely typical listener class definition, except it is defined as an inner class of the lightweight button class. Most of the code in this class was lifted directly from the processKeyEvent() method in the previous version, and has exactly the same purpose.

Recall that the purpose of the key event processor is to convert a keyPressed() event into a mousePressed() event, and to convert a keyReleased() event into a mouseReleased() event for those cases where the key event is the space bar. You should be able to recognize those actions in the two methods of this class.

A slightly different approach to servicing the keyboard was used in this version to see if it would help with the problem which results from holding the space bar down until it starts repeating. It didn't help.

Note that the code in this class uses the special instance variable named mouseListener to gain access to the methods of the mouse listener object.
 
  class MyKeyListenerClass extends KeyAdapter{
    
    public void keyPressed(KeyEvent e){
      if(e.getKeyCode() == KeyEvent.VK_SPACE) 
        mouseListener.mousePressed(new MouseEvent(
                        refToThis,MouseEvent.MOUSE_PRESSED,
                                         0,0,0,0,0,false));
    }//end keyPressed()
      
    public void keyReleased(KeyEvent e){
      if(e.getKeyCode() == KeyEvent.VK_SPACE)
        mouseListener.mouseReleased(new MouseEvent(
                       refToThis,MouseEvent.MOUSE_RELEASED,
                                         0,0,0,0,0,false));
    }//end keyReleased()
  }//end MyKeyListenerClass
The next interesting code fragment is the focus listener class. As before, most of the code in this class came directly from the code in the processFocusEvent() method in the earlier version of the lightweight button class.

The purpose of this class is to trap focusGained() and focusLost() events and to set an instance variable named gotFocus in the lightweight button class to true or false.

Code in the class also forces the lightweight button to be repainted when the focus is gained or lost in order to display the change in text style used as visual feedback of a change in focus.

Note that the code in this class uses the special instance variable named refToThis to gain access to the methods of the lightweight button class such as invalidate() and paint().
 
  class MyFocusListenerClass implements FocusListener{
    
    public void focusGained(FocusEvent e){
      gotFocus = true; //set the gotFocus flag  
      refToThis.invalidate();      
      refToThis.repaint();
    }//end focusGained()  
  
    public void focusLost(FocusEvent e){
      gotFocus = false; //clear the gotFocus flag    
      refToThis.invalidate();      
      refToThis.repaint();
    }//end focusLost()
  }//end MyFocusListenerClass
The final interesting code fragment is the mouse listener class which replaces the processMouseEvent() method in the earlier version of the lightweight button class.

The purposes of the methods in this class are:

The first purpose is accomplished by setting an instance variable named pressed to true or false. This instance variable is used by the overridden paint() method to determine how to render the image of the button.

In addition to setting the instance variable named pressed to true or false, the code also forces a repaint so that the paint() method will be invoked to do something with the new value of the instance variable.

The code to accomplish the second two purposes is fairly obvious.

There is very little difference in the code in the methods of this class and the code in the processMouseEvent() method of the previous version. If you understood that version, you should have no trouble recognizing corresponding code in this version.
 
  class MyMouseListenerClass extends MouseAdapter{       
    
    public void mousePressed(MouseEvent e){
      pressed = true;
      refToThis.invalidate();        
      refToThis.repaint();    
    }//end mousePressed()
    
    public void mouseReleased(MouseEvent e){
      if(actionListener != null) {

        actionListener.actionPerformed(new ActionEvent(
          refToThis, ActionEvent.ACTION_PERFORMED, label));
      }//end if on actionListener
      
      if(pressed == true) {
        pressed = false;
        refToThis.requestFocus();
        refToThis.invalidate();          
        refToThis.repaint();
      }//end if on pressed    
    }//end mouseReleased()
  }//end MyMouseListenerClass
A complete listing of the lightweight button class is provided in the next section.

Program Listing for New Lightweight Button Class

This program listing is followed by a listing for a program that can be used to exercise the new version of the lightweight button.

Most of the new material in this listing has been highlighted in boldface.

After this lesson was published, it was pointed out by a reader named Kees Kuip that this version of the program contains code that could lead to a memory leak, because the object has an embedded reference to itself which could possibly prevent it from being garbage-collected. A solution to this potential problem (as recommended by Kees) is provided as an exercise for the student in the Review section at the end of the lesson. As it turns out, this is not a problem, and a demonstration program to that effect is also included in the Review section.

Even though this turns out to have been a false alarm, it was a very useful exercise because it led to two additional programming exercises: one to prevent the problem (if it is a problem), and another to demonstrate that it is not a problem. Thanks go out to Kees for pointing out this potential problem, and also for recommending a solution.
 
/* File LWButton02.java Copyright 1997, R.G.Baldwin
This class replicates the functionality of the class
named LWButton01.

However, the LWButton01 class was implemented using the 
enableEvents()--process...Event() methodology whereas this
class uses the source/listener methodology.  The listener
classes in this class are named inner classes.  This 
gives them access to the necessary state variables
without the requirement to pass references to those
variables to the constructors of the listener classes.

========================

This class is used to instantiate a 3D lightweight button
object that behaves quite a bit like a heavyweight Button
object but is much more responsive than a heavyweight
button under JDK 1.1.3 and Win95 on a 133 mhz Pentium
processor.

The color of the button is based on the background color 
of its container, but is one shade brighter than the color
of the background. 

Normally, it appears to protrude slightly out of the 
screen with highlights on the left and top edges and 
shadows on the bottom and right edges.  Note that the
highlighting only works if the background color does not
contain components with values of 255.

When you click the button with the mouse, it appears to 
retreat into the screen and then pops back out.  As with
a heavyweight button, this causes it to gain the focus.

When it appears to retreat into the screen, its color 
changes to match that of the background with heavy shadows
on the left and top and a faint outline on the bottom and
right.

The visual indication of focus is that the text on the
button is rendered in bold italics.

When you click the button, it generates an action event.

When the button has the focus and you press the space
bar, it generates an action event.

This class was tested using JDK 1.1.3 under Win95.
*/
//=======================================================//
import java.awt.*;
import java.awt.event.*;
//=======================================================//
class LWButton02 extends Component {
  //Save the raw label provided by the user here to make
  // it available to the getLabel() method.
  String rawLabel;
  //The following instance variable contains the raw
  // label with two spaces appended to each end to make
  // it easier to use for sizing the LWButton.
  String label;
  // The following instance variable is set to true if 
  // the LWButton is pressed and not released.
  boolean pressed = false; 
  //The following instance variable is set to true when 
  // the LWButton has focus
  boolean gotFocus = false;
  //The following instance variable refers to a list of 
  // registered ActionListener objects.
  ActionListener actionListener;
  //The following is a reference to this object that is
  // accessible by methods of the inner classes
  LWButton02 refToThis; //ref to this object
  //The following is a reference to the MouseListener
  // object that is used by methods of the KeyListener  
  // class to make calls to mousePressed() and 
  // mouseReleased() when keyPressed() and keyReleased()
  // events occur.    
  MyMouseListenerClass mouseListener;
  //-----------------------------------------------------//
  
  //Constructor for an LWButton with no label.
  public LWButton02() {
    //Invoke the parameterized constructor with an 
    // empty string
    this("");
  }//end constructor

  //Constructor for an LWButton with a label.
  public LWButton02(String rawLabel) {
    this.rawLabel = rawLabel;
    //Add spaces on either end and save it that way
    this.label = "  " + rawLabel + "  ";

    //Instantiate a named listener object for the mouse
    // listener so that the methods of the object can be
    // accessed by the methods of the key listener to
    // convert key events into mouse events.
    mouseListener = new MyMouseListenerClass();
    
    //Register the named mouse listener object along with
    // anonymous key listener and focus listener objects
    // on this lightweight button object.
    this.addMouseListener(mouseListener);
    this.addKeyListener(new MyKeyListenerClass());
    this.addFocusListener(new MyFocusListenerClass());
    
    //Store a reference to this lightweight button object 
    // in an instance variable of the class so that it 
    // is accessible to the code in the inner listener
    // classes for mouse, key, and focus.
    refToThis = this;                 
  }//end constructor
  //-----------------------------------------------------//
  
  //The following method uses the AWTEventMulticaster 
  // class to construct a list of ActionListener objects 
  // that are registered on the LWButton object
  public void addActionListener(ActionListener listener) {
    actionListener = AWTEventMulticaster.add(
                                 actionListener, listener);
  }//end addActionListener()
  //-----------------------------------------------------//
  
  //The following method removes ActionListener objects 
  // from the list described above
  public void removeActionListener(ActionListener listener){
     actionListener = AWTEventMulticaster.remove(
                                 actionListener, listener);
  }//end removeActionListener

  //-----------------------------------------------------//
  
  //The following two methods provide the preferred size 
  // and the minimum size of the LWButton to be used by 
  // the layout managers.  
  
  //Override the getPreferredSize() method.  Base the
  // preferred size on the size of the text in the
  // LWButton object.  Recall that two spaces have been
  // appended to each end of the text in the LWButton.
  public Dimension getPreferredSize() {
    if(getFont() != null) {
      FontMetrics fm = getFontMetrics(getFont());
      return new Dimension(fm.stringWidth(label), 
                                      fm.getHeight() + 10);
    } else return new Dimension(10, 10);//no font
  }//end getPreferredSize()
  
  //Override the getMinimumSize() method and specify
  // an arbitrary minimum size for the LWButton.
  public Dimension getMinimumSize() {
      return new Dimension(10, 10);
  }//end getMinimumSize()
  //-----------------------------------------------------//
  
  //The following two methods are available to get and set
  // the label of an LWButton object.
  public String getLabel() {//gets the label
    return rawLabel;
  }//end getLabel()
  
  public void setLabel(String rawLabel) {//sets the label
    this.rawLabel = rawLabel; //save the raw label
    //Add spaces to each end of the rawLabel to make it
    // easier to center the label in the LWButton.
    this.label = "  " + rawLabel + "  ";
    this.invalidate();
    this.repaint();
  }//end setLabel()
  //-----------------------------------------------------//
  
  //The following overridden paint() method paints the 
  // LWButton  The appearance of the LWButton depends on
  // the pressed and gotFocus flags.  When pressed is true,
  // the LWButton is rendered to appear that it has been
  // pressed into the screen.  When it is false, the
  // LWButton is rendered to appear that it is protruding
  // from the screen.  When gotFocus is true, the
  // text is rendered in bold italics as the visual 
  // indication that the LWButton has the focus.  When
  // gotFocus is false, the text is rendered plain.
  
  //Note also that the position of the text also depends
  // on the pressed flag.  When pressed is false, the text
  // is rendered slightly up and to the left of its
  // pressed position.  This enhances the illusion that
  // the button is being pressed into the screen.
  
  //The color of the LWButton object is tied to the
  // background color.  When protruding, the button is 
  // rendered one shade brighter than the background.  
  // When pressed, the LWButton is rendered the same color
  // as the background.

  //The actual size of the LWButton object is determined 
  // by the layout manager which may or may not honor the
  // preferred and minimum size specifications.
  
  public void paint(Graphics g) {//paints the LWButton
    //If LWButton has the focus, display the text in
    // bold italics.  Otherwise display plain.
    if(gotFocus)
      g.setFont(new Font(getFont().getName(),
            Font.BOLD | Font.ITALIC,getFont().getSize()));
    else g.setFont(new Font(getFont().getName(),
                         Font.PLAIN,getFont().getSize()));
    
    if(pressed){ //if the pressed flag is true
      g.setColor(getBackground());
      g.fillRect(//fill rectangle with background color
           0,0,this.getSize().width,this.getSize().height);
           
      //Draw shadows three shades darker than background
      g.setColor(
               getBackground().darker().darker().darker());
      //Note that three offset rectangles are drawn to
      // produce a shadow effect on the left and top of
      // the rectangle.               
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
      g.drawRect(
           1,1,this.getSize().width,this.getSize().height);
      g.drawRect(
           2,2,this.getSize().width,this.getSize().height);
           
      //Now draw a faint outline on the bottom and right of
      // the rectangle.
      g.setColor(getBackground().darker());
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
           
      //Now center the text in the LWButton object
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
               getSize().width/2 - fm.stringWidth(label)/2,
                    getSize().height/2 + fm.getAscent()/2);
    }//end if(pressed)
    
    else{//not pressed
      //Make the protruding LWButton object one shade
      // brighter than the background.
      g.setColor(getBackground().brighter());
      g.fillRect(//and fill a rectangle
           0,0,this.getSize().width,this.getSize().height);

      //Set the color for the shadows three shades darker
      // than the background.
      g.setColor(
               getBackground().darker().darker().darker());
      //Draw two offset rectangles to create shadows on 
      // the right and bottom.               
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
      g.drawRect(
         -2,-2,this.getSize().width,this.getSize().height);
         
      //Highlight the left and top two shades brighter 
      // than the background, one shade brighter than the
      // color of the LWButton itself which is one shade
      // brighter than the background.
      g.setColor(
               getBackground().brighter().brighter());
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
         
      //Now place the text in the LWButton object shifted
      // by two pixels up and to the left.           
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
           getSize().width/2 - fm.stringWidth(label)/2 - 2,
                getSize().height/2 + fm.getAscent()/2 - 2);
    }//end else
  }//end overridden paint() method
  
  //-----------------------------------------------------//
  
  class MyKeyListenerClass extends KeyAdapter{
    /*
    This class is used to cause the LWButton to behave as
    if a mouse event occurred on it whenever it has the
    focus and the space bar is pressed and then released.
    Holding the space bar down generates repetitive 
    events due to the repeat feature of the keyboard (this
    would need to be disabled in a real program).
    */
    
    public void keyPressed(KeyEvent e){
      //Generate mousePressed() event when the space bar 
      // is pressed by invoking the mousePressed() method
      // of the MouseListener object, and passing an event 
      // object that impersonates a mouse pressed event.
      if(e.getKeyCode() == KeyEvent.VK_SPACE) 
        mouseListener.mousePressed(new MouseEvent(
                        refToThis,MouseEvent.MOUSE_PRESSED,
                                         0,0,0,0,0,false));
    }//end keyPressed()
      
    public void keyReleased(KeyEvent e){
      //Generate mouseReleased() event when the space bar 
      // is released by invoking the mouseReleased() method
      // of the MouseListener object, and passing an event 
      // object that impersonates a mouse released event.
      if(e.getKeyCode() == KeyEvent.VK_SPACE)
        mouseListener.mouseReleased(new MouseEvent(
                       refToThis,MouseEvent.MOUSE_RELEASED,
                                         0,0,0,0,0,false));
    }//end keyReleased()
  }//end MyKeyListenerClass
  
  //-----------------------------------------------------//

  class MyFocusListenerClass implements FocusListener{
    /*
    This methods of this class are invoked when a focus 
    event occurs on the LWButton.  This happens when the 
    requestFocus() method is called inside the 
    mouseReleased() event handler for the LWButton.  This 
    sets or clears the gotFocus flag that is used to cause 
    the text renderer to modify the text to indicate that 
    the LWButton has the focus. When the LWButton has the 
    focus, the text is rendered in bold italics.    */
    
    public void focusGained(FocusEvent e){
      gotFocus = true; //set the gotFocus flag  
      refToThis.invalidate();      
      refToThis.repaint();
    }//end focusGained()  
  
    public void focusLost(FocusEvent e){
      gotFocus = false; //clear the gotFocus flag    
      refToThis.invalidate();      
      refToThis.repaint();
    }//end focusLost()
  }//end MyFocusListenerClass

  //-----------------------------------------------------//
    
  class MyMouseListenerClass extends MouseAdapter{
    /*
    The  purpose of this methods in this class is threefold:
    1.  Modify the appearance of the LWButton object when
        the user clicks on it.
    2.  Invoke the actionPerformed() method in the Listener 
        object that is registered to listen to this 
        LWButton object.
    3.  Request focus for the LWButton.     */       
    
    public void mousePressed(MouseEvent e){
      //When the mouse is pressed on the LWButton object,
      // set the "pressed" state of the object to true 
      // and force it to be repainted to change its 
      // appearance. When pressed is true, the button is
      // rendered as though it has been pressed into the
      // screen.
      pressed = true;
      refToThis.invalidate();        
      refToThis.repaint();    
    }//end mousePressed()
    
    
    public void mouseReleased(MouseEvent e){
      //When the mouse is released on the LWButton object:
      // 1. Invoke the actionPerformed() method in the 
      //    Listener objects that are registered to 
      //    listen to the LWButton object.
      // 2. Confirm that the "pressed" state is true and 
      //    if so, set it to false and force the object 
      //    to be repainted to change its appearance.
      //    When pressed is false, the button is rendered
      //    so as to appear to protrude out of the 
      //    screen.
      // 3. Request the focus for the LWButton object.
      
      //if an ActionListener is registered
      if(actionListener != null) {
        //Invoke the actionPerformed() method on the list
        // of listener objects registered on the 
        // LWButton object.  Instantiate and pass an
        // ActionEvent object as a parameter.
        actionListener.actionPerformed(new ActionEvent(
          refToThis, ActionEvent.ACTION_PERFORMED, label));
      }//end if on actionListener
      
      if(pressed == true) {
        pressed = false;
        refToThis.requestFocus();
        refToThis.invalidate();          
        refToThis.repaint();
      }//end if on pressed    
    }//end mouseReleased()
  }//end MyMouseListenerClass
  
}//end class LWButton02
//=======================================================//
The next section contains the source code for a program that can be used to exercise this lightweight button class.

Test Program Listing

The following test program is identical to one of the test programs in a previous program that was used to exercise the earlier version of the lightweight button class. Only the reference to the lightweight button class has been changed.
 
/* File Lightweight07.java Copyright 1997, R.G.Baldwin
This program was designed specifically to test the class
named LWButton02 and requires access to that class.
The classed named LWButton02 is a lightweight 3D button 
class which replicates the functionality of the 
LWButton01 class, but implements that functionality using
source/listener methodology instead of enableEvents() --
process...Event() methodology.

====================

You will probably need to compile and run the program to
really appreciate what it does.

The purpose of the program is to exercise the lightweight
button class named LWButton02 under a BorderLayout
manager.

The BorderLayout manager does not honor both dimensions
of the preferred size of a component in any of its 
five positions. Components in the North and South 
positions probably have the vertical dimension of their 
preferred size honored.

Similarly, components in the East and West positions
probably have the horizontal dimension of their preferred
size honored.

Neither dimension of the preferred size is honored for
components in the Center.  Components in the center
simply occupy all of the available space.

This program places three lightweight buttons of type
LWButton02, one heavyweight button of type Button, and
a Label object in a Frame object with a ten-pixel gap
between all components.

The lightweight buttons occupy the East, North, and Center
positions in the Frame.

The heavyweight button occupies the West position.

The Label occupies the South position.

When you click on any of the four buttons, the color of
the Label toggles between red and green.

You can experiment with the manner in which the preferred
size is or is not honored by resizing the Frame.

Note that this program makes use of a named inner class.

This program was tested using JDK 1.1.3 under Win95.
*/
//=======================================================//
import java.awt.*;
import java.awt.event.*;
//=======================================================//
public class Lightweight07 extends Frame{
  Label myLabel;
  
  public static void main(String[] args){
    new Lightweight07();//instantiate object of this type
  }//end main

//-------------------------------------------------------//
  public Lightweight07(){//constructor
    this.setTitle("Copyright 1997, R.G.Baldwin");
    //Set background to a dull yellow
    this.setBackground(new Color(128,128,0));
    
    //Create a borderLayout object with gaps and apply
    // it to the Frame object.
    BorderLayout myLayout = new BorderLayout();
    myLayout.setHgap(10);
    myLayout.setVgap(10);
    this.setLayout(myLayout);    
    
    //Instantiate three lightweight buttons
    LWButton02 eastLWButton = new LWButton02("East");
    LWButton02 northLWButton = new LWButton02("North");
    LWButton02 centerLWButton = new LWButton02("Center");
    
    //Instantiate a Label object and initialize it to green
    myLabel = new Label("Label Object");
    myLabel.setBackground(Color.green);
    
    //Instantiate a heavyweight button object
    Button myButton = new Button("Heavyweight Button");
    
    //Add all five components to the Frame object.
    this.add(eastLWButton,"East");    
    this.add(northLWButton,"North");    
    this.add(centerLWButton,"Center");
    this.add(myButton,"West");
    this.add(myLabel,"South");

    //Instantiate an ActionListener object
    MyActionListener myActionListener = 
                                   new MyActionListener();
                             
    //Register the ActionListener object on all four
    // of the buttons.                             
    eastLWButton.addActionListener(myActionListener);
    northLWButton.addActionListener(myActionListener);
    centerLWButton.addActionListener(myActionListener);
    myButton.addActionListener(myActionListener);
    
    this.setSize(300,200);
    this.setVisible(true);

    //Anonymous inner-class listener to terminate program
    this.addWindowListener(new WindowAdapter(){
               public void windowClosing(WindowEvent e){
                 System.exit(0);}});//end addWindowListener
  }//end constructor
  //-----------------------------------------------------//
  
  //Inner Class to respond to action events.  Make this an
  // inner class for easy access to myLabel.
  class MyActionListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      if(myLabel.getBackground() == Color.green)
        myLabel.setBackground(Color.red);
      else myLabel.setBackground(Color.green);
    }//end actionPerformed
  }//end class MyActionListener
}//end class Lightweight07
//=======================================================//

Review

Q - A reader named Kees Kuip has pointed out that the program named LWButton02 has a potential memory leak. Without viewing the code that follows, identify the potential memory leak in LWButton02 and modify the program to correct it.

Thanks go to Kees for pointing out this potential problem, and also for providing a recommended solution.

Also, a reader named Myles Harding sent in the following suggestion:
 
Thanks very much for your excellent Java Tutorials.  I recommend them to all my students.  The mind set your lessons project of keeping it simple and testing everything from first principles sends the right message. 

May I make a suggestion regarding you Lighweight Components Lessons. 

The reftothis variable, or the getThis() method in the Inner classes can be replaced with LWButton.this so your getThis().requestFocus(), or refToThis.requestFocus() becomes LWButton01.this.requestFocus(); and so on.  

The benefits of this scheme becomes more marked when the Inner class has further Inner classes. 

I have not seen this way of accessing outer named  this's documented anywhere but it seems to work OK. 

Regards 
Professor Myles Harding 
Mathematical Sciences 
Swinburne University of Technology 
Victoria Australia 

.
 
/* File SampProg143.java Copyright 1997, R.G.Baldwin
From lesson 180.

A reader has pointed out that the program named LWButton02
contains code that could lead to a memory leak.

Without viewing the solution that follows, see if you can
find the potential memory leak and modify the program to
correct the problem.  This modified program should 
replicate the functionality of the lightweight button
named LWButton02.

========================

This class is used to instantiate a 3D lightweight button
object.

The color of the button is based on the background color 
of its container, but is one shade brighter than the color
of the background. 

Normally, it appears to protrude slightly out of the 
screen with highlights on the left and top edges and 
shadows on the bottom and right edges.  Note that the
highlighting only works if the background color does not
contain components with values of 255.

When you click the button with the mouse, it appears to 
retreat into the screen and then pops back out.  As with
a heavyweight button, this causes it to gain the focus.

When it appears to retreat into the screen, its color 
changes to match that of the background with heavy shadows
on the left and top and a faint outline on the bottom and
right.

The visual indication of focus is that the text on the
button is rendered in bold italics.

When you click the button, it generates an action event.

When the button has the focus and you press the space
bar, it generates an action event.

This class was tested using JDK 1.1.3 under Win95.
*/
//=======================================================//
import java.awt.*;
import java.awt.event.*;
//=======================================================//
class SampProg143 extends Component {
  //Save the raw label provided by the user here to make
  // it available to the getLabel() method.
  String rawLabel;
  //The following instance variable contains the raw
  // label with two spaces appended to each end to make
  // it easier to use for sizing the LWButton.
  String label;
  // The following instance variable is set to true if 
  // the LWButton is pressed and not released.
  boolean pressed = false; 
  //The following instance variable is set to true when 
  // the LWButton has focus
  boolean gotFocus = false;
  //The following instance variable refers to a list of 
  // registered ActionListener objects.
  ActionListener actionListener;
  //The following is a reference to the MouseListener
  // object that is used by methods of the KeyListener  
  // class to make calls to mousePressed() and 
  // mouseReleased() when keyPressed() and keyReleased()
  // events occur.    
  MyMouseListenerClass mouseListener;
  //-----------------------------------------------------//
  
  //Constructor for an LWButton with no label.
  public SampProg143() {
    //Invoke the parameterized constructor with an 
    // empty string
    this("");
  }//end constructor

  //Constructor for an LWButton with a label.
  public SampProg143(String rawLabel) {
    this.rawLabel = rawLabel;
    //Add spaces on either end and save it that way
    this.label = "  " + rawLabel + "  ";

    //Instantiate a named listener object for the mouse
    // listener so that the methods of the object can be
    // accessed by the methods of the key listener to
    // convert key events into mouse events.
    mouseListener = new MyMouseListenerClass();
    
    //Register the named mouse listener object along with
    // anonymous key listener and focus listener objects
    // on this lightweight button object.
    this.addMouseListener(mouseListener);
    this.addKeyListener(new MyKeyListenerClass());
    this.addFocusListener(new MyFocusListenerClass());
  }//end constructor
  //-----------------------------------------------------//

  //The following method replaces the instance variable
  // named refToThis in the version named LWButton02.  It 
  // was pointed out by a reader that the use of the 
  // refToThis variable could lead to a memory leak, 
  // because the object had an embedded reference to 
  // itself which would prevent it from being 
  // garbage-collected.
  
  SampProg143 getThis(){
    return this;
  }//end getThis()
  //-----------------------------------------------------//
  
  //The following method uses the AWTEventMulticaster 
  // class to construct a list of ActionListener objects 
  // that are registered on the LWButton object
  public void addActionListener(ActionListener listener) {
    actionListener = AWTEventMulticaster.add(
                                 actionListener, listener);
  }//end addActionListener()
  //-----------------------------------------------------//
  
  //The following method removes ActionListener objects 
  // from the list described above
  public void removeActionListener(ActionListener listener){
     actionListener = AWTEventMulticaster.remove(
                                 actionListener, listener);
  }//end removeActionListener

  //-----------------------------------------------------//
  
  //The following two methods provide the preferred size 
  // and the minimum size of the LWButton to be used by 
  // the layout managers.  
  
  //Override the getPreferredSize() method.  Base the
  // preferred size on the size of the text in the
  // LWButton object.  Recall that two spaces have been
  // appended to each end of the text in the LWButton.
  public Dimension getPreferredSize() {
    if(getFont() != null) {
      FontMetrics fm = getFontMetrics(getFont());
      return new Dimension(fm.stringWidth(label), 
                                      fm.getHeight() + 10);
    } else return new Dimension(10, 10);//no font
  }//end getPreferredSize()
  
  //Override the getMinimumSize() method and specify
  // an arbitrary minimum size for the LWButton.
  public Dimension getMinimumSize() {
      return new Dimension(10, 10);
  }//end getMinimumSize()
  //-----------------------------------------------------//
  
  //The following two methods are available to get and set
  // the label of an LWButton object.
  public String getLabel() {//gets the label
    return rawLabel;
  }//end getLabel()
  
  public void setLabel(String rawLabel) {//sets the label
    this.rawLabel = rawLabel; //save the raw label
    //Add spaces to each end of the rawLabel to make it
    // easier to center the label in the LWButton.
    this.label = "  " + rawLabel + "  ";
    this.invalidate();
    this.repaint();
  }//end setLabel()
  //-----------------------------------------------------//
  
  //The following overridden paint() method paints the 
  // LWButton  The appearance of the LWButton depends on
  // the pressed and gotFocus flags.  When pressed is true,
  // the LWButton is rendered to appear that it has been
  // pressed into the screen.  When it is false, the
  // LWButton is rendered to appear that it is protruding
  // from the screen.  When gotFocus is true, the
  // text is rendered in bold italics as the visual 
  // indication that the LWButton has the focus.  When
  // gotFocus is false, the text is rendered plain.
  
  //Note also that the position of the text also depends
  // on the pressed flag.  When pressed is false, the text
  // is rendered slightly up and to the left of its
  // pressed position.  This enhances the illusion that
  // the button is being pressed into the screen.
  
  //The color of the LWButton object is tied to the
  // background color.  When protruding, the button is 
  // rendered one shade brighter than the background.  
  // When pressed, the LWButton is rendered the same color
  // as the background.

  //The actual size of the LWButton object is determined 
  // by the layout manager which may or may not honor the
  // preferred and minimum size specifications.
  
  public void paint(Graphics g) {//paints the LWButton
    //If LWButton has the focus, display the text in
    // bold italics.  Otherwise display plain.
    if(gotFocus)
      g.setFont(new Font(getFont().getName(),
            Font.BOLD | Font.ITALIC,getFont().getSize()));
    else g.setFont(new Font(getFont().getName(),
                         Font.PLAIN,getFont().getSize()));
    
    if(pressed){ //if the pressed flag is true
      g.setColor(getBackground());
      g.fillRect(//fill rectangle with background color
           0,0,this.getSize().width,this.getSize().height);
           
      //Draw shadows three shades darker than background
      g.setColor(
               getBackground().darker().darker().darker());
      //Note that three offset rectangles are drawn to
      // produce a shadow effect on the left and top of
      // the rectangle.               
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
      g.drawRect(
           1,1,this.getSize().width,this.getSize().height);
      g.drawRect(
           2,2,this.getSize().width,this.getSize().height);
           
      //Now draw a faint outline on the bottom and right of
      // the rectangle.
      g.setColor(getBackground().darker());
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
           
      //Now center the text in the LWButton object
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
               getSize().width/2 - fm.stringWidth(label)/2,
                    getSize().height/2 + fm.getAscent()/2);
    }//end if(pressed)
    
    else{//not pressed
      //Make the protruding LWButton object one shade
      // brighter than the background.
      g.setColor(getBackground().brighter());
      g.fillRect(//and fill a rectangle
           0,0,this.getSize().width,this.getSize().height);

      //Set the color for the shadows three shades darker
      // than the background.
      g.setColor(
               getBackground().darker().darker().darker());
      //Draw two offset rectangles to create shadows on 
      // the right and bottom.               
      g.drawRect(
         -1,-1,this.getSize().width,this.getSize().height);
      g.drawRect(
         -2,-2,this.getSize().width,this.getSize().height);
         
      //Highlight the left and top two shades brighter 
      // than the background, one shade brighter than the
      // color of the LWButton itself which is one shade
      // brighter than the background.
      g.setColor(
               getBackground().brighter().brighter());
      g.drawRect(//
           0,0,this.getSize().width,this.getSize().height);
         
      //Now place the text in the LWButton object shifted
      // by two pixels up and to the left.           
      FontMetrics fm = getFontMetrics(getFont());
      g.setColor(getForeground());
      g.drawString(label,
           getSize().width/2 - fm.stringWidth(label)/2 - 2,
                getSize().height/2 + fm.getAscent()/2 - 2);
    }//end else
  }//end overridden paint() method
  
  //-----------------------------------------------------//
  
  class MyKeyListenerClass extends KeyAdapter{
    /*
    This class is used to cause the LWButton to behave as
    if a mouse event occurred on it whenever it has the
    focus and the space bar is pressed and then released.
    Holding the space bar down generates repetitive 
    events due to the repeat feature of the keyboard (this
    would need to be disabled in a real program).
    */
    
    public void keyPressed(KeyEvent e){
      //Generate mousePressed() event when the space bar 
      // is pressed by invoking the mousePressed() method
      // of the MouseListener object, and passing an event 
      // object that impersonates a mouse pressed event.
      if(e.getKeyCode() == KeyEvent.VK_SPACE) 
        mouseListener.mousePressed(new MouseEvent(
                       getThis(),MouseEvent.MOUSE_PRESSED,
                                         0,0,0,0,0,false));
    }//end keyPressed()
      
    public void keyReleased(KeyEvent e){
      //Generate mouseReleased() event when the space bar 
      // is released by invoking the mouseReleased() method
      // of the MouseListener object, and passing an event 
      // object that impersonates a mouse released event.
      if(e.getKeyCode() == KeyEvent.VK_SPACE)
        mouseListener.mouseReleased(new MouseEvent(
                      getThis(),MouseEvent.MOUSE_RELEASED,
                                         0,0,0,0,0,false));
    }//end keyReleased()
  }//end MyKeyListenerClass
  
  //-----------------------------------------------------//

  class MyFocusListenerClass implements FocusListener{
    /*
    This methods of this class are invoked when a focus 
    event occurs on the LWButton.  This happens when the 
    requestFocus() method is called inside the 
    mouseReleased() event handler for the LWButton.  This 
    sets or clears the gotFocus flag that is used to cause
    the text renderer to modify the text to indicate that 
    the LWButton has the focus. When the LWButton has the 
    focus, the text is rendered in bold italics.    */
    
    public void focusGained(FocusEvent e){
      gotFocus = true; //set the gotFocus flag  
      getThis().invalidate();      
      getThis().repaint();
    }//end focusGained()  
  
    public void focusLost(FocusEvent e){
      gotFocus = false; //clear the gotFocus flag    
      getThis().invalidate();      
      getThis().repaint();
    }//end focusLost()
  }//end MyFocusListenerClass

  //-----------------------------------------------------//
    
  class MyMouseListenerClass extends MouseAdapter{
    /*
    The  purpose of this methods in this class is threefold:
    1.  Modify the appearance of the LWButton object when
        the user clicks on it.
    2.  Invoke the actionPerformed() method in the Listener 
        object that is registered to listen to this 
        LWButton object.
    3.  Request focus for the LWButton.     */       
    
    public void mousePressed(MouseEvent e){
      //When the mouse is pressed on the LWButton object,
      // set the "pressed" state of the object to true 
      // and force it to be repainted to change its 
      // appearance. When pressed is true, the button is
      // rendered as though it has been pressed into the
      // screen.
      pressed = true;
      getThis().invalidate();        
      getThis().repaint();    
    }//end mousePressed()
    
    
    public void mouseReleased(MouseEvent e){
      //When the mouse is released on the LWButton object:
      // 1. Invoke the actionPerformed() method in the 
      //    Listener objects that are registered to 
      //    listen to the LWButton object.
      // 2. Confirm that the "pressed" state is true and 
      //    if so, set it to false and force the object 
      //    to be repainted to change its appearance.
      //    When pressed is false, the button is rendered
      //    so as to appear to protrude out of the 
      //    screen.
      // 3. Request the focus for the LWButton object.
      
      //if an ActionListener is registered
      if(actionListener != null) {
        //Invoke the actionPerformed() method on the list
        // of listener objects registered on the 
        // LWButton object.  Instantiate and pass an
        // ActionEvent object as a parameter.
        actionListener.actionPerformed(new ActionEvent(
          getThis(), ActionEvent.ACTION_PERFORMED, label));
      }//end if on actionListener
      
      if(pressed == true) {
        pressed = false;
        getThis().requestFocus();
        getThis().invalidate();          
        getThis().repaint();
      }//end if on pressed    
    }//end mouseReleased()
  }//end MyMouseListenerClass
  
}//end class SampProg143
//=======================================================//
.
 
/* File SampProg144.java Copyright 1997, R.G.Baldwin
From lesson 180.

This program was designed specifically to test the class
named SampProg143 and requires access to that class.
The classed named SampProg143 is a lightweight 3D button 
class which replicates the functionality of the 
LWButton02 class, but eliminates the possibility of a
memory leak that exists with LWButton02.

====================

You will probably need to compile and run the program to
really appreciate what it does.

The purpose of the program is to exercise the lightweight
button class named SampProg143 under a BorderLayout
manager.

The BorderLayout manager does not honor both dimensions
of the preferred size of a component in any of its 
five positions. Components in the North and South 
positions probably have the vertical dimension of their 
preferred size honored.

Similarly, components in the East and West positions
probably have the horizontal dimension of their preferred
size honored.

Neither dimension of the preferred size is honored for
components in the Center.  Components in the center
simply occupy all of the available space.

This program places three lightweight buttons of type
SampProg143, one heavyweight button of type Button, and
a Label object in a Frame object with a ten-pixel gap
between all components.

The lightweight buttons occupy the East, North, and Center
positions in the Frame.

The heavyweight button occupies the West position.

The Label occupies the South position.

When you click on any of the four buttons, the color of
the Label toggles between red and green.

You can experiment with the manner in which the preferred
size is or is not honored by resizing the Frame.

Note that this program makes use of a named inner class.

This program was tested using JDK 1.1.3 under Win95.
*/
//=======================================================//
import java.awt.*;
import java.awt.event.*;
//=======================================================//
public class SampProg144 extends Frame{
  Label myLabel;
  
  public static void main(String[] args){
    new SampProg144();//instantiate object of this type
  }//end main

//-------------------------------------------------------//
  public SampProg144(){//constructor
    this.setTitle("Copyright 1997, R.G.Baldwin");
    //Set background to a dull yellow
    this.setBackground(new Color(128,128,0));
    
    //Create a borderLayout object with gaps and apply
    // it to the Frame object.
    BorderLayout myLayout = new BorderLayout();
    myLayout.setHgap(10);
    myLayout.setVgap(10);
    this.setLayout(myLayout);    
    
    //Instantiate three lightweight buttons
    SampProg143 eastLWButton = new SampProg143("East");
    SampProg143 northLWButton = new SampProg143("North");
    SampProg143 centerLWButton = new SampProg143("Center");
    
    //Instantiate a Label object and initialize it to green
    myLabel = new Label("Label Object");
    myLabel.setBackground(Color.green);
    
    //Instantiate a heavyweight button object
    Button myButton = new Button("Heavyweight Button");
    
    //Add all five components to the Frame object.
    this.add(eastLWButton,"East");    
    this.add(northLWButton,"North");    
    this.add(centerLWButton,"Center");
    this.add(myButton,"West");
    this.add(myLabel,"South");

    //Instantiate an ActionListener object
    MyActionListener myActionListener = 
                                   new MyActionListener();
                             
    //Register the ActionListener object on all four
    // of the buttons.                             
    eastLWButton.addActionListener(myActionListener);
    northLWButton.addActionListener(myActionListener);
    centerLWButton.addActionListener(myActionListener);
    myButton.addActionListener(myActionListener);
    
    this.setSize(300,200);
    this.setVisible(true);

    //Anonymous inner-class listener to terminate program
    this.addWindowListener(new WindowAdapter(){
               public void windowClosing(WindowEvent e){
                 System.exit(0);}});//end addWindowListener
  }//end constructor
  //-----------------------------------------------------//
  
  //Inner Class to respond to action events.  Make this an
  // inner class for easy access to myLabel.
  class MyActionListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      if(myLabel.getBackground() == Color.green)
        myLabel.setBackground(Color.red);
      else myLabel.setBackground(Color.green);
    }//end actionPerformed
  }//end class MyActionListener
}//end class SampProg144
//=======================================================//
Q - Write a Java application that meets the following specification.
 
/*File SampProg145.java
From lesson 180.

A reader has pointed out that the program named LWButton02
contains code that could lead to a memory leak because the
object contains an instance variable named refToThis that 
contains a reference to itself.

Without viewing the solution that follows, write a program
to demonstrate that this is not a problem.

The question was whether having a reference to the object
embedded in the object would prevent the object from 
becoming eligible for garbage collection and result in 
a memory leak.

This program was designed to answer that question.  In 
this program, an instance variable named refToThis is 
assigned the value of the this reference causing it to
become a reference to itself.

In addition, another instance variable is assigned a 
reference to an object of a different class creating an
embedded reference to another object.

After an object of this class is instantiated, the
reference to the object is set to null, making the outer
object eligible for garbage collection.  Then an attempt
to force garbage collection is made and a two-second
time delay is executed.

The finalize methods for both classes are overridden to
display a message.  This makes it possible to determine
when garbage collection takes place.

Several different scenarios are tested.

This program illustrates that when the outer object 
becomes eligible for garbage collection, embedded objects
automatically become eligible for garbage collection
with no requirement on the part of the programmer to set
the references to those embedded objects to null.  

In this program, when the outer object is garbage 
collected, garbage collection of the embedded object 
follows shortly thereafter (because gargage collection of 
all eligible objects was forced).

Apparently if the object contains an embedded reference to 
itself, this reference is treated the same as any
reference to an embedded object.  The embedded reference 
to the object does not prevent the object from becoming
eligible for garbage collection.

Tested using JDK 1.1.3 under Win95.

The output from running this program was:
  
 a  = new SampProg145();
In Xstr: SampProg145@1cc742 Embedded@1cc744 Nmbr = 1
 a  = null;
In SampProg145 finalize: SampProg145@1cc742 Number = 0
In Embedded finalize: Embedded@1cc744
 a  = new SampProg145();
In Xstr: SampProg145@1cc71b Embedded@1cc70d Nmbr = 1
 b  = a;
 a  = null;
 b  = null;
In SampProg145 finalize: SampProg145@1cc71b Number = 0
In Embedded finalize: Embedded@1cc70d
 a  = new SampProg145();
In Xstr: SampProg145@1cc707 Embedded@1cc704 Nmbr = 1
 a  = new SampProg145();
In Xstr: SampProg145@1cc75c Embedded@1cc75b Nmbr = 2
In SampProg145 finalize: SampProg145@1cc707 Number = 1
In Embedded finalize: Embedded@1cc704
 a  = null;
In SampProg145 finalize: SampProg145@1cc75c Number = 0
In Embedded finalize: Embedded@1cc75b  

Based on a test program from Kees.
**********************************************************/

import java.awt.*;
import java.util.*;
//=======================================================//
//Class used to embed an object inside of another object
class Embedded{
  int data = 10;
  //-----------------------------------------------------//
  protected void finalize() throws Throwable{
    System.out.println("In Embedded finalize: " + this);
  }//end finalize
}//end class Embedded
//=======================================================//

public class SampProg145{
  static int  numberOfInstances;//object counter
  SampProg145 refToThis; //reference to this object
  Embedded embeddedObj; //reference to embedded object
  //-----------------------------------------------------//

  public SampProg145(){//constructor
    //Embed an object in this object
    embeddedObj = new Embedded();
    refToThis = this;//create ref to this object
    numberOfInstances++;//increment object counter
    System.out.println("In Xstr: " + refToThis + " " 
          + embeddedObj + " Nmbr = " + numberOfInstances);
  }//end constructor
  //-----------------------------------------------------//

  static void delayAndGC()  {
    System.gc();//try to force garbage collection
    try   {Thread.sleep(2000);}//delay two seconds
    catch (Exception e) {}    
  }//end delayAndGC
  //-----------------------------------------------------//

  protected void finalize() throws Throwable{
    numberOfInstances--;
    System.out.println("In SampProg145 finalize: " + this + 
                        " Number = " + numberOfInstances);
  }//end finalize()
  //-----------------------------------------------------//

  public static void main(String args[]){
    SampProg145 a , b;

// There is no memory leak if after every test the 
// number equals 0 !

// Test 1.
    System.out.println(" a  = new SampProg145();");
    a  = new SampProg145(); SampProg145.delayAndGC();
    System.out.println(" a  = null;");
    a  = null;          SampProg145.delayAndGC();         

// Test 2.
    System.out.println(" a  = new SampProg145();");
    a  = new SampProg145(); SampProg145.delayAndGC();
    System.out.println(" b  = a;");
    b  = a;             SampProg145.delayAndGC(); 
    System.out.println(" a  = null;");
    a  = null;          SampProg145.delayAndGC();         
    System.out.println(" b  = null;");
    b  = null;          SampProg145.delayAndGC();         

// Test 3.
    System.out.println(" a  = new SampProg145();");
    a  = new SampProg145(); SampProg145.delayAndGC();
    System.out.println(" a  = new SampProg145();");
    a  = new SampProg145(); SampProg145.delayAndGC();
    System.out.println(" a  = null;");
    a  = null;          SampProg145.delayAndGC();         
  }//end main()
  //-----------------------------------------------------//
}//end class SampProg145
-end-