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

import static org.junit.Assert.*;

import org.junit.Test;

import org.springframework.config.java.internal.model.ConfigurationClass.IllegalBeanOverrideError;
import org.springframework.config.java.internal.model.ConfigurationModel.EmptyModelError;

/**
 * Unit tests for {@link ConfigurationModel}.
 *
 * @author Chris Beams
 */
public class ConfigurationModelTests {

    // -- equivalence tests ---------------------------------------------------

    public @Test void emptyModelsAreEqual() {
        ConfigurationModel modelA = new ConfigurationModel();
        ConfigurationModel modelB = new ConfigurationModel();

        assertThat(modelA, equalTo(modelB));
    }

    public @Test void emptyVsNonEmptyModelsAreNotEqual() {
        ConfigurationModel empty = new ConfigurationModel();
        ConfigurationModel nonEmpty = new ConfigurationModel().add(new ConfigurationClass("a"));

        assertThat(empty, not(equalTo(nonEmpty)));
    }

    public @Test void equivalentModelsAreEqual() {
        ConfigurationModel modelA = new ConfigurationModel().add(new ConfigurationClass("a"));
        ConfigurationModel modelB = new ConfigurationModel().add(new ConfigurationClass("a"));

        assertThat(modelA, equalTo(modelB));
    }

    public @Test void nonEquivalentModelsAreNotEqual() {
        ConfigurationModel modelA = new ConfigurationModel().add(new ConfigurationClass("a"));
        ConfigurationModel modelB = new ConfigurationModel().add(new ConfigurationClass("b"));

        assertThat(modelA, not(equalTo(modelB)));
    }

    public @Test void equivalentDeepModelsAreEqual() {
        class util {
            ConfigurationModel createDeepModel() {
                return new ConfigurationModel()
                    .add(new ConfigurationClass("classX")
                        .add(new BeanMethod("methodX")));
            }
        }
        ConfigurationModel modelA = new util().createDeepModel();
        ConfigurationModel modelB = new util().createDeepModel();

        assertThat(modelA, not(sameInstance(modelB)));
        assertThat(modelA, equalTo(modelB));
    }

    /**
     * The order that classes are introduced into the model is significant - it
     * will determine precedence in bean overriding cases.
     */
    public @Test void configClassOrderIsSignificant() {
        ConfigurationModel modelA = new ConfigurationModel()
            .add(new ConfigurationClass("classA"))
            .add(new ConfigurationClass("classB"));

        ConfigurationModel modelB = new ConfigurationModel()
            .add(new ConfigurationClass("classB"))
            .add(new ConfigurationClass("classA"));

        assertThat(modelA, not(equalTo(modelB)));
    }

    /**
     * The order that Bean methods are introduced to the model is not significant -
     * given the constraints of java classes, they are guaranteed to be unique within
     * any given class, so precedence with regard to bean overriding is not a concern.
     *
     * @see {@link #configClassOrderIsSignificant()}
     * @see {@link AsmConfigurationParserTests#beanMethodOrderIsNotSignificantA()}
     */
    public @Test void beanMethodOrderIsNotSignificant() {
        ConfigurationModel modelA = new ConfigurationModel()
            .add(new ConfigurationClass("classA")
                .add(new BeanMethod("methodA"))
                .add(new BeanMethod("methodB")))
            .add(new ConfigurationClass("classB")
                .add(new BeanMethod("methodC"))
                .add(new BeanMethod("methodD")));

        ConfigurationModel modelB = new ConfigurationModel()
            .add(new ConfigurationClass("classA")
                .add(new BeanMethod("methodA"))
                .add(new BeanMethod("methodB")))
            .add(new ConfigurationClass("classB")
                .add(new BeanMethod("methodD")) // only difference!
                .add(new BeanMethod("methodC")));

        assertThat(modelA, equalTo(modelB));
    }


    // -- validation tests ---------------------------------------------------

    public @Test void modelWithNoConfigurationClassesIsNotValid() {
        ConfigurationModel model = new ConfigurationModel();

        UsageError[] errors = model.detectUsageErrors();
        assertEquals("expected exactly one error: " + errors, 1, errors.length);
        assertThat(errors[0], instanceOf(EmptyModelError.class));
    }

    public @Test void validateCascadesToConfigurationClasses() {
        // configuration class is invalid (no beans defined), so should bubble up as an error at the top level
        UsageError[] errors = new ConfigurationModel().add(ConfigurationClassTests.INVALID_CONFIGURATION_CLASS).detectUsageErrors();
        assertTrue("should have reflected nested validation errors", errors.length > 0);
    }

    public @Test void validationChecksForIllegalBeanOverrides() {
        ConfigurationModel model = new ConfigurationModel()
            .add(new ConfigurationClass("a").add(new BeanMethod("m")))
            .add(new ConfigurationClass("b").add(new BeanMethod("m", BeanMethodTests.FINAL_BEAN_ANNOTATION)))
            .add(new ConfigurationClass("c").add(new BeanMethod("m")))
        ;

        try {
            model.assertIsValid();
            fail("should have thrown exception - configuration " +
                 "class 'c' illegally overrides final bean method 'm' declared in class 'b'");
        } catch (MalformedJavaConfigurationException ex) {
            assertTrue(ex.containsError(IllegalBeanOverrideError.class));
        }
    }

    public @Test void validationChecksForIllegalBeanOverridesIncludingImports() {
        ConfigurationModel model = new ConfigurationModel()
            .add(new ConfigurationClass("a").add(new BeanMethod("m")))
            .add(new ConfigurationClass("b").add(new BeanMethod("m", BeanMethodTests.FINAL_BEAN_ANNOTATION)))
            .add(new ConfigurationClass("c").addImportedClass(new ConfigurationClass("i").add(new BeanMethod("m"))))
        ;

        try {
            model.assertIsValid();
            fail("should have thrown exception - imported configuration " +
                 "class 'i' illegally overrides bean method 'm' declared in class 'b'");
        } catch (MalformedJavaConfigurationException ex) {
            assertTrue(ex.containsError(IllegalBeanOverrideError.class));
        }
    }

}
