diff --git a/src/org/thoughtcrime/securesms/camera/OrderEnforcer.java b/src/org/thoughtcrime/securesms/camera/OrderEnforcer.java new file mode 100644 index 000000000..bc46ba1f9 --- /dev/null +++ b/src/org/thoughtcrime/securesms/camera/OrderEnforcer.java @@ -0,0 +1,74 @@ +package org.thoughtcrime.securesms.camera; + +import android.support.annotation.NonNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +public class OrderEnforcer { + + private final E[] stages; + private final Map stageIndices; + private final Map> actions; + private final boolean[] completion; + + public OrderEnforcer(@NonNull E... stages) { + this.stages = stages; + this.stageIndices = new HashMap<>(); + this.actions = new HashMap<>(); + this.completion = new boolean[stages.length]; + + for (int i = 0; i < stages.length; i++) { + stageIndices.put(stages[i], i); + } + } + + public synchronized void run(@NonNull E stage, Runnable r) { + if (isCompletedThrough(stage)) { + r.run(); + } else { + List stageActions = actions.containsKey(stage) ? actions.get(stage) : new CopyOnWriteArrayList<>(); + stageActions.add(r); + + actions.put(stage, stageActions); + } + } + + public synchronized void markCompleted(@NonNull E stage) { + completion[stageIndices.get(stage)] = true; + + int i = 0; + while (i < completion.length && completion[i]) { + List stageActions = actions.get(stages[i]); + if (stageActions != null) { + for (Runnable r : stageActions) { + r.run(); + } + stageActions.clear(); + } + i++; + } + } + + public synchronized void reset() { + for (int i = 0; i < completion.length; i++) { + completion[i] = false; + } + actions.clear(); + } + + private boolean isCompletedThrough(@NonNull E stage) { + int index = stageIndices.get(stage); + int i = 0; + + while (i <= index && i < completion.length) { + if (!completion[i]) { + return false; + } + i++; + } + return true; + } +} diff --git a/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java b/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java new file mode 100644 index 000000000..bef016c92 --- /dev/null +++ b/test/unitTest/java/org/thoughtcrime/securesms/camera/OrderEnforcerTest.java @@ -0,0 +1,104 @@ +package org.thoughtcrime.securesms.camera; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +import static junit.framework.Assert.assertEquals; + +public class OrderEnforcerTest { + + @Test + public void markCompleted_singleEntry() { + AtomicInteger counter = new AtomicInteger(0); + + OrderEnforcer enforcer = new OrderEnforcer<>(Stage.A, Stage.B, Stage.C, Stage.D); + enforcer.run(Stage.A, new CountRunnable(counter)); + assertEquals(0, counter.get()); + + enforcer.markCompleted(Stage.A); + assertEquals(1, counter.get()); + } + + @Test + public void markCompleted_singleEntry_waterfall() { + AtomicInteger counter = new AtomicInteger(0); + + OrderEnforcer enforcer = new OrderEnforcer<>(Stage.A, Stage.B, Stage.C, Stage.D); + enforcer.run(Stage.C, new CountRunnable(counter)); + assertEquals(0, counter.get()); + + enforcer.markCompleted(Stage.A); + assertEquals(0, counter.get()); + + enforcer.markCompleted(Stage.C); + assertEquals(0, counter.get()); + + enforcer.markCompleted(Stage.B); + assertEquals(1, counter.get()); + } + + @Test + public void markCompleted_multipleEntriesPerStage_waterfall() { + AtomicInteger counter = new AtomicInteger(0); + + OrderEnforcer enforcer = new OrderEnforcer<>(Stage.A, Stage.B, Stage.C, Stage.D); + + enforcer.run(Stage.A, new CountRunnable(counter)); + enforcer.run(Stage.A, new CountRunnable(counter)); + assertEquals(0, counter.get()); + + enforcer.run(Stage.B, new CountRunnable(counter)); + enforcer.run(Stage.B, new CountRunnable(counter)); + assertEquals(0, counter.get()); + + enforcer.run(Stage.C, new CountRunnable(counter)); + enforcer.run(Stage.C, new CountRunnable(counter)); + assertEquals(0, counter.get()); + + enforcer.run(Stage.D, new CountRunnable(counter)); + enforcer.run(Stage.D, new CountRunnable(counter)); + assertEquals(0, counter.get()); + + enforcer.markCompleted(Stage.A); + assertEquals(counter.get(), 2); + + enforcer.markCompleted(Stage.D); + assertEquals(counter.get(), 2); + + enforcer.markCompleted(Stage.B); + assertEquals(counter.get(), 4); + + enforcer.markCompleted(Stage.C); + assertEquals(counter.get(), 8); + } + + @Test + public void run_alreadyCompleted() { + AtomicInteger counter = new AtomicInteger(0); + + OrderEnforcer enforcer = new OrderEnforcer<>(Stage.A, Stage.B, Stage.C, Stage.D); + enforcer.markCompleted(Stage.A); + enforcer.markCompleted(Stage.B); + + enforcer.run(Stage.B, new CountRunnable(counter)); + assertEquals(1, counter.get()); + } + + private static class CountRunnable implements Runnable { + private final AtomicInteger counter; + + public CountRunnable(AtomicInteger counter) { + this.counter = counter; + } + + @Override + public void run() { + counter.incrementAndGet(); + } + } + + private enum Stage { + A, B, C, D + } +}