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}