co.easimart.EasimartEventuallyQueue Maven / Gradle / Ivy
package co.easimart;
import android.util.SparseArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import bolts.Task;
/* package */ abstract class EasimartEventuallyQueue {
private boolean isConnected;
/**
* Gets notifications of various events happening in the command cache, so that tests can be
* synchronized.
*/
private TestHelper testHelper;
public abstract void onDestroy();
public void setConnected(boolean connected) {
isConnected = connected;
}
public boolean isConnected() {
return isConnected;
}
public abstract int pendingCount();
public void setTimeoutRetryWaitSeconds(double seconds) {
// do nothing
}
public void setMaxCacheSizeBytes(int bytes) {
// do nothing
}
/** See class TestHelper below. */
public TestHelper getTestHelper() {
if (testHelper == null) {
testHelper = new TestHelper();
}
return testHelper;
}
protected void notifyTestHelper(int event) {
notifyTestHelper(event, null);
}
protected void notifyTestHelper(int event, Throwable t) {
if (testHelper != null) {
testHelper.notify(event, t);
}
}
public abstract void pause();
public abstract void resume();
/**
* Attempts to run the given command and any pending commands. Adds the command to the pending set
* if it can't be run yet.
*
* @param command
* - The command to run.
* @param object
* - If this command references an unsaved object, we need to remove any previous command
* referencing that unsaved object. Otherwise, it will get created over and over again.
* So object is a reference to the object, if it has no objectId. Otherwise, it can be
* null.
*/
public abstract Task enqueueEventuallyAsync(EasimartRESTCommand command,
EasimartObject object);
protected EasimartRESTCommand commandFromJSON(JSONObject json)
throws JSONException {
EasimartRESTCommand command = null;
if (EasimartRESTCommand.isValidCommandJSONObject(json)) {
command = EasimartRESTCommand.fromJSONObject(json);
} else if (EasimartRESTCommand.isValidOldFormatCommandJSONObject(json)) {
// do nothing
} else {
throw new JSONException("Failed to load command from JSON.");
}
return command;
}
/* package */ Task waitForOperationSetAndEventuallyPin(EasimartOperationSet operationSet,
EventuallyPin eventuallyPin) {
return Task.forResult(null);
}
/* package */ abstract void simulateReboot();
/**
* Gets rid of all pending commands.
*/
public abstract void clear();
/**
* Fakes an object update notification for use in tests. This is used by saveEventually to make it
* look like test code has updated an object through the command cache even if it actually
* avoided executing update by determining the object wasn't dirty.
*/
void fakeObjectUpdate() {
if (testHelper != null) {
testHelper.notify(TestHelper.COMMAND_ENQUEUED);
testHelper.notify(TestHelper.COMMAND_SUCCESSFUL);
testHelper.notify(TestHelper.OBJECT_UPDATED);
}
}
/**
* Gets notifications of various events happening in the command cache, so that tests can be
* synchronized. See EasimartCommandCacheTest for examples of how to use this.
*/
public static class TestHelper {
private static final int MAX_EVENTS = 1000;
public static final int COMMAND_SUCCESSFUL = 1;
public static final int COMMAND_FAILED = 2;
public static final int COMMAND_ENQUEUED = 3;
public static final int COMMAND_NOT_ENQUEUED = 4;
public static final int OBJECT_UPDATED = 5;
public static final int OBJECT_REMOVED = 6;
public static final int NETWORK_DOWN = 7;
public static final int COMMAND_OLD_FORMAT_DISCARDED = 8;
public static String getEventString(int event) {
switch (event) {
case COMMAND_SUCCESSFUL:
return "COMMAND_SUCCESSFUL";
case COMMAND_FAILED:
return "COMMAND_FAILED";
case COMMAND_ENQUEUED:
return "COMMAND_ENQUEUED";
case COMMAND_NOT_ENQUEUED:
return "COMMAND_NOT_ENQUEUED";
case OBJECT_UPDATED:
return "OBJECT_UPDATED";
case OBJECT_REMOVED:
return "OBJECT_REMOVED";
case NETWORK_DOWN:
return "NETWORK_DOWN";
case COMMAND_OLD_FORMAT_DISCARDED:
return "COMMAND_OLD_FORMAT_DISCARDED";
default:
throw new IllegalStateException("Encountered unknown event: " + event);
}
}
private SparseArray events = new SparseArray<>();
private TestHelper() {
clear();
}
public void clear() {
events.clear();
events.put(COMMAND_SUCCESSFUL, new Semaphore(MAX_EVENTS));
events.put(COMMAND_FAILED, new Semaphore(MAX_EVENTS));
events.put(COMMAND_ENQUEUED, new Semaphore(MAX_EVENTS));
events.put(COMMAND_NOT_ENQUEUED, new Semaphore(MAX_EVENTS));
events.put(OBJECT_UPDATED, new Semaphore(MAX_EVENTS));
events.put(OBJECT_REMOVED, new Semaphore(MAX_EVENTS));
events.put(NETWORK_DOWN, new Semaphore(MAX_EVENTS));
events.put(COMMAND_OLD_FORMAT_DISCARDED, new Semaphore(MAX_EVENTS));
for (int i = 0; i < events.size(); i++) {
int event = events.keyAt(i);
events.get(event).acquireUninterruptibly(MAX_EVENTS);
}
}
public int unexpectedEvents() {
int sum = 0;
for (int i = 0; i < events.size(); i++) {
int event = events.keyAt(i);
sum += events.get(event).availablePermits();
}
return sum;
}
public List getUnexpectedEvents() {
List unexpectedEvents = new ArrayList<>();
for (int i = 0; i < events.size(); i++) {
int event = events.keyAt(i);
if (events.get(event).availablePermits() > 0) {
unexpectedEvents.add(getEventString(event));
}
}
return unexpectedEvents;
}
public void notify(int event) {
notify(event, null);
}
public void notify(int event, Throwable t) {
events.get(event).release();
}
public boolean waitFor(int event) {
return waitFor(event, 1);
}
public boolean waitFor(int event, int permits) {
try {
return events.get(event).tryAcquire(permits, 10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
}
}