package ninja.ugly.prevail.chunk;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import ninja.ugly.prevail.event.Event;
import ninja.ugly.prevail.event.dispatcher.EventDispatcher;
import ninja.ugly.prevail.event.factory.DeleteEventFactory;
import ninja.ugly.prevail.event.factory.InsertEventFactory;
import ninja.ugly.prevail.event.factory.QueryEventFactory;
import ninja.ugly.prevail.event.factory.UpdateEventFactory;
import ninja.ugly.prevail.exception.DeleteException;
import ninja.ugly.prevail.exception.InsertException;
import ninja.ugly.prevail.exception.QueryException;
import ninja.ugly.prevail.exception.UpdateException;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A default implementation of the Chunk interface that implements dispatching events.
* @param <K> The type of keys on this Chunk
* @param <V> The type of values on this Chunk
*/
public abstract class DefaultChunk<K, V> implements Chunk<K, V> {
private List<InsertEventFactory> mInsertEventFactories = new CopyOnWriteArrayList<InsertEventFactory>();
private List<QueryEventFactory> mQueryEventFactories = new CopyOnWriteArrayList<QueryEventFactory>();
private List<UpdateEventFactory> mUpdateEventFactories = new CopyOnWriteArrayList<UpdateEventFactory>();
private List<DeleteEventFactory> mDeleteEventFactories = new CopyOnWriteArrayList<DeleteEventFactory>();
private EventDispatcher mEventDispatcher = new EventDispatcher.EmptyEventDispatcher();
public DefaultChunk() {
}
/**
* {@inheritDoc}
*/
@Override
public K insert(final V value, final InsertEventFactory<K, V>... customEventFactories) throws InsertException {
final InsertEventFactory[] fs = Optional.fromNullable(customEventFactories).or(new InsertEventFactory[0]);
final Iterable<InsertEventFactory> factories = Iterables.concat(mInsertEventFactories, Lists.newArrayList(fs));
try {
sendInsertStartEvent(factories, value);
final K key = doInsert(value, new OnProgressUpdateListener() {
@Override
public void onProgressUpdate(double progress) {
sendInsertProgressEvent(factories, value, progress);
}
});
sendInsertEndEvent(factories, key, value);
return key;
} catch (InsertException e) {
sendInsertExceptionEvent(factories, value, e);
throw e;
}
}
/**
* Insert a value to the Chunk.
* <p>
* This method should be overriden by subclasses in order to store the value in an implementation
* specific manner. There is no requirement to send any events from this method.
* <p>
* The semantics of this method is implementation specific. That is, some implementations may choose
* to throw an InsertException if the given key already exists in the Chunk, whilst other implementations
* may wish to replace the value in that case.
*
* @param value The value to be stored
* @param onProgressUpdateListener
* @return a Key into the chunk for later retrieval of the given value.
* @throws InsertException
*/
protected abstract K doInsert(final V value, OnProgressUpdateListener onProgressUpdateListener) throws InsertException;
/**
* {@inheritDoc}
*/
@Override
public QueryResult<V> query(final K key, final QueryEventFactory<K, V>... customEventFactories) throws QueryException {
final QueryEventFactory[] fs = Optional.fromNullable(customEventFactories).or(new QueryEventFactory[0]);
final Iterable<QueryEventFactory> factories = Iterables.concat(mQueryEventFactories, Lists.newArrayList(fs));
try {
sendQueryStartEvent(factories, key);
final QueryResult values = doQuery(key, new OnProgressUpdateListener() {
@Override
public void onProgressUpdate(double progress) {
sendQueryProgressEvent(factories, key, progress);
}
});
sendQueryEndEvent(factories, key, values);
return values;
} catch (QueryException e) {
sendQueryExceptionEvent(factories, key, e);
throw e;
}
}
/**
* Query values from the Chunk.
* <p>
* This method should be overriden by subclasses in order to obtain the values in an implementation
* specific manner. There is no requirement to send any events from this method.
*
* @param key The key to obtain the required values
* @return a QueryResult containing the returned values.
* @throws QueryException
*/
protected abstract QueryResult doQuery(final K key, OnProgressUpdateListener onProgressUpdateListener) throws QueryException;
/**
* {@inheritDoc}
*/
@Override
public int update(final K key, final V value, final UpdateEventFactory<K, V>... customEventFactories) throws UpdateException {
final UpdateEventFactory[] fs = Optional.fromNullable(customEventFactories).or(new UpdateEventFactory[0]);
final Iterable<UpdateEventFactory> factories = Iterables.concat(mUpdateEventFactories, Lists.newArrayList(fs));
try {
sendUpdateStartEvent(factories, key, value);
final int i = doUpdate(key, value, new OnProgressUpdateListener() {
@Override
public void onProgressUpdate(double progress) {
sendUpdateProgressEvent(factories, key, value, progress);
}
});
sendUpdateEndEvent(factories, key, value, i);
return i;
} catch (UpdateException e) {
sendUpdateExceptionEvent(factories, key, value, e);
throw e;
}
}
/**
* Update the value at the given key in the Chunk.
* <p>
* This method should be overriden by subclasses in order to update the value in an implementation
* specific manner. There is no requirement to send any events from this method.
* <p>
* The semantics of this method is implementation specific. That is, some implementations may choose
* to throw an UpdateException if the given key does not exist in the Chunk, whilst other implementations
* may wish to insert the value in that case.
*
* @param key The key of the value to be updated.
* @param value The value to be stored.
* @param progressUpdateListener
* @return the number of values updated.
* @throws UpdateException
*/
protected abstract int doUpdate(final K key, final V value, OnProgressUpdateListener progressUpdateListener) throws UpdateException;
/**
* {@inheritDoc}
*/
@Override
public int delete(final K key, final DeleteEventFactory<K>... customEventFactories) throws DeleteException {
final DeleteEventFactory[] fs = Optional.fromNullable(customEventFactories).or(new DeleteEventFactory[0]);
final Iterable<DeleteEventFactory> factories = Iterables.concat(mDeleteEventFactories, Lists.newArrayList(fs));
try {
sendDeleteStartEvent(factories, key);
final int i = doDelete(key, new OnProgressUpdateListener() {
@Override
public void onProgressUpdate(double progress) {
sendDeleteProgressEvent(factories, key, progress);
}
});
sendDeleteEndEvent(factories, key, i);
return i;
} catch (DeleteException e) {
sendDeleteExceptionEvent(factories, key, e);
throw e;
}
}
/**
* Update the value at the given key in the Chunk.
* <p>
* This method should be overriden by subclasses in order to update the value in an implementation
* specific manner. There is no requirement to send any events from this method.
* <p>
* The semantics of this method is implementation specific. That is, some implementations may choose
* to throw an DeleteException if the given key does not exist in the Chunk, whilst other implementations
* may wish to do nothing.
*
* @param key The key of the value to be deleted.
* @param onProgressUpdateListener
* @return the number of values deleted.
* @throws DeleteException
*/
protected abstract int doDelete(final K key, OnProgressUpdateListener onProgressUpdateListener) throws DeleteException;
/**
* {@inheritDoc}
*/
@Override
public void setEventDispatcher(final EventDispatcher eventDispatcher) {
mEventDispatcher = Optional.fromNullable(eventDispatcher).or(new EventDispatcher.EmptyEventDispatcher());
}
/**
* {@inheritDoc}
*/
@Override
public void addEventFactory(final InsertEventFactory insertEventFactory) {
mInsertEventFactories.add(checkNotNull(insertEventFactory));
}
/**
* {@inheritDoc}
*/
@Override
public void addEventFactory(final QueryEventFactory queryEventFactory) {
mQueryEventFactories.add(checkNotNull(queryEventFactory));
}
/**
* {@inheritDoc}
*/
@Override
public void addEventFactory(final UpdateEventFactory updateEventFactory) {
mUpdateEventFactories.add(checkNotNull(updateEventFactory));
}
/**
* {@inheritDoc}
*/
@Override
public void addEventFactory(final DeleteEventFactory deleteEventFactory) {
mDeleteEventFactories.add(checkNotNull(deleteEventFactory));
}
private void sendInsertEndEvent(final InsertEventFactory eventFactory, final K key, final V value) {
final Optional<Event> endEvent = eventFactory.endEvent(key, value);
if (endEvent.isPresent()) {
mEventDispatcher.dispatchEvent(endEvent.get());
}
}
private void sendInsertEndEvent(final Iterable<InsertEventFactory> eventFactories, final K key, final V value) {
for (InsertEventFactory eventFactory : eventFactories) {
sendInsertEndEvent(eventFactory, key, value);
}
}
private void sendQueryEndEvent(final QueryEventFactory eventFactory, final K key, final QueryResult<V> values) {
final Optional<Event> endEvent = eventFactory.endEvent(key, values);
if (endEvent.isPresent()) {
mEventDispatcher.dispatchEvent(endEvent.get());
}
}
private void sendQueryEndEvent(final QueryEventFactory[] eventFactories, final K key, final QueryResult<V> value) {
sendQueryEndEvent(Lists.newArrayList(eventFactories), key, value);
}
private void sendQueryEndEvent(final Iterable<QueryEventFactory> eventFactories, final K key, final QueryResult<V> value) {
for (QueryEventFactory eventFactory : eventFactories) {
sendQueryEndEvent(eventFactory, key, value);
}
}
private void sendUpdateEndEvent(final UpdateEventFactory eventFactory, final K key, final V value, final int numValuesUpdated) {
final Optional<Event> endEvent = eventFactory.endEvent(key, value, numValuesUpdated);
if (endEvent.isPresent()) {
mEventDispatcher.dispatchEvent(endEvent.get());
}
}
private void sendUpdateEndEvent(final UpdateEventFactory[] eventFactories, final K key, final V value, final int numValuesUpdated) {
sendUpdateEndEvent(Lists.newArrayList(eventFactories), key, value, numValuesUpdated);
}
private void sendUpdateEndEvent(final Iterable<UpdateEventFactory> eventFactories, final K key, final V value, final int numValuesUpdated) {
for (UpdateEventFactory eventFactory : eventFactories) {
sendUpdateEndEvent(eventFactory, key, value, numValuesUpdated);
}
}
private void sendDeleteEndEvent(final DeleteEventFactory eventFactory, final K key, final int numValuesDeleted) {
final Optional<Event> endEvent = eventFactory.endEvent(key, numValuesDeleted);
if (endEvent.isPresent()) {
mEventDispatcher.dispatchEvent(endEvent.get());
}
}
private void sendDeleteEndEvent(final Iterable<DeleteEventFactory> eventFactories, final K key, final int numValuesDeleted) {
for (DeleteEventFactory eventFactory : eventFactories) {
sendDeleteEndEvent(eventFactory, key, numValuesDeleted);
}
}
private void sendDeleteProgressEvent(final DeleteEventFactory eventFactory, final K key, final double progress) {
final Optional<Event> progressEvent = eventFactory.progressEvent(key, progress);
if (progressEvent.isPresent()) {
mEventDispatcher.dispatchEvent(progressEvent.get());
}
}
private void sendDeleteProgressEvent(final Iterable<DeleteEventFactory> eventFactories, final K key, final double progress) {
for (DeleteEventFactory eventFactory : eventFactories) {
sendDeleteProgressEvent(eventFactory, key, progress);
}
}
private void sendInsertProgressEvent(final InsertEventFactory eventFactory, final V value, final double progress) {
final Optional<Event> progressEvent = eventFactory.progressEvent(value, progress);
if (progressEvent.isPresent()) {
mEventDispatcher.dispatchEvent(progressEvent.get());
}
}
private void sendInsertProgressEvent(final Iterable<InsertEventFactory> eventFactories, final V value, final double progress) {
for (InsertEventFactory eventFactory : eventFactories) {
sendInsertProgressEvent(eventFactory, value, progress);
}
}
private void sendQueryProgressEvent(final QueryEventFactory eventFactory, final K key, final double progress) {
final Optional<Event> progressEvent = eventFactory.progressEvent(key, progress);
if (progressEvent.isPresent()) {
mEventDispatcher.dispatchEvent(progressEvent.get());
}
}
private void sendQueryProgressEvent(final Iterable<QueryEventFactory> eventFactories, final K key, final double progress) {
for (QueryEventFactory eventFactory : eventFactories) {
sendQueryProgressEvent(eventFactory, key, progress);
}
}
private void sendUpdateProgressEvent(final UpdateEventFactory eventFactory, final K key, final V value, final double progress) {
final Optional<Event> progressEvent = eventFactory.progressEvent(key, value, progress);
if (progressEvent.isPresent()) {
mEventDispatcher.dispatchEvent(progressEvent.get());
}
}
private void sendUpdateProgressEvent(final Iterable<UpdateEventFactory> eventFactories, final K key, final V value, final double progress) {
for (UpdateEventFactory eventFactory : eventFactories) {
sendUpdateProgressEvent(eventFactory, key, value, progress);
}
}
private void sendInsertExceptionEvent(final InsertEventFactory eventFactory, final V value, final InsertException exception) {
final Optional<Event> exceptionEvent = eventFactory.exceptionEvent(value, exception);
if (exceptionEvent.isPresent()) {
mEventDispatcher.dispatchEvent(exceptionEvent.get());
}
}
private void sendInsertExceptionEvent(final Iterable<InsertEventFactory> eventFactories, final V value, final InsertException exception) {
for (InsertEventFactory eventFactory : eventFactories) {
sendInsertExceptionEvent(eventFactory, value, exception);
}
}
private void sendQueryExceptionEvent(final QueryEventFactory eventFactory, final K key, final QueryException exception) {
final Optional<Event> exceptionEvent = eventFactory.exceptionEvent(key, exception);
if (exceptionEvent.isPresent()) {
mEventDispatcher.dispatchEvent(exceptionEvent.get());
}
}
private void sendQueryExceptionEvent(final QueryEventFactory[] eventFactories, final K key, final QueryException exception) {
sendQueryExceptionEvent(Lists.newArrayList(eventFactories), key, exception);
}
private void sendQueryExceptionEvent(final Iterable<QueryEventFactory> eventFactories, final K key, final QueryException exception) {
for (QueryEventFactory eventFactory : eventFactories) {
sendQueryExceptionEvent(eventFactory, key, exception);
}
}
private void sendUpdateExceptionEvent(final UpdateEventFactory eventFactory, final K key, final V value, final UpdateException exception) {
final Optional<Event> exceptionEvent = eventFactory.exceptionEvent(key, value, exception);
if (exceptionEvent.isPresent()) {
mEventDispatcher.dispatchEvent(exceptionEvent.get());
}
}
private void sendUpdateExceptionEvent(final UpdateEventFactory[] eventFactories, final K key, final V value, final UpdateException exception) {
sendUpdateExceptionEvent(Lists.newArrayList(eventFactories), key, value, exception);
}
private void sendUpdateExceptionEvent(final Iterable<UpdateEventFactory> eventFactories, final K key, final V value, final UpdateException exception) {
for (UpdateEventFactory eventFactory : eventFactories) {
sendUpdateExceptionEvent(eventFactory, key, value, exception);
}
}
private void sendDeleteExceptionEvent(final DeleteEventFactory eventFactory, final K key, final DeleteException exception) {
final Optional<Event> exceptionEvent = eventFactory.exceptionEvent(key, exception);
if (exceptionEvent.isPresent()) {
mEventDispatcher.dispatchEvent(exceptionEvent.get());
}
}
private void sendDeleteExceptionEvent(final Iterable<DeleteEventFactory> eventFactories, final K key, final DeleteException exception) {
for (DeleteEventFactory eventFactory : eventFactories) {
sendDeleteExceptionEvent(eventFactory, key, exception);
}
}
private void sendInsertStartEvent(final InsertEventFactory eventFactory, final V value) {
final Optional<Event> startEvent = eventFactory.startEvent(value);
if (startEvent.isPresent()) {
mEventDispatcher.dispatchEvent(startEvent.get());
}
}
private void sendInsertStartEvent(final Iterable<InsertEventFactory> eventFactories, final V value) {
for (InsertEventFactory eventFactory : eventFactories) {
sendInsertStartEvent(eventFactory, value);
}
}
private void sendQueryStartEvent(final QueryEventFactory eventFactory, final K key) {
final Optional<Event> startEvent = eventFactory.startEvent(key);
if (startEvent.isPresent()) {
mEventDispatcher.dispatchEvent(startEvent.get());
}
}
private void sendQueryStartEvent(final QueryEventFactory[] eventFactories, final K key) {
sendQueryStartEvent(Lists.newArrayList(eventFactories), key);
}
private void sendQueryStartEvent(final Iterable<QueryEventFactory> eventFactories, final K key) {
for (QueryEventFactory eventFactory : eventFactories) {
sendQueryStartEvent(eventFactory, key);
}
}
private void sendUpdateStartEvent(final UpdateEventFactory eventFactory, final K key, final V value) {
final Optional<Event> startEvent = eventFactory.startEvent(key, value);
if (startEvent.isPresent()) {
mEventDispatcher.dispatchEvent(startEvent.get());
}
}
private void sendUpdateStartEvent(final UpdateEventFactory[] eventFactories, final K key, final V value) {
sendUpdateStartEvent(Lists.newArrayList(eventFactories), key, value);
}
private void sendUpdateStartEvent(final Iterable<UpdateEventFactory> eventFactories, final K key, final V value) {
for (UpdateEventFactory eventFactory : eventFactories) {
sendUpdateStartEvent(eventFactory, key, value);
}
}
private void sendDeleteStartEvent(final DeleteEventFactory eventFactory, final K key) {
final Optional<Event> startEvent = eventFactory.startEvent(key);
if (startEvent.isPresent()) {
mEventDispatcher.dispatchEvent(startEvent.get());
}
}
private void sendDeleteStartEvent(final Iterable<DeleteEventFactory> eventFactories, final K key) {
for (DeleteEventFactory eventFactory : eventFactories) {
sendDeleteStartEvent(eventFactory, key);
}
}
public interface OnProgressUpdateListener {
void onProgressUpdate(double progress);
public static class EmptyOnProgressUpdateListener implements OnProgressUpdateListener {
@Override
public void onProgressUpdate(double progress) {
// Do nothing.
}
}
}
}