001package votorola.s.gwt.scene.vote; // Copyright 2011-2012, 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.event.shared.GwtEvent; // avoid * duplicating com.google.web.bindery.event.shared
005import com.google.gwt.view.client.*;
006import com.google.web.bindery.event.shared.HandlerRegistration;
007import votorola.a.count.*;
008import votorola.a.count.gwt.*;
009import votorola.a.web.gwt.*;
010import votorola.g.hold.*;
011import votorola.g.lang.*;
012import votorola.g.web.gwt.*;
013import votorola.g.web.gwt.event.*;
014import votorola.s.gwt.scene.*;
015import votorola.s.gwt.scene.feed.*;
016import votorola.s.gwt.stage.*;
017
018
019/** A social scene structured on lines of <a
020  * href='../../../../../../../d/theory.xht#medium'>formal assent</a> in regard to a
021  * particular issue.  It relies on dart scoping to provide a degree of {@linkplain
022  * votorola.a.count.CountNode#dartSector() structural stability} in dependent views.
023  *
024  * <p>State changes are effected as late as the "finally" phase.  Associated change
025  * events are subsequently dispatched in the "deferred" phase where they appear atomic
026  * regardless of the number of state variables involved.</p>
027  *
028  *     @see VotespaceV
029  */
030public final class Votespace implements Hold, Scene, SacSelection
031{
032
033
034    /** Constructs a Votespace.  Call {@linkplain #release release}() when done with it.
035      */
036    public Votespace()
037    {
038        new Scoper(); // first to set count early, if it was cached
039        sacSetter = new SacSetter( Votespace.this,
040          /*state*/CoalescingSchedulerS.FINALLY, // after any count change
041          /*events*/CoalescingSchedulerS.DEFERRED );
042    }
043
044
045
046   // ` e a r l y ````````````````````````````````````````````````````````````````````````
047
048
049    private final Spool spool = new Spool1();
050
051
052
053   // ------------------------------------------------------------------------------------
054
055
056    /** Decodes the dart sector for the specified offset along the votepath.
057      *
058      *     @param v the character offset along the votepath.
059      *     @return a dart sector of 1 to {@value
060      *       votorola.a.count.CountNode#DART_SECTOR_MAX}.
061      *
062      *     @see #votepath()
063      *     @see votorola.a.count.CountNode#dartSector()
064      */
065    byte dartSector( final int v ) { return scoping.dartSector( v ); }
066
067
068
069    /** The atomizer for request/response exchanges related to scoping, such as for counts
070      * and count nodes.
071      */
072    JSONPAtomizer<CountCache.WAPResponse> exchangeAtomizerS() { return exchangeAtomizerS; }
073
074
075        private final JSONPAtomizer<CountCache.WAPResponse> exchangeAtomizerS =
076          new JSONPAtomizer<CountCache.WAPResponse>();
077
078
079
080    /** The name of the poll that is shown, or null if no poll is shown.  The value is
081      * bound via the {@linkplain GWTX#bus() event bus} to property name <tt>pollName</tt>.
082      * When no poll is shown the count is always null and the votepath empty.  When a new
083      * poll is shown the count may intially be null until it is fetched from the server.
084      *
085      *     @see #count()
086      *     @see #votepath()
087      *     @see <a href='http://reluk.ca/w/Category:Poll'
088      *                        target='_top'>Category:Poll</a>
089      */
090    final String pollName() { return pollName; }
091
092
093        private String pollName;
094
095
096
097    /** Prompts the scoping model to set the stage actor if that is wanted.
098      *
099      *     @see DartScoping#syncActorToStage(CountNodeJS,String)
100      */
101    void syncActorToStage( CountNodeJS _nodeOnVotepath, String _votepathFromNode )
102    {
103        scoping.syncActorToStage( _nodeOnVotepath, _votepathFromNode );
104    }
105
106
107
108    /** The votepath that is shown.  The value is bound via the {@linkplain GWTX#bus()
109      * event bus} to property name <tt>votepath</tt>, except that no event is fired when
110      * the poll name or count changes.
111      *
112      *     @see #count()
113      *     @see #pollName()
114      *     @see DartScoping#votepath()
115      */
116    String votepath() { return scoping.votepath(); }
117
118
119
120   // - H a s - H a n d l e r s ----------------------------------------------------------
121
122
123    public void fireEvent( final GwtEvent<?> e )
124    {
125        GWTX.i().bus().fireEventFromSource( e, Votespace.this );
126    }
127
128
129
130   // - H o l d --------------------------------------------------------------------------
131
132
133    public void release() { spool.unwind(); }
134
135
136
137   // - S c e n e ------------------------------------------------------------------------
138
139
140    /** {@inheritDoc} The scope is controlled by a {@linkplain DartScoping dart scoping
141      * model} that bounds and filters the scene.  Scoping is deep: each particular tree or
142      * branch includes all uptree branches.  If no scope is specified by the scoping
143      * model, then the whole forest is included by default; if no poll is specified, then
144      * the scope is unlimited.
145      */
146    public boolean inScope( final BiteJS bite )
147    {
148        if( pollName == null ) return true; // no specific poll, accept all bites
149
150        final Poll pollB = bite.poll();
151        if( pollB == null || !pollName.equals( pollB.name() )) return false;
152
153        final int vSN = votepath().length();
154        if( vSN == 0 ) return true; // no specific path, accept all bites for poll
155
156        final JsArray<PersonJS> persons = bite.persons();
157        bitePersons: for( int p = 0, pN = persons.length(); p < pN; ++p )
158        {
159            final PersonJS personB = persons.get( p );
160            JsArray<PersonJS> voteTraceB = personB.voteTraceProjected();
161            if( voteTraceB == null ) voteTraceB = personB.voteTrace();
162            if( voteTraceB == null ) break; // unknown trace, probably no count reported
163
164            final int vBN = voteTraceB.length();
165            if( vBN < vSN ) continue; // person in bite is lower in tree than scope
166
167            for( int vB = vBN - 1, vS = vSN - 1; vS >= 0; --vB, --vS ) // from bottom up
168            {
169                final PersonJS traceeB = voteTraceB.get( vB );
170                final byte sectorB = traceeB.dartSector();
171                if( sectorB < 1 ) continue bitePersons; // tracee of person in bite has no dart sector
172
173                final byte sectorS = dartSector( vS );
174                if( sectorB != sectorS ) continue bitePersons; // person in bite is out of scope
175            }
176            return true; // all sectors in scope are matched by person in bite
177        }
178        return false; // no person in scope
179    }
180
181
182
183   // - P r o v i d e s - K e y ----------------------------------------------------------
184
185
186    public Object getKey( final SacJS item ) { return item; } // each instance is unique
187
188
189
190   // - S a c - S e l e c t i o n --------------------------------------------------------
191
192
193    /** {@inheritDoc}  If no value is specified in the URL, then it is taken to
194      * designate the {@linkplain CountJS#voteSuperaccount() vote superaccount}.
195      */
196    public Switch aSacSwitch() { return aSacSwitch; }
197
198
199        private final Switch aSacSwitch = new Switch( "a", Scenes.i().history() );
200
201        {
202            spool.add( new Hold() { public void release() { aSacSwitch.clear(); }});
203        }
204
205
206
207    /** @see #pollName()
208      */
209    public CountJS count() { return count; }
210
211
212        private CountJS count;
213
214
215
216    public SacJS getSac() { return sacSetter.sac(); }
217
218
219        public void setSac( final SacJS newSac ) // indirectly through the 'a' switch
220        {
221            if( newSac != null && "Votes".equals(newSac.accountName())
222              && CountingMethodJS.SwitchMnemonic.v == newSac.countingMethodMnemonic() )
223            {
224                aSacSwitch.clear(); // it's the default
225            }
226            else aSacSwitch.set( SacJS.appendSwitch(newSac,GWTX.stringBuilderClear()).toString() );
227        }
228
229
230
231   // - S e l e c t i o n - M o d e l ----------------------------------------------------
232
233
234    public com.google.gwt.event.shared.HandlerRegistration addSelectionChangeHandler(
235      final SelectionChangeEvent.Handler handler )
236    {
237        return new HandlerRegistrationCW( GWTX.i().bus().addHandlerToSource(
238          SelectionChangeEvent.getType(), /*source*/Votespace.this, handler ));
239    }
240
241
242    public boolean isSelected( final SacJS s ) { return ObjectX.nullEquals( s, getSac() ); }
243
244
245
246    public void setSelected( final SacJS s, final boolean toSelect )
247    {
248        if( toSelect ) setSac( s );
249        else if( s != null && s.equals( getSac() )) setSac( null );
250    }
251
252
253
254//// P r i v a t e ///////////////////////////////////////////////////////////////////////
255
256
257    private final DelayedEventGun gun = new DelayedEventGun( /*source*/Votespace.this,
258      CoalescingSchedulerS.DEFERRED );
259
260
261
262    private final SacSetter sacSetter;
263
264
265
266    private final DartScoping scoping = new DartScoping( spool );
267
268
269
270   // ====================================================================================
271
272
273    private final class Scoper implements ScopeChangeHandler
274    {
275        // cf. s.gwt.stage.vote.VoteTrack.Tracker
276
277        private Scoper()
278        {
279            scoping.addHandler( Scoper.this ); // no need to unregister, registry does not outlive this listener
280            rescope( false ); // init state
281        }
282
283
284        /** @param r whether this is a regular or init call.
285          */
286        private final @Warning("init call") void rescope( final boolean r )
287        {
288            final String pollNameNew = scoping.pollName();
289            if( pollNameNew == null )
290            {
291                if( pollName != null )
292                {
293                    count = null;
294                    pollName = null;
295                    if( r )
296                    {
297                        sacSetter.schedule();
298                        gun.schedule( new PropertyChange( "count" ));
299                        gun.schedule( new PropertyChange( "pollName" ));
300                    }
301                }
302            }
303            else if( pollNameNew.equals( pollName )) // only the votepath has changed
304            {
305                if( r ) gun.schedule( new PropertyChange( "votepath" ));
306            }
307            else // new poll
308            {
309                pollName = pollNameNew;
310                final CountCache countCache = CountCache.i();
311                final CountCache.RequestRecord record = countCache.requestRecord( pollName );
312                final CountJS countNew;
313                if( record == null ) // not previously requested
314                {
315                    final String loc = CountCache.requestCount_loc( pollName,
316                      GWTX.stringBuilderClear() ).toString();
317                    final AtomicAsyncCallback<CountJS, CountCache.WAPResponse> callbackA =
318                      AtomicAsyncCallback.wrap( exchangeAtomizerS, new CountCallback<CountJS>(
319                        loc, Stage.i() )
320                    {
321                        public void onSuccess( final CountJS countNew )
322                        {
323                            count = countNew;
324                            sacSetter.schedule();
325                            gun.schedule( new PropertyChange( "count" ));
326                        }
327                        // no need to null on failure, already nulled (below) on request
328                    });
329                    callbackA.init( countCache.requestCount( pollName, loc,
330                      SpooledAsyncCallback.wrap(spool,callbackA) ));
331                    countNew = null;
332                }
333                else countNew = record.count();
334                if( countNew == null )
335                {
336                    if( count != null )
337                    {
338                        count = null;
339                        if( r )
340                        {
341                            sacSetter.schedule();
342                            gun.schedule( new PropertyChange( "count" ));
343                        }
344                    }
345                }
346                else
347                {
348                    count = countNew;
349                    if( r )
350                    {
351                        sacSetter.schedule();
352                        gun.schedule( new PropertyChange( "count" ));
353                    }
354                }
355                if( r ) gun.schedule( new PropertyChange( "pollName" ));
356            }
357        }
358
359
360       // - S c o p e - C h a n g e - H a n d l e r --------------------------------------
361
362
363        public void onScopeChange( ScopeChangeEvent _e ) { rescope( true ); }
364
365
366    }
367
368
369
370}