@Test public void testGetParentScopeNames_shouldReturnParentNames_whenThereAreParents() { //GIVEN ScopeNode parentScope = new ScopeImpl("root"); ScopeNode childScope = new ScopeImpl("child"); parentScope.addChild(childScope); //WHEN final List<Object> parentScopesNames = childScope.getParentScopesNames(); //THEN assertThat(parentScopesNames.size(), is(1)); assertThat(parentScopesNames.iterator().next(), is(parentScope.getName())); }
@Test public void testReset_shouldClearBoundAnnotations_andFlagTheScopeAsOpen() throws Exception { //GIVEN ScopeNode scope = new ScopeImpl("root"); scope.bindScopeAnnotation(CustomScope.class); scope.close(); //WHEN scope.reset(); //THEN assertThat(scope.isBoundToScopeAnnotation(CustomScope.class), is(false)); assertThat(scope.isOpen, is(true)); }
/** * @param scopeAnnotationClass an annotation that should be qualified by {@link javax.inject.Scope}. If not, * an exception is thrown. * @return the parent {@link ScopeNode} of this scope that is bound to {@code scopeAnnotationClass}. * The current {@code scope} (this) can be returned if it is bound to {@code scopeAnnotationClass}. * If no such parent exists, it throws an exception. This later case means that something scoped * is using a lower scoped dependency, which is conceptually flawed and not allowed in Toothpick. * Note that is {@code scopeAnnotationClass} is {@link Singleton}, the root scope is always returned. * Thus the {@link Singleton} scope annotation class doesn't need to be bound, it's built-in. */ @SuppressWarnings({ "unused", "used by generated code" }) @Override public ScopeNode getParentScope(Class scopeAnnotationClass) { checkIsAnnotationScope(scopeAnnotationClass); if (scopeAnnotationClass == Singleton.class) { return getRootScope(); } ScopeNode currentScope = this; while (currentScope != null) { if (currentScope.isBoundToScopeAnnotation(scopeAnnotationClass)) { return currentScope; } currentScope = currentScope.getParentScope(); } throw new IllegalStateException(format("There is no parent scope of %s bound to scope scopeAnnotationClass %s", // this.name, // scopeAnnotationClass.getName())); }
/** * Bind Scope Annotation if the Scope name is a Scope Annotation. * For example: Toothpick.openScope(MyScopeAnnotation.class) */ private void bindScopeAnnotationIfNameIsScopeAnnotation() { if (name.getClass() == Class.class // && Annotation.class.isAssignableFrom((Class) name) // && isScopeAnnotationClass((Class<? extends Annotation>) name)) { bindScopeAnnotation((Class<? extends Annotation>) name); } }
void removeChild(ScopeNode child) { if (child == null) { throw new IllegalArgumentException("Child must be non null."); } //this variable is important. It takes a snapshot of the node final ScopeNode parentScope = child.getParentScope(); if (parentScope == null) { throw new IllegalStateException(format("The scope has no parent: %s", child.getName())); } if (parentScope != this) { throw new IllegalStateException(format("The scope %s has parent: different of this: %s", // child.getName(), parentScope.getName(), getName())); } childrenScopes.remove(child.getName()); //make the ex-child a new root. child.parentScopes.clear(); }
/** * Detach a scope from its parent, this will trigger the garbage collection of this scope and it's * sub-scopes * if they are not referenced outside of Toothpick. * * @param name the name of the scope to close. */ public static void closeScope(Object name) { //we remove the scope first, so that other threads don't see it, and see the next snapshot of the tree ScopeNode scope = (ScopeNode) MAP_KEY_TO_SCOPE.remove(name); if (scope != null) { ScopeNode parentScope = scope.getParentScope(); if (parentScope != null) { parentScope.removeChild(scope); } else { ConfigurationHolder.configuration.onScopeForestReset(); } removeScopeAndChildrenFromMap(scope); } }
@Test public void testRemoveChild() { //GIVEN ScopeNode parentScope = new ScopeImpl("root"); ScopeNode childScope = new ScopeImpl("child"); parentScope.addChild(childScope); //WHEN parentScope.removeChild(childScope); //WHEN Collection<ScopeNode> childrenScopes = parentScope.getChildrenScopes(); //THEN assertThat(childrenScopes.isEmpty(), is(true)); }
@Test public void createScope_shouldReturnAnScopeWithAParent_whenThisScopeByThisKeyWasCreatedWithAParent() throws Exception { //GIVEN Toothpick.setConfiguration(Configuration.forProduction()); ScopeNode scopeParent = (ScopeNode) Toothpick.openScope("foo"); //WHEN ScopeNode scope = (ScopeNode) Toothpick.openScope("bar"); scopeParent.addChild(scope); //THEN assertThat(scope, notNullValue()); assertThat(scope.getParentScope(), sameInstance(scopeParent)); }
@Test(expected = IllegalStateException.class) public void testRemoveChild_shouldFail_whenChildHasDifferentParent() { //GIVEN ScopeNode parentScope = new ScopeImpl("root"); ScopeNode parentScope2 = new ScopeImpl("foo"); ScopeNode childScope = new ScopeImpl("child"); parentScope.addChild(childScope); //WHEN parentScope2.removeChild(childScope); //THEN fail("Should throw an exception"); }
/** * Removes all nodes of {@code scope} using DFS. We don't lock here. * * @param scope the parent scope of which all children will recursively be removed * from the map. We don't do anything else to the children nodes are they will be * garbage collected soon. We just cut a whole sub-graph in the references graph of the JVM * normally. */ private static void removeScopeAndChildrenFromMap(ScopeNode scope) { MAP_KEY_TO_SCOPE.remove(scope.getName()); scope.close(); for (ScopeNode childScope : scope.childrenScopes.values()) { removeScopeAndChildrenFromMap(childScope); } }
/** * Opens multiple scopes in a row. * Opened scopes will be children of each other in left to right order (e.g. {@code * openScopes(a,b)} opens scopes {@code a} and {@code b} * and {@code b} is a child of {@code a}. * * @param names of the scopes to open hierarchically. * @return the last opened scope, leaf node of the created subtree of scopes. */ public static Scope openScopes(Object... names) { if (names == null) { throw new IllegalArgumentException("null scope names are not allowed."); } if (names.length == 0) { throw new IllegalArgumentException("Minimally, one scope name is required."); } ScopeNode lastScope = null; ScopeNode previousScope = (ScopeNode) openScope(names[0], true); for (int i = 1; i < names.length; i++) { lastScope = (ScopeNode) openScope(names[i], false); lastScope = previousScope.addChild(lastScope); previousScope = lastScope; } return previousScope; }
@Test public void testReset_shouldRebindScopeAnnotation() throws Exception { //GIVEN ScopeNode scope = new ScopeImpl(CustomScope.class); //WHEN scope.reset(); //THEN assertThat(scope.isBoundToScopeAnnotation(CustomScope.class), is(true)); } }
List<Object> getParentScopesNames() { List<Object> parentScopesNames = new ArrayList<>(); for (ScopeNode parentScope : parentScopes) { parentScopesNames.add(parentScope.getName()); } return parentScopesNames; }
@Test public void testGetChildrenScopes_shouldReturnChildren_whenHasChildren() { //GIVEN ScopeNode parentScope = new ScopeImpl("root"); ScopeNode childScope = new ScopeImpl("child"); parentScope.addChild(childScope); //WHEN Collection<ScopeNode> childrenScopes = parentScope.getChildrenScopes(); //THEN assertThat(childrenScopes.isEmpty(), is(false)); assertThat(childrenScopes.size(), is(1)); assertThat(childrenScopes.iterator().next(), is(childScope)); }
@Test(expected = IllegalArgumentException.class) public void testRemoveChild_shouldFail_whenChildIsNull() { //GIVEN ScopeNode parentScope = new ScopeImpl("root"); //WHEN parentScope.removeChild(null); //THEN fail("Should throw an exception"); }
private void checkIsAnnotationScope(Class<? extends Annotation> scopeAnnotationClass) { if (!isScopeAnnotationClass(scopeAnnotationClass)) { throw new IllegalArgumentException(format("The annotation %s is not a scope annotation, " + "it is not qualified by javax.inject.Scope.", scopeAnnotationClass.getName())); } }
/** * Binds a {@code scopeAnnotationClass}, to the current scope. The current scope will accept all classes * that are scoped using this {@code scopeAnnotationClass}. * * @param scopeAnnotationClass an annotation that should be qualified by {@link javax.inject.Scope}. If not, * an exception is thrown. * Note that the {@link Singleton} scope annotation class doesn't need to be bound, it's built-in. * @see #getParentScope(Class) */ @Override public void bindScopeAnnotation(Class<? extends Annotation> scopeAnnotationClass) { checkIsAnnotationScope(scopeAnnotationClass); if (scopeAnnotationClass == Singleton.class) { throw new IllegalArgumentException(format("The annotation @Singleton is already bound " + "to the root scope of any scope. It can't be bound dynamically.")); } scopeAnnotationClasses.add(scopeAnnotationClass); }
final ScopeNode parentScope = child.getParentScope(); if (parentScope == this) { return child; ScopeNode scope = childrenScopes.putIfAbsent(child.getName(), child); if (scope != null) { return scope;
/** * Detach a scope from its parent, this will trigger the garbage collection of this scope and it's sub-scopes * if they are not referenced outside of ToothPick. * * @param name the name of the scope to close. */ public static void closeScope(Object name) { //we remove the scope first, so that other threads don't see it, and see the next snapshot of the tree ScopeNode scope = (ScopeNode) MAP_KEY_TO_SCOPE.remove(name); if (scope != null) { ScopeNode parentScope = scope.getParentScope(); if (parentScope != null) { parentScope.removeChild(scope); } removeScopeAndChildrenFromMap(scope); } }
/** * Bind Scope Annotation if the Scope name is a Scope Annotation. * For example: Toothpick.openScope(MyScopeAnnotation.class) */ private void bindScopeAnnotationIfNameIsScopeAnnotation() { if (name.getClass() == Class.class // && Annotation.class.isAssignableFrom((Class) name) // && isScopeAnnotationClass((Class<? extends Annotation>) name)) { bindScopeAnnotation((Class<? extends Annotation>) name); } }