package architect;
import java.util.ArrayList;
import java.util.List;
import mortar.MortarScope;
/**
* Dispatch consequences of history manipulation
*
* @author Lukasz Piliszczuk - lukasz.pili@gmail.com
*/
class Dispatcher {
private final Navigator navigator;
private final List<History.Entry> entries = new ArrayList<>();
private boolean dispatching;
private boolean killed;
private boolean active;
Dispatcher(Navigator navigator) {
this.navigator = navigator;
}
/**
* Stop the dispatcher forever
* Its only salvation lies in garbage collection now
*/
void kill() {
Preconditions.checkArgument(!killed, "Did you try to kill the dispatcher twice? Not cool brah");
killed = true;
}
/**
* Attach the dispatcher on new context
*/
void activate() {
Preconditions.checkArgument(!active, "Dispatcher already active");
Preconditions.checkArgument(entries.isEmpty(), "Dispatcher stack must be empty");
Preconditions.checkNotNull(navigator.getScope(), "Navigator scope cannot be null");
// clean dead entries that may had happen on history while dispatcher
// was inactive
List<History.Entry> dead = navigator.history.removeAllDead();
if (dead != null && !dead.isEmpty()) {
History.Entry entry;
for (int i = 0; i < dead.size(); i++) {
entry = dead.get(i);
Logger.d("Dead entry: %s", entry.scopeName);
MortarScope scope = navigator.getScope().findChild(entry.scopeName);
if (scope != null) {
Logger.d("Clean and destroy scope %s", entry.scopeName);
scope.destroy();
}
}
}
History.Entry entry = navigator.history.getLastAlive();
Preconditions.checkNotNull(entry, "No alive entry");
Logger.d("Last alive entry: %s", entry.scopeName);
MortarScope entryScope = navigator.getScope().findChild(entry.scopeName);
if (entryScope == null) {
entryScope = StackFactory.createScope(navigator.getScope(), entry.path, entry.scopeName);
}
List<Dispatch> dispatches = null;
if (entry.isModal()) {
// entry modal, get previous displayed
List<History.Entry> previous = navigator.history.getPreviousOfModal(entry);
if (previous != null && !previous.isEmpty()) {
dispatches = new ArrayList<>(previous.size() + 1);
History.Entry prevEntry;
MortarScope scope;
for (int i = previous.size() - 1; i >= 0; i--) {
prevEntry = previous.get(i);
Logger.d("Get previous entry: %s", prevEntry.scopeName);
scope = navigator.getScope().findChild(prevEntry.scopeName);
if (scope == null) {
scope = StackFactory.createScope(navigator.getScope(), prevEntry.path, prevEntry.scopeName);
}
dispatches.add(new Dispatch(prevEntry, scope));
}
}
}
if (dispatches == null) {
dispatches = new ArrayList<>(1);
}
dispatches.add(new Dispatch(entry, entryScope));
navigator.presenter.restore(dispatches);
active = true;
}
void desactivate() {
Preconditions.checkArgument(active, "Dispatcher already desactivated");
active = false;
entries.clear();
}
void dispatch(List<History.Entry> e) {
if (!active) return;
entries.addAll(e);
startDispatch();
}
void dispatch(History.Entry entry) {
if (!active) return;
entries.add(entry);
startDispatch();
}
void startDispatch() {
Preconditions.checkArgument(active, "Dispatcher must be active");
if (killed || dispatching || !navigator.presenter.isActive() || entries.isEmpty()) return;
dispatching = true;
Preconditions.checkNotNull(navigator.getScope(), "Dispatcher navigator scope cannot be null");
Preconditions.checkArgument(!navigator.history.isEmpty(), "Cannot dispatch on empty history");
Preconditions.checkArgument(!entries.isEmpty(), "Cannot dispatch on empty stack");
final History.Entry entry = entries.remove(0);
Preconditions.checkArgument(navigator.history.existInHistory(entry), "Entry does not exist in history");
Logger.d("Get entry with scope: %s", entry.scopeName);
ViewTransitionDirection direction;
History.Entry nextEntry;
final History.Entry previousEntry;
if (entry.dead) {
direction = ViewTransitionDirection.BACKWARD;
previousEntry = entry;
nextEntry = navigator.history.getLeftOf(entry);
} else {
direction = ViewTransitionDirection.FORWARD;
nextEntry = entry;
previousEntry = navigator.history.getLeftOf(entry);
}
Preconditions.checkNotNull(nextEntry, "Next entry cannot be null");
Preconditions.checkNotNull(previousEntry, "Previous entry cannot be null");
Preconditions.checkNull(nextEntry.receivedResult, "Next entry cannot have already a result");
if (!entries.isEmpty()) {
boolean fastForwarded = fastForward(entry, nextEntry, previousEntry);
if (fastForwarded) {
return;
}
}
if (entry.dead && previousEntry.returnsResult != null) {
nextEntry.receivedResult = previousEntry.returnsResult;
previousEntry.returnsResult = null;
}
present(nextEntry, previousEntry, direction);
}
private void present(History.Entry nextEntry, final History.Entry previousEntry, ViewTransitionDirection direction) {
MortarScope currentScope = navigator.presenter.getCurrentScope();
Preconditions.checkNotNull(currentScope, "Current scope cannot be null");
Logger.d("Current container scope is: %s", currentScope.getName());
navigator.presenter.present(createDispatch(nextEntry), previousEntry, direction, new Callback() {
@Override
public void onComplete() {
if (previousEntry.dead) {
destroyDead(previousEntry);
}
endDispatch();
startDispatch();
}
});
}
private boolean fastForward(History.Entry entry, History.Entry nextEntry, History.Entry previousEntry) {
boolean fastForwarded = false;
boolean fastForwardCanAffectsRoot = false;
List<Dispatch> modals = null;
History.Entry nextDispatch = null;
while (!entries.isEmpty() && (nextDispatch = entries.get(0)) != null) {
Logger.d("Get next dispatch: %s", nextDispatch.scopeName);
if (entry.isModal()) {
// fast forward for modals works only with other modals
// it will animate all the fast-forwarded modals at once (in parallel)
if (!nextDispatch.isModal()) {
break;
}
if (modals == null) {
modals = new ArrayList<>();
modals.add(createDispatch(entry));
}
fastForwarded = true;
entries.remove(0);
modals.add(createDispatch(nextDispatch));
Logger.d("Modal fast forward to next entry: %s", nextDispatch.scopeName);
continue;
}
// non-modal fast-forward
if (nextDispatch.isModal()) {
// fast forward only on non-modals
break;
}
fastForwarded = true;
entries.remove(0);
if (nextDispatch.dead) {
// the entry just before root is dead
if (navigator.history.indexOf(nextDispatch) == 1) {
fastForwardCanAffectsRoot = true;
}
destroyDead(nextDispatch);
}
Logger.d("Fast forward to next entry: %s", nextDispatch.scopeName);
}
if (!fastForwarded) {
return false;
}
// BAD DESIGN =>
// special case when fastforward remove all stack until root,
// then add new entries
// because root entry is never included in the entries to dispatch,
// we need to make sure to remove it
// conditions are: at least one entry destroyed (= the one before root), and the last one is not dead
if ((fastForwardCanAffectsRoot && !nextDispatch.dead) || (entry.dead && navigator.history.indexOf(entry) == 1)) {
History.Entry root = navigator.history.getRoot();
// if the fast forward removes some entries and add some new entries without affecting root
// ignore it
if (root.dead) {
destroyDead(root);
}
}
if (nextDispatch.dead) {
nextDispatch = navigator.history.getLastAlive();
Preconditions.checkNotNull(nextDispatch, "Fast forward end of the chain entry must be alive");
}
if (entry.dead && previousEntry.returnsResult != null) {
nextDispatch.receivedResult = previousEntry.returnsResult;
previousEntry.returnsResult = null;
Logger.d("Pass result from %s to %s", previousEntry.scopeName, nextDispatch.scopeName);
}
if (entry.isModal()) {
final List<Dispatch> finalModals = modals;
navigator.presenter.presentModals(modals, new Callback() {
@Override
public void onComplete() {
Dispatch dispatch;
for (int i = 0; i < finalModals.size(); i++) {
dispatch = finalModals.get(i);
if (dispatch.entry.dead) {
destroyDead(dispatch.entry);
}
}
endDispatch();
startDispatch();
}
});
} else {
ViewTransitionDirection direction;
if (nextDispatch.direction != null) {
direction = nextDispatch.direction;
nextDispatch.direction = null;
} else {
direction = previousEntry.dead ? ViewTransitionDirection.BACKWARD : ViewTransitionDirection.FORWARD;
}
present(nextDispatch, previousEntry, direction);
}
return true;
}
private Dispatch createDispatch(History.Entry entry) {
MortarScope nextScope = navigator.getScope().findChild(entry.scopeName);
if (nextScope == null) {
nextScope = StackFactory.createScope(navigator.getScope(), entry.path, entry.scopeName);
}
return new Dispatch(entry, nextScope);
}
private void destroyDead(History.Entry entry) {
Logger.d("Remove dead entry: %s", entry.scopeName);
navigator.history.remove(entry);
// remove as well from dispatching entry, just in case
entries.remove(entry);
if (navigator.getScope() != null) {
MortarScope scope = navigator.getScope().findChild(entry.scopeName);
if (scope != null) {
Logger.d("Destroy scope %s", entry.scopeName);
scope.destroy();
}
}
}
private void endDispatch() {
Preconditions.checkArgument(dispatching, "Calling endDispatch while not dispatching");
dispatching = false;
}
interface Callback {
void onComplete();
}
// enum Direction {
// FORWARD, BACKWARD
// }
static class Dispatch {
History.Entry entry;
MortarScope scope;
public Dispatch(History.Entry entry, MortarScope scope) {
Preconditions.checkArgument(entry.scopeName.equals(scope.getName()), "Dispatch entry scope name does not match");
this.entry = entry;
this.scope = scope;
}
}
}