/* Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package grails.test;

import groovy.lang.Closure;

/**
 * Skeleton implementation of a wrapper class for closures that allows
 * you to intercept invocations of the closure. The wrapper can be used
 * anywhere that the target closure can be used.
 */
@SuppressWarnings("serial")
public abstract class AbstractClosureProxy extends Closure {

    private Closure target;

    /**
     * Creates a new instance that wraps the target closure and sends
     * profiling events to the given profiler log.
     * @param closure The target closure to wrap.
     */
    public AbstractClosureProxy(Closure closure) {
        super(closure.getOwner(), closure.getThisObject());
        target = closure;
    }

    /**
     * This method is called before the target closure is invoked.
     * This is a passive interceptor, so you cannot prevent the
     * call to the target closure. You can modify the arguments,
     * though, but it's not recommended unless you really know
     * what you're doing.
     * @param args The arguments passed to the closure.
     */
    protected abstract void doBeforeCall(Object[] args);

    /**
     * This method is called after the target closure is invoked.
     * It will be triggered whether or not an exception is thrown
     * by the target closure.
     * @param args The arguments passed to the closure.
     */
    protected abstract void doAfterCall(Object[] args);

    /**
     * Called when a new instance of the proxy needs to be created for
     * the given closure. Usually the implementation simply creates a
     * new instance of the current class, copying over the existing
     * proxy properties:
     * <pre>
     *    return new MyClosureProxy(c, this.field1, ...)
     * </pre>
     * @param c The closure to wrap/proxy.
     */
    protected abstract Closure createWrapper(Closure c);

    /**
     * This is the important one: logs entry and exit of the closure call.
     */
    @Override
    public Object call(Object[] objects) {
        doBeforeCall(objects);

        try {
            return target.call(objects);
        }
        finally {
            doAfterCall(objects);
        }
    }

    /**
     * Compares based on identities, but unlike the standard implementation
     * this one will return <code>true</code> if the given object is the
     * target closure for this wrapper as well.
     */
    @Override
    public boolean equals(Object obj) {
        return this == obj || target == obj;
    }

    @Override
    public int hashCode() {
        return target.hashCode();
    }

    @Override
    public Closure curry(Object[] objects) {
        return createWrapper(target.curry(objects));
    }

    @Override
    public boolean isCase(Object o) {
        return target.isCase(o);
    }

    @Override
    public Closure asWritable() {
        return target.asWritable();
    }

    @Override
    public Object getProperty(String property) {
        return target.getProperty(property);
    }

    @Override
    public void setProperty(String s, Object o) {
        target.setProperty(s, o);
    }

    @Override
    public int getMaximumNumberOfParameters() {
        return target.getMaximumNumberOfParameters();
    }

    @Override
    public Class<?>[] getParameterTypes() {
        return target.getParameterTypes();
    }

    @Override
    public Object getDelegate() {
        return target.getDelegate();
    }

    @Override
    public void setDelegate(Object o) {
        target.setDelegate(o);
    }

    @Override
    public int getDirective() {
        return target.getDirective();
    }

    @Override
    public void setDirective(int i) {
        target.setDirective(i);
    }

    @Override
    public int getResolveStrategy() {
        return target.getResolveStrategy();
    }

    @Override
    public void setResolveStrategy(int i) {
        target.setResolveStrategy(i);
    }
}
