/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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 org.seasar.framework.aop.proxy;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Iterator;

import junit.framework.TestCase;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.Aspect;
import org.seasar.framework.aop.Pointcut;
import org.seasar.framework.aop.impl.AspectImpl;
import org.seasar.framework.aop.impl.PointcutImpl;
import org.seasar.framework.aop.interceptors.TraceInterceptor;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.SerializeUtil;

/**
 * @author higa
 * 
 */
public class AopProxyTest extends TestCase {

    /**
     * @throws Exception
     */
    public void testIntercept() throws Exception {
        TestInvocation invocation = new TestInvocation();
        Pointcut pointcut = new PointcutImpl(new String[] { "getTime" });
        Aspect aspect = new AspectImpl(invocation, pointcut);
        AopProxy aopProxy = new AopProxy(Date.class, new Aspect[] { aspect });
        Date proxy = (Date) aopProxy.create();
        System.out.println(proxy.getTime());
        assertEquals("2", true, invocation.invoked_);
    }

    /**
     * @throws Exception
     */
    public void testInterface() throws Exception {
        Pointcut pointcut = new PointcutImpl(new String[] { "greeting" });
        Aspect aspect = new AspectImpl(new HelloInterceptor(), pointcut);
        AopProxy aopProxy = new AopProxy(Hello.class, new Aspect[] { aspect });
        Hello proxy = (Hello) aopProxy.create();
        assertEquals("1", "Hello", proxy.greeting());
    }

    /**
     * @throws Exception
     */
    public void testCreateForArgs() throws Exception {
        Aspect aspect = new AspectImpl(new TraceInterceptor());
        AopProxy aopProxy = new AopProxy(HelloImpl.class,
                new Aspect[] { aspect });
        Hello proxy = (Hello) aopProxy.create(new Class[] { String.class },
                new Object[] { "Hello" });
        assertEquals("1", "Hello", proxy.greeting());
        System.out.println(proxy.hashCode());
    }

    /**
     * @throws Exception
     */
    public void testEquals() throws Exception {
        Pointcut pointcut = new PointcutImpl(new String[] { "greeting" });
        Aspect aspect = new AspectImpl(new HelloInterceptor(), pointcut);
        AopProxy aopProxy = new AopProxy(Hello.class, new Aspect[] { aspect });
        Hello proxy = (Hello) aopProxy.create();
        assertEquals("1", true, proxy.equals(proxy));
        assertEquals("2", false, proxy.equals(null));
        assertEquals("3", false, proxy.equals("hoge"));
    }

    /**
     * 
     */
    public void testEqualsByValue() {
        Pointcut pointcut = new PointcutImpl(new String[] { "toString" });
        Aspect aspect = new AspectImpl(new HelloInterceptor(), pointcut);
        AopProxy aopProxy = new AopProxy(BigDecimal.class,
                new Aspect[] { aspect });
        BigDecimal proxy = (BigDecimal) aopProxy.create(
                new Class[] { String.class }, new Object[] { "10" });
        assertTrue("1", proxy.equals(proxy));
        assertTrue("2", proxy.equals(new BigDecimal("10")));
        assertFalse("3", proxy.equals(null));
        assertFalse("3", proxy.equals(new BigDecimal("100")));
        assertFalse("3", proxy.equals("Hoge"));
    }

    /**
     * @throws Exception
     */
    public void testFinalMethod() throws Exception {
        Pointcut pointcut = new PointcutImpl(new String[] { "greeting" });
        Aspect aspect = new AspectImpl(new HelloInterceptor(), pointcut);
        AopProxy aopProxy = new AopProxy(HelloImpl3.class,
                new Aspect[] { aspect });
        Hello proxy = (Hello) aopProxy.create();
        assertEquals("1", "hoge", proxy.greeting());
    }

    /**
     * Java8で追加されたデフォルトメソッドに対するインターセプタのテスト
     * 
     * @throws Exception
     */
    public void testDefaultMethod() throws Exception {
        if (!MethodUtil.isDefaultMethod(ClassUtil.getMethod(Iterator.class,
                "remove", null))) {
            // Java7以前はスキップ
            return;
        }
        System.out.println("before default test");
        try {
            Aspect aspect = new AspectImpl(new TraceInterceptor());
            AopProxy aopProxy = new AopProxy(Iterator.class,
                    new Aspect[] { aspect });
            Iterator proxy = (Iterator) aopProxy.create();
            proxy.remove();
        } catch (UnsupportedOperationException expected) {
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    /**
     * @throws Exception
     */
    public void testSerialize() throws Exception {
        Aspect aspect = new AspectImpl(new TraceInterceptor());
        AopProxy aopProxy = new AopProxy(HelloImpl.class,
                new Aspect[] { aspect });
        Hello proxy = (Hello) aopProxy.create(new Class[] { String.class },
                new Object[] { "Hello" });
        Hello copy = (Hello) SerializeUtil.serialize(proxy);
        assertEquals("1", "Hello", copy.greeting());
    }

    /**
     * @throws Exception
     */
    public void testPerformance() throws Exception {
        Date target = new Date();
        Pointcut pointcut = new PointcutImpl(new String[] { "compareTo" });
        Aspect aspect = new AspectImpl(new TestInvocation(), pointcut);
        AopProxy aopProxy = new AopProxy(Date.class, new Aspect[] { aspect });
        Date proxy = (Date) aopProxy.create();

        MyInvocationHandler handler = new MyInvocationHandler(target);
        Comparable proxy2 = (Comparable) Proxy.newProxyInstance(Date.class
                .getClassLoader(), new Class[] { Comparable.class }, handler);

        final int num = 2000000;
        long start = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            // target.hashCode();
            target.compareTo(target);
        }
        System.out.println("target:" + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            // proxy.hashCode();
            proxy.compareTo(target);
        }
        System.out.println("AOP proxy:" + (System.currentTimeMillis() - start));

        start = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            // proxy.hashCode();
            proxy2.compareTo(target);
        }
        System.out.println("JDK proxy:" + (System.currentTimeMillis() - start));
    }

    /**
     *
     */
    public class TestInvocation implements MethodInterceptor {

        private boolean invoked_ = false;

        public Object invoke(MethodInvocation invocation) throws Throwable {
            invoked_ = true;
            return invocation.proceed();
        }

    }

    /**
     *
     */
    public class MyInvocation implements MethodInterceptor {

        public Object invoke(MethodInvocation invocation) throws Throwable {
            return invocation.proceed();
        }

    }

    /**
     *
     */
    public interface Hello extends Serializable {
        /**
         * @return
         */
        public String greeting();
    }

    /**
     *
     */
    public static class HelloImpl implements Hello {

        private static final long serialVersionUID = -6888891433122734807L;

        private String str_;

        /**
         * @param str
         */
        public HelloImpl(String str) {
            str_ = str;
        }

        public String greeting() {
            return str_;
        }
    }

    /**
     *
     */
    public static class Hello2Impl implements Hello {

        private static final long serialVersionUID = -7275896388662514561L;

        public String greeting() {
            return "Hello2";
        }
    }

    /**
     *
     */
    public static class HelloImpl3 implements Hello {

        private static final long serialVersionUID = 5992242320294594184L;

        public final String greeting() {
            return "hoge";
        }
    }

    /**
     *
     */
    public class HelloInterceptor implements MethodInterceptor {
        public Object invoke(MethodInvocation invocation) throws Throwable {
            return "Hello";
        }
    }

    /**
     *
     */
    public static class MyInvocationHandler implements InvocationHandler {
        private Object target;

        /**
         * @param target
         */
        public MyInvocationHandler(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            return method.invoke(target, args);
        }
    }
}