/*
 * 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.atimport;

import static org.junit.Assert.*;

import static test.common.util.Assert.assertBeanDefinitionCount;

import org.junit.After;
import org.junit.Assert;
import org.junit.Test;

import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.ExternalBean;
import org.springframework.config.java.annotation.Import;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.internal.model.ConfigurationClass.IllegalBeanOverrideError;
import org.springframework.config.java.internal.model.MalformedJavaConfigurationException;
import org.springframework.config.java.support.ConfigurationSupport;

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


/**
 * System tests for {@link Import} annotation support.
 *
 * @author  Chris Beams
 */
public class ImportTests {
    private JavaConfigApplicationContext ctx;

    @After
    public void tearDown() {
        try {
            // just in case some test fails during loading and refresh is not
            // called
            ctx.refresh();
            ctx.close();
        } catch (Exception ex) {
            // ignore - it's cleanup time
        }

        ctx = null;
    }

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

    @Test
    public void hiddenBeansInImportedClasses() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(A.class);

        Assert.assertTrue(ctx.containsBean("publicFromA"));
        Assert.assertFalse(ctx.containsBean("hiddenFromA"));

        Assert.assertTrue(ctx.containsBean("publicFromB"));
        Assert.assertFalse(ctx.containsBean("hiddenFromB"));

        TestBean hiddenFromB = ctx.getBean(TestBean.class, "refersToHiddenFromB");
        Assert.assertEquals("hidden-b", hiddenFromB.getName());
    }

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

    @Test
    public void testProcessImports() {
        ctx = new JavaConfigApplicationContext(ConfigurationWithImportAnnotation.class);

        int configClasses = 2;
        int beansInClasses = 2;

        assertBeanDefinitionCount(ctx, (configClasses + beansInClasses));
    }

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

    @Test
    public void testImportAnnotationWithTwoLevelRecursion() {
        ctx = new JavaConfigApplicationContext(AppConfig.class);

        int configClasses = 2;
        int beansInClasses = 3;

        assertBeanDefinitionCount(ctx, (configClasses + beansInClasses));
    }

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

    @Test
    public void testImportAnnotationWithThreeLevelRecursion() {
        ctx = new JavaConfigApplicationContext(FirstLevel.class);

        int configClasses = 3;
        int beansInClasses = 5;

        assertBeanDefinitionCount(ctx, (configClasses + beansInClasses));
    }

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

    @Test
    public void testImportAnnotationWithMultipleArguments() {
        ctx = new JavaConfigApplicationContext(WithMultipleArgumentsToImportAnnotation.class);

        int configClasses = 3;
        int beansInClasses = 3;

        assertBeanDefinitionCount(ctx, (configClasses + beansInClasses));
    }


    @Test
    public void testImportAnnotationWithMultipleArgumentsResultingInDuplicateBeanDefinition() {
        try {
            ctx = new JavaConfigApplicationContext(WithMultipleArgumentsThatWillCauseDuplication.class);
            fail("exception expected");
        } catch (MalformedJavaConfigurationException ex) {
            assertTrue(ex.containsError(IllegalBeanOverrideError.class));
        }
    }

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

    @Test
    public void testImportAnnotationOnInnerClasses() {
        ctx = new JavaConfigApplicationContext(OuterConfig.InnerConfig.class);

        int configClasses = 2;
        int beansInClasses = 2;

        assertBeanDefinitionCount(ctx, (configClasses + beansInClasses));
    }

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

    @Test
    public void testImportInConjunctionWithDeclaringClass() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(Config.class);
        ctx.getBean(String.class, "name");
        ctx.getBean("foo");
        TestBean bar = ctx.getBean(TestBean.class, "bar");
        assertEquals("lewis", bar.getName());
    }

    @Configuration
    @Import(B.class)
    public abstract static class A {
        @Bean
        public TestBean publicFromA() { return new TestBean(); }

        @Bean
        TestBean hiddenFromA() { return new TestBean(); }

        @Bean
        public TestBean refersToPublicFromB() { return publicFromB(); }

        @Bean
        public TestBean refersToHiddenFromB() { return hiddenFromB(); }


        @ExternalBean
        public abstract TestBean publicFromB();

        @ExternalBean
        abstract TestBean hiddenFromB();
    }

    @Configuration
    public static class B {
        @Bean
        public TestBean publicFromB() { return new TestBean(); }

        @Bean
        TestBean hiddenFromB() { return new TestBean("hidden-b"); }
    }

    @Configuration
    @Import(OtherConfiguration.class)
    static class ConfigurationWithImportAnnotation {
        @Bean
        public ITestBean one() { return new TestBean(); }
    }

    @Configuration
    static class OtherConfiguration {
        @Bean
        public ITestBean two() { return new TestBean(); }
    }

    @Configuration
    @Import(DataSourceConfig.class)
    static class AppConfig extends ConfigurationSupport {
        @Bean
        public ITestBean transferService() { return new TestBean(accountRepository()); }

        @Bean
        public ITestBean accountRepository() { return new TestBean((ITestBean) this.getBean("dataSourceA")); }
    }

    @Configuration
    static class DataSourceConfig {
        @Bean
        public ITestBean dataSourceA() { return new TestBean(); }
    }

    @Configuration
    @Import(SecondLevel.class)
    static class FirstLevel {
        @Bean
        public TestBean m() { return new TestBean(); }
    }

    @Configuration
    @Import(ThirdLevel.class)
    static class SecondLevel {
        @Bean
        public TestBean n() { return new TestBean(); }
    }

    @Configuration
    static class ThirdLevel {
        @Bean
        public ITestBean thirdLevelA() { return new TestBean(); }

        @Bean
        public ITestBean thirdLevelB() { return new TestBean(); }

        @Bean
        public ITestBean thirdLevelC() { return new TestBean(); }
    }

    @Configuration
    @Import({ LeftConfig.class, RightConfig.class })
    static class WithMultipleArgumentsToImportAnnotation {
        @Bean
        public TestBean m() { return new TestBean(); }
    }

    @Configuration
    static class LeftConfig {
        @Bean
        public ITestBean left() { return new TestBean(); }
    }

    @Configuration
    static class RightConfig {
        @Bean
        public ITestBean right() { return new TestBean(); }
    }

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

    @Configuration
    @Import({ Foo1.class, Foo2.class })
    static class WithMultipleArgumentsThatWillCauseDuplication { }

    @Configuration
    static class Foo1 {
        @Bean(allowOverriding = false)
        public ITestBean foo() { return new TestBean(); }
    }

    @Configuration
    static class Foo2 {
        @Bean
        public ITestBean foo() { return new TestBean(); }
    }

    @Configuration
    static class ExternalConfig {
        @Bean
        public ITestBean extBean() { return new TestBean(); }
    }

    @Configuration
    static class OuterConfig {
        @Bean
        String whatev() { return "whatev"; }

        @Configuration
        @Import(ExternalConfig.class)
        static class InnerConfig {
            @Bean
            public ITestBean innerBean() { return new TestBean(); }
        }
    }

    @Configuration
    public static class NameConfig {
        @Bean
        public String name() { return "lewis"; }
    }

    @Configuration
    @Import(NameConfig.class)
    public abstract static class Outer {
        @Bean
        public TestBean foo() { return new TestBean("foo"); }

        @Bean
        public TestBean bar() { return new TestBean(name()); }

        @ExternalBean
        abstract String name();

        @Configuration
        static class Other {
            @Bean
            TestBean alice() { return new TestBean("alice"); }
        }
    }

    @Configuration
    @Import({ Outer.Other.class })
    public static class Config { }

}
