

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() ;
}
