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

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.*;
import static test.common.properties._package.PROPERTIES_PATH;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.HashMap;

import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.CustomEditorConfigurer;
import org.springframework.config.java.annotation.Bean;
import org.springframework.config.java.annotation.Configuration;
import org.springframework.config.java.annotation.ExternalValue;
import org.springframework.config.java.annotation.valuesource.PropertiesValueSource;
import org.springframework.config.java.context.ConfigurableJavaConfigApplicationContext;
import org.springframework.config.java.context.JavaConfigApplicationContext;
import org.springframework.core.io.Resource;

import test.common.beans.TestBean;


/**
 * System tests for properties resolution via {@code @XxxValueSource} annotations
 * and {@link ExternalValue @ExternalValue} methods.
 *
 * @see     PropertiesValueSource
 * @see     EnvironmentValueSource
 * @see     ValueResolver
 * @see     ExternalValueFieldTests
 *
 * @author  Rod Johnson
 * @author  Chris Beams
 */
public class ExternalValueMethodTests {

    /** Test fixture: each test method must initialize */
    private ConfigurableJavaConfigApplicationContext ctx;

    /**
     * It is up to each individual test to initialize the context; null it out before each
     * subsequent test just to be safe.
     */
    @After
    public void nullOutContext() { ctx = null; }


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

    @Test
    public void testStringAndBooleanProperty() throws Exception {
        ctx = new JavaConfigApplicationContext(AbstractConfigurationDependsOnProperties.class);
        TestBean rod = ctx.getBean(TestBean.class, "rod");
        assertEquals("String property must be resolved correctly", "Rod", rod.getName());
        assertTrue("Boolean property must be resolved correctly", rod.isJedi());
    }

    @Test
    public void testIntProperty() throws Exception {
        ctx = new JavaConfigApplicationContext(AbstractConfigurationDependsOnProperties.class);
        TestBean rod = ctx.getBean(TestBean.class, "rod");
        assertEquals("int property must be resolved correctly", 37, rod.getAge());
    }

    @Configuration
    @PropertiesValueSource(locations=PROPERTIES_PATH)
    abstract static class AbstractConfigurationDependsOnProperties {
        @Bean
        public TestBean rod() {
            TestBean rod = new TestBean();
            rod.setName(getName());
            rod.setAge(ignoreThisNameDueToAnnotationValue());

            rod.setJedi(jedi());
            return rod;
        }

        @ExternalValue
        public abstract String getName();

        @ExternalValue("age")
        public abstract int ignoreThisNameDueToAnnotationValue();

        @ExternalValue
        protected abstract boolean jedi();
    }

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


    @Test
    public void testDefaultValueInImplementationBody() throws Exception {
        ctx = new JavaConfigApplicationContext(DefaultValuesConfig.class);
        TestBean rod = ctx.getBean(TestBean.class, "rod");
        assertEquals("int property must default correctly if there's a concrete method", 25, rod.getAge());
    }

    @Configuration
    @PropertiesValueSource(locations=PROPERTIES_PATH)
    abstract static class DefaultValuesConfig {
        @Bean
        public TestBean rod() {
            TestBean rod = new TestBean();
            rod.setName(getName());
            rod.setAge(otherNumber());
            return rod;
        }

        @ExternalValue
        public abstract String getName();

        @ExternalValue
        public int otherNumber() { return 25; }
    }


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

    @Test(expected = BeanCreationException.class)
    public void testUnresolved() throws Exception {
        // unresolvedName will cause an exception
        ctx = new JavaConfigApplicationContext(MissingValuesConfig.class);
    }

    @Configuration
    @PropertiesValueSource(locations=PROPERTIES_PATH)
    abstract static class MissingValuesConfig {
        @Bean
        public TestBean rod() { return new TestBean(unresolvedName()); }

        @ExternalValue
        public abstract String unresolvedName();
    }


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

    @Test
    public void resolveExternalValueAsResource() throws IOException {
        ctx = new JavaConfigApplicationContext(ResourceExternalValuesConfig.class);
        Resource resource = ctx.getBean(ResourceHolder.class).resource;
        assertThat(resource, notNullValue());
        int b = resource.getInputStream().read();
        assertThat(b, equalTo(-1));
    }

    public static class ResourceHolder {
        public Resource resource;
    }

    @Configuration
    @PropertiesValueSource(locations="resources.properties", relativeTo=ResourceExternalValuesConfig.class)
    public static abstract class ResourceExternalValuesConfig {
        public abstract @ExternalValue("resource.1") Resource resource1();

        public @Bean ResourceHolder holder() {
            ResourceHolder holder = new ResourceHolder();
            holder.resource = resource1();
            return holder;
        }
    }


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

    @Ignore // see SJC-179
    @Test
    public void resolveCustomExternalValue() {
        ctx = new JavaConfigApplicationContext(CustomExternalValuesConfig.class);
        CustomValue value = ctx.getBean(CustomValueHolder.class).value;
        assertThat(value, notNullValue());
    }

    public static class CustomValueHolder {
        public CustomValue value;
    }

    public static class CustomValue {
        private String value;
        public void setValue(String value) { this.value = value; }
        public String getValue() { return value; }
    }

    public static class CustomValuePropertyEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            CustomValue custom = new CustomValue();
            custom.setValue(text);
            setValue(custom);
        }
    }

    @Configuration
    @PropertiesValueSource(locations="resources.properties", relativeTo=ResourceExternalValuesConfig.class)
    public static abstract class CustomExternalValuesConfig {
        public abstract @ExternalValue("resource.1") CustomValue resource1();

        public @Bean CustomValueHolder holder() {
            CustomValueHolder holder = new CustomValueHolder();
            holder.value = resource1();
            return holder;
        }

        public @Bean CustomEditorConfigurer customEditorConfigurer() {
            HashMap<Class<?>, PropertyEditor> customEditors = new HashMap<Class<?>, PropertyEditor>();
            customEditors.put(CustomValue.class, new CustomValuePropertyEditor());

            CustomEditorConfigurer registrar = new CustomEditorConfigurer();
            registrar.setCustomEditors(customEditors);
            return registrar;
        }
    }
}
