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

import static org.junit.Assert.*;

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

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

import org.springframework.config.java.annotation.AutoBean;
import org.springframework.config.java.annotation.AutowireMode;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.config.java.internal.model.AutoBeanMethod.AbstractAutoBeanReturnTypeError;
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 AutoBean} annotation support.
 *
 * @author  Chris Beams
 */
public class AutoBeanTests {

    @Test
    public void instantiationViaAutoBean() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(InstantiationViaAutoBean.class);
        Assert.assertNotNull(ctx.getBean("a"));
    }


    @Test
    public void setterAutowiring() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(BasicSetterAutowiring.class);
        Assert.assertNotNull(ctx.getBean(Service.class).repository);
    }

    @Test
    public void constructorAutowiring() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(ConstructorAutowiring.class);
        Service service = ctx.getBean(Service.class);
        Assert.assertNotNull(service);
        Assert.assertNotNull(service.repository);
    }


    @Test
    public void autoBeanReturnTypeMustBeConcreteType() {
        try {
            new JavaConfigApplicationContext(InterfaceAutoBeanConfig.class);
            fail("exception expected");
        } catch (MalformedJavaConfigurationException ex) {
            assertTrue(ex.containsError(AbstractAutoBeanReturnTypeError.class));
        }
    }


    @Test
    public void testValidAutoBean() {
        JavaConfigApplicationContext ctx = new JavaConfigApplicationContext(ValidAutoBeanConfig.class);

        TestBean kerry = ctx.getBean(TestBean.class, "kerry");
        assertEquals("AutoBean was not autowired", "Rod", kerry.getSpouse().getName());
    }

    @Test
    public void testBeanDefinitionCount() throws Exception {
        // 1 @Bean + 1 @Autobean + 1 Configuration
        assertEquals(3, getNonInternalBeanDefinitionCount(new JavaConfigApplicationContext(ValidAutoBeanConfig.class)));
    }

    @Test
    public void testInvalidAutoBean() {
        try {
            new JavaConfigApplicationContext(InvalidAutoBeanConfig.class);
            fail("exception expected");
        } catch (MalformedJavaConfigurationException ex) {
            assertTrue(ex.containsError(AbstractAutoBeanReturnTypeError.class));
        }
    }

    @Configuration
    abstract static class InstantiationViaAutoBean {
        @AutoBean
        public abstract TestBean a();
    }

    @Configuration
    abstract static class BasicSetterAutowiring {
        @AutoBean
        public abstract Service service();

        @Bean
        public Repository repository() { return new Repository(new DataSource()); }
    }

    @Configuration
    abstract static class ConstructorAutowiring {
        @Bean
        public Service service() {
            Service service = new Service();
            service.setRepository(repos());
            return service;
        }

        @AutoBean(autowire = AutowireMode.CONSTRUCTOR)
        public abstract Repository repos();

        @AutoBean
        public abstract DataSource dataSource();
    }

    @Configuration
    abstract static class InterfaceAutoBeanConfig {
        @AutoBean
        public abstract ITestBean alice();
    }

    abstract static class ValidAutoBeanConfig extends ConfigurationSupport {
        @Bean
        public TestBean rod() { return new TestBean("Rod"); }

        @AutoBean
        public abstract TestBean kerry();
    }

    public abstract static class InvalidAutoBeanConfig extends ConfigurationSupport {
        @Bean
        public TestBean rod() { return new TestBean(kerry()); }

        // Invalid, as it's on an interface type
        @AutoBean
        public abstract ITestBean kerry();
    }


}


class Service {
    Repository repository;

    public void setRepository(Repository repos) { this.repository = repos; }
}

class Repository {
    final DataSource dataSource;

    public Repository(DataSource dataSource) { this.dataSource = dataSource; }
}

class DataSource { }
