import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Implements a <em>Dispatcher</em> design pattern by using reflection to
* determine the most specific runtime method signature appropriate for
* visiting an object. This avoids the use of double-dispatch which is
* fairly intrusive.
*
* @layer<visitor>
*/
public abstract class Dispatcher {
/**
* Using reflection, this method calls a specific <code>visit</code>
* method based on dynamic argument type.
*
* @throws IllegalStateException
* if trying to dispatch to <code>visit</code> causes an exception
* (the original exception is saved as the "cause").
*
* @layer<visitor>
*/
public void dispatch( Object object ) {
try {
Class klass = ( object != null ) ? object.getClass() : null ;
Method method = getVisitMethod( klass ) ;
method.invoke( this, new Object[] {object} ) ;
}
catch ( Exception exception ) {
error( object, exception ) ;
}
}
/**
* This <code>visit</code> method is for the case where an exception
* occurs while trying to visit an object. This can occur in the
* <code>visit</code> method during reflection and a derived method
* may also call this method for unexpected exceptions.
*
* @throws IllegalStateException for all arguments.
*
* @layer<visitor>
*/
public void error( Object object, Throwable thrown ) {
String msg = "thrown when visiting " + object.getClass() ;
IllegalStateException exception = new IllegalStateException( msg ) ;
exception.initCause( thrown ) ;
throw exception ;
}
/**
* Implements a search for a {@link Method} named <code>visit</code>
* with an argument matching one of the superclasses of the argument.
* Has a cache for to reduce extra searches.
*
* @param baseClass
* a non-null reference to the {@link Class} of an object to visit.
*
* @layer<visitor>
*/
private Method getVisitMethod( Class baseClass ) {
// Check the cache first just in case the search has been done:
//
Method method = ( Method ) methodMap.get( baseClass ) ;
if ( methodMap.containsKey( baseClass ) )
return method ;
// First, loop through the superclasses:
//
Class superClass = baseClass ;
while ( method == null && superClass != null )
try {
classArray [0] = superClass ;
method = getClass().getMethod( "visit", classArray ) ;
}
catch ( NoSuchMethodException exception ) {
superClass = superClass.getSuperclass() ;
}
// Otherwise, try the interfaces:
//
if ( method == null ) {
Class[] interfaces = baseClass.getInterfaces() ;
for ( int n = 0 ; n < interfaces.length ; ++n )
try {
classArray [0] = interfaces [n] ;
method = getClass().getMethod( "visit", classArray ) ;
break ;
}
catch ( NoSuchMethodException exception ) {
/* Body deliberately left empty (loop handles this). */
}
}
// Save the result in the cache, then return it:
// (may be null!)
//
methodMap.put( baseClass, method ) ;
return method ;
}
final private Class[] classArray = new Class [1] ;
final private Map methodMap = new HashMap() ;
}