/*______________________________________________________________________________
 * 
 * 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 net.innig.collect.*;

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

/**
    Creates dynamic proxy objects which log method calls.
    
    @see java.lang.reflect.Proxy
*/
public abstract class LoggingProxy
    extends DelegatingProxy
    {
    public static Object newProxyInstance(Object delegate, LoggingProxy logger)
        {
        Set interfaces =
            InnigCollections.select(
                Graphs.reachableNodes(
                    delegate.getClass(),
                    new GraphWalker()
                        {
                        public Collection getEdgesFrom(Object node)
                            {
                            Class parent = (Class) node;
                            List edges = new ArrayList(Arrays.asList(parent.getInterfaces()));
                            if(parent.getSuperclass() != null)
                                edges.add(parent.getSuperclass());
                            return edges;
                            }
                        }),
                new Selector()
                    {
                    public boolean select(Object o)
                        { return ((Class) o).isInterface(); }
                    });
        
        return Proxy.newProxyInstance(
            delegate.getClass().getClassLoader(),
            (Class[]) new ArrayList(interfaces).toArray(new Class[0]),
            logger);
        }
    
    public LoggingProxy(Object delegate)
        {
        super(new Object[] { delegate });
        this.delegate = delegate;
        }
    
    public LoggingProxy(Object delegate, InvocationHandler finalHandler)
        {
        super(new Object[] { delegate }, finalHandler);
        this.delegate = delegate;
        }
    
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable
        {
        logStart(proxy, method, args);
        long startTime = System.currentTimeMillis();
        try {
            Object result = super.invoke(proxy, method, args);
            logEnd(proxy, method, args, result, System.currentTimeMillis() - startTime);
            return result;
            }
        catch(Throwable t)
            {
            logError(proxy, method, args, t, System.currentTimeMillis() - startTime);
            throw t;
            }
        }
    
    public String getDelegateName()
        { return getDelegate().getClass().getName(); }
    
    protected abstract void logStart(Object proxy, Method method, Object[] args);
    protected abstract void logEnd(Object proxy, Method method, Object[] args, Object result, long time);
    protected abstract void logError(Object proxy, Method method, Object[] args, Throwable thrown, long time);
        
    protected String getStartMessage(Object proxy, Method method, Object[] args)
        {
        return "-> " + getDelegateName()
            + '.' + method.getName()
            + paramsToString(method);
        }
    
    protected String getEndMessage(Object proxy, Method method, Object[] args, Object result, long time)
        {
        return "<- " + getDelegateName()
            + '.' + method.getName()
            + paramsToString(method)
            + " [" + time + "ms]";
        }

    protected String getErrorMessage(Object proxy, Method method, Object[] args, Throwable thrown, long time)
        {
        return "X- " + getDelegateName()
            + '.' + method.getName()
            + paramsToString(method)
            + " [" + time + "ms]"
            + " threw " + thrown;
        }
    
    protected final Object getDelegate()
        { return delegate; }
    
    private String paramsToString(Method method)
        {
        StringBuffer buf = new StringBuffer("(");
        Class[] paramTypes = method.getParameterTypes();
        for(int p = 0; p < paramTypes.length; p++)
            {
            if(p > 0)
                buf.append(',');
            buf.append(paramTypes[p].getName());
            }
        buf.append(')');
        return buf.toString();
        }
    
    private Object delegate;
    }


