Components - JavaBeans/Serialization/Remote Method Invocation

Document last modified: 

Overview

Components are generally defined as self-contained software that follow a specific interface protocol that allows communication with other components. JavaBeans are Java objects that follow a specific interface protocol for communicating with other Java objects. Advantages of components can be illustrated by the following example.

Two objects, a tick object that notifies for each minute ticked and a counter object that counts each time notified can be connected to create a clock.

Suppose the tick and counter are available as regular Java objects. The programmer would need to know precisely how the tick notified that the tick event had occurred and what method was used for counter counting in order to connect the tick notification to the count. The programmer would also need to write some Java code to, in effect, glue the tick and counter object together. In contrast, a tick and counter component that follow the standard JavaBean protocol can expose events and properties used to communicate the tick event notification to the counter. The programmer would not need to know much about either the tick or counter object, in fact, because of the component interface standardization, the glue code could be generated automatically. Using a graphical programming environment, we'll see that components can be combined by connecting from one component output method to the input of a component input method.

Generally the JavaBean component protocol consists of:

  1. Naming conventions for property accessor methods (get to read and set to write property value).
  2. Notification to property users when property value changes (bound property).
  3. Ability by a property user to veto a property change (constrained property).
  4. Event notification by source to list of event handlers.
  5. Customization to allow Graphical User Interface development environments to display and modify properties.
  6. Persistence of component by writing and reading properties to permanent storage.
  7. Introspection by a component into the methods and properties of another (i.e. examine component for specific naming patterns (e.g. get and set)).
  8. Reflection, essentially the reverse of introspection, where a component allows or assists the examination of properties and methods by following naming conventions (e.g. get and set) .

While Java objects can be developed using the JDK (Java Development Kit), JavaBean components can be developed graphically using the BDK (Bean Development Kit), Sun NetBeans, Borland JBuilder. The BDK includes a graphical environment called the BeanBox in which components can be connected together graphically and tested. NetBeans provides a complete development environment for Java and JavaBeans and will be used in the following. A number of sample JavaBeans are provided to allow experimentation with components.

A Simple Example

We'd like to connect a keypad to a display without knowing much of how either works. Whenever the keypad gets a new digit, the display should automatically receive the digit.

Two component classes:

  1. Keypad - Has a property, a single digit.  When digit (property) changes, any users of that property are notified.
  2. Display - Notified when a property to which it is listening changes.

Components are connected in the main class.

From the point of view of our Example class, the keypad connects to the display by calling:

keypad.addDigitListener( display );

Calling the keypad method setDigit changes the digit on the keypad and, since we connected the keypad and display, also sends the digit to the display.

In the following:
  1. Example connects a property source (KeyPad) to a listener (Display).      
  2. Example calls KeyPad setDigit().
  3. KeyPad calls firePropertyChange() to notify all listeners that a property value (digit) has changed.
  4. Display propertyChange() is called when the property changes.

Question - What is the effect of three: keypad.addDigitListener( display ); ?

 

      Example
KeyPad Þ Display

 

public class Example {
  public static void main(String a[]) {
    KeyPad keypad=new KeyPad();
    Display display=new Display();

    keypad.addDigitListener( display );
                           
// Connect source to listener

    keypad.setDigit("0");
    keypad.setDigit("1");
    keypad.setDigit("2");
  }
}
import java.beans.*;

public class Display implements PropertyChangeListener {

  public void propertyChange(PropertyChangeEvent e) {
     System.out.println( "Display "+ e.getNewValue() );
  }
}
import java.beans.*;

public class KeyPad {
   private PropertyChangeSupport pcDigit =
                                     new PropertyChangeSupport(this);
   private String digit=null;

   public void addDigitListener(PropertyChangeListener l) {
     pcDigit.addPropertyChangeListener(l);
   }

   public void setDigit(String newDigit) {
     String oldDigit = this.digit;
     this.digit=newDigit;
     pcDigit.firePropertyChange("digit", oldDigit, newDigit);
   }
}
Execution

Display 0

Display 1

Display 2

Communication with bound objects

Example, KeyPad and Display Classes

KeyPad uses property change notification of any registered listeners whenever the digit property changes.

The essentials of property change notification are:

Display implements PropertyChangeListener

Means that is agrees to define a method with signature:

 public void propertyChange(PropertyChangeEvent e)

to be called by firePropertyChange whenenver the "digit" value is changed.

Example has two objects:

  1. keypad - a bound property (object) meaning it can register and notify listeners.
  2. display - an object that must be notified by the keypad whenever changes occur.

Bound Properties

Properties can report when changed to listeners with the same property type (String, float, etc.). The change is normally reported to listeners by calling the listener's set property method. By connecting a bound property in one bean to a property (bound or not) in another bean, both properties can be updated simultaneously to the same value. Beans with bound properties can also generate an event when a property changes, involving a public method in a bean when the property changes. A bound property requires a method for registering listeners to be notified that the bound property changed (addDigitListener).

To notify listeners, the method firePropertyChange is called with three parameters, a string defining the name of the bound property, the bound property old value and new value. If old ¹ new value the property listener is notified of the change.

While simple to use, PropertyChangeListeners are limited.

For example, it gets a little messy when listening to more than one property change at a time, requiring the listener to know and examine the source of the property.

 

Connecting Events with Handlers

We'll now start at the beginning of component communication by first examining a non-component approach to handling external events, then compare with components.

The first step in handling an event is determining what caused the event.

With a calculator button click, one can check each possible button case by the button text label, as illustrated at left below. When the Hit me button receives the action clicked an event is passed to the action method where it is examined to determine which event and which button was acted on by comparing the label. As we saw in Homework 6 and the Calculator, handling a large number of button events can get messy.

Try it.



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

public class ButtonApplet extends Applet { 
        Button button; 
        public void init() { 

               button = new Button("Hit me"); 

                add(button); 
        } 
        public boolean action (Event e, Object o) { 

                if (o.equals("Hit me")) 
                           button.setLabel("Ouch"); 

               return true; 
        } 
}

 

 

ButtonApplet.htm

<APPLET code="ButtonApplet.class"
             width=100 height=100>
</APPLET>

import java.applet.*; 
import java.awt.*; 
import java.awt.event.*; 

public class ButtonApplet extends Applet { 
        Button button; 
        public void init() { 
                button = new Button("Hit me"); 

               buttonListener bl = new ButtonListener(button); 
               button.addActionListener(bl);

               add(button); 
        } 

class ButtonListener implements ActionListener { 
        Button aButton; 
        ButtonListener(Button button) { aButton = button; } 

        public void actionPerformed(ActionEvent e) { 
                aButton.setLabel("Ouch"); 
        } 
}

 

Inner Classes and Listeners

Obviously in a GUI with many buttons, sliders, etc. that each generates multiple events, a single action method would be very complicated, needing to check the event against every possible case. A more scalable approach is to define an individual handler or listener for each event as in the example at right above. Buttons define the single ActionEvent event which must be connected to the listener code that handles the event.

One approach is to define a listener class for each type of event, in the example the ActionEvent listener is implemented in the ButtonListener class.

Because ButtonListener implements ActionListener it must define the actionPerformed method which is invoked whenever the ActionEvent occurs. The ButtonListener object bl must be registered as a listener with the button.
 

Naming conventions

Below is the Java Class Library documentation on MouseListener:

Interface MouseListener

All Superinterfaces:
EventListener
All Known Subinterfaces:
MouseInputListener
All Known Implementing Classes:
AWTEventMulticaster, MouseDragGestureRecognizer, MouseAdapter, DefaultCaret, BasicButtonListener

public interface MouseListener
extends EventListener

The listener interface for receiving "interesting" mouse events (press, release, click, enter, and exit) on a component. (To track mouse moves and mouse drags, use the MouseMotionListener.)

The class that is interested in processing a mouse event either implements this interface (and all the methods it contains) or extends the abstract MouseAdapter class (overriding only the methods of interest).

The listener object created from that class is then registered with a component using the component's addMouseListener method. A mouse event is generated when the mouse is pressed, released clicked (pressed and released). A mouse event is also generated when the mouse cursor enters or leaves a component. When a mouse event occurs the relevant method in the listener object is invoked, and the MouseEvent is passed to it.

 

Since:
1.1
See Also:
MouseAdapter, MouseEvent, Tutorial: Writing a Mouse Listener, Reference: The Java Class Libraries (update file)

Method Summary
 void mouseClicked(MouseEvent e)
          Invoked when the mouse has been clicked on a component.
 void mouseEntered(MouseEvent e)
          Invoked when the mouse enters a component.
 void mouseExited(MouseEvent e)
          Invoked when the mouse exits a component.
 void mousePressed(MouseEvent e)
          Invoked when a mouse button has been pressed on a component.
 void mouseReleased(MouseEvent e)
          Invoked when a mouse button has been released on a component.
Exercise 1 - Event listeners and adapters follow a naming convention where:

void addxxxListener( xxxListener object)

defines the method to register a listener with a component. An interface is defined as:

xxxListener

and event methods and classes are defined as:

void xxxevent( xxxEvent e)

For example, the mouse is:

    void addMouseListener( MouseListener object)
    void mouseClicked( MouseEvent e)
    void mouseExited(MouseEvent e)
     

  1. Give the code pattern to implement a subclass of MouseListener named ouchListener that overrides the mouseClicked method to print "Ouch!" on standard output using System.out.print("Ouch!");.
    class ouchListener implements MouseListener {
          public void mouseClicked(MouseEvent e) {
             System.out.println("Ouch")
          }
    }
  2. Give the code to construct and register an ouchListener object.
    button.addMouseListener( new ouchListener() );

 

Inner classes

Writing a new listener class just to implement the event handling method (e.g. actionPerformed) is tedious, clutters the name space, and is error prone. One simplification is inner classes or classes that are defined in the scope of another class. The following defines at right an anonymous inner class to listen for the ActionEvent of the Button. At left is the code used earlier.

buttonListener bl = new buttonListener(button); 
button.addActionListener(bl);

class buttonListener implements ActionListener { 
        Button button; 
        buttonListener(Button button) { this.button = button; } 
        public void actionPerformed(ActionEvent e) { 
                button.setLabel("Ouch"); 
        } 

button.addActionListener(new ActionListener( )
 {
    public void actionPerformed(ActionEvent e)
   {
      button.setLabel("Ouch");
   }
 }
);

import java.applet.*; 
import java.awt.*; 
import java.awt.event.*; 

public class ButtonApplet extends Applet { 
        Button button; 
        public void init() { 
                button = new Button("Hit me"); 

               buttonListener bl = new ButtonListener(button); 
               button.addActionListener(bl);

               add(button); 
        } 

class ButtonListener implements ActionListener { 
        Button aButton; 
        ButtonListener(Button button) { aButton = button; } 

        public void actionPerformed(ActionEvent e) { 
                aButton.setLabel("Ouch"); 
        } 
}

 

import java.applet.*; 
import java.awt.*; 
import java.awt.event.*; 

public class ButtonApplet extends Applet { 
        Button button; 
        public void init() { 
                button = new Button("Hit me"); 

                button.addActionListener(new ActionListener( ) { 
                        public void actionPerformed(ActionEvent e) { 
                                button.setLabel("Ouch"); 
                        } 
                   } 
                ); 

               add(button); 
        } 

Exercise 2 - Implement the inner-class for Exercise 1, given again below.
button.addMouseListener( new MouseListener() {
      public void mouseClicked(MouseEvent e) {
         System.out.println("Ouch");
      }
   }
);
  1. Give the code pattern to implement a subclass of MouseListener named ouchListener that overrides the mouseClicked method to print "Ouch!" on standard output using System.out.print("Ouch!");.
    class ouchListener implements MouseListener {
          public void mouseClicked(MouseEvent e) {
             System.out.println("Ouch");
          }
    }

 

Calculator Example

Recall from Homework 6 the Calculator; at left is the basic calculator using inner-class to define the event handler, at right is the calculator with a switch statement to determine the appropriate action.


public class Calculator extends Applet {
  public Calculator()         {      
    Button button1 = new Button("1"),
               buttonequal = new Button("="),
               buttonplus = new Button("+");
button1.addActionListener(new ActionListener( ) { 
    public void actionPerformed(ActionEvent e) { 
        setAccumulator(getAccumulator() * 10+1); 
        updateDisplay(getAccumulator()+"");
    } 
  } 
); 
buttonequal.addActionListener(new ActionListener( ) { 
   public void actionPerformed(ActionEvent e) { 
        equal(); 
   } 
 } 
);   
buttonplus.addActionListener(new ActionListener( ) { 
    public void actionPerformed(ActionEvent e) { 
       setOperation('+');
       setOperand(accumulator);
       setAccumulator(0.0); 
    } 
  } 
);

 

public class Calculator extends UserInterface {
    public Calculator()  {      
                 addButton("1");
                 addButton("+");
                 addButton("=");
    }
public void onClick(char c) {
    switch (c) {
          case '1' : accumulator = accumulator * 10+1;
                        break;
          case '=' : equal();
                        break;
          case '+' : operation = c;
                        operand = accumulator;
                        accumulator = 0.0;
                        break;
          default  : throw new Exception();
     }
     updateDisplay(getAccumulator()+"");
}

Creating individual handlers for each button of a calculator can be automated.

A more extensible approach creates 10 digit buttons and a handler for each, see makeDigitButton(int n).

public class Calculator extends Applet {
         private TextField      display;
         private Panel           keypad;     
        
         public Calculator()
         {      
                Button              buttonequal = new Button("="),
                                        buttonplus = new Button("+");
 
                buttonequal.addActionListener(new ActionListener( ) { 
                        public void actionPerformed(ActionEvent e) { 
                                equal(); 
                        } 
                   } 
                ); 
                buttonplus.addActionListener(new ActionListener( ) { 
                        public void actionPerformed(ActionEvent e) { 
                                setOperation('+');
                                setOperand(accumulator);
                                setAccumulator(0.0); 
                        } 
                   } 
                ); 

               keypad = new Panel();

               for (int i=0; i<=9; i++)
                   makeDigitButton(i);

 
               keypad.add(buttonplus); 
               keypad.add(buttonequal); 
         }
   void makeDigitButton(int n) {
       final int i = n;
       Button digit = new Button(i+"");
       keypad.add(digit);

       digit.addActionListener(
           new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                   setAccumulator(getAccumulator()*10+i);
                   updateDisplay(getAccumulator()+"");
              }
           }
      );
  }

Counter Bean Example

A JavaBean is a regular, programmer defined Java class that follows a standard protocol for naming methods that access internal attributes (properties) and for event notification. By following the protocol, other JavaBeans or programs can interact with a JavaBean through the standard interface. One key advantage is that the JavaBean is self-contained, communication is restricted through the consistently named protocol methods. This allows JavaBeans to implement large, independent components such as a word processor or graphing component or small components such as buttons that can communicate with other independent components. The component can be integrated into an application more easily than a class, often with little or no programming.

One obvious part of the naming protocol defines mutator methods for setting and accessor methods for getting the values of JavaBean class attributes.

In the Counter bean, the maxValue attribute has two methods, the accessor and mutator methods of:

setMaxValue
getMaxValue
Sets the value of attribute maxValue; a mutator method
Gets the value of attribute maxValue; an accessor method

 

Example

The following illustrates the naming protocol for property maxValue and that the protocol can be ignored for property count. We'll try to do better but need to keep things simple.

The above Button example has been modified to increment a Counter bean by calling an increment() method (mutator) whenever a button actionPerformed event is generated, the button is clicked. The counter displays the counter value in blue. The changes to the ButtonApplet are modest and highlighted below.

Try it.

ButtonApplet2


import java.applet.*; 
import java.awt.*; 
import java.awt.event.*; 

public class ButtonApplet2 extends Applet { 
   Button button; 
   Counter counter; 
   public void init( ) { 
      button = new Button("Hit me"); 
      counter = new Counter(); 

      button.addActionListener(new ActionListener( ) { 
         public void actionPerformed(ActionEvent e) { 

            counter.increment( );

          } 
        } 
      ); 

      counter.setMaxValue(4);

      add(button); 
      add(counter); 
  } 

Counter

import java.awt.*; 

public class Counter extends Panel { 
  private long count=0; 
  private Label label; 

  private long maxValue=0;

  public void setMaxValue(long max) {
        maxValue = max;
  } 
  public long getMaxValue() { return maxValue; }  

   public Counter() { 
         setBackground(Color.blue); 
         setForeground(Color.white); 
         label = new Label(""+count); 
         add(label); 
   } 

    public void increment () { 
      if (count < maxValue) { 
          count++; 
          label.setText(count+" ");
      } 
      else label.setText("!!");    
    } 

 

 

JavaBean Events Example

JavaBeans have at a minimum four parts for event xxx:

  1. a registration method used by event listeners named:

    void addxxxListener( xxxListener object)

  2. An interface defining event listeners named:

    xxxListener

  3. Event classes defining events named:

    xxxEvent

  4. Event handler methods of a listener called when an event occurs as:

    void xxxEventhandler( xxxEvent e )

 

We have seen that JavaBean events and event handlers conform to a protocol of naming conventions. The following example illustrates the protocol more fully by defining events and event handlers for counter values that exceed the maximum value. The example is based on one in Essential JavaBeans Fast. It defines two JavaBeans, a Counter and Alarm bean, and two regular classes for the event (MaxValueEvent) and event listener (MaxValueListener).

 

Note in the following that the Counter and Alarm definitions are not explicitly connected.

Counter -  Implements a visual counter that triggers an event whenever the counter exceeds a defined value. The MaxValueReached event is then sent to all registered MaxValueListeners by calling their maxValueReached method. Attributes (properties) maxValue can be examined and modified externally by calling methods to setMaxValue or getMaxValue the property.

The addMaxValueListener and removeMaxValueListener methods register/unregister listeners that are to be notified when a MaxValueEvent occurs. In this example only one listener can be registered, normally multiple listeners could be registered and be notified of the same event by managing a list of listeners.

MaxValueEvent - Defines the MaxValueEvent, inherits from the Java EventObject class. The MaxValueEvent constructor calls the parent class constructor.

MaxValueListener -  An interface class. That means that it is incomplete or abstract, only defines the methods that must be completed by a class that actually completes or implements the MaxValueListener class. Inherits from Java EventListener.

Alarm - Implements MaxValueListener interface by defining the maxValueReached method called by the Counter bean whenever the counter exceeds a defined value.


Counter

package counterEvent; 
 import java.awt.*;
 import java.util.*; 

 public class Counter extends Panel { 
  private long count=0; 
  private MaxValueListener ml;
  private Label label; 

  private long maxValue=3;

  public void setMaxValue(long max) {
        maxValue = max;
  } 
  public long getMaxValue() { return maxValue; }  

   public Counter() { 
         setBackground(Color.blue); 
         setForeground(Color.white); 
         label = new Label(""+count); 
         add(label); 
   } 
 

   public synchronized void     // Add listener for event
           addMaxValueListener(MaxValueListener ml)
   {
           this.ml = ml;
   }

   public synchronized void       // Remove listener
     removeMaxValueListener (MaxValueListener ml)
   {
           this.ml = null;
   }

    public void increment () { 
      if (count < maxValue) { 
          count++; 
          label.setText(count+" ");
      } 
      else { 
         label.setText("!!"); 
         MaxValueEvent mve = new MaxValueEvent(this); 

        ml.maxValueReached( mve );  // Signal event

      } 
    } 

Alarm

package counterEvent; 
import java.awt.*; 
public class Alarm extends Panel 
                          implements MaxValueListener
    public Alarm () { setBackground(Color.green); } 
 
    public void maxValueReached (MaxValueEvent e) 
    { 

                  setBackground(Color.red);
     } 


 

MaxValueEvent

package counterEvent; 
 public class MaxValueEvent 
                               extends java.util.EventObject { 
     public MaxValueEvent (Object object) 

            { super( object ); } 
 }


 

MaxValueListener

package counterEvent; 
 public interface MaxValueListener 
                          extends java.util.EventListener { 
    void maxValueReached (MaxValueEvent m); 

 }

 

ButtonApplet.htm

<APPLET code="ButtonApplet.class"
             width=100 height=100>
</APPLET>

 

 

 

Using Events - MaxValueEvent and MaxValueListener

Note in the following that the Counter and Alarm definitions are not explicitly connected. But the two can be connected through the listener notification by:

     counter.addMaxValueListener(alarm);     // Connects counter event to alarm listener

However, a Counter object can notify a MaxValueListener that a MaxValueEvent has occurred.

The Alarm implements MaxValueListener so must implement the maxValueReached method, which changes the background to red.

In the following applet the 1) button ActionEvent executes the counter.increment() method; 2) the counter.increment() method notifies all MaxValueListener such as alarm of the MaxValueReachedEvent. The alarm changes the background to red.



import java.applet.*; 
import java.awt.*; 
import java.awt.event.*;
import counterEvent.Alarm;
import counterEvent.Counter; 

public class ButtonApplet extends Applet { 
   Button button; 
   Counter counter;
   Alarm alarm;

   public void init() { 
       button = new Button("Hit me"); 
       counter = new Counter(); 
       alarm = new Alarm();

       button.addActionListener(new ActionListener( ) { 
              public void actionPerformed(ActionEvent e) { 
                    counter.increment(); 
              } 
         } 
      ); 

     counter.addMaxValueListener(alarm);    
                                 
// Connects counter event to alarm listener

      add(button); // Add button, counter and alarm objects to applet
      add(counter); 
      add(alarm);
   } 

Running the example - The ButtonApplet uses classes grouped into the counterEvent package; which must occupy a subdirectory counterEvent below ButtonApplet.java location.

  1. Create a subdirectory counterEvent, the same as the package name.
  2. Copy Counter.java, Alarm.java, MaxValueListener.java, MaxValueEvent.java to counterEvent subdirectory.
  3. Copy ButtonApplet.java and ButtonApplet.htm to parent directory. Compile.
  4. Run by:

    appletviewer ButtonApplet.htm

 

Exercise 3

The Alarm class implements the MaxValueListener interface requiring a maxValueReached method to be implemented as part of the interface specifications. Changing the Alarm class to that below and using an inner-class in the ButtonApplet above accomplishes the same results without requiring the Alarm class to incorporate any information about the MaxValueListener.

public class Alarm extends Panel implements MaxValueListener {  
   public Alarm () { setBackground(Color.green); } 

   public void maxValueReached (MaxValueEvent e)   {
                   setBackground(Color.red);
   } 

  public class Alarm extends Panel {
          public Alarm ( ) { setBackground(Color.green); } 
  }

Question

Give the inner-class for ButtonApplet that accomplishes the same results as:

counter.addMaxValueListener(alarm); 

Answer

                 counter.addMaxValueListener(new MaxValueListener( ) { 
                         public void maxValueReached(MaxValueEvent e) {
                                    alarm.setBackground(Color.red );
                             
}
                       }
                 );

Note - The downside is that the user must know more about the Alarm object. For example, the interfacing details to the setBackground() method.

 

Jarring Beans

Beans that are used as a group can be placed together into a jar file, essentially a library of classes; the classes defined earlier will be jarr'ed and used later in the following examples. The steps to creating a jar with the above beans are:

  1. Create a subdirectory that matches the package name, counterEvent.
  2. Change to the counterEvent subdirectory.
  3. Copy the Java files to the directory: Alarm.java, Counter.java, MaxValueEvent.java, MaxValueListener.java
  4. Compile by: javac *.java
  5. Create a manifest.man file that contains the following:

  6.  

      Name: MaxValueListener.class
      Java-Bean: False

      Name: MaxValueEvent.class
      Java-Bean: False

      Name: Counter.class
      Java-Bean: True

      Name: Alarm.class
      Java-Bean: True

  7. To create a jar file counterEvent.jar with all the classes at the command line enter:

 

Using NetBeans for Bean Development

Some development environments automate the definition of inner-class event adapters. The following illustrates how it can be used to remove the programming of inner-class event adapters.

At Home:

Connecting the Button to the Counter Bean

Previous examples illustrated how events generated by clicking the mouse could be connected to a listener method by manually writing code such as:

button.addActionListener(new ActionListener( ) {
        public void actionPerformed(ActionEvent e) {
            counter.increment();
        }
    }
);

NetBeans and other Java development environments can automatically generate the code needed to connect events and listeners. First connect the JButton event actionPerformed with the Counter increment( ) method.

Connecting the Counter Bean to the Alarm

The Counter generates an event that can be connected to the Alarm maxValueReached( ) method. Connect the Counter event maxValue with the Alarm method maxValueReached.

private void counter1MaxValueReached(counterEvent.MaxValueEvent evt) {
    alarm1.setBackground(java.awt.Color.red);
}

 

Running the Applet

 

Bound Properties/Reflection & Introspection/BeanInfo

We have seen that events serve to connect object behavior. Standardization for naming event and listener classes and registration methods are meant to provide the programmer with useful information on the purpose of each; recall:

  1. a registration method used by event listeners named:

    void addxxxListener( xxxListener object)

  2. An interface defining event listeners named:

    xxxListener

  3. Event classes defining events named:

    xxxEvent

  4. Event handler methods of a listener called when an event occurs as:

    void xxxEventhandler( xxxEvent e )

 

You may also recall that when constructing a application using beans, the programmer still needed to supply these names; implying there was not sufficient information available for the IDE to figure out how things should be connected. Ideally, the component could provide enough information to an application (e.g. IDE) that connections between objects could be established without programmer involvement; part of the solution in Java is to use bound properties.

 

Counter, Monitor and CounterBeanInfo Classes

The Counter will be modified to use property change notification rather than events. The essentials of property change notification are:

The Counter bean example is modified in three ways:

  1. Event notification has been removed.
  2. A bound property has been added to Counter to notify other beans when the count changes.
  3. Introspection is supported through the CounterBeanInfo class.

Bound Properties - Properties can report when changed to listeners with the same property type (String, float, etc.). The change is normally reported to listeners by calling the listener's set property method. By connecting a bound property in one bean to a property (bound or not) in another bean, both properties can be updated simultaneously to the same value. Beans with bound properties can also generate an event when a property changes, involving a public method in a bean when the property changes. A bound property requires two methods, (addCounterListener and removeCounterListener) one for registering listeners to be notified that the bound property changed, and a method to unregister listeners. To notify listeners, the method firePropertyChange is called with three parameters, a string defining the name of the bound property, the bound property old value and new value.

Reflection and Introspection - Reflection allows the discovery of a bean's contents, for example the names of public functions, properties, etc. Introspection is the process which queries a bean to discover a bean's contents using reflection. The CounterBeanInfo provides methods and data (e.g. an icon to represent the Counter) for improved introspection of a bean.

BeanInfo - A special object that assists in describing a bean properties, icon, public methods, etc. simplifying introspection. A subclass SimpleBeanInfo is generally extended through inheritance rather than implementing the more detailed BeanInfo. The key difference is that BeanInfo requires a significant number of methods to be defined that are implemented to return null in SimpleBeanInfo. Both can also be used to limit the list of public methods and properties one has access to, useful for excluding inherited methods, etc. from event handling. CounterBeanInfo is a  SimpleBeanInfo object that controls which methods and properties of the Counter bean are published and defines the icon that represents the Counter bean.

Counter - Implements a bound property count, which when changed notifies other listener beans of the new count value. Key parts are:

  1. addPropertyChangeListener() - Adding listeners to be notified,
  2. removePropertyChangeListener () - Removing listeners,
  3. firePropertyChange() - Notifying listeners.
Counter with Bound Property

import java.awt.*;
import java.beans.*; 

public class Counter extends Panel { 
     private int count=0; 
     private Label label; 
     private PropertyChangeSupport pcsupport = 
                                                              new PropertyChangeSupport(this); 

     public Counter() { 
         setBackground(Color.blue); 
         setForeground(Color.white); 
         label = new Label(""+count); 
         add(label); 
     } 
 

     public void addPropertyChangeListener(PropertyChangeListener l) { 
         pcsupport.addPropertyChangeListener(l); 
     } 
     public void removePropertyChangeListener (PropertyChangeListener l) { 
         pcsupport.removePropertyChangeListener(l); 
     }
     public void setCount(int count) { this.count = count; } 
     public int getCount() { return count; } 

     public void increment() { 
        int oldcount = count;
        count++;
        label.setText(count+"");

       pcsupport.firePropertyChange
                      ("count", new Integer(oldcount), new Integer(count)); 

     } 

Monitor - Listens for property changes. Can be registered with and listen for a change in Counter or property of another bean type. When the count property of a connected Counter bean is changed, the propertyChange() method of the Monitor bean is invoked, updating the n property (i.e. the text on the label) of the Monitor bean. Note that by implementing PropertyChangeListener, Monitor must define propertyChange() method.

Monitor

import java.awt.*;
import java.beans.*;

public class Monitor extends Panel implements PropertyChangeListener {
   private TextField n = new TextField(4);
   public Monitor() {
      add(n);
      n.setText(" ");
      setBackground(Color.orange);
   }
   public void propertyChange(PropertyChangeEvent e) {
      n.setText( (Integer)e.getNewValue() + "");
   }

}

     

BeanCounter - Here we add two listeners, monitor1 and monitor2, each notified of the Counter property change. Key part is registration as a listener of changes to the Counter property, counter1.addCounterListener( monitor1 ). When Counter calls firePropertyChange() method, all listeners are notified.

BeanCounter.java

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class BeanCounter extends Applet {
   private Counter counter1;
   private Button button1;
   private Monitor monitor1, monitor2;

   public void init() {
      button1 = new Button("button1");
      counter1 = new Counter();
      monitor1 = new Monitor();
      monitor2 = new Monitor() ;

      button1.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent evt) {
            counter1.increment();
         }
      });
 
      counter1.addCounterListener( monitor1 );
      counter1.addCounterListener( monitor2 );

      setLayout(new FlowLayout());
      add(button1);
      add(counter1);
      add(monitor1);
      add(monitor2);
   }
}

<APPLET code="BeanCounter.class" width=100 height=100> </APPLET>

Summary

Why use properties? The advantage can be seen in BeanCounter.java which connects a Counter and Monitor object in counter1.addCounterListener( monitor1 ). How and what is passed between the two objects is handled by the objects themselves; the BeanCounter is not involved in the execution of a change in the Counter or notification of the Monitor.

Essentials

Counter with Bound Property

import java.beans.*; 

public class Counter extends Panel { 
     private PropertyChangeSupport pcsupport =  new PropertyChangeSupport(this); 

     public void addPropertyChangeListener(PropertyChangeListener l) { 
         pcsupport.addPropertyChangeListener(l); 
     } 

     public void increment() { 

       pcsupport.firePropertyChange
                      ("count", new Integer(oldcount), new Integer(count)); 

     } 

Monitor

import java.beans.*;

public class Monitor extends Panel implements PropertyChangeListener {
   public void propertyChange(PropertyChangeEvent e) {
      n.setText( (Integer)e.getNewValue() + "");
   }

}

BeanCounter.java

public class BeanCounter extends Applet {
   private Counter counter1;
   private Monitor monitor1;

   public void init() {
      counter1 = new Counter();
      monitor1 = new Monitor();
      counter1.addCounterListener( monitor1 );

   }
}

 

CounterBeanInfo - Provides additional control of reflection for a Counter bean properties and methods. The icon representation is reflected through the getIcon method. Information that the count property is bound and that the Display Name of the count property is the Counter is defined though the PropertyDescriptor object. The publication of Counter methods is restricted to increment through the MethodDescriptor object.

CounterBeanInfo

import java.awt.*; 
import java.beans.*; 
import java.lang.reflect.*; 

public class CounterBeanInfo extends SimpleBeanInfo { 
    private final static Class beanClass = Counter.class; 
    public Image getIcon(int iconSize) { 
        return loadImage("Counter.gif"); 
    } 
    public PropertyDescriptor[] getPropertyDescriptors()
        PropertyDescriptor count = null; 
        try { 
            count = new PropertyDescriptor(" count", beanClass); 
            count.setDisplayName(" the Counter"); 
            count.setBound(true); 
        } catch(IntrospectionException e) {} 
        PropertyDescriptor result[] = { count }; 
        return result; 
    } 
    public MethodDescriptor [] getMethodDescriptors() { 
        Method method = null; 
        MethodDescriptor result[] = new MethodDescriptor[1]; 
        try { 
            method = beanClass.getMethod(" increment", null); 
            result[0] = new MethodDescriptor(method); 
        } catch(Exception e) {} 
         return result; 
    } 

     

    manifest.tmp & Counter.GIF

    Name: countProperty/Counter.class
    Java-Bean: True

    Name: countProperty/Monitor.class
    Java-Bean: True

    Name: countProperty/CounterBeanInfo.class
    Java-Bean: False

    Name: countProperty/Counter.gif
    Java-Bean: False

    Click to download Counter.gif

     


Serialization

Serialization is a means of creating persistent objects, those that can be transmitted for locally storing or transfer across a network. Serialization refers to the output of an object in a representation that can be input in its original state; important for saving the state of an object but also for transmitting an object state over a network in a distributed environment.

Serialization requires reading and writing objects and is implemented by methods readObject and writeObject. Any object that inherits or implements Serializable can be read or written although, in some cases, the programmer may need to implement appropriate methods. The following example serializes and writes to a file the state of a simple SketchApp object. Since SketchApp inherits from Frame it inherits serialization and a show() method to display the frame. The SketchRead reads a serialized SketchApp object and displays all properties at the point the SketchApp was serialized.

Writing - SketchApp object is written by the Sketch class. The key to writing a serialized SketchApp object is output.writeObject(sketchapp); which writes all serializable properties to the file "Sketch.ser". Note that the Vector points is one of the properties written.

Reading - SketchApp object is read by the SketchRead class. The key to reading a serialized SketchApp object is (SketchApp) input.readObject();. The input.readObject( ) reads the object input file "Sketch.ser", (SketchApp) casts the object read into a SketchApp object. The Vector points is input with the values at the time the SketchApp object was serialized.

Sketch.java
import java.awt.*; 
import java.awt.event.*; 
import java.io.*;
import java.util.*;

class SketchApp extends Frame { 
   Point endp; 
   Vector points = new Vector();

   public SketchApp () { 
       super("Sketch");
       setBackground(Color.yellow); 
       setVisible(true);
       setSize(200,200);
   } 
   public boolean mouseDown(Event evt, int x, int y) { 
      endp = new Point(x,y); 
      return true; 
   } 
   public boolean mouseDrag(Event evt, int x, int y) { 
      Point startp = endp;
      endp = new Point(x,y); 
      points.addElement( startp ); 
      points.addElement( endp ); 
      repaint(); 
      return true; 
   } 
   public void paint(Graphics g) { 
       Point p1, p2;
       int x1, y1, x2, y2;
       for(int i = 0; i < points.size()-2; i=i+2) {
          p1 = (Point) points.elementAt(i);
          x1 = (int) p1.getX();
          y1 = (int) p1.getY();
          p2 = (Point) points.elementAt(i+1);
          x2 = (int) p2.getX();
          y2 = (int) p2.getY();
          g.drawLine(x1, y1, x2, y2); 
       }
    } 

public class Sketch { 
   public static void main(String arg[]) { 
      Button serialize = new Button("Serialize");

      final SketchApp sketchapp = new SketchApp();
      sketchapp.add(serialize, BorderLayout.SOUTH );
      sketchapp.show();
 
     serialize.addActionListener(new ActionListener( ) {
        public void actionPerformed(ActionEvent ae) {
           ObjectOutputStream output;
           try {
             output =
                new ObjectOutputStream( 
                   new FileOutputStream("Sketch.ser"));
             output.writeObject(sketchapp);
             output.close();
          }
          catch(IOException ioe) {
                         System.out.println(ioe);
          }
       }
     }
    );

  }
}

SketchRead.java
import java.awt.*; 
import java.awt.event.*; 
import java.io.*;

public class SketchRead { 
   public static void main(String arg[]) { 

      SketchApp sketchapp;
      ObjectInputStream input;

      try {
           input = new ObjectInputStream(new FileInputStream("Sketch.ser"));

          sketchapp = (SketchApp) input.readObject();

           input.close();
           sketchapp.show();                                 // Call inherited show method
      }
      catch(Exception e) { System.out.println(e); }
   }
}

What was serialized by Sketch
and restored by SketchRead.

  Use - cannot be executed as an applet because must access file system to save the serialized sketch.

  1. Copy and compile Sketch.java and execute. Click the Serialize button to write the state to the file Sketch.ser and enter Ctrl C to terminate.
  2. Copy and compile SketchRead.java and execute. The state of the sketch should be displayed. Enter Ctrl C to terminate.
  3. The Ctrl C to terminate should be entered in the Command window, window handlers were not included for simplification..

Remote Method Invocation

RMI (Remote Method Invocation) supports distributed processing by providing for an object on one machine to invoke a method on another and receive the results. The Java text does not discuss RMI. The machines could be the same or connected over a network. The server object provides resources through a set of methods that can be called remotely from the client. In some ways, RMI is similar to network programming via sockets. The primary advantage of RMI is that it is simple. Objects can be passed between two machines as a single entity rather than implementing a protocol to transfer the object over a connection. One key disadvantage is that it is only useable between RMI clients and servers, restricting it to Java. The key elements of an RMI system are:

  1. Java files
    1. Server - Registers on the host machine as an RMI server. Defines and executes the remote methods when requested by a client.
    2. Client - Contacts the server, requesting the method for invocation and receives results.
    3. RMI method - Must be implemented by the server and invoked by the client.
  2. rmiregistery - Maintains a list of RMI servers running on the host machine and the methods they can invoke. Serves as central point of contact for client machines to connect to servers.
  3. rmic - RMI compiler.

It is important to note that method arguments and returned results are arbitrarily complex objects. To transport an object over a network it must be serialized into a form that could be written to a file and read later or transmitted and reconstructed on the receiving end. Both the client and the server methods have full access to the same object.

The following implements a "Hello World" server. A client invokes the sayHello() method on the server which returns the string "Hello World". In this case the client and server are both on the same machine but, by adding a network name, remote servers could be contacted. In this example, security is a function of object definition. If a server method or attribute is public a client has access, reasonable considering that is the same external access were the object part of a single program.

The following list and diagram shows the communication between an RMI client and server. The steps to executing the RMI program below are:

  1. The client program sayHello() method call and parameters are serialized,
  2. passed to the RMI client,
  3. the RMI server is contacted with the registered name "HelloWorldServer") that is looked-up giving HelloServer class,
  4. the HelloServer method call and parameters passed (i.e. sayHello( ) method of the HelloServer) are deserialized and method invoked ,
  5. the sayHello() method is executed,
  6. results "Hello World!" are serialized and returned to the RMI server,
  7. the RMI server returns the results  "Hello World!" to the RMI client,
  8. the results  "Hello World!" are returned to the client program,
  9.  "Hello World!" is deserialized,
  10.  "Hello World!" is printed by the client program.


 

RMI Java Files


// Interface file on both client and server machines  - Hello.java
import java.rmi.Remote; 
import java.rmi.RemoteException; 

public interface Hello extends Remote { 
    String sayHello() throws RemoteException; 
}

// Client program  - HelloClient.java
import java.rmi.Naming; 
import java.rmi.RemoteException; 

public class HelloClient {
    public static void main(String args[]) throws Exception { 
                                           // obj references the remote object that implements "Hello" interface

          Hello obj = (Hello)Naming.lookup("HelloWorldServer"); 

         System.out.println( obj.sayHello() );                 // RMI of sayHello method on HelloServer 

    } 
}

// Server program - HelloServer.java
import java.rmi.Naming; 
import java.rmi.RemoteException; 
import java.rmi.RMISecurityManager; 
import java.rmi.server.UnicastRemoteObject; 

public class HelloServer extends UnicastRemoteObject implements Hello { 

    public HelloServer() throws RemoteException { 
        super(); 
    } 

    public String sayHello () throws RemoteException { return  "Hello World!"; } 

    public static void main(String args[]) throws Exception { 

        if (System.getSecurityManager() == null) {     // Create and install a security manager 
            System.setSecurityManager(new RMISecurityManager()); 
        } 

        HelloServer obj = new HelloServer(); 
        Naming.rebind("HelloWorldServer", obj);     // Registry bind HelloServer obj to "HelloWorldServer"

        System.out.println("HelloWorldServer bound in registry"); 
    } 
}

Use - The steps required for compiling and registering the server on 2000/XP are:

  1. Use a machine with a working network connection.
  2. Copy the HelloClient.java, HelloServer.java, and Hello.java files to a directory.
  3. Compile by: javac *.java
  4. Compile the HelloServer.class using RMI compiler (it generates two files) by: rmic -classpath . HelloServer
  5. Start the registry by: start rmiregistry
  6. Run the server by: java -cp . HelloServer
  7. Run the client in another window by: java -cp . HelloClient
  8. After a few seconds, the client should receive and print "Hello World".
  9. If you experience problems, stop the rmiregistry since the HelloServer may have been registered from an earlier attempt. Go back to Step 5.


Document last modified: