/*
 * 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 org.springframework.config.java.internal.model;

import static org.easymock.EasyMock.*;

import java.lang.reflect.Modifier;

import static org.junit.Assert.assertEquals;

import static org.springframework.beans.factory.support.AbstractBeanDefinition.AUTOWIRE_BY_TYPE;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;

import static org.springframework.config.java.internal.factory.BeanVisibility.HIDDEN;
import static org.springframework.config.java.internal.factory.BeanVisibility.PUBLIC;
import static org.springframework.config.java.internal.model.AutoBeanMethodTests.VALID_AUTOBEAN_METHOD;
import static org.springframework.config.java.internal.util.AnnotationExtractionUtils.extractClassAnnotation;
import static org.springframework.config.java.internal.util.AnnotationExtractionUtils.extractMethodAnnotation;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import org.springframework.beans.BeanMetadataAttribute;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.ImportXml;
import org.springframework.config.java.annotation.Primary;
import org.springframework.config.java.context.DefaultBeanFactoryProvider;
import org.springframework.config.java.internal.factory.DefaultJavaConfigBeanFactory;
import org.springframework.config.java.internal.factory.JavaConfigBeanFactory;
import org.springframework.config.java.internal.factory.support.ConfigurationModelBeanDefinitionReader;
import org.springframework.config.java.internal.util.AnnotationExtractionUtils;
import org.springframework.config.java.internal.util.MethodAnnotationPrototype;
import org.springframework.config.java.naming.MethodNameStrategy;
import org.springframework.config.java.util.DefaultScopes;

import test.common.beans.TestBean;


/**
 * Unit tests for {@link ConfigurationModelBeanDefinitionReader}.
 *
 * <p>TODO: clean up duplication between the tests herein. The expectations against EasyMock are
 * very fragile, and often break when refactoring other parts of the system. consider a different
 * approach. Note: now that createNiceMock is being used, cleaning this up should be quite a bit
 * easier.</p>
 *
 * @author  Chris Beams
 */
@Ignore // the mocks in these tests are far too fragile.  Rework this class.
        // in the meantime, system tests catch all this same stuff.
public class ConfigurationModelBeanDefinitionReaderTests {

    private ConfigurationModelBeanDefinitionReader renderer;
    private JavaConfigBeanFactory registry;
    private ConfigurationModel model;

    @Before
    public void setUp() {
        registry = createNiceMock(JavaConfigBeanFactory.class);
        renderer = new ConfigurationModelBeanDefinitionReader(registry);
        model = new ConfigurationModel();
    }

    @Test
    public void testXmlImport() {
        DefaultListableBeanFactory parentBF = new DefaultListableBeanFactory();
        JavaConfigBeanFactory bf = new DefaultJavaConfigBeanFactory(parentBF, new DefaultBeanFactoryProvider());
        ConfigurationModelBeanDefinitionReader reader = new ConfigurationModelBeanDefinitionReader(bf);

        @ImportXml(locations="org/springframework/config/java/internal/model/xmlForImport.xml")
        class Prototype { }

        ImportXml importAnno = AnnotationExtractionUtils.extractClassAnnotation(ImportXml.class, Prototype.class);

        ConfigurationModel model =
            new ConfigurationModel()
                .add(
                    new ConfigurationClass("c")
                        .addPluginAnnotation(importAnno)
                        .add(new BeanMethod("javaConfigured", Modifier.PUBLIC))
                    );

        reader.loadBeanDefinitions(model);

        // check the parent (publicly visible beans) bean factory to see that both
        // Java and XML beans have been registered
        Assert.assertTrue("should have contained java-configured bean",
                bf.getParentBeanFactory().containsBeanDefinition("javaConfigured"));
        Assert.assertTrue("should have contained xml-configured bean",
                bf.getParentBeanFactory().containsBeanDefinition("xmlConfigured"));
    }

    @Test
    public void render() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = "order";

        // create a simple configuration model
        model.add(new ConfigurationClass(configClassName).add(new BeanMethod(beanName)));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.getBeanNamingStrategy()).andReturn(new MethodNameStrategy());

        expect(registry.containsBeanDefinition(beanName)).andReturn(false);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);

        expect(registry.getParentBeanFactory()).andReturn(null);

        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);
        expectLastCall();

        @SuppressWarnings("deprecation")
        BeanDefinition beanDef = rootBeanDefinition((String) null).setFactoryBean(configClassName, beanName)
                                                                  .getBeanDefinition();
        registry.registerBeanDefinition(beanName, beanDef, HIDDEN);
        expectLastCall();

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

    @Test
    public void withScope() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = "order";

        // create a simple configuration model
        Bean metadata = extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean(scope = DefaultScopes.PROTOTYPE)
                public void targetMethod() { }
            }.getClass());
        model.add(new ConfigurationClass(configClassName).add(new BeanMethod(beanName, metadata)));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.getBeanNamingStrategy()).andReturn(new MethodNameStrategy());

        expect(registry.containsBeanDefinition(beanName)).andReturn(false);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);
        
        expect(registry.getParentBeanFactory()).andReturn(null);

        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);
        expectLastCall();


        RootBeanDefinition rbd = new RootBeanDefinition();
        rbd.setFactoryBeanName(configClassName);
        rbd.setFactoryMethodName(beanName);
        rbd.setScope(DefaultScopes.PROTOTYPE);
        registry.registerBeanDefinition(beanName, rbd, HIDDEN);
        expectLastCall();

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

    @Test
    public void withAutoBean() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = VALID_AUTOBEAN_METHOD.getName();

        // create a simple configuration model
        model.add(new ConfigurationClass(configClassName).add(VALID_AUTOBEAN_METHOD));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);

        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);
        expectLastCall();

        RootBeanDefinition rbd = new RootBeanDefinition();
        rbd.setBeanClassName(TestBean.class.getName());
        rbd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        registry.registerBeanDefinition(beanName, rbd, PUBLIC);
        expectLastCall();

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

    @Test
    public void renderWithPrimaryBean() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = "order";

        // create a simple configuration model
        Bean metadata = extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean(primary = Primary.TRUE)
                public void targetMethod() { }
            }.getClass());
        model.add(new ConfigurationClass(configClassName).add(new BeanMethod(beanName, metadata)));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.getBeanNamingStrategy()).andReturn(new MethodNameStrategy());

        expect(registry.containsBeanDefinition(beanName)).andReturn(false);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);

        expect(registry.getParentBeanFactory()).andReturn(null);

        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);
        expectLastCall();


        RootBeanDefinition rbd = new RootBeanDefinition();
        rbd.setFactoryBeanName(configClassName);
        rbd.setFactoryMethodName(beanName);
        rbd.setPrimary(true);
        registry.registerBeanDefinition(beanName, rbd, HIDDEN);
        expectLastCall();

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

    /*
     * Tests @Configuration(defaultAutowire=Autowire.BY_TYPE)
     */
    @Test
    public void renderWithDefaultAutowireByType() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = "order";

        // create a simple configuration model
        @Configuration(defaultAutowire = Autowire.BY_TYPE)
        class Prototype { }

        Configuration metadata = extractClassAnnotation(Configuration.class, Prototype.class);

        model.add(new ConfigurationClass(configClassName, metadata).add(new BeanMethod(beanName)));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.getBeanNamingStrategy()).andReturn(new MethodNameStrategy());

        expect(registry.containsBeanDefinition(beanName)).andReturn(false);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);

        expect(registry.getParentBeanFactory()).andReturn(null);

        // expect the registration of our Configuration class above
        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);
        expectLastCall();

        // expect the registration of the @Bean method above
        RootBeanDefinition rbd = new RootBeanDefinition();
        rbd.setFactoryBeanName(configClassName);
        rbd.setFactoryMethodName(beanName);
        rbd.setAutowireMode(AUTOWIRE_BY_TYPE);
        registry.registerBeanDefinition(beanName, rbd, HIDDEN);
        expectLastCall();

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

    /*
     * Tests @Bean(autowire=Autowire.BY_TYPE)
     */
    @Test
    public void renderWithBeanAutowireByType() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = "order";

        // create a simple configuration model
        Bean metadata = AnnotationExtractionUtils.extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean(autowire = Autowire.BY_TYPE)
                public void targetMethod() { }
            }.getClass());

        model.add(new ConfigurationClass(configClassName).add(new BeanMethod(beanName, metadata)));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.getBeanNamingStrategy()).andReturn(new MethodNameStrategy());

        expect(registry.containsBeanDefinition(beanName)).andReturn(false);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);

        expect(registry.getParentBeanFactory()).andReturn(null);

        // expect the registration of our Configuration class above
        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);
        expectLastCall();

        // expect the registration of the @Bean method above
        RootBeanDefinition rbd = new RootBeanDefinition();
        rbd.setFactoryBeanName(configClassName);
        rbd.setFactoryMethodName(beanName);
        rbd.setAutowireMode(AUTOWIRE_BY_TYPE);
        registry.registerBeanDefinition(beanName, rbd, HIDDEN);
        expectLastCall();

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

    @Test
    public void renderWithAliases() {
        String configClassName = "com.acme.OrderConfig";
        String beanName = "order";

        // create a simple configuration model
        Bean metadata = extractMethodAnnotation(Bean.class, new MethodAnnotationPrototype() {
                @Bean(aliases = { "tom", "dick", "harry" })
                public void targetMethod() { }
            }.getClass());

        model.add(new ConfigurationClass(configClassName).add(new BeanMethod(beanName, metadata)));
        model.assertIsValid();

        // encode expectations
        expect(registry.getBeanDefinitionCount()).andReturn(0);

        expect(registry.getBeanNamingStrategy()).andReturn(new MethodNameStrategy());

        expect(registry.containsBeanDefinition(beanName)).andReturn(false);

        expect(registry.containsBeanDefinition(configClassName, PUBLIC)).andReturn(false);

        expect(registry.getParentBeanFactory()).andReturn(null);

        // expect the registration of our Configuration class above
        RootBeanDefinition configBeanDef = new RootBeanDefinition();
        configBeanDef.setBeanClassName(configClassName);
        configBeanDef.addMetadataAttribute(new BeanMetadataAttribute(ConfigurationClass.IS_CONFIGURATION_CLASS, true));
        registry.registerBeanDefinition(configClassName, configBeanDef, PUBLIC);

        // expect the registration of the @Bean method above
        RootBeanDefinition rbd = new RootBeanDefinition();
        rbd.setFactoryBeanName(configClassName);
        rbd.setFactoryMethodName(beanName);
        registry.registerBeanDefinition(beanName, rbd, HIDDEN);

        // expect registration of aliases
        registry.registerAlias("order", "tom", PUBLIC);
        registry.registerAlias("order", "dick", PUBLIC);
        registry.registerAlias("order", "harry", PUBLIC);

        expect(registry.getBeanDefinitionCount()).andReturn(2);

        // all done with expectations
        replay(registry);

        // execute assertions and verifications
        assertEquals(2, renderer.loadBeanDefinitions(model));

        verify(registry);
    }

}
