package 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. import com.google.gwt.core.client.*; import com.google.gwt.http.client.URL; import com.google.gwt.jsonp.client.JsonpRequest; import com.google.gwt.user.client.rpc.AsyncCallback; import votorola.a.count.*; import votorola.a.web.gwt.*; import votorola.a.web.wap.*; import votorola.g.web.gwt.*; /** A positional accounting node in a delegate cascade implemented as a JavaScript overlay * type. It holds the registers for all resource superaccounts at the position. It is * immutable, except that its {@linkplain #voters() voters} may be lazily initialized. * * @see SacRegisterJS * @see Category:Position */ public final class CountNodeJS extends JavaScriptObject implements CountNode { protected CountNodeJS() {} // "precisely one constructor... protected, empty, and no-argument" /** Initializes this node from a {@linkplain votorola.s.wap.CountWAP server response}. * If this node does not exist on the server, it is assigned default values for all * state variables, including its {@linkplain #voters() voters}. * * @see #name() * @param wiredNodes the "nodes" as received in the response. */ void init( final String name, final JsMap wiredNodes, final CountJS count ) { _set( "name", name ); // fill in detail excluded on wire _set( "count", count ); JsArrayString voterNames = _get( "voters" ); if( registers() == null ) // no actual node was counted { _set( "registers", DEFAULT_REGISTERS ); if( voterNames == null ) voterNames = createArray().cast(); // definitely no voters for uncounted node, so eagerly initialize accordingly else assert voterNames.length() == 0; // though never occurs with current CountWAP _setByte( "dartSector", (byte)0 ); _setString( "directVoterCount", "0" ); _setBoolean( "isCycler", false ); } if( voterNames != null ) initVoters( voterNames, wiredNodes ); } /** Initializes this node's voters from a {@linkplain votorola.s.wap.CountWAP server * response}. * * @param voterNames the 'voters' as named in the response. * @param wiredNodes the "nodes" as received in the response. */ void initVoters( final JsArrayString voterNames, final JsMap wiredNodes ) { final JsArray voters; if( voterNames.length() == 0 ) voters = NULL_BOARD; else { voters = createArray( DART_SECTOR_MAX ).cast(); final int vN = voterNames.length(); for( int v = 0; v < vN; ++v ) // convert dense array of names to sparse array of nodes { final String name = voterNames.get( v ); final CountNodeJS node = count().cachedOrWireNode( name, wiredNodes ); voters.set( node.dartSector() - 1, node ); } } _set( "voters", voters ); GWTX.i().bus().fireEventFromSource( new CountNodeEvent(CountNodeJS.this), CountCache.i() ); } // ------------------------------------------------------------------------------------ /** The parent count of this count node. */ public native CountJS count() /*-{ return this.count; }-*/; /** Returns the node of the specified name from the array of nodes, or null if there * is no such node. * * @param nodes the array of nodes to search, which may contain null elements. */ public static CountNodeJS findNode( final String name, final JsArray nodes ) { for( int n = nodes.length() - 1; n >= 0; --n ) { final CountNodeJS node = nodes.get( n ); if( node == null ) continue; if( name.equals( node.name() )) return node; } return null; } /** An array of length {@value votorola.a.count.CountNode#DART_SECTOR_MAX} filled with * null values. Do not modify it. */ public static final JsArray NULL_BOARD = JavaScriptObjectX.createArray( DART_SECTOR_MAX ).cast(); /** Returns this node's superaccount register for the specified counting method and * account name; or null if no such register exists. */ public R register( final String countingMethodName, final String accountName ) { final JsMap> methodMap = registers(); R register = null; final CountingMethodJS method = methodMap.get( countingMethodName ); if( method != null ) register = method.get( accountName ); return register; } /** Superaccount registers at this node, indexed first by the page name of the * counting method, then by the account name. * * @see votorola.s.wap.CountWAP */ public native JsMap> registers() /*-{ return this.registers; // defaulted in init }-*/; /** Requests this node's voters from the count engine. * * @param callback the callback to receive the response, the response being * this count node with its voters set. * * @see #voters() * @see #requestVoters_loc(StringBuilder) */ public JsonpRequest requestVoters( final String loc, final AsyncCallback callback ) { assert voters() == null: "successful voter requests not repeated"; return App.i().jsonpWAP().requestObject( loc, new WAPCleanCallback( count().pollName() ) { public void onFailure( final Throwable x ) { callback.onFailure( x ); } public void onSuccess( final CountCache.WAPResponseP responseP ) { if( voters() == null ) { final JsMap wiredNodes = responseP._get( "nodes" ); final JsArrayString voterNames = wiredNodes.get(name())._get( "voters" ); initVoters( voterNames, wiredNodes ); } // else collision with another request, so just report success: callback.onSuccess( CountNodeJS.this ); } }); } /** Appends the location for calls to requestVoters and returns the * string builder. */ public StringBuilder requestVoters_loc( final StringBuilder b ) { return count().requestNode_loc( name(), b ); } /** The vote register at this node. */ public native SacRegisterJS_v voteRegister() /*-{ return this.registers["Wiki:Vote count"]["Votes"]; }-*/; /** A sparse array of the {@linkplain CountNode#dartSector() dart sectored} voters, if * known. The array is indexed from zero with null elements for empty sectors. When * the value of this array becomes known, the cache fires a {@linkplain * CountNodeEvent count node event} via the {@linkplain GWTX#bus() event bus}. * * @return an array of length {@value votorola.a.count.CountNode#DART_SECTOR_MAX}, * or null if the voters are not yet known. * * @see #requestVoters(String,AsyncCallback) * @see #initVoters(JsArrayString,JsMap) */ public native JsArray voters() /*-{ return this.voters; }-*/; // FIX to index from 1 not zero, so as to match range of dart sectors. Meantime as // a convention to prevent confusion, let variables based on the name "sector" or // "s" range from 1-20 (reserving zero for unsectored nodes), while those based on // "n" (or whatever) range from 0-19. // - C o u n t - N o d e -------------------------------------------------------------- public native String candidateName() /*-{ return this.candidateName; }-*/; public native byte dartSector() /*-{ return this.dartSector; }-*/; // defaulted in init public native Long directVoterCount() /*-{ return @java.lang.Long::new(Ljava/lang/String;)( this.directVoterCount ); // defaulted in init }-*/; public native String displayTitle() /*-{ return this.displayTitle; }-*/; public boolean isBaseCandidate() { return isCycler() || isRootCandidate(); } public boolean isCandidate() { return directVoterCount() > 0; } public native boolean isCycler() /*-{ return this.isCycler; }-*/; // defaulted in init public boolean isRootCandidate() { return !isVoter() && isCandidate(); } public boolean isVoter() { final String candidateName = candidateName(); return candidateName != null && !candidateName.equals(name()); } public native String name() /*-{ return this.name; }-*/; //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static final JavaScriptObject DEFAULT_REGISTERS = JavaScriptObject.createObject(); static { final JavaScriptObject method = JavaScriptObject.createObject(); DEFAULT_REGISTERS._set( "Wiki:Vote count", method ); method._set( "Votes", SacRegisterJS_v.DEFAULT_REGISTER ); } }