package ninja.ugly.prevail.example; import com.google.common.eventbus.Subscribe; import java.io.Closeable; import java.io.IOException; import java.util.InputMismatchException; import java.util.Iterator; import java.util.Scanner; import ninja.ugly.prevail.chunk.QueryResult; import ninja.ugly.prevail.datamodel.DataModel; import ninja.ugly.prevail.event.DataChangeEndEvent; import ninja.ugly.prevail.event.QueryEndEvent; import ninja.ugly.prevail.event.dispatcher.EventDispatcher; import ninja.ugly.prevail.event.factory.DeleteEndEventFactory; import ninja.ugly.prevail.event.factory.InsertEndEventFactory; import ninja.ugly.prevail.event.factory.QueryEndEventFactory; import ninja.ugly.prevail.event.factory.UpdateEndEventFactory; /** * A class to manage 'To Do' items by interaction with System.in and System.out. */ public class TodoListManager { private final DataModel dataModel; private final Scanner scanner = new Scanner(System.in); public TodoListManager(DataModel dataModel, EventDispatcher eventDispatcher) { this.dataModel = dataModel; // Register the subscribers here in the constructor. More generally, // if this object had a managed life-cycle in another application container, // then registration an un-registration should occur within that lifecycle. eventDispatcher.register(new DataChangeEventSubscriber()); eventDispatcher.register(new QueryEventSubscriber()); } /** * Start the interaction on System.in and System.out to manage 'To Do' list items. */ public void start() { // Grab the next instruction from the user. next(); } /** * Requests input from the user from System.in in order to select an action to perform * (Add, Delete, or Edit). */ private void next() { System.out.print("[A]dd, [D]elete or [E]dit: "); String s = scan("[AaDdEe]"); Action a = new ActionFactory(dataModel).getAction(s); a.doIt(); } private interface Action { void doIt(); } /** * A Factory to map key-presses to actions */ private class ActionFactory { private final DataModel dataModel; private final TodoItem mItem; public ActionFactory(DataModel dataModel) { this(dataModel, null); } public ActionFactory(DataModel dataModel, TodoItem item) { this.dataModel = dataModel; mItem = item; } public Action getAction(String s) { if ("a".equalsIgnoreCase(s)) { return new InsertAction(); } else if ("e".equalsIgnoreCase(s)) { return new UpdateAction(); } else if ("d".equalsIgnoreCase(s)) { return new DeleteAction(); } else if ("c".equalsIgnoreCase(s)) { return new ToggleCompleteAction(mItem); } else if ("n".equalsIgnoreCase(s)) { return new ChangeNameAction(mItem); } else { return new EmptyAction(); } } } /** * An empty Action that just loops back to the next input cycle. */ private class EmptyAction implements Action { @Override public void doIt() { next(); } } /** * An Action that kicks of an item update. This action just queries the DataModel. * The result of that query is received in a previously registered QueryEventSubscriber that * continues the update operation. */ private class UpdateAction implements Action { @Override public void doIt() { System.out.print("Id: "); String id = scanner.next(); dataModel.<String>query(id, new QueryEndEventFactory()); } } /** * An Action that inserts a new TodoItem to the DataModel. */ private class InsertAction implements Action { @Override public void doIt() { System.out.print("Name: "); String name = scan(".*"); dataModel.insert(new TodoItem(name), new InsertEndEventFactory()); } } /** * An Action that deletes a TodoItem, by id, from the DataModel. */ private class DeleteAction implements Action { @Override public void doIt() { System.out.print("Id: "); String name = scan("[0-9]*"); dataModel.<String>delete(name, new DeleteEndEventFactory()); } } /** * An Action that changes the name of a TodoItem, and then writes to the DataModel. */ private class ChangeNameAction implements Action { private TodoItem mItem; public ChangeNameAction(TodoItem item) { mItem = item; } @Override public void doIt() { System.out.print("New name: "); String name = scan(".*"); mItem.setName(name); updateOrInsert(mItem); } } /** * An Action that toggle the completion state on a TodoItem, and then writes to the DataModel. */ private class ToggleCompleteAction implements Action { private TodoItem mItem; public ToggleCompleteAction(TodoItem item) { mItem = item; } @Override public void doIt() { mItem.setComplete(!mItem.isComplete()); updateOrInsert(mItem); } } /** * Update or insert the given TodoItem to the DataModel. * <p/> * The DataModel contains TodoItemChunk at the default segment. The implementation * of TodoItemChunk has strict update and insert semantics. In other implementations, * it may be that TodoItem will be inserted on failure to update. * * @param item */ private void updateOrInsert(TodoItem item) { if (item.optionalId().isPresent()) { String id = Integer.toString(item.optionalId().get()); dataModel.<String, TodoItem>update(id, item, new UpdateEndEventFactory()); } else { dataModel.insert(item, new InsertEndEventFactory()); } } /** * And event subscriber registered to respond to the end of any insert, update or delete operation. */ private class DataChangeEventSubscriber { @Subscribe public void dataChanged(DataChangeEndEvent event) { dataModel.<String>query("*", new QueryEndEventFactory()); } } /** * And event subscriber registered to respond to the end of any query operation. * <p/> * In this example, queries are performed by Strings. The queries are of the form * "*" or "{id}". Querying for "*" returns all TodoItems from the DataModel, whereas * querying for "{id}" returns a particular TodoItem. Other implementations may have * a more complex query language in the String, or else the key itself may be a more complex * object than a String. */ private class QueryEventSubscriber { @Subscribe public void queryEnd(QueryEndEvent<String, TodoItem> event) { if ("*".equals(event.getKey())) { handleQueryAll(event); } else { // This was a query for a single item, from UpdateAction handleQueryOne(event); } } private void handleQueryOne(QueryEndEvent<String, TodoItem> event) { QueryResult<TodoItem> result = event.getResult(); try { Iterator<TodoItem> iterator = result.iterator(); if (iterator.hasNext()) { TodoItem item = iterator.next(); System.out.println("Editing: " + item); System.out.print("Toggle [c]omplete or change [n]ame: "); String s = scan("[cCnN]"); Action a = new ActionFactory(dataModel, item).getAction(s); a.doIt(); } else { next(); } } finally { close(result); } } private void handleQueryAll(QueryEndEvent<String, TodoItem> event) { // This was a query for all items. QueryResult<TodoItem> result = event.getResult(); try { for (TodoItem todoItem : result) { System.out.println(todoItem); } next(); } finally { close(result); } } private void close(QueryResult<TodoItem> result) { try { result.close(); } catch (IOException e) { e.printStackTrace(); } } } private String scan(String pattern) { String s; try { s = scanner.next(pattern); } catch (InputMismatchException e) { scanner.skip(".*"); s = ""; } return s; } }