/*
* 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.PokeException;
import com.shipdream.lib.poke.exception.ProvideException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.inject.Singleton;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@SuppressWarnings("unchecked")
public class TestCircularDependencies extends BaseTestCases {
private Component component;
private Graph graph;
@Before
public void setUp() throws Exception {
component = new Component("AppSingleton");
graph = new Graph();
graph.setRootComponent(component);
}
@SuppressWarnings("unchecked")
@Test
public void should_not_throw_circular_dependency_exception_on_finite_circular_dependency() throws PokeException {
Provider.ReferencedListener<Power> powerOnInject = mock(Provider.ReferencedListener.class);
ProviderByClassType powerProvider = new ProviderByClassType<>(Power.class, PowerImpl.class);
powerProvider.registerOnReferencedListener(powerOnInject);
Provider.ReferencedListener<Driver> driverOnInject = mock(Provider.ReferencedListener.class);
ProviderByClassType driverProvider = new ProviderByClassType(Driver.class, DriverImpl.class);
driverProvider.registerOnReferencedListener(driverOnInject);
Provider.ReferencedListener<Robot> robotOnInject = mock(Provider.ReferencedListener.class);
ProviderByClassType robotProvider = new ProviderByClassType(Robot.class, RobotImpl.class);
robotProvider.registerOnReferencedListener(robotOnInject);
component.register(powerProvider);
component.register(robotProvider);
component.register(driverProvider);
Factory factory = new Factory();
graph.inject(factory, MyInject.class);
Assert.assertNotNull(factory);
PowerImpl power = (PowerImpl) factory.power;
Assert.assertNotNull(power);
RobotImpl robot = (RobotImpl) power.robot;
Assert.assertNotNull(robot);
DriverImpl driver = (DriverImpl) robot.driver;
Assert.assertNotNull(driver);
}
static class RobotImpl2 implements Robot {
@MyInject
private Driver driver;
@MyInject
private Power power;
}
static class DriverImpl2 implements Driver {
@MyInject
private Power power;
@MyInject
private Robot robot;
}
static class PowerImpl2 implements Power {
@MyInject
private Robot robot;
@MyInject
private Driver driver;
public Robot getConnectedRobot() {
return robot;
}
}
@Test(expected = CircularDependenciesException.class)
public void should_detect_infinite_circular_dependencies() throws PokeException {
//Create a new unscoped component
Component c = new Component(null, false);
graph.setRootComponent(c);
c.register(new ProviderByClassType(Power.class, PowerImpl2.class));
c.register(new ProviderByClassType(Driver.class, DriverImpl2.class));
c.register(new ProviderByClassType(Robot.class, RobotImpl2.class));
Factory factory = new Factory();
graph.inject(factory, MyInject.class);
}
@Test
public void shouldNotifyReferencedCallbackWhenObjectFullyInjectedWithCircularDependencies() throws PokeException {
final Factory factory = new Factory();
ProviderByClassType powerProvider = new ProviderByClassType(Power.class, PowerImpl.class);
powerProvider.registerOnReferencedListener(new Provider.ReferencedListener<Power>() {
@Override
public void onReferenced(Provider<Power> provider, Power instance) {
Assert.assertNotNull(((PowerImpl) instance).robot);
}
});
Assert.assertEquals(1, powerProvider.getReferencedListeners().size());
ProviderByClassType driverProvider = new ProviderByClassType(Driver.class, DriverImpl.class);
driverProvider.registerOnReferencedListener(new Provider.ReferencedListener<Driver>() {
@Override
public void onReferenced(Provider<Driver> provider, Driver instance) {
Assert.assertNotNull(((DriverImpl) instance).power);
}
});
ProviderByClassType robotProvider = new ProviderByClassType(Robot.class, RobotImpl.class);
robotProvider.registerOnReferencedListener(new Provider.ReferencedListener<Robot>() {
@Override
public void onReferenced(Provider<Robot> provider, Robot instance) {
Assert.assertNotNull(((RobotImpl) instance).driver);
}
});
component.register(powerProvider);
component.register(robotProvider);
component.register(driverProvider);
graph.inject(factory, MyInject.class);
powerProvider.clearOnReferencedListener();
Assert.assertEquals(null, powerProvider.getReferencedListeners());
}
@Test
public void shouldNotifyCreatedCallbackWhenObjectFullyInjectedWithCircularDependencies() throws PokeException {
class Plant {
@MyInject
private Power power;
}
Provider powerProvider = new Provider<Power>(Power.class, new ScopeCache()) {
@Override
protected Power createInstance() throws ProvideException {
return new Power() {
};
}
};
component.register(powerProvider);
Provider.CreationListener creationListener = mock(Provider.CreationListener.class);
powerProvider.registerCreationListener(creationListener);
Assert.assertEquals(1, powerProvider.getCreationListeners().size());
Plant plant = new Plant();
graph.getRootComponent().scopeCache = null;
graph.inject(plant, MyInject.class);
verify(creationListener, times(1)).onCreated(eq(powerProvider), eq(plant.power));
powerProvider.unregisterCreationListener(creationListener);
Assert.assertEquals(null, powerProvider.getCreationListeners());
graph.inject(plant, MyInject.class);
verify(creationListener, times(1)).onCreated(eq(powerProvider), any(Power.class));
powerProvider.registerCreationListener(creationListener);
graph.inject(plant, MyInject.class);
verify(creationListener, times(2)).onCreated(eq(powerProvider), any(Power.class));
powerProvider.clearCreationListeners();
graph.inject(plant, MyInject.class);
verify(creationListener, times(2)).onCreated(eq(powerProvider), any(Power.class));
}
@Test
public void component_cache_should_override_provider_cache() throws PokeException {
class Plant {
@MyInject
private Power power;
}
ScopeCache scopeCache = new ScopeCache();
Provider powerProvider = new Provider<Power>(Power.class, scopeCache) {
@Override
protected Power createInstance() throws ProvideException {
return new Power() {
};
}
};
Assert.assertTrue(scopeCache == powerProvider.getScopeCache());
component.register(powerProvider);
Assert.assertTrue(scopeCache != powerProvider.getScopeCache());
Assert.assertTrue(powerProvider.getScopeCache() == component.scopeCache);
}
@Test
public void shouldInjectObjectOnlyOnceWithCircularDependencies() throws PokeException, NoSuchFieldException {
final Factory factory = new Factory();
final Provider.ReferencedListener<Power> powerOnInject = mock(Provider.ReferencedListener.class);
final ProviderByClassType<Power> powerProvider = new ProviderByClassType(Power.class, PowerImpl.class);
powerProvider.registerOnReferencedListener(new Provider.ReferencedListener<Power>() {
@Override
public void onReferenced(Provider<Power> provider, Power instance) {
if (provider.getReferenceCount() == 1) {
powerOnInject.onReferenced(provider, instance);
}
}
});
final Provider.ReferencedListener<Driver> driverOnInject = mock(Provider.ReferencedListener.class);
ProviderByClassType<Driver> driverProvider = new ProviderByClassType(Driver.class, DriverImpl.class);
driverProvider.registerOnReferencedListener(new Provider.ReferencedListener<Driver>() {
@Override
public void onReferenced(Provider<Driver> provider, Driver instance) {
if (provider.getReferenceCount() == 1) {
driverOnInject.onReferenced(provider, instance);
}
}
});
final Provider.ReferencedListener<Robot> robotOnInject = mock(Provider.ReferencedListener.class);
ProviderByClassType<Robot> robotProvider = new ProviderByClassType(Robot.class, RobotImpl.class);
robotProvider.registerOnReferencedListener(new Provider.ReferencedListener<Robot>() {
@Override
public void onReferenced(Provider<Robot> provider, Robot instance) {
if (provider.getReferenceCount() == 1) {
robotOnInject.onReferenced(provider, instance);
}
}
});
component.register(powerProvider);
component.register(robotProvider);
component.register(driverProvider);
graph.inject(factory, MyInject.class);
verify(powerOnInject, times(1)).onReferenced(eq(powerProvider), any(Power.class));
verify(driverOnInject, times(1)).onReferenced(eq(driverProvider), any(Driver.class));
verify(robotOnInject, times(1)).onReferenced(eq(robotProvider), any(Robot.class));
}
@Test
public void test_should_inject_and_release_correctly_on_single_object() throws PokeException {
prepareInjection();
final Factory factory = new Factory();
graph.inject(factory, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.release(factory, MyInject.class);
Assert.assertTrue(component.scopeCache.getCachedInstances().isEmpty());
}
@Test
public void test_should_inject_and_release_correctly_on_multiple_objects() throws PokeException {
final ScopeCache scopeCache = new ScopeCache();
prepareInjection();
final Factory factory1 = new Factory();
final Factory factory2 = new Factory();
graph.inject(factory1, MyInject.class);
Provider<Power> powerProvider = component.findProvider(Power.class, null);
Provider<Driver> driverProvider = component.findProvider(Driver.class, null);
Provider<Robot> robotProvider = component.findProvider(Robot.class, null);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.inject(factory2, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.release(factory2, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.release(factory1, MyInject.class);
Assert.assertTrue(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertTrue(powerProvider.owners.isEmpty());
Assert.assertEquals(0, powerProvider.getReferenceCount());
Assert.assertTrue(driverProvider.owners.isEmpty());
Assert.assertEquals(0, driverProvider.getReferenceCount());
Assert.assertTrue(robotProvider.owners.isEmpty());
Assert.assertEquals(0, robotProvider.getReferenceCount());
}
@Test
public void test_should_inject_and_release_correctly_even_with_same_cached_objects_multiple_times()
throws PokeException {
prepareInjection();
final Factory factory = new Factory();
graph.inject(factory, MyInject.class);
Provider<Power> powerProvider = component.findProvider(Power.class, null);
Provider<Driver> driverProvider = component.findProvider(Driver.class, null);
Provider<Robot> robotProvider = component.findProvider(Robot.class, null);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.inject(factory, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.release(factory, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.release(factory, MyInject.class);
Assert.assertTrue(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertTrue(powerProvider.owners.isEmpty());
Assert.assertEquals(0, powerProvider.getReferenceCount());
Assert.assertTrue(driverProvider.owners.isEmpty());
Assert.assertEquals(0, driverProvider.getReferenceCount());
Assert.assertTrue(robotProvider.owners.isEmpty());
Assert.assertEquals(0, robotProvider.getReferenceCount());
}
@Test
public void test_should_inject_and_release_correctly_on_multiple_objects_even_with_same_cached_objects_multiple_times()
throws PokeException {
prepareInjection();
final Factory factory1 = new Factory();
graph.inject(factory1, MyInject.class);
Provider<Power> powerProvider = component.findProvider(Power.class, null);
Provider<Driver> driverProvider = component.findProvider(Driver.class, null);
Provider<Robot> robotProvider = component.findProvider(Robot.class, null);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
final Factory factory2 = new Factory();
graph.inject(factory2, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
graph.inject(factory1, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertNotNull(factory1);
Assert.assertNotNull(factory1.power);
Assert.assertNotNull(factory1.driver);
Assert.assertNotNull(((PowerImpl) factory1.power).driver);
Assert.assertNotNull(((PowerImpl) factory1.power).robot);
Assert.assertNotNull(((DriverImpl) factory1.driver).power);
Assert.assertNotNull(((DriverImpl) factory1.driver).robot);
Assert.assertNotNull(((RobotImpl) ((DriverImpl) factory1.driver).robot).driver);
Assert.assertNotNull(((RobotImpl) ((DriverImpl) factory1.driver).robot).power);
Assert.assertNotNull(factory2);
Assert.assertNotNull(factory2.power);
Assert.assertNotNull(factory2.driver);
Assert.assertNotNull(((PowerImpl)factory2.power).driver);
Assert.assertNotNull(((PowerImpl)factory2.power).robot);
Assert.assertNotNull(((DriverImpl)factory2.driver).power);
Assert.assertNotNull(((DriverImpl)factory2.driver).robot);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).driver);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).power);
graph.release(factory1, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertNotNull(factory1);
Assert.assertNotNull(factory1.power);
Assert.assertNotNull(factory1.driver);
Assert.assertNotNull(((PowerImpl) factory1.power).driver);
Assert.assertNotNull(((PowerImpl) factory1.power).robot);
Assert.assertNotNull(((DriverImpl) factory1.driver).power);
Assert.assertNotNull(((DriverImpl) factory1.driver).robot);
Assert.assertNotNull(((RobotImpl) ((DriverImpl) factory1.driver).robot).driver);
Assert.assertNotNull(((RobotImpl) ((DriverImpl) factory1.driver).robot).power);
Assert.assertNotNull(factory2);
Assert.assertNotNull(factory2.power);
Assert.assertNotNull(factory2.driver);
Assert.assertNotNull(((PowerImpl)factory2.power).driver);
Assert.assertNotNull(((PowerImpl)factory2.power).robot);
Assert.assertNotNull(((DriverImpl)factory2.driver).power);
Assert.assertNotNull(((DriverImpl)factory2.driver).robot);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).driver);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).power);
graph.release(factory1, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertNotNull(factory1);
Assert.assertNotNull(factory1.power);
Assert.assertNotNull(factory1.driver);
Assert.assertNotNull(factory2);
Assert.assertNotNull(factory2.power);
Assert.assertNotNull(factory2.driver);
Assert.assertNotNull(((PowerImpl)factory2.power).driver);
Assert.assertNotNull(((PowerImpl)factory2.power).robot);
Assert.assertNotNull(((DriverImpl)factory2.driver).power);
Assert.assertNotNull(((DriverImpl)factory2.driver).robot);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).driver);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).power);
graph.release(factory1, MyInject.class);
Assert.assertFalse(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertNotNull(factory1);
Assert.assertNotNull(factory1.power);
Assert.assertNotNull(factory1.driver);
Assert.assertNotNull(factory2);
Assert.assertNotNull(factory2.power);
Assert.assertNotNull(factory2.driver);
Assert.assertNotNull(((PowerImpl)factory2.power).driver);
Assert.assertNotNull(((PowerImpl)factory2.power).robot);
Assert.assertNotNull(((DriverImpl)factory2.driver).power);
Assert.assertNotNull(((DriverImpl)factory2.driver).robot);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).driver);
Assert.assertNotNull(((RobotImpl)((DriverImpl) factory2.driver).robot).power);
graph.release(factory2, MyInject.class);
Assert.assertTrue(component.scopeCache.getCachedInstances().isEmpty());
Assert.assertTrue(powerProvider.owners.isEmpty());
Assert.assertEquals(0, powerProvider.getReferenceCount());
Assert.assertTrue(driverProvider.owners.isEmpty());
Assert.assertEquals(0, driverProvider.getReferenceCount());
Assert.assertTrue(robotProvider.owners.isEmpty());
Assert.assertEquals(0, robotProvider.getReferenceCount());
}
private void prepareInjection() throws PokeException {
ProviderByClassType powerProvider = new ProviderByClassType(Power.class, PowerImpl.class);
ProviderByClassType driverProvider = new ProviderByClassType(Driver.class, DriverImpl.class);
ProviderByClassType robotProvider = new ProviderByClassType(Robot.class, RobotImpl.class);
component.register(powerProvider);
component.register(robotProvider);
component.register(driverProvider);
}
@Singleton
static class Factory {
@MyInject
private Power power;
@MyInject
private Driver driver;
}
@Singleton
static class RobotImpl implements Robot {
@MyInject
private Driver driver;
@MyInject
private Power power;
}
@Singleton
static class DriverImpl implements Driver {
@MyInject
private Power power;
@MyInject
private Robot robot;
}
@Singleton
static class PowerImpl implements Power {
@MyInject
private Robot robot;
@MyInject
private Driver driver;
public Robot getConnectedRobot() {
return robot;
}
}
interface Robot {
}
interface Driver {
}
interface Power {
}
}