001/*
002 * (c) 2003-2005, 2009, 2010 ThoughtWorks Ltd
003 * All rights reserved.
004 *
005 * The software in this package is published under the terms of the BSD
006 * style license a copy of which has been included with this distribution in
007 * the LICENSE.txt file.
008 * 
009 * Created on 27-Jul-2004
010 */
011package com.thoughtworks.proxy.toys.echo;
012
013import java.io.PrintWriter;
014import java.lang.reflect.Method;
015
016import com.thoughtworks.proxy.ProxyFactory;
017import com.thoughtworks.proxy.toys.decorate.Decorating;
018import com.thoughtworks.proxy.toys.decorate.Decorator;
019
020/**
021 * A {@link com.thoughtworks.proxy.toys.decorate.Decorator} implementation that echoes any invocation to a {@link PrintWriter}.
022 * <p>
023 * The implementation will try to create new proxies for every return value, that can be proxied by the
024 * {@link ProxyFactory} in use.
025 * </p>
026 *
027 * @author Dan North
028 * @author J&ouml;rg Schaible
029 * @since 0.1
030 */
031public class EchoDecorator<T> extends Decorator<T> {
032    private static final long serialVersionUID = 1L;
033    private final PrintWriter out;
034    private final ProxyFactory factory;
035
036    /**
037     * Construct an EchoingDecorator.
038     *
039     * @param out     the {@link PrintWriter} receiving the logs
040     * @param factory the {@link ProxyFactory} to use
041     * @since 0.2
042     */
043    public EchoDecorator(final PrintWriter out, final ProxyFactory factory) {
044        this.out = out;
045        this.factory = factory;
046    }
047
048    @Override
049    public Object[] beforeMethodStarts(final T proxy, final Method method, final Object[] args) {
050        printMethodCall(method, args);
051        return super.beforeMethodStarts(proxy, method, args);
052    }
053
054        @Override
055    @SuppressWarnings("unchecked")
056    public Object decorateResult(final T proxy, final Method method, final Object[] args, Object result) {
057        Class returnType = method.getReturnType();
058        printMethodResult(result);
059        if (returnType != Object.class && factory.canProxy(returnType)) {
060            result = Decorating.proxy(result, returnType).visiting(this).build(factory);
061        } else if (result != null && returnType == Object.class && factory.canProxy(result.getClass())) {
062            returnType = result.getClass();
063                        result = Decorating.proxy(result, returnType).visiting(this).build(factory);
064        }
065        return result;
066    }
067
068    @Override
069    public Throwable decorateTargetException(
070            final T proxy, final Method method, final Object[] args, final Throwable cause) {
071        printTargetException(cause);
072        return super.decorateTargetException(proxy, method, args, cause);
073    }
074
075    @Override
076    public Exception decorateInvocationException(
077            final T proxy, final Method method, final Object[] args, final Exception cause) {
078        printInvocationException(cause);
079        return super.decorateInvocationException(proxy, method, args, cause);
080    }
081
082    private void printMethodCall(Method method, Object[] args) {
083        final StringBuilder buf = new StringBuilder("[");
084        buf.append(Thread.currentThread().getName());
085        buf.append("] ");
086        buf.append(method.getDeclaringClass().getName());
087        buf.append(".").append(method.getName());
088
089        if (args == null) {
090            args = new Object[0];
091        }
092        buf.append("(");
093        for (int i = 0; i < args.length; i++) {
094            buf.append(i == 0 ? "<" : ", <").append(args[i]).append(">");
095        }
096        buf.append(") ");
097        out.print(buf);
098        out.flush();
099    }
100
101    private void printMethodResult(final Object result) {
102        final StringBuilder buf = new StringBuilder("--> <");
103        buf.append(result == null ? "NULL" : result.toString());
104        buf.append(">");
105        out.println(buf);
106        out.flush();
107    }
108
109    private void printTargetException(final Throwable throwable) {
110        final StringBuilder buf = new StringBuilder("throws ");
111        buf.append(throwable.getClass().getName());
112        buf.append(": ");
113        buf.append(throwable.getMessage());
114        out.println(buf);
115        out.flush();
116    }
117
118    private void printInvocationException(final Throwable throwable) {
119        out.print("INTERNAL ERROR, ");
120        printTargetException(throwable);
121    }
122}