001package votorola.a.count.gwt; // Copyright 2011-2013, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import com.google.gwt.core.client.*; 004import com.google.gwt.http.client.URL; 005import com.google.gwt.jsonp.client.JsonpRequest; 006import com.google.gwt.user.client.rpc.AsyncCallback; 007import votorola.a.count.*; 008import votorola.a.web.gwt.*; 009import votorola.a.web.wap.*; 010import votorola.g.web.gwt.*; 011 012 013/** A positional accounting node in a delegate cascade implemented as a JavaScript overlay 014 * type. It holds the registers for all resource superaccounts at the position. It is 015 * immutable, except that its {@linkplain #voters() voters} may be lazily initialized. 016 * 017 * @see SacRegisterJS 018 * @see <a href='http://reluk.ca/w/Category:Position'>Category:Position</a> 019 */ 020public final class CountNodeJS extends JavaScriptObject implements CountNode 021{ 022 023 024 protected CountNodeJS() {} // "precisely one constructor... protected, empty, and no-argument" 025 026 027 028 /** Initializes this node from a {@linkplain votorola.s.wap.CountWAP server response}. 029 * If this node does not exist on the server, it is assigned default values for all 030 * state variables, including its {@linkplain #voters() voters}. 031 * 032 * @see #name() 033 * @param wiredNodes the "nodes" as received in the response. 034 */ 035 void init( final String name, final JsMap<CountNodeJS> wiredNodes, final CountJS count ) 036 { 037 _set( "name", name ); // fill in detail excluded on wire 038 _set( "count", count ); 039 JsArrayString voterNames = _get( "voters" ); 040 if( registers() == null ) // no actual node was counted 041 { 042 _set( "registers", DEFAULT_REGISTERS ); 043 if( voterNames == null ) voterNames = createArray().cast(); 044 // definitely no voters for uncounted node, so eagerly initialize accordingly 045 else assert voterNames.length() == 0; // though never occurs with current CountWAP 046 _setByte( "dartSector", (byte)0 ); 047 _setString( "directVoterCount", "0" ); 048 _setBoolean( "isCycler", false ); 049 } 050 if( voterNames != null ) initVoters( voterNames, wiredNodes ); 051 } 052 053 054 055 /** Initializes this node's voters from a {@linkplain votorola.s.wap.CountWAP server 056 * response}. 057 * 058 * @param voterNames the 'voters' as named in the response. 059 * @param wiredNodes the "nodes" as received in the response. 060 */ 061 void initVoters( final JsArrayString voterNames, final JsMap<CountNodeJS> wiredNodes ) 062 { 063 final JsArray<CountNodeJS> voters; 064 if( voterNames.length() == 0 ) voters = NULL_BOARD; 065 else 066 { 067 voters = createArray( DART_SECTOR_MAX ).cast(); 068 final int vN = voterNames.length(); 069 for( int v = 0; v < vN; ++v ) // convert dense array of names to sparse array of nodes 070 { 071 final String name = voterNames.get( v ); 072 final CountNodeJS node = count().cachedOrWireNode( name, wiredNodes ); 073 voters.set( node.dartSector() - 1, node ); 074 } 075 } 076 _set( "voters", voters ); 077 GWTX.i().bus().fireEventFromSource( new CountNodeEvent(CountNodeJS.this), CountCache.i() ); 078 } 079 080 081 082 // ------------------------------------------------------------------------------------ 083 084 085 /** The parent count of this count node. 086 */ 087 public native CountJS count() /*-{ return this.count; }-*/; 088 089 090 091 /** Returns the node of the specified name from the array of nodes, or null if there 092 * is no such node. 093 * 094 * @param nodes the array of nodes to search, which may contain null elements. 095 */ 096 public static CountNodeJS findNode( final String name, final JsArray<CountNodeJS> nodes ) 097 { 098 for( int n = nodes.length() - 1; n >= 0; --n ) 099 { 100 final CountNodeJS node = nodes.get( n ); 101 if( node == null ) continue; 102 103 if( name.equals( node.name() )) return node; 104 } 105 106 return null; 107 } 108 109 110 111 /** An array of length {@value votorola.a.count.CountNode#DART_SECTOR_MAX} filled with 112 * null values. Do not modify it. 113 */ 114 public static final JsArray<CountNodeJS> NULL_BOARD = 115 JavaScriptObjectX.createArray( DART_SECTOR_MAX ).cast(); 116 117 118 119 /** Returns this node's superaccount register for the specified counting method and 120 * account name; or null if no such register exists. 121 */ 122 public <R extends SacRegisterJS> R register( final String countingMethodName, 123 final String accountName ) 124 { 125 final JsMap<CountingMethodJS<R>> methodMap = registers(); 126 R register = null; 127 final CountingMethodJS<R> method = methodMap.get( countingMethodName ); 128 if( method != null ) register = method.get( accountName ); 129 return register; 130 } 131 132 133 134 /** Superaccount registers at this node, indexed first by the page name of the 135 * counting method, then by the account name. 136 * 137 * @see votorola.s.wap.CountWAP 138 */ 139 public native <R extends SacRegisterJS> JsMap<CountingMethodJS<R>> registers() 140 /*-{ 141 return this.registers; // defaulted in init 142 }-*/; 143 144 145 146 /** Requests this node's voters from the count engine. 147 * 148 * @param callback the callback to receive the response, the response being 149 * this count node with its voters set. 150 * 151 * @see #voters() 152 * @see #requestVoters_loc(StringBuilder) 153 */ 154 public JsonpRequest<CountCache.WAPResponse> requestVoters( final String loc, 155 final AsyncCallback<CountNodeJS> callback ) 156 { 157 assert voters() == null: "successful voter requests not repeated"; 158 return App.i().jsonpWAP().requestObject( loc, new WAPCleanCallback( count().pollName() ) 159 { 160 public void onFailure( final Throwable x ) { callback.onFailure( x ); } 161 162 public void onSuccess( final CountCache.WAPResponseP responseP ) 163 { 164 if( voters() == null ) 165 { 166 final JsMap<CountNodeJS> wiredNodes = responseP._get( "nodes" ); 167 final JsArrayString voterNames = wiredNodes.get(name())._get( "voters" ); 168 initVoters( voterNames, wiredNodes ); 169 } 170 // else collision with another request, so just report success: 171 callback.onSuccess( CountNodeJS.this ); 172 } 173 }); 174 } 175 176 177 178 /** Appends the location for calls to <code>requestVoters</code> and returns the 179 * string builder. 180 */ 181 public StringBuilder requestVoters_loc( final StringBuilder b ) 182 { 183 return count().requestNode_loc( name(), b ); 184 } 185 186 187 188 /** The vote register at this node. 189 */ 190 public native SacRegisterJS_v voteRegister() 191 /*-{ 192 return this.registers["Wiki:Vote count"]["Votes"]; 193 }-*/; 194 195 196 197 /** A sparse array of the {@linkplain CountNode#dartSector() dart sectored} voters, if 198 * known. The array is indexed from zero with null elements for empty sectors. When 199 * the value of this array becomes known, the cache fires a {@linkplain 200 * CountNodeEvent count node event} via the {@linkplain GWTX#bus() event bus}. 201 * 202 * @return an array of length {@value votorola.a.count.CountNode#DART_SECTOR_MAX}, 203 * or null if the voters are not yet known. 204 * 205 * @see #requestVoters(String,AsyncCallback) 206 * @see #initVoters(JsArrayString,JsMap) 207 */ 208 public native JsArray<CountNodeJS> voters() /*-{ return this.voters; }-*/; 209 // FIX to index from 1 not zero, so as to match range of dart sectors. Meantime as 210 // a convention to prevent confusion, let variables based on the name "sector" or 211 // "s" range from 1-20 (reserving zero for unsectored nodes), while those based on 212 // "n" (or whatever) range from 0-19. 213 214 215 216 // - C o u n t - N o d e -------------------------------------------------------------- 217 218 219 public native String candidateName() /*-{ return this.candidateName; }-*/; 220 221 222 223 public native byte dartSector() /*-{ return this.dartSector; }-*/; // defaulted in init 224 225 226 227 public native Long directVoterCount() 228 /*-{ 229 return @java.lang.Long::new(Ljava/lang/String;)( this.directVoterCount ); 230 // defaulted in init 231 }-*/; 232 233 234 235 public native String displayTitle() /*-{ return this.displayTitle; }-*/; 236 237 238 239 public boolean isBaseCandidate() { return isCycler() || isRootCandidate(); } 240 241 242 243 public boolean isCandidate() { return directVoterCount() > 0; } 244 245 246 247 public native boolean isCycler() /*-{ return this.isCycler; }-*/; // defaulted in init 248 249 250 251 public boolean isRootCandidate() { return !isVoter() && isCandidate(); } 252 253 254 255 public boolean isVoter() 256 { 257 final String candidateName = candidateName(); 258 return candidateName != null && !candidateName.equals(name()); 259 } 260 261 262 263 public native String name() /*-{ return this.name; }-*/; 264 265 266 267//// P r i v a t e /////////////////////////////////////////////////////////////////////// 268 269 270 private static final JavaScriptObject DEFAULT_REGISTERS = JavaScriptObject.createObject(); 271 272 static 273 { 274 final JavaScriptObject method = JavaScriptObject.createObject(); 275 DEFAULT_REGISTERS._set( "Wiki:Vote count", method ); 276 method._set( "Votes", SacRegisterJS_v.DEFAULT_REGISTER ); 277 } 278 279}