/* * Copyright 2016 Kejun Xia * * 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 com.shipdream.lib.poke; import com.shipdream.lib.poke.exception.CircularDependenciesException; import com.shipdream.lib.poke.exception.ProvideException; import com.shipdream.lib.poke.exception.ProviderConflictException; import com.shipdream.lib.poke.exception.ProviderMissingException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import javax.inject.Singleton; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; public class TestNestedInjectionAndRelease extends BaseTestCases { interface Service { } interface Controller { } class OnFree { void onFreed() {} } Object module; class ControllerImpl implements Controller { @MyInject Service service; } class ViewA { @MyInject Controller controller; } class ViewB { @MyInject Controller controller; } class ViewC { @MyInject Controller controller; } private OnFree serviceOnFreed; private OnFree controllerOnFreed; private Service serviceMock; private Graph graph; private Component component; @Before public void setUp() throws Exception { serviceOnFreed = mock(OnFree.class); controllerOnFreed = mock(OnFree.class); serviceMock = mock(Service.class); graph = new Graph(); module = new Object() { @Provides @Singleton public Controller provideController() { return new ControllerImpl(); } @Provides @Singleton public Service providesFood2() { return serviceMock; } }; component = new Component("AppSignleton"); component.register(module); graph.registerDereferencedListener(new Provider.DereferenceListener() { @Override public <T> void onDereferenced(Provider<T> provider, T instance) { if (provider.type() == Service.class) { if (provider.getReferenceCount() == 0) { serviceOnFreed.onFreed(); } } else if (provider.type() == Controller.class) { if (provider.getReferenceCount() == 0) { controllerOnFreed.onFreed(); } } } }); graph.setRootComponent(component); } @Test public void should_not_release_nested_instance_until_all_of_its_holders_are_disposed_by_instance_injection() throws ProvideException, ProviderConflictException, CircularDependenciesException, ProviderMissingException { ViewA viewA = new ViewA(); ViewB viewB = new ViewB(); ViewC viewC = new ViewC(); //Simulate to navigate to ViewA graph.inject(viewA, MyInject.class); Controller controllerInA = viewA.controller; Service serviceInA = ((ControllerImpl) viewA.controller).service; verify(serviceOnFreed, times(0)).onFreed(); verify(controllerOnFreed, times(0)).onFreed(); //Simulate to navigate to ViewB graph.inject(viewB, MyInject.class); Controller controllerInB = viewB.controller; Service serviceInB = ((ControllerImpl) viewB.controller).service; graph.release(viewA, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); //Simulate to navigate to ViewC graph.inject(viewC, MyInject.class); Controller controllerInC = viewB.controller; Service serviceInC = ((ControllerImpl) viewC.controller).service; graph.release(viewB, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInC); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInC); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); Assert.assertTrue(controllerInA == controllerInB); Assert.assertTrue(controllerInB == controllerInC); //Simulate to navigate back to ViewB graph.inject(viewB, MyInject.class); Assert.assertTrue(controllerInC == viewB.controller); Assert.assertTrue(serviceInC == serviceInB); graph.release(viewC, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); //Simulate to navigate back to ViewA graph.inject(viewA, MyInject.class); Assert.assertTrue(controllerInB == viewA.controller); Assert.assertTrue(serviceInB == serviceInA); graph.release(viewB, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); //Simulate to navigate back to exit graph.release(viewA, MyInject.class); verify(controllerOnFreed, times(1)).onFreed(); verify(serviceOnFreed, times(1)).onFreed(); Assert.assertNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertEquals(0, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(0, component.findProvider(Service.class, null).getReferenceCount()); } @Test public void should_count_reference_correctly_for_reference_dereference_methods() throws ProvideException, CircularDependenciesException, ProviderMissingException { ViewA viewA = new ViewA(); ViewB viewB = new ViewB(); //Simulate to navigate to ViewA graph.reference(Controller.class, null, MyInject.class); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); graph.inject(viewA, MyInject.class); Assert.assertEquals(2, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(2, component.findProvider(Service.class, null).getReferenceCount()); graph.dereference(viewA.controller, Controller.class, null, MyInject.class); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); verify(serviceOnFreed, times(0)).onFreed(); verify(controllerOnFreed, times(0)).onFreed(); //Simulate to navigate to ViewB graph.reference(Controller.class, null, MyInject.class); Assert.assertEquals(2, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(2, component.findProvider(Service.class, null).getReferenceCount()); graph.inject(viewB, MyInject.class); Assert.assertEquals(3, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(3, component.findProvider(Service.class, null).getReferenceCount()); graph.release(viewA, MyInject.class); Assert.assertEquals(2, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(2, component.findProvider(Service.class, null).getReferenceCount()); graph.dereference(viewB.controller, Controller.class, null, MyInject.class); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); } @Test public void should_not_release_nested_instance_until_all_of_its_holders_are_disposed_by_reference_methods() throws ProvideException, ProviderConflictException, CircularDependenciesException, ProviderMissingException { ViewA viewA = new ViewA(); ViewB viewB = new ViewB(); ViewC viewC = new ViewC(); //Simulate to navigate to ViewA graph.reference(Controller.class, null, MyInject.class); graph.inject(viewA, MyInject.class); Controller controllerInA = viewA.controller; Service serviceInA = ((ControllerImpl)viewA.controller).service; graph.dereference(controllerInA, Controller.class, null, MyInject.class); verify(serviceOnFreed, times(0)).onFreed(); verify(controllerOnFreed, times(0)).onFreed(); //Simulate to navigate to ViewB graph.reference(Controller.class, null, MyInject.class); graph.inject(viewB, MyInject.class); Controller controllerInB = viewB.controller; Service serviceInB = ((ControllerImpl)viewB.controller).service; graph.release(viewA, MyInject.class); graph.dereference(controllerInB, Controller.class, null, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); //Simulate to navigate to ViewC graph.reference(Controller.class, null, MyInject.class); graph.inject(viewC, MyInject.class); Controller controllerInC = viewB.controller; Service serviceInC = ((ControllerImpl)viewC.controller).service; graph.release(viewB, MyInject.class); graph.dereference(controllerInC, Controller.class, null, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInC); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInC); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); Assert.assertTrue(controllerInA == controllerInB); Assert.assertTrue(controllerInB == controllerInC); //Simulate to navigate back to ViewB graph.reference(Controller.class, null, MyInject.class); graph.inject(viewB, MyInject.class); Assert.assertTrue(controllerInC == viewB.controller); Assert.assertTrue(serviceInC == serviceInB); graph.release(viewC, MyInject.class); graph.dereference(viewB.controller, Controller.class, null, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); //Simulate to navigate back to ViewA graph.reference(Controller.class, null, MyInject.class); graph.inject(viewA, MyInject.class); Assert.assertTrue(controllerInB == viewA.controller); Assert.assertTrue(serviceInB == serviceInA); graph.release(viewB, MyInject.class); graph.dereference(viewA.controller, Controller.class, null, MyInject.class); verify(controllerOnFreed, times(0)).onFreed(); verify(serviceOnFreed, times(0)).onFreed(); Assert.assertNotNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNotNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInA); Assert.assertTrue(component.findProvider(Controller.class, null).get() == controllerInB); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInA); Assert.assertTrue(component.findProvider(Service.class, null).get() == serviceInB); Assert.assertEquals(1, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(1, component.findProvider(Service.class, null).getReferenceCount()); //Simulate to navigate back to exit graph.release(viewA, MyInject.class); verify(controllerOnFreed, times(1)).onFreed(); verify(serviceOnFreed, times(1)).onFreed(); Assert.assertNull(component.scopeCache.findInstance(Controller.class, null)); Assert.assertNull(component.scopeCache.findInstance(Service.class, null)); Assert.assertEquals(0, component.findProvider(Controller.class, null).getReferenceCount()); Assert.assertEquals(0, component.findProvider(Service.class, null).getReferenceCount()); } }