Skip to content

Instantly share code, notes, and snippets.

@tadfisher
Created September 15, 2016 19:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tadfisher/4c0fde30641d95d3e5a3224e19d4eba0 to your computer and use it in GitHub Desktop.
Save tadfisher/4c0fde30641d95d3e5a3224e19d4eba0 to your computer and use it in GitHub Desktop.
Dagger runtime component management idea
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