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

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

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

import org.springframework.config.java.annotation.AutoBean;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.context.JavaConfigApplicationContext;


/**
 * Demonstrates a natural limitation of JavaConfig, and some ways to work around it.
 *
 * <p/>JavaConfig cannot gracefully handle explicit circular references. Because object creation is
 * in the developer's hands (i.e.: there are no PropertyValues as in XML) JavaConfig has no way of
 * preventing the user from creating a circular reference between beans see
 * {@link IllegalCircularConfiguration} below.</p>
 *
 * <p>With that said, a number of possiblities do exist for handling this situation:</p>
 *
 * <ul>
 *   <li>use {@link AutoBean @AutoBean} (see {@link LegalCircularConfigWithOneAutoBean})</li>
 *   <li>use {@link Bean#autowire() @Bean(autowire=...)} (see
 *     {@link LegalCircularConfigWithTwoBeanAutowires})</li>
 *   <li>use {@link Autowired} in combination with {@link AutowiredAnnotationBeanPostProcessor} (see
 *     {@link LegalCircularConfigWithAtAutowiredSupport})</li>
 * </ul>
 *
 * @author  Chris Beams
 * @see     SJC-94
 */
public class CircularDependenciesTests {

    @Ignore // actually causing a StackOverflow takes too long
    @Test
    public void illegalCircularConfigurationThrowsStackOverflow() {
        new JavaConfigApplicationContext(IllegalCircularConfiguration.class);
    }

    @Test
    public void legalCircularConfigurationWithOneAutoBean() {
        JavaConfigApplicationContext context =
            new JavaConfigApplicationContext(LegalCircularConfigWithOneAutoBean.class);
        Assert.assertNotNull(context.getBean(A.class).getB());
        Assert.assertNotNull(context.getBean(B.class).getA());
    }


    @Test
    public void legalCircularConfigurationWithTwoBeanAutowires() {
        JavaConfigApplicationContext context =
            new JavaConfigApplicationContext(LegalCircularConfigWithTwoBeanAutowires.class);
        Assert.assertNotNull(context.getBean(A.class).getB());
        Assert.assertNotNull(context.getBean(B.class).getA());
    }


    @Test
    public void legalCircularConfigurationWithTwoAutoBeans() {
        JavaConfigApplicationContext context =
            new JavaConfigApplicationContext(LegalCircularConfigWithTwoAutoBeans.class);
        Assert.assertNotNull(context.getBean(A.class).getB());
        Assert.assertNotNull(context.getBean(B.class).getA());
    }


    @Test
    public void legalCircularConfigurationWithAtAutowiredSupport() {
        JavaConfigApplicationContext context =
            new JavaConfigApplicationContext(LegalCircularConfigWithAtAutowiredSupport.class);
        Assert.assertNotNull(context.getBean(X.class).getY());
        Assert.assertNotNull(context.getBean(Y.class).getX());
    }

    static class IllegalCircularConfiguration {
        @Bean
        public A a() {
            A a = new A();
            a.setB(b());
            return a;
        }

        @Bean
        public B b() {
            B b = new B();
            b.setA(a());
            return b;
        }
    }

    @Configuration
    abstract static class LegalCircularConfigWithOneAutoBean {
        @AutoBean
        public abstract A a();

        @Bean(autowire = Autowire.BY_TYPE)
        public B b() {
            B b = new B();
            return b;
        }
    }

    @Configuration
    static class LegalCircularConfigWithTwoBeanAutowires {
        @Bean(autowire = Autowire.BY_TYPE)
        public A a() { return new A(); }

        @Bean(autowire = Autowire.BY_TYPE)
        public B b() { return new B(); }
    }

    @Configuration
    abstract static class LegalCircularConfigWithTwoAutoBeans {
        @AutoBean
        public abstract A a();

        @AutoBean
        public abstract B b();
    }

    @Configuration
    abstract static class LegalCircularConfigWithAtAutowiredSupport {
        @Bean
        public X a() { return new X(); }

        @Bean
        public Y b() { return new Y(); }

        @AutoBean
        public abstract AutowiredAnnotationBeanPostProcessor aabpp();
    }

}

class B {
    private A a;

    public A getA() { return a; }

    public void setA(A a) { this.a = a; }
}

class A {
    private B b;

    public B getB() { return b; }

    public void setB(B b) { this.b = b; }
}


class X {
    private Y y;

    public Y getY() { return y; }

    @Autowired
    public void setY(Y y) { this.y = y; }
}

class Y {
    private X x;

    public X getX() { return x; }

    @Autowired
    public void setX(X x) { this.x = x; }
}
