Created
September 15, 2016 19:33
-
-
Save tadfisher/4c0fde30641d95d3e5a3224e19d4eba0 to your computer and use it in GitHub Desktop.
Dagger runtime component management idea
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.banksimple.dagger; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.LinkedHashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.NoSuchElementException; | |
/** | |
* Cache intended to store Dagger components. Notifies any registered listeners when a component | |
* is added or removed. Effectively, this provides a "scope" containing components which comprise | |
* the runtime state of the application. | |
* <p /> | |
* Components are stored in a stack-like data structure. When removing a component, all components | |
* higher in the stack are removed and their listeners are notified in turn. A consequence of | |
* this design is that only one instance of a component interface can live in the stack at | |
* one time; thus this store should only be used to reference implicitly "active" components that | |
* shouldn't be constructed often throughout the lifetime of the application. | |
* <p /> | |
* This class does not enforce Dagger scoping rules; i.e. it is possible to add subcomponents | |
* before their parent components. Care must be taken to prevent undesirable behavior, thus | |
* components should be added in order of their position in the scope hierarchy. | |
*/ | |
public class ComponentStack { | |
/** | |
* Callback for changes to the stack pertaining to a component interface type. | |
*/ | |
public interface Listener<C> { | |
/** | |
* Called when a component has been added to the stack. | |
*/ | |
void onComponentAdded(C component); | |
/** | |
* Called when a component has been removed from the stack. | |
*/ | |
void onComponentRemoved(C component); | |
} | |
/** | |
* Convenience {@link Listener} implementation for callbacks which only respond to one event. | |
*/ | |
public static class ListenerAdapter<C> implements Listener<C> { | |
/** | |
* Called when a component has been added to the stack. The base implementation does | |
* nothing. | |
*/ | |
@Override | |
public void onComponentAdded(C component) {} | |
/** | |
* Called when a component has been removed from the stack. The base implementation does | |
* nothing. | |
*/ | |
@Override | |
public void onComponentRemoved(C component) {} | |
} | |
private final Map<Class<?>, Object> mStack; | |
private final Map<Class<?>, List<Listener<?>>> mListeners; | |
ComponentStack() { | |
mStack = new LinkedHashMap<>(); | |
mListeners = new LinkedHashMap<>(); | |
} | |
/** | |
* @return the implementation of {@code componentInterface} if it exists in the stack or | |
* {@code null}. | |
*/ | |
public <C> C peek(Class<C> componentInterface) { | |
return componentInterface.cast(mStack.get(componentInterface)); | |
} | |
/** | |
* Remove the instance of {@code componentInterface} and all components higher in the | |
* stack. Calls {@link Listener#onComponentRemoved(Object)} for any listeners registered for | |
* the removed component interface types. | |
* <p /> | |
* Component removal occurs in reverse order of their position in the stack; thus higher-level | |
* components will be removed before lower-level components. | |
* @param componentInterface the component interface to remove. | |
* @return the instance in the stack which implements {@code componentInterface}. | |
* @throws java.util.NoSuchElementException if an instance of {@code componentInterface} does | |
* not exist in the stack. | |
*/ | |
public <C> C pop(Class<C> componentInterface) { | |
return pop(componentInterface, true); | |
} | |
/** | |
* Removes all components in higher-level positions in the stack, leaving the instance of | |
* {@code componentInterface} intact. Calls {@link Listener#onComponentRemoved(Object)} for | |
* any listeners registered for the removed component interface types. | |
* @param componentInterface the component interface which will become the new top of the stack. | |
* @return the instance in the stack which implements {@code componentInterface}. | |
* @throws java.util.NoSuchElementException if an instance of {@code componentInterface} does | |
* not exist in the stack. | |
*/ | |
public <C> C popTo(Class<C> componentInterface) { | |
return pop(componentInterface, false); | |
} | |
private <C> C pop(Class<C> componentInterface, boolean inclusive) { | |
if (componentInterface == null) { | |
throw new NullPointerException("componentInterface == null"); | |
} | |
if (!mStack.containsKey(componentInterface)) { | |
throw new NoSuchElementException(componentInterface.getSimpleName() + | |
" does not exist in the component stack"); | |
} | |
List<Class<?>> keys = new ArrayList<>(mStack.keySet()); | |
Collections.reverse(keys); | |
for (Class<?> key : keys) { | |
if (key.equals(componentInterface)) { | |
if (inclusive) { | |
return remove(componentInterface); | |
} else { | |
return peek(componentInterface); | |
} | |
} | |
remove(key); | |
} | |
throw new IllegalStateException(componentInterface.getSimpleName() + " not found in the " | |
+ "stack; this should never occur"); | |
} | |
private <C> C remove(Class<C> componentInterface) { | |
C component = componentInterface.cast(mStack.remove(componentInterface)); | |
notifyRemoved(componentInterface, component); | |
return component; | |
} | |
/** | |
* Pushes a new component instance on the top of the stack. The component interface must not | |
* have an existing implementation on the stack. | |
* @param componentInterface The interface implemented by {@code component}. | |
* @param component An instance implementing {@code componentInterface}. | |
* @return the component instance which was pushed on the stack. | |
* @throws IllegalArgumentException if an instance of {@code componentInterface} already | |
* exists in the stack. | |
*/ | |
public <C> C push(Class<C> componentInterface, Object component) { | |
if (componentInterface == null) { | |
throw new NullPointerException("componentInterface == null"); | |
} | |
if (component == null) { | |
throw new NullPointerException("component == null"); | |
} | |
if (!componentInterface.isInterface()) { | |
throw new IllegalArgumentException(componentInterface.getSimpleName() + | |
" is not an interface"); | |
} | |
if (!componentInterface.isInstance(component)) { | |
throw new IllegalArgumentException(component.getClass().getSimpleName() + | |
" does not implement " + componentInterface.getSimpleName()); | |
} | |
if (mStack.containsKey(componentInterface)) { | |
throw new IllegalArgumentException("An instance of " + | |
componentInterface.getSimpleName() + " already exists on the stack: " + | |
peek(componentInterface).getClass().getSimpleName()); | |
} | |
mStack.put(componentInterface, component); | |
C castComponent = componentInterface.cast(component); | |
notifyAdded(componentInterface, castComponent); | |
return castComponent; | |
} | |
/** | |
* Register a listener for a component interface type. This listener will be called whenever | |
* any instance of that type is added or removed from the stack. Multiple listeners can be | |
* registered for a given component interface type. | |
* @param componentInterface The component interface to listen for. | |
* @param listener The listener notified for changes in the stack. | |
*/ | |
public <C> void register(Class<C> componentInterface, Listener<C> listener) { | |
if (componentInterface == null) { | |
throw new NullPointerException("componentInterface == null"); | |
} | |
if (!componentInterface.isInterface()) { | |
throw new IllegalArgumentException(componentInterface.getSimpleName() + | |
" is not an interface"); | |
} | |
if (listener == null) { | |
throw new NullPointerException("listener == null"); | |
} | |
List<Listener<?>> listeners = mListeners.get(componentInterface); | |
if (listeners == null) { | |
listeners = new ArrayList<>(); | |
} | |
listeners.add(listener); | |
mListeners.put(componentInterface, listeners); | |
} | |
/** | |
* Remove a listener for a component interface type. | |
* @param componentInterface The component interface to listen for. | |
* @param listener The listener notified for changes in the stack. | |
*/ | |
public <C> void unregister(Class<C> componentInterface, Listener<C> listener) { | |
if (componentInterface == null) { | |
throw new NullPointerException("componentInterface == null"); | |
} | |
if (!componentInterface.isInterface()) { | |
throw new IllegalArgumentException(componentInterface.getSimpleName() + | |
" is not an interface"); | |
} | |
if (listener == null) { | |
throw new NullPointerException("listener == null"); | |
} | |
List<Listener<?>> listeners = mListeners.get(componentInterface); | |
if (listeners != null) { | |
listeners.remove(listener); | |
} | |
} | |
private <C> void notifyAdded(Class<C> componentInterface, C component) { | |
List<Listener<?>> listeners = mListeners.get(componentInterface); | |
if (listeners == null) { | |
return; | |
} | |
for (Listener<?> listener : listeners) { | |
// noinspection unchecked | |
((Listener<C>) listener).onComponentAdded(component); | |
} | |
} | |
private <C> void notifyRemoved(Class<C> componentInterface, C component) { | |
List<Listener<?>> listeners = mListeners.get(componentInterface); | |
if (listeners == null) { | |
return; | |
} | |
for (Listener<?> listener : listeners) { | |
// noinspection unchecked | |
((Listener<C>) listener).onComponentRemoved(component); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment