/*
 * 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.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.junit.Test;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.Import;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.plugin.aop.AspectJAutoProxy;
import org.springframework.core.Ordered;

import test.common.beans.ITestBean;
import test.common.beans.TestBean;


/**
 * Integration tests for {@link AspectJAutoProxy @AspectJAutoProxy}.
 *
 * @author Chris Beams
 */
public class AspectJAutoProxyTests {

    private static final String ADVISED_NAME = "advised";
    private static final String ORIGINAL_NAME = "alice";
    private static final String GET_NAME_POINTCUT = "execution(String test.common.beans.TestBean.getName())";

    // ---------------------------------

    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true)
    static class InheritanceBasedConfig extends BaseConfig { }

    public @Test void testInheritanceBasedConfig() {
        assertAdvised(InheritanceBasedConfig.class, TestBean.class);
    }

    // ---------------------------------

    @Configuration
    @Import(BaseConfig.class)
    @AspectJAutoProxy(proxyTargetClass=true)
    static class ImportBasedConfig { }

    public @Test void testImportBasedConfig() {
        assertAdvised(ImportBasedConfig.class, TestBean.class);
    }

    // ---------------------------------

    @Configuration
    @AspectJAutoProxy
    static class InterfaceProxyConfig extends BaseConfig { }

    public @Test void testDynamicInterfaceProxy() {
        assertAdvised(InterfaceProxyConfig.class, ITestBean.class);
    }

    // ---------------------------------

    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true)
    static class ProxyTargetClassConfig extends BaseConfig { }

    public @Test void testTargetClassProxy() {
        assertAdvised(ProxyTargetClassConfig.class, TestBean.class);
    }

    // ---------------------------------

    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true, include={"bogus"})
    static class InvalidIncludePatternsConfig extends BaseConfig { }

    public @Test void testInvalidIncludePattern() {
        assertNotAdvised(InvalidIncludePatternsConfig.class, TestBean.class);
    }

    // ---------------------------------

    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true, include={"nameChangingAspect"})
    static class ValidIncludePatternsConfig extends BaseConfig { }

    public @Test void testValidIncludePattern() {
        assertAdvised(ValidIncludePatternsConfig.class, TestBean.class);
    }

    // ---------------------------------

    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true, include={"^name.*Aspect$"})
    static class ValidRegexIncludePatternsConfig extends BaseConfig { }

    public @Test void testValidRegexIncludePattern() {
        assertAdvised(ValidRegexIncludePatternsConfig.class, TestBean.class);
    }

    // ---------------------------------

    @Aspect
    private static class NamePrefixingAspect implements Ordered {
        private final int order;
        private final String prefix;

        public NamePrefixingAspect(String prefix, int order) {
            this.prefix = prefix;
            this.order = order;
        }

        @Before(GET_NAME_POINTCUT)
        public void prefixBeanName(JoinPoint jp) {
            TestBean testBean = (TestBean) jp.getTarget();
            testBean.setName(prefix + testBean.getName());
        }

        public int getOrder() {
            return order;
        }
    }

    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true)
    static class OrderedAspectsFooThenBarConfig {
        public @Bean TestBean testBean() {
            return new TestBean("beanName");
        }

        public @Bean NamePrefixingAspect foo() {
            return new NamePrefixingAspect("FOO", Ordered.LOWEST_PRECEDENCE);
        }

        public @Bean NamePrefixingAspect bar() {
            return new NamePrefixingAspect("BAR", Ordered.HIGHEST_PRECEDENCE);
        }
    }

    public @Test void testOrderingFooThenBarIsSupported() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(OrderedAspectsFooThenBarConfig.class);
        TestBean testBean = ctx.getBean(TestBean.class);
        assertThat(testBean.getName(), equalTo("FOOBARbeanName"));
    }


    @Configuration
    @AspectJAutoProxy(proxyTargetClass=true)
    static class OrderedAspectsBarThenFooConfig {
        public @Bean TestBean testBean() {
            return new TestBean("beanName");
        }

        public @Bean NamePrefixingAspect foo() {
            return new NamePrefixingAspect("FOO", Ordered.HIGHEST_PRECEDENCE);
        }

        public @Bean NamePrefixingAspect bar() {
            return new NamePrefixingAspect("BAR", Ordered.LOWEST_PRECEDENCE);
        }
    }

    public @Test void testOrderingBarThenFooIsSupported() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(OrderedAspectsBarThenFooConfig.class);
        TestBean testBean = ctx.getBean(TestBean.class);
        assertThat(testBean.getName(), equalTo("BARFOObeanName"));
    }

    // ---------------------------------
    
    /**
     * Given that Configuration classes are instantiated as 'just another bean',
     * it should be no problem to annotate a Configuration class as {@Code @Aspect}
     * as well and have any inline advice methods get applied.
     * 
     * @see SJC-215 for details about dependence on changes in AspectJ (the Aspect
     * annotation needs to be {@code @Inherited} for this to work.
     * NOTE: THIS TEST WILL CEASE TO PASS WHEN SJC-191 IS COMPLETE!
     */
    public @Test void atAspectConfigurationWithInlineAdviceIsSupported() {
        assertAdvised(AspectConfiguration.class, TestBean.class);
    }
    
    @AspectJAutoProxy(proxyTargetClass=true)
    @Aspect
    @Configuration
    static abstract class AspectConfiguration {
        @Around(GET_NAME_POINTCUT)
        public String returnAdvisedName() {
            return ADVISED_NAME;
        }
        
        public @Bean TestBean testBean() {
            return new TestBean(ORIGINAL_NAME);
        }
    }

    // ---------------------------------

    @Configuration
    static class BaseConfig {
        public @Bean NameChangingAspect nameChangingAspect() {
            return new NameChangingAspect();
        }

        public @Bean TestBean testBean() {
            return new TestBean(ORIGINAL_NAME);
        }
    }

    @Aspect
    static class NameChangingAspect {
        @Around(GET_NAME_POINTCUT)
        public String returnAdvisedName() {
            return ADVISED_NAME;
        }
    }

    private static <T extends ITestBean> void assertAdvised(Class<?> configClass, Class<T> beanType) {
        JavaConfigApplicationContext ctx =
            new JavaConfigApplicationContext(configClass);
        T bean = ctx.getBean(beanType);
        assertThat(bean.getName(), equalTo(ADVISED_NAME));
    }

    private static <T extends ITestBean> void assertNotAdvised(Class<?> configClass, Class<T> beanType) {
        JavaConfigApplicationContext ctx =
            new JavaConfigApplicationContext(configClass);
        T bean = ctx.getBean(beanType);
        assertThat(bean.getName(), equalTo(ORIGINAL_NAME));
    }

}
