/*______________________________________________________________________________
 * 
 * Copyright 2002 Paul Cantrell
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * (1) Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 * (2) Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution. 
 *
 * (3) The name of the author may not be used to endorse or promote
 *     products derived from this software without specific prior
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *_______________________________________________________________________________
 */

package net.innig.util;

import java.lang.reflect.*;
import java.util.*;

/**
    Creates dynamic proxy objects which pass the methods of an interface through to a collection
    of delegate objects.  This lets you glue together several objects into a complete implementation
    of an interface, or override one method of a complex object without subclassing it.
    <p>
    The static methods in this class take a set of <b>interfaces</b> and a set of <b>delegate
    objects</b>, and return a new <b>delegating proxy</b> object which implements all of the
    interfaces you passed in.  When you call a particular method on the proxy object, it
    walks through the each of delegate objects in turn until it finds a method with an identical
    signature.
    <p>
    For example, suppose we have the following three classes:
    <pre>  public interface WackyInterface
    *      {
    *      public void someMethod();
    *      public void anotherMethod();
    *      }
    *
    *  public class ClassOne
    *      {
    *      public void someMethod()
    *          { System.out.println("called ClassOne.someMethod()"); }
    *      }
    *
    *  public class ClassTwo
    *      {
    *      public void someMethod()
    *          { System.out.println("called ClassTwo.someMethod()"); }
    *      public void anotherMethod()
    *          { System.out.println("called ClassTwo.anotherMethod()"); }
    *      }
    </pre>
    The following code...
    <pre>ClassOne thingOne = new ClassOne();
    *  ClassTwo thingTwo = new ClassTwo();
    *
    *  WackyInterface wacky = (WackyInterface) Proxy.newProxyInstance(
    *      WackyInterface.class,
    *      new Object[] { thingOne, thingTwo } );
    *
    *  wacky.someMethod();
    *  wacky.anotherMethod();
    </pre>
    ...will product the output:
    <pre>  called ClassOne.someMethod()
    *  called ClassTwo.anotherMethod()
    </pre>
    The object <code>wacky</code> implements <code>WackyInterface</code>.  Internally, it holds on
    to <code>thingOne</code> and <code>thingTwo</code>.  The first call, <code>wacky.someMethod()</code>,
    gets delegated to <code>thingOne.someMethod()</code>.  However, since <code>thingOne</code>
    doesn't have <code>anotherMethod()</code>, the second call passes through to
    <code>thingTwo.anotherMethod()</code>.
    <p>
    If a method in the interface does not appear in any of the delegate objects, then by default,
    calling that method results in an {@link UnsupportedOperationException}.  Alternatively, you can
    specify a <b>final handler</b> which takes care of calls that none of the delegates handled.
    
    @see java.lang.reflect.Proxy
*/
public class DelegatingProxy
    implements InvocationHandler
    {
    public static <T> T newProxyInstance(
            Class<T> iface,
            Object[] delegates)
        {
        return (T) Proxy.newProxyInstance(
            iface.getClassLoader(),
            new Class[] { iface },
            new DelegatingProxy(delegates));
        }

    public static <T> T newProxyInstance(
            Class<T> iface,
            Object[] delegates,
            InvocationHandler finalHandler)
        {
        return (T) Proxy.newProxyInstance(
            iface.getClassLoader(),
            new Class[] { iface },
            new DelegatingProxy(delegates, finalHandler));
        }

    public static Object newProxyInstance(
            ClassLoader classLoader,
            Class[] interfaces,
            Object[] delegates)
        {
        return Proxy.newProxyInstance(
            classLoader,
            interfaces,
            new DelegatingProxy(delegates));
        }

    public static Object newProxyInstance(
            ClassLoader classLoader,
            Class[] interfaces,
            Object[] delegates,
            InvocationHandler finalHandler)
        {
        return Proxy.newProxyInstance(
            classLoader,
            interfaces,
            new DelegatingProxy(delegates, finalHandler));
        }
    
    public static final InvocationHandler UNSUPPORTED_OPERATION_HANDLER =
        new InvocationHandler()
            {
            public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable
                { throw new UnsupportedOperationException(); }
            };
    
    public DelegatingProxy(Object[] delegates)
        { this(delegates, UNSUPPORTED_OPERATION_HANDLER); }
    
    public DelegatingProxy(Object[] delegates, InvocationHandler finalHandler)
        {
        this.delegates = delegates;
        this.finalHandler = finalHandler;
        methodCache = new HashMap();
        }
    
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable
        {
        for(int n = 0; n < delegates.length; n++)
            {
            try {
                return delegates[n].getClass()
                                   .getMethod(
                                       method.getName(),
                                       method.getParameterTypes())
                                   .invoke(delegates[n], args);
                }
            catch(NoSuchMethodException nsme)
                { continue; }
            catch(IllegalArgumentException iae)
                { continue; }
            catch(IllegalAccessException iae2)
                { continue; }
            catch(InvocationTargetException ite)
                { throw ite.getTargetException(); }
            // allow SecurityException to go out unchecked
            }
                
        return finalHandler.invoke(proxy, method, args);
        }

    private final Object[] delegates;
    private final InvocationHandler finalHandler;
    private Map methodCache;
    }


