package com.yoursway.utils.dependencies; import static com.google.common.collect.Lists.newArrayListWithCapacity; import static com.google.common.collect.Maps.newIdentityHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.yoursway.utils.annotations.MeaningfulWhen; import com.yoursway.utils.annotations.NonReentrant_SynchronizeExternallyOrUseFromSingleThread; import com.yoursway.utils.annotations.Reentrant_CallFromAnyThread; import com.yoursway.utils.annotations.SynchronizedWithMonitorOfField; import com.yoursway.utils.annotations.CallFromAnyThread_NonReentrant; import com.yoursway.utils.annotations.WhenNotMeaningfulHasUndefinedValue; import com.yoursway.utils.bugs.Bugs; public class DependentCodeRunner { private static final Dependee[] NO_DEPENDEES = new Dependee[0]; private enum DependeeState { OLD_AND_ALIVE(false, false), REMOVED(false, true), ADDED(true, false); public final boolean add; public final boolean remove; public final boolean alive; private DependeeState(boolean add, boolean remove) { this.add = add; this.remove = remove; this.alive = !remove; } } private static class DependenciesUpdater implements DependeesRequestor { private Map<Dependee, DependeeState> states; private final DependeeListener listener; public DependenciesUpdater(Dependee[] dependees, DependeeListener listener) { if (listener == null) throw new NullPointerException("listener is null"); this.listener = listener; Map<Dependee, DependeeState> states = newIdentityHashMap(); for (Dependee dependee : dependees) states.put(dependee, DependeeState.REMOVED); this.states = states; } @CallFromAnyThread_NonReentrant public synchronized void dependsOn(Dependee dependee) { DependeeState oldState = states.put(dependee, DependeeState.OLD_AND_ALIVE); if (oldState == null) { states.put(dependee, DependeeState.ADDED); dependee.dependeeEvents().addListener(listener); } } @NonReentrant_SynchronizeExternallyOrUseFromSingleThread public Dependee[] update() { List<Dependee> dependees = newArrayListWithCapacity(states.size()); for (Entry<Dependee, DependeeState> entry : states.entrySet()) { Dependee dependee = entry.getKey(); DependeeState state = entry.getValue(); if (state.remove) dependee.dependeeEvents().removeListener(listener); if (state.alive) dependees.add(dependee); } return dependees.toArray(new Dependee[dependees.size()]); } } private DependeeListener listener = new DependeeListener() { @CallFromAnyThread_NonReentrant public void changed(Dependee dependee) { recalculate(); } @CallFromAnyThread_NonReentrant public void removed(Dependee dependee) { recalculate(); } }; @SynchronizedWithMonitorOfField("listener") private Dependee[] dependees = NO_DEPENDEES; @SynchronizedWithMonitorOfField("listener") private boolean recalculationInProgress = false; @SynchronizedWithMonitorOfField("listener") @MeaningfulWhen("recalculationInProgress == true") @WhenNotMeaningfulHasUndefinedValue private boolean recalculateAgain = false; @SynchronizedWithMonitorOfField("listener") private boolean disposed = false; private final Runnable runnable; public DependentCodeRunner(Runnable runnable) { this.runnable = runnable; recalculate(); } @Reentrant_CallFromAnyThread public void dispose() { Dependee[] oldDependees; synchronized (listener) { if (disposed) return; disposed = true; oldDependees = this.dependees; this.dependees = NO_DEPENDEES; } for (Dependee dependee : oldDependees) try { dependee.dependeeEvents().removeListener(listener); } catch (Throwable e) { Bugs.cleanupFailed(e, dependee); } } @Reentrant_CallFromAnyThread public void recalculate() { Dependee[] oldDependees; synchronized (listener) { if (disposed) return; if (recalculationInProgress) { recalculateAgain = true; return; } recalculationInProgress = true; recalculateAgain = false; oldDependees = this.dependees; } // if another thread demands recalculation from now on, it will set recalculateAgain to true boolean again; do { DependenciesUpdater updater = new DependenciesUpdater(oldDependees, listener); Tracker.runAndTrack(runnable, updater); synchronized (listener) { if (disposed) return; this.dependees = updater.update(); again = recalculateAgain; if (again) { recalculateAgain = false; // if another thread demands recalculation from now on, it will set recalculateAgain to true } else { recalculationInProgress = false; // if another thread demands recalculation from now on, it will successfully start it } } } while (again); } }