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 }