/*
 * Copyright 2002-2016 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.context.support;

import java.util.concurrent.CopyOnWriteArrayList;

import org.junit.Test;

import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.Lifecycle;
import org.springframework.context.LifecycleProcessor;
import org.springframework.context.SmartLifecycle;
import org.springframework.tests.Assume;
import org.springframework.tests.TestGroup;

import static org.junit.Assert.*;

/**
 * @author Mark Fisher
 * @since 3.0
 */
public class DefaultLifecycleProcessorTests {

	@Test
	public void defaultLifecycleProcessorInstance() {
		StaticApplicationContext context = new StaticApplicationContext();
		context.refresh();
		Object lifecycleProcessor = new DirectFieldAccessor(context).getPropertyValue("lifecycleProcessor");
		assertNotNull(lifecycleProcessor);
		assertEquals(DefaultLifecycleProcessor.class, lifecycleProcessor.getClass());
	}

	@Test
	public void customLifecycleProcessorInstance() {
		BeanDefinition beanDefinition = new RootBeanDefinition(DefaultLifecycleProcessor.class);
		beanDefinition.getPropertyValues().addPropertyValue("timeoutPerShutdownPhase", 1000);
		StaticApplicationContext context = new StaticApplicationContext();
		context.registerBeanDefinition("lifecycleProcessor", beanDefinition);
		context.refresh();
		LifecycleProcessor bean = context.getBean("lifecycleProcessor", LifecycleProcessor.class);
		Object contextLifecycleProcessor = new DirectFieldAccessor(context).getPropertyValue("lifecycleProcessor");
		assertNotNull(contextLifecycleProcessor);
		assertSame(bean, contextLifecycleProcessor);
		assertEquals(1000L, new DirectFieldAccessor(contextLifecycleProcessor).getPropertyValue(
				"timeoutPerShutdownPhase"));
	}

	@Test
	public void singleSmartLifecycleAutoStartup() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
		bean.setAutoStartup(true);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean", bean);
		assertFalse(bean.isRunning());
		context.refresh();
		assertTrue(bean.isRunning());
		context.stop();
		assertFalse(bean.isRunning());
		assertEquals(1, startedBeans.size());
	}

	@Test
	public void singleSmartLifecycleAutoStartupWithLazyInit() throws Exception {
		StaticApplicationContext context = new StaticApplicationContext();
		RootBeanDefinition bd = new RootBeanDefinition(DummySmartLifecycleBean.class);
		bd.setLazyInit(true);
		context.registerBeanDefinition("bean", bd);
		context.refresh();
		DummySmartLifecycleBean bean = context.getBean("bean", DummySmartLifecycleBean.class);
		assertTrue(bean.isRunning());
		context.stop();
		assertFalse(bean.isRunning());
	}

	@Test
	public void singleSmartLifecycleAutoStartupWithLazyInitFactoryBean() throws Exception {
		StaticApplicationContext context = new StaticApplicationContext();
		RootBeanDefinition bd = new RootBeanDefinition(DummySmartLifecycleFactoryBean.class);
		bd.setLazyInit(true);
		context.registerBeanDefinition("bean", bd);
		context.refresh();
		DummySmartLifecycleFactoryBean bean = context.getBean("&bean", DummySmartLifecycleFactoryBean.class);
		assertTrue(bean.isRunning());
		context.stop();
		assertFalse(bean.isRunning());
	}

	@Test
	public void singleSmartLifecycleWithoutAutoStartup() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
		bean.setAutoStartup(false);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean", bean);
		assertFalse(bean.isRunning());
		context.refresh();
		assertFalse(bean.isRunning());
		assertEquals(0, startedBeans.size());
		context.start();
		assertTrue(bean.isRunning());
		assertEquals(1, startedBeans.size());
		context.stop();
	}

	@Test
	public void singleSmartLifecycleAutoStartupWithNonAutoStartupDependency() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean bean = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
		bean.setAutoStartup(true);
		TestSmartLifecycleBean dependency = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
		dependency.setAutoStartup(false);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean", bean);
		context.getBeanFactory().registerSingleton("dependency", dependency);
		context.getBeanFactory().registerDependentBean("dependency", "bean");
		assertFalse(bean.isRunning());
		assertFalse(dependency.isRunning());
		context.refresh();
		assertTrue(bean.isRunning());
		assertFalse(dependency.isRunning());
		context.stop();
		assertFalse(bean.isRunning());
		assertFalse(dependency.isRunning());
		assertEquals(1, startedBeans.size());
	}

	@Test
	public void smartLifecycleGroupStartup() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans);
		TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forStartupTests(1, startedBeans);
		TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forStartupTests(2, startedBeans);
		TestSmartLifecycleBean bean3 = TestSmartLifecycleBean.forStartupTests(3, startedBeans);
		TestSmartLifecycleBean beanMax = TestSmartLifecycleBean.forStartupTests(Integer.MAX_VALUE, startedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean3", bean3);
		context.getBeanFactory().registerSingleton("beanMin", beanMin);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("beanMax", beanMax);
		context.getBeanFactory().registerSingleton("bean1", bean1);
		assertFalse(beanMin.isRunning());
		assertFalse(bean1.isRunning());
		assertFalse(bean2.isRunning());
		assertFalse(bean3.isRunning());
		assertFalse(beanMax.isRunning());
		context.refresh();
		assertTrue(beanMin.isRunning());
		assertTrue(bean1.isRunning());
		assertTrue(bean2.isRunning());
		assertTrue(bean3.isRunning());
		assertTrue(beanMax.isRunning());
		context.stop();
		assertEquals(5, startedBeans.size());
		assertEquals(Integer.MIN_VALUE, getPhase(startedBeans.get(0)));
		assertEquals(1, getPhase(startedBeans.get(1)));
		assertEquals(2, getPhase(startedBeans.get(2)));
		assertEquals(3, getPhase(startedBeans.get(3)));
		assertEquals(Integer.MAX_VALUE, getPhase(startedBeans.get(4)));
	}

	@Test
	public void contextRefreshThenStartWithMixedBeans() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestLifecycleBean simpleBean1 = TestLifecycleBean.forStartupTests(startedBeans);
		TestLifecycleBean simpleBean2 = TestLifecycleBean.forStartupTests(startedBeans);
		TestSmartLifecycleBean smartBean1 = TestSmartLifecycleBean.forStartupTests(5, startedBeans);
		TestSmartLifecycleBean smartBean2 = TestSmartLifecycleBean.forStartupTests(-3, startedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("simpleBean1", simpleBean1);
		context.getBeanFactory().registerSingleton("smartBean1", smartBean1);
		context.getBeanFactory().registerSingleton("simpleBean2", simpleBean2);
		context.getBeanFactory().registerSingleton("smartBean2", smartBean2);
		assertFalse(simpleBean1.isRunning());
		assertFalse(simpleBean2.isRunning());
		assertFalse(smartBean1.isRunning());
		assertFalse(smartBean2.isRunning());
		context.refresh();
		assertTrue(smartBean1.isRunning());
		assertTrue(smartBean2.isRunning());
		assertFalse(simpleBean1.isRunning());
		assertFalse(simpleBean2.isRunning());
		assertEquals(2, startedBeans.size());
		assertEquals(-3, getPhase(startedBeans.get(0)));
		assertEquals(5, getPhase(startedBeans.get(1)));
		context.start();
		assertTrue(smartBean1.isRunning());
		assertTrue(smartBean2.isRunning());
		assertTrue(simpleBean1.isRunning());
		assertTrue(simpleBean2.isRunning());
		assertEquals(4, startedBeans.size());
		assertEquals(0, getPhase(startedBeans.get(2)));
		assertEquals(0, getPhase(startedBeans.get(3)));
	}

	@Test
	public void contextRefreshThenStopAndRestartWithMixedBeans() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestLifecycleBean simpleBean1 = TestLifecycleBean.forStartupTests(startedBeans);
		TestLifecycleBean simpleBean2 = TestLifecycleBean.forStartupTests(startedBeans);
		TestSmartLifecycleBean smartBean1 = TestSmartLifecycleBean.forStartupTests(5, startedBeans);
		TestSmartLifecycleBean smartBean2 = TestSmartLifecycleBean.forStartupTests(-3, startedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("simpleBean1", simpleBean1);
		context.getBeanFactory().registerSingleton("smartBean1", smartBean1);
		context.getBeanFactory().registerSingleton("simpleBean2", simpleBean2);
		context.getBeanFactory().registerSingleton("smartBean2", smartBean2);
		assertFalse(simpleBean1.isRunning());
		assertFalse(simpleBean2.isRunning());
		assertFalse(smartBean1.isRunning());
		assertFalse(smartBean2.isRunning());
		context.refresh();
		assertTrue(smartBean1.isRunning());
		assertTrue(smartBean2.isRunning());
		assertFalse(simpleBean1.isRunning());
		assertFalse(simpleBean2.isRunning());
		assertEquals(2, startedBeans.size());
		assertEquals(-3, getPhase(startedBeans.get(0)));
		assertEquals(5, getPhase(startedBeans.get(1)));
		context.stop();
		assertFalse(simpleBean1.isRunning());
		assertFalse(simpleBean2.isRunning());
		assertFalse(smartBean1.isRunning());
		assertFalse(smartBean2.isRunning());
		context.start();
		assertTrue(smartBean1.isRunning());
		assertTrue(smartBean2.isRunning());
		assertTrue(simpleBean1.isRunning());
		assertTrue(simpleBean2.isRunning());
		assertEquals(6, startedBeans.size());
		assertEquals(-3, getPhase(startedBeans.get(2)));
		assertEquals(0, getPhase(startedBeans.get(3)));
		assertEquals(0, getPhase(startedBeans.get(4)));
		assertEquals(5, getPhase(startedBeans.get(5)));
	}

	@Test
	public void smartLifecycleGroupShutdown() throws Exception {
		Assume.group(TestGroup.PERFORMANCE);

		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 300, stoppedBeans);
		TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(3, 100, stoppedBeans);
		TestSmartLifecycleBean bean3 = TestSmartLifecycleBean.forShutdownTests(1, 600, stoppedBeans);
		TestSmartLifecycleBean bean4 = TestSmartLifecycleBean.forShutdownTests(2, 400, stoppedBeans);
		TestSmartLifecycleBean bean5 = TestSmartLifecycleBean.forShutdownTests(2, 700, stoppedBeans);
		TestSmartLifecycleBean bean6 = TestSmartLifecycleBean.forShutdownTests(Integer.MAX_VALUE, 200, stoppedBeans);
		TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(3, 200, stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean1", bean1);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("bean3", bean3);
		context.getBeanFactory().registerSingleton("bean4", bean4);
		context.getBeanFactory().registerSingleton("bean5", bean5);
		context.getBeanFactory().registerSingleton("bean6", bean6);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.refresh();
		context.stop();
		assertEquals(Integer.MAX_VALUE, getPhase(stoppedBeans.get(0)));
		assertEquals(3, getPhase(stoppedBeans.get(1)));
		assertEquals(3, getPhase(stoppedBeans.get(2)));
		assertEquals(2, getPhase(stoppedBeans.get(3)));
		assertEquals(2, getPhase(stoppedBeans.get(4)));
		assertEquals(1, getPhase(stoppedBeans.get(5)));
		assertEquals(1, getPhase(stoppedBeans.get(6)));
	}

	@Test
	public void singleSmartLifecycleShutdown() throws Exception {
		Assume.group(TestGroup.PERFORMANCE);

		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean bean = TestSmartLifecycleBean.forShutdownTests(99, 300, stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean", bean);
		context.refresh();
		assertTrue(bean.isRunning());
		context.stop();
		assertEquals(1, stoppedBeans.size());
		assertFalse(bean.isRunning());
		assertEquals(bean, stoppedBeans.get(0));
	}

	@Test
	public void singleLifecycleShutdown() throws Exception {
		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		Lifecycle bean = new TestLifecycleBean(null, stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean", bean);
		context.refresh();
		assertFalse(bean.isRunning());
		bean.start();
		assertTrue(bean.isRunning());
		context.stop();
		assertEquals(1, stoppedBeans.size());
		assertFalse(bean.isRunning());
		assertEquals(bean, stoppedBeans.get(0));
	}

	@Test
	public void mixedShutdown() throws Exception {
		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		Lifecycle bean1 = TestLifecycleBean.forShutdownTests(stoppedBeans);
		Lifecycle bean2 = TestSmartLifecycleBean.forShutdownTests(500, 200, stoppedBeans);
		Lifecycle bean3 = TestSmartLifecycleBean.forShutdownTests(Integer.MAX_VALUE, 100, stoppedBeans);
		Lifecycle bean4 = TestLifecycleBean.forShutdownTests(stoppedBeans);
		Lifecycle bean5 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
		Lifecycle bean6 = TestSmartLifecycleBean.forShutdownTests(-1, 100, stoppedBeans);
		Lifecycle bean7 = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 300, stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("bean1", bean1);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("bean3", bean3);
		context.getBeanFactory().registerSingleton("bean4", bean4);
		context.getBeanFactory().registerSingleton("bean5", bean5);
		context.getBeanFactory().registerSingleton("bean6", bean6);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.refresh();
		assertTrue(bean2.isRunning());
		assertTrue(bean3.isRunning());
		assertTrue(bean5.isRunning());
		assertTrue(bean6.isRunning());
		assertTrue(bean7.isRunning());
		assertFalse(bean1.isRunning());
		assertFalse(bean4.isRunning());
		bean1.start();
		bean4.start();
		assertTrue(bean1.isRunning());
		assertTrue(bean4.isRunning());
		context.stop();
		assertFalse(bean1.isRunning());
		assertFalse(bean2.isRunning());
		assertFalse(bean3.isRunning());
		assertFalse(bean4.isRunning());
		assertFalse(bean5.isRunning());
		assertFalse(bean6.isRunning());
		assertFalse(bean7.isRunning());
		assertEquals(7, stoppedBeans.size());
		assertEquals(Integer.MAX_VALUE, getPhase(stoppedBeans.get(0)));
		assertEquals(500, getPhase(stoppedBeans.get(1)));
		assertEquals(1, getPhase(stoppedBeans.get(2)));
		assertEquals(0, getPhase(stoppedBeans.get(3)));
		assertEquals(0, getPhase(stoppedBeans.get(4)));
		assertEquals(-1, getPhase(stoppedBeans.get(5)));
		assertEquals(Integer.MIN_VALUE, getPhase(stoppedBeans.get(6)));
	}

	@Test
	public void dependencyStartedFirstEvenIfItsPhaseIsHigher() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans);
		TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forStartupTests(2, startedBeans);
		TestSmartLifecycleBean bean99 = TestSmartLifecycleBean.forStartupTests(99, startedBeans);
		TestSmartLifecycleBean beanMax = TestSmartLifecycleBean.forStartupTests(Integer.MAX_VALUE, startedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("beanMin", beanMin);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("bean99", bean99);
		context.getBeanFactory().registerSingleton("beanMax", beanMax);
		context.getBeanFactory().registerDependentBean("bean99", "bean2");
		context.refresh();
		assertTrue(beanMin.isRunning());
		assertTrue(bean2.isRunning());
		assertTrue(bean99.isRunning());
		assertTrue(beanMax.isRunning());
		assertEquals(4, startedBeans.size());
		assertEquals(Integer.MIN_VALUE, getPhase(startedBeans.get(0)));
		assertEquals(99, getPhase(startedBeans.get(1)));
		assertEquals(bean99, startedBeans.get(1));
		assertEquals(2, getPhase(startedBeans.get(2)));
		assertEquals(bean2, startedBeans.get(2));
		assertEquals(Integer.MAX_VALUE, getPhase(startedBeans.get(3)));
		context.stop();
	}

	@Test
	public void dependentShutdownFirstEvenIfItsPhaseIsLower() throws Exception {
		Assume.group(TestGroup.PERFORMANCE);

		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 100, stoppedBeans);
		TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
		TestSmartLifecycleBean bean99 = TestSmartLifecycleBean.forShutdownTests(99, 100, stoppedBeans);
		TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(2, 300, stoppedBeans);
		TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(7, 400, stoppedBeans);
		TestSmartLifecycleBean beanMax = TestSmartLifecycleBean.forShutdownTests(Integer.MAX_VALUE, 400, stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("beanMin", beanMin);
		context.getBeanFactory().registerSingleton("bean1", bean1);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.getBeanFactory().registerSingleton("bean99", bean99);
		context.getBeanFactory().registerSingleton("beanMax", beanMax);
		context.getBeanFactory().registerDependentBean("bean99", "bean2");
		context.refresh();
		assertTrue(beanMin.isRunning());
		assertTrue(bean1.isRunning());
		assertTrue(bean2.isRunning());
		assertTrue(bean7.isRunning());
		assertTrue(bean99.isRunning());
		assertTrue(beanMax.isRunning());
		context.stop();
		assertFalse(beanMin.isRunning());
		assertFalse(bean1.isRunning());
		assertFalse(bean2.isRunning());
		assertFalse(bean7.isRunning());
		assertFalse(bean99.isRunning());
		assertFalse(beanMax.isRunning());
		assertEquals(6, stoppedBeans.size());
		assertEquals(Integer.MAX_VALUE, getPhase(stoppedBeans.get(0)));
		assertEquals(2, getPhase(stoppedBeans.get(1)));
		assertEquals(bean2, stoppedBeans.get(1));
		assertEquals(99, getPhase(stoppedBeans.get(2)));
		assertEquals(bean99, stoppedBeans.get(2));
		assertEquals(7, getPhase(stoppedBeans.get(3)));
		assertEquals(1, getPhase(stoppedBeans.get(4)));
		assertEquals(Integer.MIN_VALUE, getPhase(stoppedBeans.get(5)));
	}

	@Test
	public void dependencyStartedFirstAndIsSmartLifecycle() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean beanNegative = TestSmartLifecycleBean.forStartupTests(-99, startedBeans);
		TestSmartLifecycleBean bean99 = TestSmartLifecycleBean.forStartupTests(99, startedBeans);
		TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forStartupTests(7, startedBeans);
		TestLifecycleBean simpleBean = TestLifecycleBean.forStartupTests(startedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("beanNegative", beanNegative);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.getBeanFactory().registerSingleton("bean99", bean99);
		context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
		context.getBeanFactory().registerDependentBean("bean7", "simpleBean");
		context.refresh();
		context.stop();
		startedBeans.clear();
		// clean start so that simpleBean is included
		context.start();
		assertTrue(beanNegative.isRunning());
		assertTrue(bean99.isRunning());
		assertTrue(bean7.isRunning());
		assertTrue(simpleBean.isRunning());
		assertEquals(4, startedBeans.size());
		assertEquals(-99, getPhase(startedBeans.get(0)));
		assertEquals(7, getPhase(startedBeans.get(1)));
		assertEquals(0, getPhase(startedBeans.get(2)));
		assertEquals(99, getPhase(startedBeans.get(3)));
		context.stop();
	}

	@Test
	public void dependentShutdownFirstAndIsSmartLifecycle() throws Exception {
		Assume.group(TestGroup.PERFORMANCE);

		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 400, stoppedBeans);
		TestSmartLifecycleBean beanNegative = TestSmartLifecycleBean.forShutdownTests(-99, 100, stoppedBeans);
		TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
		TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(2, 300, stoppedBeans);
		TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(7, 400, stoppedBeans);
		TestLifecycleBean simpleBean = TestLifecycleBean.forShutdownTests(stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("beanMin", beanMin);
		context.getBeanFactory().registerSingleton("beanNegative", beanNegative);
		context.getBeanFactory().registerSingleton("bean1", bean1);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
		context.getBeanFactory().registerDependentBean("simpleBean", "beanNegative");
		context.refresh();
		assertTrue(beanMin.isRunning());
		assertTrue(beanNegative.isRunning());
		assertTrue(bean1.isRunning());
		assertTrue(bean2.isRunning());
		assertTrue(bean7.isRunning());
		// should start since it's a dependency of an auto-started bean
		assertTrue(simpleBean.isRunning());
		context.stop();
		assertFalse(beanMin.isRunning());
		assertFalse(beanNegative.isRunning());
		assertFalse(bean1.isRunning());
		assertFalse(bean2.isRunning());
		assertFalse(bean7.isRunning());
		assertFalse(simpleBean.isRunning());
		assertEquals(6, stoppedBeans.size());
		assertEquals(7, getPhase(stoppedBeans.get(0)));
		assertEquals(2, getPhase(stoppedBeans.get(1)));
		assertEquals(1, getPhase(stoppedBeans.get(2)));
		assertEquals(-99, getPhase(stoppedBeans.get(3)));
		assertEquals(0, getPhase(stoppedBeans.get(4)));
		assertEquals(Integer.MIN_VALUE, getPhase(stoppedBeans.get(5)));
	}

	@Test
	public void dependencyStartedFirstButNotSmartLifecycle() throws Exception {
		CopyOnWriteArrayList<Lifecycle> startedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forStartupTests(Integer.MIN_VALUE, startedBeans);
		TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forStartupTests(7, startedBeans);
		TestLifecycleBean simpleBean = TestLifecycleBean.forStartupTests(startedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("beanMin", beanMin);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
		context.getBeanFactory().registerDependentBean("simpleBean", "beanMin");
		context.refresh();
		assertTrue(beanMin.isRunning());
		assertTrue(bean7.isRunning());
		assertTrue(simpleBean.isRunning());
		assertEquals(3, startedBeans.size());
		assertEquals(0, getPhase(startedBeans.get(0)));
		assertEquals(Integer.MIN_VALUE, getPhase(startedBeans.get(1)));
		assertEquals(7, getPhase(startedBeans.get(2)));
		context.stop();
	}

	@Test
	public void dependentShutdownFirstButNotSmartLifecycle() throws Exception {
		Assume.group(TestGroup.PERFORMANCE);

		CopyOnWriteArrayList<Lifecycle> stoppedBeans = new CopyOnWriteArrayList<>();
		TestSmartLifecycleBean bean1 = TestSmartLifecycleBean.forShutdownTests(1, 200, stoppedBeans);
		TestLifecycleBean simpleBean = TestLifecycleBean.forShutdownTests(stoppedBeans);
		TestSmartLifecycleBean bean2 = TestSmartLifecycleBean.forShutdownTests(2, 300, stoppedBeans);
		TestSmartLifecycleBean bean7 = TestSmartLifecycleBean.forShutdownTests(7, 400, stoppedBeans);
		TestSmartLifecycleBean beanMin = TestSmartLifecycleBean.forShutdownTests(Integer.MIN_VALUE, 400, stoppedBeans);
		StaticApplicationContext context = new StaticApplicationContext();
		context.getBeanFactory().registerSingleton("beanMin", beanMin);
		context.getBeanFactory().registerSingleton("bean1", bean1);
		context.getBeanFactory().registerSingleton("bean2", bean2);
		context.getBeanFactory().registerSingleton("bean7", bean7);
		context.getBeanFactory().registerSingleton("simpleBean", simpleBean);
		context.getBeanFactory().registerDependentBean("bean2", "simpleBean");
		context.refresh();
		assertTrue(beanMin.isRunning());
		assertTrue(bean1.isRunning());
		assertTrue(bean2.isRunning());
		assertTrue(bean7.isRunning());
		assertFalse(simpleBean.isRunning());
		simpleBean.start();
		assertTrue(simpleBean.isRunning());
		context.stop();
		assertFalse(beanMin.isRunning());
		assertFalse(bean1.isRunning());
		assertFalse(bean2.isRunning());
		assertFalse(bean7.isRunning());
		assertFalse(simpleBean.isRunning());
		assertEquals(5, stoppedBeans.size());
		assertEquals(7, getPhase(stoppedBeans.get(0)));
		assertEquals(0, getPhase(stoppedBeans.get(1)));
		assertEquals(2, getPhase(stoppedBeans.get(2)));
		assertEquals(1, getPhase(stoppedBeans.get(3)));
		assertEquals(Integer.MIN_VALUE, getPhase(stoppedBeans.get(4)));
	}


	private static int getPhase(Lifecycle lifecycle) {
		return (lifecycle instanceof SmartLifecycle) ?
				((SmartLifecycle) lifecycle).getPhase() : 0;
	}


	private static class TestLifecycleBean implements Lifecycle {

		private final CopyOnWriteArrayList<Lifecycle> startedBeans;

		private final CopyOnWriteArrayList<Lifecycle> stoppedBeans;

		private volatile boolean running;


		static TestLifecycleBean forStartupTests(CopyOnWriteArrayList<Lifecycle> startedBeans) {
			return new TestLifecycleBean(startedBeans, null);
		}

		static TestLifecycleBean forShutdownTests(CopyOnWriteArrayList<Lifecycle> stoppedBeans) {
			return new TestLifecycleBean(null, stoppedBeans);
		}

		private TestLifecycleBean(CopyOnWriteArrayList<Lifecycle> startedBeans,  CopyOnWriteArrayList<Lifecycle> stoppedBeans) {
			this.startedBeans = startedBeans;
			this.stoppedBeans = stoppedBeans;
		}

		@Override
		public boolean isRunning() {
			return this.running;
		}

		@Override
		public void start() {
			if (this.startedBeans != null) {
				this.startedBeans.add(this);
			}
			this.running = true;
		}

		@Override
		public void stop() {
			if (this.stoppedBeans != null) {
				this.stoppedBeans.add(this);
			}
			this.running = false;
		}
	}


	private static class TestSmartLifecycleBean extends TestLifecycleBean implements SmartLifecycle {

		private final int phase;

		private final int shutdownDelay;

		private volatile boolean autoStartup = true;

		static TestSmartLifecycleBean forStartupTests(int phase, CopyOnWriteArrayList<Lifecycle> startedBeans) {
			return new TestSmartLifecycleBean(phase, 0, startedBeans, null);
		}

		static TestSmartLifecycleBean forShutdownTests(int phase, int shutdownDelay, CopyOnWriteArrayList<Lifecycle> stoppedBeans) {
			return new TestSmartLifecycleBean(phase, shutdownDelay, null, stoppedBeans);
		}

		private TestSmartLifecycleBean(int phase, int shutdownDelay, CopyOnWriteArrayList<Lifecycle> startedBeans, CopyOnWriteArrayList<Lifecycle> stoppedBeans) {
			super(startedBeans, stoppedBeans);
			this.phase = phase;
			this.shutdownDelay = shutdownDelay;
		}

		@Override
		public int getPhase() {
			return this.phase;
		}

		@Override
		public boolean isAutoStartup() {
			return this.autoStartup;
		}

		public void setAutoStartup(boolean autoStartup) {
			this.autoStartup = autoStartup;
		}

		@Override
		public void stop(final Runnable callback) {
			// calling stop() before the delay to preserve
			// invocation order in the 'stoppedBeans' list
			stop();
			final int delay = this.shutdownDelay;
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(delay);
					}
					catch (InterruptedException e) {
						// ignore
					}
					finally {
						callback.run();
					}
				}
			}).start();
		}
	}


	public static class DummySmartLifecycleBean implements SmartLifecycle {

		public boolean running = false;

		@Override
		public boolean isAutoStartup() {
			return true;
		}

		@Override
		public void stop(Runnable callback) {
			this.running = false;
			callback.run();
		}

		@Override
		public void start() {
			this.running = true;
		}

		@Override
		public void stop() {
			this.running = false;
		}

		@Override
		public boolean isRunning() {
			return this.running;
		}

		@Override
		public int getPhase() {
			return 0;
		}
	}


	public static class DummySmartLifecycleFactoryBean implements FactoryBean<Object>, SmartLifecycle {

		public boolean running = false;

		DummySmartLifecycleBean bean = new DummySmartLifecycleBean();

		@Override
		public Object getObject() throws Exception {
			return this.bean;
		}

		@Override
		public Class<?> getObjectType() {
			return DummySmartLifecycleBean.class;
		}

		@Override
		public boolean isSingleton() {
			return true;
		}

		@Override
		public boolean isAutoStartup() {
			return true;
		}

		@Override
		public void stop(Runnable callback) {
			this.running = false;
			callback.run();
		}

		@Override
		public void start() {
			this.running = true;
		}

		@Override
		public void stop() {
			this.running = false;
		}

		@Override
		public boolean isRunning() {
			return this.running;
		}

		@Override
		public int getPhase() {
			return 0;
		}
	}

}
