/*
 * Copyright 2002-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 test.feature.aop;

import static org.junit.Assert.*;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import org.junit.Test;

import org.springframework.beans.factory.annotation.Autowire;

import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.DependencyCheck;
import org.springframework.config.java.context.ConfigurableJavaConfigApplicationContext;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.plugin.aop.AspectJAutoProxy;
import org.springframework.config.java.util.DefaultScopes;

import test.common.beans.DependsOnTestBean;
import test.common.beans.TestBean;

import java.util.HashMap;
import java.util.Map;


/**
 * TODO: JAVADOC
 *
 * @author  Rod Johnson
 * @author  Chris Beams
 */
public class AspectJConfigurationProcessorTests {

    private ConfigurableJavaConfigApplicationContext ctx;

    @org.junit.After
    public void nullOutContext() { ctx = null; }


    @Test
    public void testSharedAfterAdvice() throws Throwable {
        ctx = new JavaConfigApplicationContext(AfterAdvice.class);

        TestBean advised = ctx.getBean(TestBean.class, "advised");
        AfterAdvice.count = 0;
        advised.absquatulate();
        assertEquals(0, AfterAdvice.count);
        advised.exceptional(null);
        assertEquals(1, AfterAdvice.count);
        try {
            advised.exceptional(new Exception());
        } catch (Throwable t) { /* Expected */ }

        assertEquals("After advice should count failure", 2, AfterAdvice.count);
    }

    
    @Test
    public void testAspectJAroundAdviceWithImplicitScope() throws Exception {
        ctx = new JavaConfigApplicationContext(AroundSingletonCountingAdvice.class);

        TestBean advised1 = (TestBean) ctx.getBean("advised");
        int newAge = 24;
        advised1.setAge(newAge);
        assertEquals("Invocations must work on target without around advice", newAge, advised1.getAge());
        assertEquals("around", advised1.getName());
    }


    @Test
    public void testAspectJAroundAdviceWithImplicitScopeAndNamedPointcut() throws Exception {
        doTestAspectJAroundAdviceWithImplicitScope(AroundAdviceWithNamedPointcut.class);
    }


    private void doTestAspectJAroundAdviceWithImplicitScope(Class<?> clazz) throws Exception {
        ctx = new JavaConfigApplicationContext(clazz);

        TestBean advised1 = (TestBean) ctx.getBean("advised");
        int newAge = 24;
        advised1.setAge(newAge);
        assertEquals("Invocations must work on target without around advice", newAge, advised1.getAge());
        assertEquals("around", advised1.getName());
    }


    @Test
    public void testAspectJNoAroundAdvice() throws Exception {
        // Superclass doesn't have around advice
        ctx = new JavaConfigApplicationContext(SingletonCountingAdvice.class);

        TestBean advised1 = ctx.getBean(TestBean.class, "advised");
        int newAge = 24;
        advised1.setAge(newAge);
        assertEquals("Invocations must work on target without around advice", newAge, advised1.getAge());
        assertEquals("tony", advised1.getName());
    }


    @Test
    public void testAroundAdviceWithArguments() throws Exception {
        ctx = new JavaConfigApplicationContext(SumAroundAdvice.class);
        ReturnZero rz = ctx.getBean(ReturnZero.class, "willAdd");
        assertEquals("Must add arguments, not return zero", 25, rz.returnZero(10, 15));
    }

    @Aspect
    public abstract static class PerInstanceCountingAdvice extends CountingConfiguration {
        @Before("execution(* *.getSpouse())")
        public void doesntMatter() { }
    }

    @Aspect
    public static class AfterAdvice extends CountingConfiguration {
        public static int count = 0;

        @After("execution(* *.exceptional(Throwable))")
        public void after() { ++count; }
    }

    /**
     * Technically a valid Aspect, but it is supplied to the constructor above, which is incorrect
     * usage.
     */
    @Configuration @Aspect
    public static class ValidAspectAnnotation {
        @Around("execution(* *.getName())")
        public Object invalid() throws Throwable { return "around"; }
    }

    @Configuration
    public static class ValidConfigClass {
        @Bean
        public TestBean alice() { return new TestBean(); }
    }

    public static class InvalidInheritanceFromConcreteAspect extends AroundSingletonCountingAdvice { }

    @Aspect
    public static class AroundSingletonCountingAdvice extends AbstractSingletonCountingAdvice {
        @Around("execution(* *.getName())")
        public Object newValue() throws Throwable { return "around"; }
    }

    @Aspect
    public static class AroundAdviceWithNamedPointcut extends AbstractSingletonCountingAdvice {

        @Pointcut("execution(* *.getName())")
        public void getName() { }

        @Around("getName()")
        public Object newValue() throws Throwable { return "around"; }
    }


    @Aspect
    public abstract static class AbstractSingletonCountingAdvice extends CountingConfiguration {
        @Before("execution(* *.getSpouse())")
        public void doesntMatter() { }
    }

    public static class InnerClassAdviceConfig extends CountingConfiguration {
        // This is enough to bring it in
        @Aspect
        static class InnerAroundAdvice extends AroundAdviceClass { }
    }

    public static class SingletonCountingAdvice extends AbstractSingletonCountingAdvice { }


    @AspectJAutoProxy(proxyTargetClass=true)
    @Configuration
    public abstract static class CountingConfiguration {
        // map from target to invocation count
        public static Map<Object, Integer> counts = new HashMap<Object, Integer>();

        public static int getCount(Object target) {
            Integer count = counts.get(target);
            return (count != null) ? count : 0;
        }

        @Bean(scope = DefaultScopes.PROTOTYPE)
        public TestBean advised() { return new TestBean("tony"); }

        @Bean(autowire = Autowire.BY_TYPE, dependencyCheck = DependencyCheck.ALL)
        public DependsOnTestBean dotb() { return new DependsOnTestBean(); }
    }


    // Invalid, doesn't have aspect tag
    public static class InvalidAroundAdviceClassWithNoAspectAnnotation {
        @Around("execution(* *.getName())")
        public Object newValue() throws Throwable { return "around"; }
    }

    @Aspect
    public abstract static class ValidAroundAdviceClassWithAspectAnnotation {
        @Around("execution(* *.getName())")
        public Object newValue() throws Throwable { return "around"; }
    }

    @Aspect
    @Configuration
    public abstract static class AroundAdviceClass extends ValidAroundAdviceClassWithAspectAnnotation { }

    @AspectJAutoProxy
    @Aspect
    @Configuration
    public static class SumAroundAdvice {
        @Around("execution(int *.returnZero(int, int)) && args(a,b)")
        public Object newValue(int a, int b) throws Throwable { return a + b; }

        @Bean
        public ReturnZero willAdd() { return new ReturnZero(); }
    }

    public static class ReturnZero {
        public int returnZero(int a, int b) { return 0; }
    }

}
