package 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. import com.google.gwt.event.logical.shared.*; import com.google.gwt.regexp.shared.*; import com.google.gwt.user.client.*; import com.google.web.bindery.event.shared.HandlerRegistration; import votorola.a.count.gwt.*; import votorola.a.web.gwt.*; import votorola.g.hold.*; import votorola.g.lang.*; import votorola.g.web.gwt.*; import votorola.g.web.gwt.event.*; import votorola.s.gwt.scene.*; import votorola.s.gwt.stage.*; /** Position scoping based on dart sectors. A user's stance in regard to a particular * issue is specified by reference to a poll name and a votepath of dart sectors. The * scoping state is exposed in the {@linkplain Scenes#sScopingSwitch() 's' switch}: * * * * * * * * * * * * * * *
SwitchControlled stateDefault
sThe scoping state. Its form is s={@linkplain #pollName() * poll-name}*{@linkplain #votepath() votepath}. The vote path is optional. * Examples are "s=Sys/p/sandbox" and "s=Sys/p/sandbox*9kc2".None, which means all positions in all polls.
* *

When the stage is ready to be initialized, this scoping model sets the {@linkplain * Stage#setActorName(String) actor} and {@linkplain Stage#setPollName(String) poll} * according to the 's' switch. It thereafter keeps itself and the stage in synchrony. * At present it cannot synchonize to a change of actor on the stage, however, nor can it * honour the configuration of a {@linkplain Stage#getDefaultActorName() default actor}; * when these become necessary, please refer to the implementation plan in the code.

* * @see Scoping * @see Category:Position * @see votorola.a.count.CountNode#dartSector() */ final class DartScoping implements Scoping { // cf. my archived ._/PositionScoping.javas based on username rather than dart sector /** Constructs a DartScoping. * * @param _spool the spool for the release of associated holds. When unwound it * releases the holds of the instance and thereby disables it. * @throws IllegalStateException if the stage is configured with a {@linkplain * Stage#getDefaultActorName() default actor}, which is not currently * supported. */ public DartScoping( Spool _spool ) { spool = _spool; Stage.i().addInitializer( new TheatreInitializer0() // auto-removed { public @Override void initFrom( Stage _s, boolean _isReferencePending ) { init(); } // Does the reverse (init to) as part and parcel of model initialization, // but this should be entirely without effect on the stage, otherwise the // stage is wrong and needs correcting. The purpose of initFrom (aside from // triggering init) is to convey state that was persisted by the stage. But // this is unecessary for the state variables that are synchronized between // the stage and dart scoping (actor and poll) because both of these // variables are persisted and controlled by the 's' scoping switch in the // URL. This model passively obeys that switch on initialization and // enforces the same obedience on the stage. public @Override void initTo( Stage _s ) { init(); } public @Override void initTo( Stage _s, TheatrePage _referrer ) { init(); } }); } private void init() { spool.add( new Hold() // sync from page URL: { final HandlerRegistration hR = Scenes.i().sScopingSwitch().addHandler( new ValueChangeHandler() { public void onValueChange( final ValueChangeEvent e ) { rescope( e.getValue() ); } }); public void release() { hR.removeHandler(); } }); spool.add( new Hold() // sync from stage: { final HandlerRegistration hR = GWTX.i().bus().addHandlerToSource( PropertyChange.TYPE, /*source*/Stage.i(), new PropertyChangeHandler() { public void onPropertyChange( final PropertyChange e ) { if( "pollName".equals( e.propertyName() )) { final String newPollName = Stage.i().getPollName(); if( ObjectX.nullEquals( pollName, newPollName )) return; Scenes.i().sScopingSwitch().set( appendSwitch( GWTX.stringBuilderClear(), newPollName, votepath ).toString() ); } // else if( "actorName".equals( e.propertyName() )) // // Never happens because the actor is changed only from this scoping // model and never externally. Support for external changes would // require a map in the CountCache to look up the count node by the new // actor's name, or to request it from the server. Then once the node is // obtained and verified to still match the actor on stage, the dart // sector would be set here in the scoping model. syncActorToStage would // also have to guard against setting the actor after such such an // external change so as not to clobber it. } }); public void release() { hR.removeHandler(); } }); rescope( Scenes.i().sScopingSwitch().get() ); // init state } // ------------------------------------------------------------------------------------ /** Appends a value for the 's' scoping switch to accord with the specified * parameters. * * @return the same string builder. * * @see #pollName() * @see #votepath() */ static StringBuilder appendSwitch( final StringBuilder b, final String pollName, final String votepath ) { if( pollName != null && (votepath.length() > 0 || !pollName.equals(Stage.i().getDefaultPollName())) ) { // b.append( pollName.replace( '/', '!' )); /// no need, slash not reserved in fragment, http://tools.ietf.org/html/rfc2396#section-4.1 b.append( pollName ); if( votepath.length() > 0 ) { b.append( '*' ); b.append( votepath ); } } return b; } /** Decodes the dart sector for the specified offset along the votepath. * * @param v the character offset along the votepath. * @return a dart sector of 1 to {@value * votorola.a.count.CountNode#DART_SECTOR_MAX}. * * @see #votepath() * @see votorola.a.count.CountNode#dartSector() */ byte dartSector( final int v ) { return votepathDecoded[v]; } /** A string of encoded digits for use in translation from radix 10 to 21. * Usage:
      *
      *     char radix21Digit = DIGIT_ENCODER[radix10DartSector];
*/ static final String DIGIT_ENCODER = "0123456789bcdfghjkmnp"; // radix 21 digit // ^0 ^10 ^20 radix 10 value // ^30 ^62 ^70 hex character /** An array of bytes for use in translating from radix 21 to 10. Usage:
          *
          *     byte radix10DartSector = DIGIT_DECODER[radix21Digit - '0'];
*/ private static final byte[] DIGIT_DECODER = new byte[] { // 0 1 2 3 4 5 6 7 8 9 radix 21 digit 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // radix 10 value // 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f hex character 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f // b c d f g h j k m n 0, 0, 10, 11, 12, 0, 13, 14, 15, 0, 16, 17, 0, 18, 19, 0, // 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f // p 20 // 70 }; /** The name of the poll in which the user takes a position, or null if no poll is * specified. If no poll is specifed, then the votepath will be empty. * * @see votorola.a.count.Poll#name() */ final String pollName() { return pollName; } private String pollName; /** Prompts this scoping model to set the stage actor if that is wanted. This is a * temporary workaround for the difficult task of mapping votepaths to usernames. * Instead we depend on VotespaceV to discover the information for its own purposes * and then to send this prompt. * * @param nodeOnVotepath a node that is on the current votepath, or null if a * missing node is detected. * @param votepathFromNode the votepath from the node to the terminal candidate, * which may be null if the node is null. */ void syncActorToStage( final CountNodeJS nodeOnVotepath, final String votepathFromNode ) { if( !syncActorToStage_isPending ) return; if( nodeOnVotepath == null ) Stage.setActorName( null ); else { if( !votepath.equals( votepathFromNode )) return; Stage.setActorName( nodeOnVotepath.name() ); } syncActorToStage_isPending = false; } private boolean syncActorToStage_isPending; // guard against busy testing/setting /** The path of a vote expressed as a sequence of count nodes, each node being * identified by an encoded dart sector. The path extends from the voter node (index * 0) through each candidate node that receives the vote. The dart sector of each * node is encoded in radix 21 using the following ASCII characters as digits: * *
{@value #DIGIT_ENCODER}
* *

Dart sectors are therefore encoded as single digits in the range 1 to p. For * example, the path of encoded digits "9kc2" decodes to sector numbers (9, 17, 11, * 2).

* * @return the votepath which may be an empty string. * * @see #dartSector(int) * @see votorola.a.count.CountNode#dartSector() */ String votepath() { return votepath; } private String votepath; private byte[] votepathDecoded; // contains no zeros // - O b j e c t ---------------------------------------------------------------------- public @Override String toString() { return "dart scope = poll(" + pollName + ") votepath(" + votepath + ")"; } // - S c o p i n g -------------------------------------------------------------------- public HandlerRegistration addHandler( final ScopeChangeHandler handler ) { return GWTX.i().bus().addHandlerToSource( ScopeChangeEvent.TYPE, /*source*/DartScoping.this, handler ); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private @Warning("init call") void rescope( final String s ) { try { if( s == null ) { rescopeToDefault(); return; } final MatchResult m = S_PATTERN.exec( s ); if( m == null ) { Window.alert( "Malformed scoping switch: s=" + s ); rescopeToDefault(); return; } pollName = m.getGroup( 1 ).replace( '!', '/' ); // we used to encode '/' thus, so we decode it for sake of old URLs final Stage st = Stage.i(); if( pollName == null ) pollName = st.getDefaultPollName(); votepath = m.getGroup( 2 ); // if any if( votepath == null ) votepath = ""; final int vN = votepath.length(); votepathDecoded = new byte[vN]; for( int v = 0; v < vN; ++v ) { final byte dartSector = DIGIT_DECODER[votepath.charAt(v) - '0']; if( dartSector == 0 ) { Window.alert( "Malformed scoping switch, votepath contains a zero: s=" + s ); rescopeToDefault(); return; } votepathDecoded[v] = dartSector; } st.setPollName( pollName ); // sync to stage if( vN == 0 ) Stage.setActorName( null ); else syncActorToStage_isPending = true; } finally{ GWTX.i().bus().fireEventFromSource( new ScopeChangeEvent(), DartScoping.this ); } } private void rescopeToDefault() { final Stage st = Stage.i(); pollName = st.getDefaultPollName(); if( st.getDefaultActorName() != null ) throw new IllegalStateException( "stage configured with default actor" ); // not supported, per API doc votepath = ""; votepathDecoded = new byte[0]; st.setPollName( pollName ); // sync to stage Stage.setActorName( null ); } /** The pattern of the 's' scoping switch. Sets group (1) to the poll name and (2) * to the encoded votepath, if any. The asterisk '*' separator was chosen because * the following were rejected for the reasons specified:A left bracket '(' is allowed in place of the '*' separator for backward * compatibility with early prototypes (0.2.3), URLs of which are in the archives of * the Metagov and Votorola lists. This usage is deprecated. * * @see * ietf.org/html/rfc2396#section-2.3 */ private static final RegExp S_PATTERN = RegExp.compile( "^([A-Z][^*(]*)(?:[*(]([" + DIGIT_ENCODER + "]+))?$" ); // POLL * VOTEPATH private final Spool spool; }