001 // Copyright 2007 Waterken Inc. under the terms of the MIT X license
002 // found at http://www.opensource.org/licenses/mit-license.html
003 package org.waterken.bang;
004
005 import static org.ref_send.test.Logic.join;
006 import static org.ref_send.test.Logic.was;
007
008 import org.ref_send.list.List;
009 import org.ref_send.promise.Eventual;
010 import org.ref_send.promise.Promise;
011
012 /**
013 * An introduction to eventual operations in Java.
014 * <p>
015 * This class provides an introduction to eventual operations by using them to
016 * update and query a counter held in an object of type {@link Drum}.
017 * </p>
018 */
019 public final class
020 Beat {
021 private Beat() { /* no instance interface */ }
022
023 /**
024 * Runs a unit test.
025 * <p>
026 * This method is called by the infrastructure code that manages the
027 * lifecycle of vats. The return from this method is an object that will be
028 * returned to the creator of the new vat. In this case, the vat creator
029 * will get a promise for a boolean.
030 * </p>
031 * <p>
032 * By convention, an instance of {@link Eventual} is held in a variable
033 * named "_" and referred to as the "eventual operator". The eventual
034 * operator provides all eventual control flow operations, as well as the
035 * ability to produce "eventual references", which are references that
036 * schedule future invocation of a method, instead of invoking a method
037 * immediately. All references known to be eventual are also stored in
038 * variables whose name is suffixed with the '_' character. Consequently,
039 * you can scan down a page of code, looking for the character sequence "_."
040 * to find all the operations that are expected to be eventual.
041 * </p>
042 * @param _ eventual operator
043 * @param drum test subject
044 */
045 static public Promise<?>
046 make(final Eventual _, final Drum drum) {
047 /*
048 * First, ensure that we have an eventual reference to the drum, since
049 * the caller may have provided an immediate reference. The _()
050 * operation takes a reference that may be either immediate or eventual
051 * and returns a reference that is guaranteed to be eventual.
052 */
053 final Drum drum_ = _._(drum);
054
055 /*
056 * Start the test sequence by checking that the caller provided a new
057 * drum. We get the initial hit count by doing an eventual invocation of
058 * the getHits() method. If the provided drum is in another vat, this
059 * invocation will result in an HTTP GET request being sent to the
060 * hosting server. The return from the getHits() invocation is a promise
061 * for the number of hits. Using the when() operation, we register an
062 * observer on this promise, to receive a notification after the HTTP
063 * GET response has come back. The observer, constructed by the was()
064 * method, produces a promise for a boolean, indicating whether or not
065 * the number of hits specified in the HTTP GET response was the number
066 * expected. We'll hold onto this promise and use it to produce the
067 * promise returned to our caller.
068 */
069 final Promise<?> zero = _.when(drum_.getHits(), was(0));
070
071 /*
072 * Increment the hit counter by doing an eventual invocation of
073 * the bang() method. If the provided drum is in another vat,
074 * this invocation will result in an HTTP POST request being
075 * sent to the hosting server.
076 */
077 drum_.bang(1);
078
079 /*
080 * Requests sent on an eventual reference are sent in order, and
081 * so another check of the drum's hit count will see a value 1
082 * more than the previous check.
083 */
084 final Promise<?> one = _.when(drum_.getHits(), was(1));
085
086 // We can queue up as many requests as we like...
087 drum_.bang(2);
088
089 // ...and they will all be sent in order.
090 final Promise<?> three = _.when(drum_.getHits(), was(3));
091
092 /*
093 * The Waterken server can log the causal chaining of all these
094 * events. Comments can be inserted into this log via the
095 * eventual operator.
096 */
097 _.log.comment("all bang requests queued");
098
099 /*
100 * We now have 3 promises for checks on the expected value of the drum's
101 * hit count. We'll combine these 3 promises into 1 by doing an eventual
102 * join operation on them. The promise returned to our caller will
103 * resolve as soon as any one of the promises is rejected, or after all
104 * of them are fulfilled. Note that none of the HTTP requests have
105 * actually been sent yet; we've just scheduled them to be sent and
106 * setup code to be run when the responses eventually come back.
107 */
108 return join(_, zero, one, three);
109
110 /*
111 * In total, we've scheduled 3 GET requests and 2 POST requests. If any
112 * of these communications are interrupted, due to a server crash or
113 * lost connection, the software will remember where it left off and
114 * resume as soon as network connections can be re-established, which
115 * the software will also periodically retry. Regardless of how often
116 * this process is interrupted, or for how long, the hit count on the
117 * drum will only be incremented by 3, the number specified by the
118 * algorithm above.
119 */
120 }
121
122 // Command line interface
123
124 /**
125 * Executes the test.
126 * <p>
127 * This class can also be run from the command line, to run tests against a
128 * local, transient {@link Drum}. Most factory classes won't provide a
129 * command line test suite and so won't have a {@link #main} method.
130 * </p>
131 * @param args ignored
132 * @throws Exception test failed
133 */
134 static public void
135 main(final String[] args) throws Exception {
136 /*
137 * All the eventual control flow operations bottom out in runnable tasks
138 * on an event loop. This method provides a simple implementation for
139 * the event loop. The Waterken Server provides a more complete
140 * implementation that supports multiple concurrent event loops with
141 * transparent persistence and across the network messaging.
142 */
143 final List<Promise<?>> work = List.list();
144 final Eventual _ = new Eventual(work.appender());
145 final Promise<?> result = make(_, Bang.make());
146 while (!work.isEmpty()) { work.pop().call(); }
147 result.call();
148 }
149 }