Reflection and the Method Class - III

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.

Introduction

In an earlier lesson, I promised you that I was going to give you a practical
example of the use of the invoke() method of the Method class
of the reflection API. Well, this is it.

Note that this lesson also makes heavy use of Hashtables. If
you aren't really comfortable with the use of a hashtable, you might want
to go back and review the lesson on Vectors, Enumerations, and Hashtables.
As of the time of this writing, that was lesson 76 in the Intermediate
Java tutorial.

In this lesson, we will develop a sample program that is a sample of
a large class of programs often referred to as smart adapters. The
form of the program that we will develop will only be indicative of what
can be accomplished using numerous variations of this powerful technique.

Sample Program

This program uses reflection and hashtables to receive ActionEvents
from several different sources and route them to the same or different
destination methods on the same or different destination objects.

In other words, an action event handler is designed to operate in between
the source of the event and the method that will ultimately respond to
the event. In one case, this event handler provides some filtering capability.
There many variations on the benefits that can accrue from an operation
structured in this manner.

There can be a very large number of sources, and they can be any objects
capable of generating action events.

By making a fairly simple modification to the program to give it the
capability to unregister previously registered associations, the
manner in which events are delivered to destination methods could be modified
at runtime, and could change during the execution of a program.

A primary requirement is that the formal argument list for all destination
methods must be the same.

Typically the formal argument list would include an object of type ActionEvent.
In this program, it also includes an object of type Date() in order
to timestamp the events.

A reader has pointed out that for clarification, I should remind everyone
that even though I am passing an object of type ActionEvent to the
destination method, it is not a "standard" action listener method. Rather,
it is one step removed from the action listener method (named actionPerformed)
and is being called by the action listener method. That is why I can get
by with passing a Date object in addition to the ActionEvent
object.

The same destination method can receive events from two or more sources.

The same source can be registered on only one destination. In this program,
once a source is registered on a given destination, any attempt to register
that source on another destination will result in the attempt being ignored
and an exception being thrown. However, nothing is done with the exception
in this program other than to display it for demonstration purposes.

Provisions were not made to unregister a source from a destination,
but it wouldn't be difficult to do so. This would make it possible to rearrange
the source-destination links at runtime.

In addition to providing the routing capability described above, this
program also demonstrates an event filtering capability.

Event filtering is usually interpreted to mean that the event handler
does some preprocessing of event data before delivering the event to the
destination method. In some cases, the data could be modified before forwarding
the event, and in some cases, the event might not be forwarded at all.
The latter is the case in this demonstration program.

If the source of the event is a TextField and the text value
is an empty String, the event is ignored and is not delivered to
the registered destination method.

Now lets discuss some of the details of operation.

An ActionListener class is defined that receives action events
from any number of sources and dispatches them to the same or different
methods on the same or different destination objects. It uses a Hashtable
as the dispatching table to associate the event source with the required
destination object and the required method in that object.

The general methodology is to save an object containing a reference
to a Method object that represents the destination method and a
reference to the destination object along with the associated action
event source in a hash table. The event source is the key. The object
containing the two references is the value in the key/value
pair of a hashtable.

When an action event occurs later, the program uses the event
source as the key to retrieve the object containing the references
to the Method object and the destination object from the table.

The program then uses the the invoke() method of the Method
class on the Method object to invoke the destination method
on the destination object.

To do all of this, it is first necessary to create the Method object
that represents a destination method. This requires the name
of the destination method along with an array of type Class whose
elements represent the types of the arguments in the formal argument
list of the destination method.

Since it is assumed that the types of the formal argument list are known
and may not change in this demonstration program, a static final
array of type Class is instantiated and initialized with this information.
This array is named destinationMethodArgTypes.

A new instance of a Hashtable is also declared and instantiated
when the class is loaded. This object will be used as the dispatching table.

The class contains a method named registerDestinationMethod()
that receives three parameters.

One parameter is a reference to an action event source.

A second parameter is a reference to the object that contains the
destination method.

A third parameter is a String object which contains the name
of the destination method that is to be associated with that particular
event source for event dispatching purposes.

This method uses the name of the destination method, along with the Class
array named destinationMethodArgTypes, as parameters to the
getMethod() method of the Class class to create an object
of type Method that represents the destination method.

A reference to this Method object, along with a reference
to the destination object are encapsulated in an object of type Combine.

Then an entry is made in the dispatch hash table with the action event
source as the key and the reference to the object of type
Combine as the value. This makes it possible to come back later
and retrieve the reference to the Method object and the reference
to the destination object from the dispatch table using the source
of an action event as the key.

All classes that service action events must define the actionPerformed()
method because it is declared in the ActionListener interface. Therefore,
this class has an overridden actionPerformed() method.

When an action event is generated by a source object on which this listener
object is registered, the actionPerformed() method of this object
is invoked. This method uses the source of the event as the key
and extracts the object of type Combine from the Hashtable.
It then extracts the reference to the Method object and the reference
to the destination object from that object.

This Method object, along with the invoke() method of
the Method class will be used to invoke the destination method on
the destination object that is associated with the source of the event
in the dispatch table.

This invoke() method requires two parameters.

The first parameter is a reference to the object on which the destination
method is to be invoked. This is the destination object that was extracted
from the object retrieved from the hashtable.

The second parameter is an array of type Object whose elements are
the parameters required by the destination method.

In this program, the destination method is defined to require two parameters.

The first parameter is the object of type ActionEvent associated
with the actual event.

The second parameter is an object of type Date() which provides
a date stamps for the event.

The requisite Object array is instantiated and populated.

Then the invoke() method is invoked on the reference to the object
of type Method that was extracted from the dispatch Hashtable
with the following being passed as parameters.

the reference to the destination object, and

the array of Object references

This causes the destination method to be invoked on the destination
object.

Beyond this, everything is pretty straightforward. Two classes are defined
from which destination objects can be instantiated.

One of these classes defines two different destination methods that
have the same required formal argument list. The other class defines one
destination method with the same formal argument list.

To keep things as simple as possible, all that these methods do is to
display some information when they are invoked, but they could be
put to useful purposes in a real program. If they are modified to do anything
that is expected to consume a lot of time, they should spawn another thread
to do the work and return as soon as possible.

A class named GUI is defined that instantiates and adds three
Button objects and a TextField object to a Frame object.
These components are used as sources of action events for test purposes.

Then the program instantiates two destination objects and an
action listener object. It registers the destination methods
and destination objects on the sources of action events by way of the action
listener object, specifying which source is to be associated with which
destination method and destination object. Several combinations of associations
are made between event sources and destination methods.

Then the action listener object is registered on all four event sources
where it will receive all action events generated by those sources and
dispatch them to the requisite destination method.

If the action listener object receives an action event from the TextField
object at a time when the TextField is empty, the event will
be filtered out and will not be dispatched to its intended destination.

Finally, a WindowListener object is registered on the Frame
so that the program will terminate when the user closes the Frame
object.

This program was tested using JDK 1.1.3 under Win95.

The output produced by the program when action events were generated
on all four components in left to right order will be shown later.

Interesting
Code Fragments

With that explanation of the program behind us, let's look at some code.
We will begin with the ActionListener class, and will break it up
and discuss it in pieces.

This is the ActionListener class that receives action events
and dispatches them to the same or different methods on the same or different
destination objects. It uses a Hashtable as the dispatching table
to associate the event source with the required destination object and
method.

The first interesting code fragment declares, instantiates, and initializes
a static final array of type Class which contains objects
that represent the types of arguments in the formal argument list. (Note
the braces used in the initialization of this array.)

Two different pieces of information are going to be associated with each
key in the Hashtable object. Therefore, we will define an inner
class that can be used to encapsulate the two pieces of information
into a single object that can be referenced as a value in the Hashtable
object.

The definition of that inner class is shown below. Note that the instance
variables in this class have default or package access, making them directly
available to other code later in the program.

This is followed by the method named registerDestinationMethod()
that is used to register destination methods on event sources. This is
what instructs the action listener object as to which destination method
on which destination object is to be notified when an action event occurs
on a particular source. Recall that a source can be registered on only
one destination method.

This is a fairly long method, so we will probably need to break it into
smaller fragments and discuss them separately.

The first fragment simply shows the method header and shows how the
Class object representing the destination object is obtained by
invoking the getClass() method on the reference to the destination
object. This Class object is required in order to get the Method
object that represents the method of interest.

The next code fragment uses the getMethod() method of the Class
class to create a Method object that represents a method with
a matching name and formal argument list. Note that this method
needs the name of the method as a String object and the Class
array that represents the types of the arguments of the method as parameters.

Once we have the Method object, we can encapsulate it, along with
the reference to the destination object, in an object of type Combine
for storage in the Hashtable.

Combine combinedStuff =
new Combine(destinationObj,theMethod);

Now it is time to make an entry in the Hashtable. This entry associates
a reference to an object that is the source of an action event with
a reference to an object of type Combine that contains a reference
to a Method object representing the method that is to be invoked
whenever that source generates an event and also contains a reference to
the object that contains the method.

We don't want to allow duplicate keys (duplicate references to the same
event source) so we will test for duplicate keys at this point. If a duplicate
key is specified, we will ignore it (not put it in the table) and throw
an exception.

Following this is the actionPerformed() method which is invoked
whenever a source that this listener object is registered on generates
an action event.

This method goes into the dispatch table with a reference to the the
source object of the event and comes back with a reference to an object
that contains a reference to the Method object that is associated
with that source and a reference to the destination object that
contains the method.

Then it uses the Method object to invoke the destination method
on the destination object, passing a reference to the ActionEvent object
and a reference to a Date() object as parameters to the method.

However if the source is a TextField object which contains an
empty string, the program filters the event and doesn't forward
it to its intended receiver.

We will also break this method into a couple of fragments and review
them separately. As before, we will ignore the try/catch blocks.

When this method is invoked, it receives an ActionEvent object
from the source object. The reference to this object is known locally as
evt.

We invoke the getSource() method on the ActionEvent object
to obtain a reference to the object that generated the event. This becomes
the key by which we will access our Hashtable.

There is some pretty ugly downcasting going on here, because only references
of type Object are stored in a Hashtable. Whenever we access
a Hashtable, we normally need to downcast the result to make it
useful.

Once we have a reference to the object of type Combine named
combinedStuff, we can access the two instance variables of that
object directly (as mentioned earlier). This gives us references to the
two objects that we need for the next step in the process.

At this point, we are going to do some filtering just to illustrate how
it might be done. We test to determine if the source object for the event
was a TextField object, and if so we test to determine it it contained
an empty string when the event occurred. If it was not a TextField
object, or if it was a TextField object but did not contain an empty
string, we go ahead with the dispatching operation.

If it was a TextField object containing an empty string, we simply
bypass the dispatching operation.

The dispatching operation consists of two steps:

Instantiate an array of type Object containing the actual parameters
to be passed to the method.

Use the invoke() method of the Method class to invoke
the method represented by the Method object on the destination object,
passing the array of parameter values as a parameter to the invoke()
method. If this doesn't make sense, go back and review the simple program
on the invoke() method in an earlier lesson.

And that is the end of the actionPerformed() method and also the
end of the ActionListener class.

This is followed by a couple of simple class definitions that are used
in the demonstration program to instantiate destination objects containing
destination methods. There is nothing in these classes that you haven't
already seen many times before, so I am not going to discuss them.

This is then followed by the class named GUI that is used to
exercise the ActionListener class. Much of this is completely standard
stuff, so I am going to delete lots of code and replace that code by comments.
I will keep the statements that are interesting insofar as this smart adapter
program is concerned.

As you review this code, recall that the argument list for the method
named registerDestinationMethod() consists of a reference to the
destination object, a reference to a specific source object for action
events, and a String object containing the name of the method to
be invoked. I have extracted one such statement and presented it below
for your review.

This is the only thing that is really new in this entire test program.

class GUI{
GUI(){//constructor
//Put three buttons and a TextField object in a Frame
// and make it visible
//Instantiate the destination objects that contain the
// destination methods
abcClass destinationObject0 = new abcClass();
defClass destinationObject1 = new defClass();
//Instantiate the action listener object
MyActionListener actionListener =
new MyActionListener();
//MAKE CERTAIN THAT YOU UNDERSTAND THE DIFFERENCE
// BETWEEN THE FOLLOWING TWO REGISTRATION PROCESSES.
//The following code registers destination methods on
// specific sources by informing the actionListener
// object which method on which destination object
// is to be invoked whenever one of the sources
// generates an action event.
actionListener.registerDestinationMethod(
destinationObject0,myButton0,"abcMethod0");
actionListener.registerDestinationMethod(
destinationObject0,myTextField,"abcMethod0");
actionListener.registerDestinationMethod(
destinationObject0,myButton1,"abcMethod1");
actionListener.registerDestinationMethod(
destinationObject1,myButton2,"defMethod");
//Force an exception to be thrown by attempting to
// register the same source on two different
// destinations.
actionListener.registerDestinationMethod(
destinationObject1,myButton0,"defMethod");
//The following code registers the actionListener
// object on the individual sources
myButton0.addActionListener(actionListener);
myTextField.addActionListener(actionListener);
myButton1.addActionListener(actionListener);
myButton2.addActionListener(actionListener);
//snip
}//end constructor
}// end class GUI

The output from running this program and generating an action event on
each of the objects in the Frame going from left to right is shown
below. Manual line breaks were inserted to force this material to fit on
the page.

You should be able to explain this output on the basis of what you have
seen above.

Program Listing

/*File Reflections06.java Copyright 1998, R.G.Baldwin
This program uses reflection and hash tables to receive
action events from several different sources and route
them to the same or different destination methods on
the same or different
destination objects.
This program was tested using JDK 1.1.3 under Win95.
**********************************************************/
import java.lang.reflect.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
//=======================================================//
class Reflections06 {
public static void main(String[] args) {
//Instantiate a GUI object to control the program.
GUI guiObj = new GUI();
}//end main()
}//end Reflections06
//=======================================================//
//This is an ActionListener class that receives
// action events and dispatches them to the same or
// different methods on the same or different destination
// objects. It uses a Hashtable as the dispatching table
// to associate the event source with the required
// destination object and method.
class MyActionListener implements ActionListener{
//Initialize the array of Class objects to match the
// required types in the formal argument list of the
// destination methods.
final static Class[] destinationMethodArgTypes =
{ActionEvent.class, Date.class};
Hashtable dispatchTable = new Hashtable();
//-----------------------------------------------------//
//This inner class is used to encapsulate the reference
// to the Method object and the reference to the
// destination object in a new object that can be
// referenced by the value component of a hashtable.
class Combine{
Object theDestinationObj;
Method theMethod;
//---------------------------------------------------//
//Constructor for inner class
Combine(Object theDestinationObj, Method theMethod){
this.theDestinationObj = theDestinationObj;
this.theMethod = theMethod;
}//end constructor for inner class
}//end class Combine
//-----------------------------------------------------//
//This method is used to register specific destination
// methods and their objects with specific action event
// sources.
public void registerDestinationMethod(
Object destinationObj,Object whichSource,
String methodName){
Class theDestinationObjClass =
destinationObj.getClass();
try{
//Use the getMethod() method of the Class class to
// create a Method object that represents a method
// with a matching name and formal argument list.
Method theMethod = theDestinationObjClass.getMethod(
methodName,destinationMethodArgTypes);
//Encapsulate the two refrences in a new object for
// referencing in the dispatch table.
Combine combinedStuff =
new Combine(destinationObj,theMethod);
//Make an entry in the dispatch table that associates
// the source of an action event with a reference
// to an object that contains a reference to method
// that is to be invoked whenever that source
// generates an event and a reference to the object
// that contains the method.
//Don't allow duplicate keys (duplicate references
// to the same event source). If a duplicate is
// specified, ignore it and throw an exception.
if(!dispatchTable.containsKey(whichSource))
dispatchTable.put(whichSource,combinedStuff);
else throw new IllegalAccessException();
}//end try block
catch(NoSuchMethodException e){System.out.println(e);}
catch(IllegalAccessException e){
System.out.println(e);
System.out.println("Attempt to register same " +
"source \non more than one method");
}//end catch block
}//end registerDestinationMethod()
//-----------------------------------------------------//
//This actionPerformed() method is invoked whenever a
// source that this listener object is registered on
// generates an action event. This method goes into the
// dispatch table with the source of the event and
// comes back with a reference to an object that
// contains a reference to the Method object that is
// associated with that source and a reference to the
// destination object that contains the method. Then it
// uses the Method object to invoke the destination
// method on the destination object, passing the
// ActionEvent object along with a Date() object.
//If the source is a TextField object which returns
// an empty string, filter the event and don't forward
// it to its intended receiver.
public void actionPerformed(ActionEvent evt){
try{
Object key = evt.getSource();
Combine combinedStuff =
(Combine)dispatchTable.get(key);
Method theMethod = (Method)combinedStuff.theMethod;
Object theDestinationObj =
(Object)combinedStuff.theDestinationObj;
//Filter out empty strings from TextField object
if( !((key instanceof TextField)
&& (((TextField)key).getText().equals("") )) ){
//Instantiate and populate an array of Object
// references with the parameters that are to be
// passed to the destination method when it is
// invoked. Note the initialization syntax.
Object methodParameters[] = { evt, new Date() };
//Invoke the destination method on the destination
// object passing the parameters generated above.
theMethod.invoke(
theDestinationObj,methodParameters);
}//end if
}//end try
catch(InvocationTargetException e){
System.out.println(e);}
catch(IllegalAccessException e){System.out.println(e);}
}//end actionPerformed()
}//end class MyActionListener
//=======================================================//
//Objects of this class can be registered on the
// ActionListener object to have specific methods
// invoked when specific sources generate action events.
class abcClass{
public void abcMethod0(ActionEvent e, Date d){
System.out.println("In object :" + this);
System.out.println("In abcMethod0 " + e);
System.out.println("Time Stamp " + d + "\n");
}//end abcMethod0
//-----------------------------------------------------//
public void abcMethod1(ActionEvent e, Date d){
System.out.println("In object :" + this);
System.out.println("In abcMethod1 " + e);
System.out.println("Time Stamp " + d + "\n");
}//end abcMethod1
}//end abcClass
//=======================================================//
//Objects of this class can also be registered on the
// ActionListener object to have specific methods
// invoked when specific sources generate action events.
class defClass{
public void defMethod(ActionEvent e, Date d){
System.out.println("In object :" + this);
System.out.println("In defMethod " + e);
System.out.println("Time Stamp " + d + "\n");
}//end defMethod
}//end defClass
//=======================================================//
class GUI{
GUI(){//constructor
//Put three buttons and a TextField object in a Frame
// and make it visible
Button myButton0 = new Button("Button0");
TextField myTextField = new TextField("TextField");
Button myButton1 = new Button("Button1");
Button myButton2 = new Button("Button2");
Frame myFrame = new Frame(
"Copyright 1998, R.G.Baldwin");
myFrame.setLayout(new FlowLayout());
myFrame.add(myButton0);
myFrame.add(myTextField);
myFrame.add(myButton1);
myFrame.add(myButton2);
myFrame.setSize(300,100);
myFrame.setVisible(true);
//MAKE CERTAIN THAT YOU UNDERSTAND THE DIFFERENCE
// BETWEEN THE FOLLOWING TWO REGISTRATION PROCESSES.
//Instantiate the destination objects that contain the
// methods to be invoked when the components generate
// action events.
abcClass destinationObject0 = new abcClass();
defClass destinationObject1 = new defClass();
//Instantiate the action listener object that will take
// care of dispatching action events from the different
// sources to the different destination methods.
MyActionListener actionListener =
new MyActionListener();
//The following code registers destination methods on
// specific sources by informing the actionListener
// object which method on which destination object
// is to be invoked whenever one of the buttons
// generates an action event.
//Register different combinations of destination
// methods and destination objects on sources.
actionListener.registerDestinationMethod(
destinationObject0,myButton0,"abcMethod0");
actionListener.registerDestinationMethod(
destinationObject0,myTextField,"abcMethod0");
actionListener.registerDestinationMethod(
destinationObject0,myButton1,"abcMethod1");
actionListener.registerDestinationMethod(
destinationObject1,myButton2,"defMethod");
//Force an exception to be thrown by attempting to
// register the same source on two different
// destinations.
actionListener.registerDestinationMethod(
destinationObject1,myButton0,"defMethod");
//The following code registers the actionListener
// object on the individual sources so that the
// actionListener object will be notified when one
// of the sources generates an action event. In this
// case, the same actionListener object is being
// registered on all of the sources. The
// actionListener object will dispatch the event to
// the correct destination method and object as
// defined above.
myButton0.addActionListener(actionListener);
myTextField.addActionListener(actionListener);
myButton1.addActionListener(actionListener);
myButton2.addActionListener(actionListener);
//Register listener to terminate program when
// user closes the Frame.
myFrame.addWindowListener(new Terminate());
}//end constructor
}// end class GUI
//=======================================================//
class Terminate extends WindowAdapter{
public void windowClosing(WindowEvent e){
System.exit(0);
}//end windowClosing()
}//end class Terminate
//=======================================================//