001package votorola.a.count; // Copyright 2007-2009, 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 java.io.*;
004import java.sql.*;
005import java.util.*;
006import javax.xml.stream.*;
007import votorola.a.count.gwt.CountNodeJS;
008import votorola.a.voter.*;
009import votorola.g.*;
010import votorola.g.lang.*;
011import votorola.g.sql.*;
012import votorola.s.line.*;
013
014
015/** A positional accounting node that is writable.  It is only partly serializeable.  Any
016  * deserialized instance (d) is detached from the backing table, with the consequence
017  * that calling any table-dependent method such as d.write or d.trace will result in a
018  * runtime exception.
019  */
020  @SuppressWarnings("overrides")/* overrides equals, but not hashCode*/
021public class CountNodeW implements Cloneable, CountNode, Serializable
022{
023
024    // cf. a/trust/TraceNodeW
025
026    private static final long serialVersionUID = 3L;
027
028
029
030    /** Partially creates a CountNodeW with default values, for {@linkplain #init(IDPair)
031      * init} to finish.
032      *
033      *     @see #tablePV()
034      */
035    CountNodeW( CountTable.PollView _tablePV )
036    {
037        if( _tablePV == null ) throw new NullPointerException(); // fail fast
038
039        tablePV = _tablePV;
040    }
041
042
043
044    /** Creates a CountNodeW with default values.
045      *
046      *     @see #tablePV()
047      *     @see #person()
048      */
049    CountNodeW( CountTable.PollView _tablePV, IDPair _person )
050    {
051        if( _tablePV == null || _person == null ) throw new NullPointerException(); // fail fast
052
053        tablePV = _tablePV;
054        initPerson( _person, /*isChanged*/true );
055    }
056
057
058
059    /** Constructs a CountNodeW.
060      *
061      *     @see #tablePV()
062      *     @see #person()
063      *     @see #getBar()
064      *     @see #getCandidateEmail()
065      *     @see #carryVolume()
066      *     @see #dartSector()
067      *     @see #directVoterCount()
068      *     @see #isCast()
069      *     @see #isCycler()
070      *     @see #getRank()
071      *     @see #getRankIndex()
072      *     @see #receiveVolume()
073      *     @see #getTime()
074      *     @param xml the XML encoding of the node's non-relational properties,
075      *       {@linkplain #displayTitle() displayTitle}, {@linkplain #getLocation()
076      *       location} and {@linkplain #getSource() source}, or null if all of them are
077      *       at default values.
078      */
079    CountNodeW( CountTable.PollView _tablePV, IDPair _person, String _bar, String _candidateEmail,
080      long _carryVolume, byte _dartSector, long _directVoterCount, boolean _isCast,
081      boolean _isCycler, long _rank, long _rankIndex, long _receiveVolume, long _time,
082      final String xml ) throws XMLStreamException
083    {
084        if( _tablePV == null || _person == null ) throw new NullPointerException(); // fail fast
085
086        tablePV = _tablePV;
087        initPerson( _person, /*isChanged*/false );
088
089        // adding fields?  increment serialVersionUID and Count.serialVersionUID
090        bar = _bar;
091        candidateEmail = _candidateEmail;
092        carryVolume = _carryVolume;
093        dartSector = _dartSector;
094        directVoterCount = _directVoterCount;
095        isCast = _isCast;
096        isCycler = _isCycler;
097        rank = _rank;
098        rankIndex = _rankIndex;
099        receiveVolume = _receiveVolume;
100        time = _time;
101        if( xml != null )
102        {
103            final StringReader rS = new StringReader( xml );
104            try
105            {
106                final XMLStreamReader r = XMLColumnAppender.newStreamReader( rS );
107                try
108                {
109                    while( r.hasNext() )
110                    {
111                        r.next();
112                        if( r.isStartElement() && "X".equals( r.getLocalName() ))
113                        {
114                            // adding fields?  increment serialVersionUID and Count.serialVersionUID
115                            displayTitle = r.getAttributeValue( /*ns*/null, "displayTitle" );
116                            location = r.getAttributeValue( /*ns*/null, "location" );
117                            source = r.getAttributeValue( /*ns*/null, "source" );
118                            break;
119                        }
120                    }
121                }
122                finally{ r.close(); }
123            }
124            finally{ rS.close(); }
125        }
126    }
127
128
129
130    /** Initializes the node by identifying the person.
131      *
132      *     @see #person()
133      *
134      *     @throws IllegalStateException if the person was already identified.
135      */
136    final void init( final IDPair _person )
137    {
138        if( _person == null ) throw new NullPointerException(); // fail fast
139        if( person != null ) throw new IllegalStateException();
140
141        initPerson( _person, /*isChanged*/true );
142    }
143
144
145
146    private final void initPerson( final IDPair _person, final boolean _isChanged )
147    {
148        person = _person;
149        isPersonal = isPersonal( person.username(), tablePV.table().readyDirectory() );
150        isChanged = _isChanged;
151    }
152
153
154
155   // ------------------------------------------------------------------------------------
156
157
158    /** An SQL order clause {@value} that sorts (1) numerically from highest to lowest
159      * carry count, and (2) lexically by email address.  The purpose of including the
160      * email address is to stabilize the order.
161      */
162    static final String CARRY_VOLUME_ORDER_CLAUSE = "ORDER BY carryVolume DESC, email";
163
164
165
166    /** The volume of {@linkplain #receiveVolume() received votes} that flows to the
167      * candidate node along with the {@linkplain #castVolume() cast volume}.  The cast
168      * volume is excluded from this count.
169      */
170    public final long carryVolume() { return carryVolume; }
171
172
173        private long carryVolume;
174
175
176
177    /** The {@linkplain #voteWeight() weight} of any original vote that is {@linkplain
178      * #isCast() actually cast} for the {@linkplain #getCandidateEmail() candidate} by
179      * this node; either 0 or 1.
180      *
181      *     @see #carryVolume()
182      */
183    public final long castVolume() { return isCast? voteWeight():0L; }
184
185
186
187    /** A comparator that sorts by dart sector.
188      */
189    public static final Comparator<CountNode> DART_SECTOR_COMPARATOR = new Comparator<CountNode>()
190    {
191        public int compare( final CountNode n1, final CountNode n2 )
192        {
193            return Byte.compare( n1.dartSector(), n2.dartSector() );
194        }
195    };
196
197
198
199    /** Returns {@linkplain #person() person}().{@linkplain IDPair#email() email}().
200      */
201    public final String email() { return person.email(); }
202
203
204
205    /** Returns the email address of the final recipient of the trace, or the localized
206      * string for "nobody".
207      *
208      *     @param resA a resource bundle of type application (A).
209      */
210    public static String finalRecipientOrNobody( final CountNodeW[] trace,
211      final ResourceBundle resA )
212    {
213        final CountNodeW finalNode = trace[trace.length-1];
214        final String finalRecipient;
215        if( trace.length == 1 ) finalRecipient = null;
216        else finalRecipient = finalNode.email();
217        return IDPair.emailOrNobody( finalRecipient, resA );
218    }
219
220
221
222    /** Describes any bar against the person voting in the poll.  A barred person may
223      * still cast a vote, but the vote has a {@linkplain #voteWeight() weight} of zero.
224      * Bars are set only for {@linkplain #isBarrable() barrable nodes}.
225      *
226      *     @return a description of the bar, or null if no bar has been set.
227      *
228      *     @see VoteCastingContext#getBar()
229      *     @see #isBarrable()
230      *     @see #setBar(String)
231      */
232    public final String getBar() { return bar; }
233
234
235        private String bar;
236
237
238        /** Answers whether a bar might be set.  Bars are not set for {@linkplain
239          * #isPersonal impersonal nodes}, which are always zero-weight regardless.
240          *
241          *     @see #getBar()
242          */
243        final boolean isBarrable() { return isPersonal; }
244
245
246        /** Answers whether the specified person would be {@linkplain #isBarrable()
247          * barrable}.
248          *
249          *     @see #getBar()
250          */
251        static boolean isBarrable( final String username, final ReadyDirectory readyDirectory )
252        {
253            return isPersonal( username, readyDirectory );
254        }
255
256
257        /** Sets a bar against the person.
258          *
259          *     @see #getBar()
260          */
261        final void setBar( final String newBar )
262        {
263            if( ObjectX.nullEquals( newBar, bar )) return;
264
265            assert isBarrable() || newBar == null; // per contract of getBar
266            bar = newBar;
267            isChanged = true;
268        }
269
270
271
272    /** Identifies the {@linkplain Vote#getCandidateEmail() candidate selected} by the
273      * person, for whom a vote is to be cast if the person {@linkplain #isVoter() is a
274      * voter}.
275      *
276      *     @return the {@linkplain
277      *       votorola.g.mail.InternetAddressX#canonicalAddress(String) canonical email
278      *       address} of the candidate, or null if none was selected.
279      *
280      *     @see #candidateName()
281      *     @see #setCandidateEmail(String)
282      */
283    public final String getCandidateEmail() { return candidateEmail; }
284
285
286        private String candidateEmail;
287
288
289        /** Changes the candidate selected by the person.  In order for the change to
290          * properly affect the count, call {@linkplain #uncast() uncast} beforehand, then
291          * {@linkplain #cast() cast} afterwards.
292          *
293          *     @see #getCandidateEmail()
294          */
295        final void setCandidateEmail( final String newCandidateEmail )
296        {
297            if( ObjectX.nullEquals( newCandidateEmail, candidateEmail )) return;
298
299            candidateEmail = newCandidateEmail;
300            isChanged = true;
301        }
302
303
304
305    /** The non-default location of the person's position document, or null if the
306      * document is at the default location in the pollwiki.  This facility is intended
307      * for the support of vote mirroring and currently has no connection with the remote
308      * drafts of free-range drafting.
309      *
310      *     @see #setLocation(String)
311      *     @see <a href='http://reluk.ca/w/Category:Draft' target='_top'
312      *                                     >Category:Draft</a>
313      */
314    public final String getLocation() { return location; }
315
316
317        private String location;
318
319
320        /** Sets the non-default location of the person's position document.
321          *
322          *     @see #getLocation()
323          */
324        final void setLocation( final String newLocation )
325        {
326            if( ObjectX.nullEquals( newLocation, location )) return;
327
328            location = newLocation;
329            isChanged = true;
330        }
331
332
333
334    /** The rank assigned to this node, with respect to {@linkplain #receiveVolume() votes
335      * received}.  The numerical minimum is a rank of 1 (top rank), assigned to the
336      * node(s) receiving the most votes.  Rank progresses incrementally, with gaps.  All
337      * nodes receiving the same volume of votes are assigned the same rank.  This can
338      * result in gaps, as shown here (rank 3 is missing):
339      *
340      * <p class='indent'>1, 2, 2, 4, 5, 5, 5, 5, 5, 5, 5</p>
341      *
342      * <p>In the example above, two candidates are tied for second place (rank 2).  The
343      * bottom rank of 5 is shared by 7 people who are probably non-candidates, receiving
344      * no votes.</p>
345      *
346      *     @return a rank of 1, or larger.  Or zero if no rank has been assigned, because
347      *       the count is still in progress.
348      *
349      *     @see #setRank(long)
350      */
351    public final long getRank() { return rank; }
352
353
354        private long rank;
355
356
357        /** Assigns a rank to this node.
358          *
359          *     @see #getRank()
360          */
361        final void setRank( final long newRank )
362        {
363            if( newRank == rank ) return;
364
365            rank = newRank;
366            isChanged = true;
367        }
368
369
370
371    /** The unique index that is assigned to this node by order of {@linkplain #getRank()
372      * rank}, and (secondarily) {@linkplain #email() email address}.
373      *
374      *     @return a rank index of 0 or larger.
375      *
376      *     @see #setRankIndex(long)
377      *     @see CountTable#createIndices()
378      */
379    public final long getRankIndex() { return rankIndex; }
380
381
382        private long rankIndex;
383
384
385        /** Assigns a rank index to this node.
386          *
387          *     @see #getRankIndex()
388          */
389        final void setRankIndex( final long newRankIndex )
390        {
391            if( newRankIndex == rankIndex ) return;
392
393            rankIndex = newRankIndex;
394            isChanged = true;
395        }
396
397
398
399    /** The mnemonic of the source engine if it is not the local vote-server.  When
400      * non-null, the return value is the name of a mirror source directory S located at
401      * <code>~/votorola/in/vomir/S</code>, and any vote that is cast is an image.
402      *
403      *     @return mnemonic of source engine, or null if the source is the local
404      *       vote-server.
405      *
406      *     @see #setSource(String)
407      *     @see votorola.a.InputStore.U#mirrorSourceDirectories(VoteServer)
408      */
409    public final String getSource() { return source; }
410
411
412        private String source;
413
414
415        /** Sets the source engine.
416          *
417          *     @see #getSource()
418          */
419        final void setSource( final String newSource )
420        {
421            if( ObjectX.nullEquals( newSource, source )) return;
422
423            source = newSource;
424            isChanged = true;
425        }
426
427
428
429    /** Specifies the time at which the person last altered the vote.
430      *
431      *     @return time in milliseconds since the Epoch, or 0L if unknown or if the vote
432      *       has never been altered by the person.
433      *
434      *     @see #setTime(long)
435      *     @see Vote#getTime()
436      */
437    public final long getTime() { return time; }
438
439
440        private long time;
441
442
443
444        /** Sets the time at which the person last altered the vote.
445          *
446          *     @see #getTime()
447          */
448        final void setTime( final long newTime )
449        {
450            if( newTime == time ) return;
451
452            time = newTime;
453            isChanged = true;
454        }
455
456
457
458    /** The volume of votes held.  This is simply {@linkplain #receiveVolume()
459      * receiveVolume} - {@linkplain #carryVolume() carryVolume}.
460      */
461    public final long holdVolume() { return receiveVolume - carryVolume; }
462
463
464
465    /** Answers whether a vote of {@linkplain #voteWeight() any weight} (zero or one) has
466      * actually been cast for the {@linkplain #getCandidateEmail() chosen candidate}.
467      * The value is identical to {@linkplain #isVoter() isVoter} in a fully tallied
468      * count, or after {@linkplain #cast() cast} or {@linkplain #castSolo() castSolo} is
469      * called.
470      *
471      *     @see #uncast()
472      */
473    public final boolean isCast() { return isCast; } // OPT remove as serves only for error detection
474
475
476        private boolean isCast;
477
478
479        /** Casts the vote if there {@linkplain #isVoter() is one}, and returns the
480          * resulting {@linkplain #trace() vote trace}.
481          *
482          *     @throws IllegalStateException if the vote is already cast.
483          *     @see #isCast()
484          */
485        final CountNodeW[] cast() throws SQLException, XMLStreamException
486        {
487            if( isCast ) throw new IllegalStateException( "casting twice" );
488
489            assert carryVolume == 0L : "carry votes once only";
490            final CountNodeW[] trace = trace( /*toForce*/false );
491            final int iLast = trace.length - 1;
492            if( iLast > 0 )
493            {
494                if( email().equals( trace[iLast].getCandidateEmail() )) castCyclic( trace );
495                else castStraight( trace ); // this node not in cycle
496            }
497            assert isCast == isVoter(); // per contract of isCast()
498            return trace;
499        }
500
501
502        /** Casts the vote (if there {@linkplain #isVoter() is one}) without carrying any
503          * received votes along.  This is relatively fast and straightforward, but it
504          * works only when exhaustively casting all nodes from scratch.  Do not use this
505          * method when shifting a vote.
506          *
507          *     @return the resulting {@linkplain #trace(boolean) vote trace}, which is a
508          *       forced trace.
509          *
510          *     @throws IllegalStateException if the vote is already cast.
511          *     @see #isCast()
512          */
513        final CountNodeW[] castSolo() throws SQLException, XMLStreamException
514        {
515            if( isCast ) throw new IllegalStateException( "casting twice" );
516
517            final CountNodeW[] trace = trace( /*toForce*/true );
518            final int iLast = trace.length - 1;
519            if( iLast > 0 )
520            {
521                flowAsStraight( trace, /*outVolume*/voteWeight(),
522                  /*isCycler*/email().equals(trace[iLast].getCandidateEmail()) );
523            }
524            assert isCast == isVoter(); // per contract of isCast()
525            return trace;
526        }
527
528
529        /** Withdraws the vote if one was cast, and returns the resulting {@linkplain
530          * #trace() vote trace}.  Does not {@linkplain #setCandidateEmail(String) null
531          * the candidate}.
532          *
533          *     @see #isCast()
534          */
535        final CountNodeW[] uncast() throws SQLException, XMLStreamException
536        {
537            final CountNodeW[] trace = trace();
538            if( isCast )
539            {
540                if( trace.length <= 1 ) throw new IllegalStateException( "lost trace of cast vote" );
541
542                if( email().equals( trace[trace.length-1].getCandidateEmail() ))
543                {
544                    uncastCyclic( trace );
545                }
546                else uncastStraight( trace );
547            }
548            return trace;
549        }
550
551
552
553    /** True if the source of the vote is another voting engine, false if the source is
554      * the local vote-server.
555      *
556      *     @see #getSource()
557      */
558    public final boolean isImage() { return source != null; }
559
560
561
562    /** Answers whether the {@linkplain #person() person} is actual (true), as opposed to
563      * merely formal (false).  Only an actual person may cast a vote of non-zero
564      * {@linkplain #voteWeight() weight}.
565      *
566      *     @see <a href='http://reluk.ca/w/Category:Impersonal_user' target='_top'
567      *                                     >Category:Impersonal user</a>
568      */
569    public final boolean isPersonal() { return isPersonal; }
570
571
572        private boolean isPersonal; // final after init
573
574
575        /** Answers whether the specified person would be {@linkplain #isPersonal()
576          * personal}.
577          */
578        static boolean isPersonal( final String username, final ReadyDirectory readyDirectory )
579        {
580            return !readyDirectory.pipeRecognizer().isPipeName( username );
581        }
582
583
584
585    /** The person for whom this node accounts.
586      */
587    public final IDPair person() { return person; }
588
589
590        private IDPair person; // final after init
591
592
593
594 // /** A comparator that sorts (1) numerically from highest to lowest receive count, and
595 //   * (2) lexically by email address.  The purpose of including the email address is to
596 //   * stabilize the order.
597 //   */
598 // public static final Comparator<CountNodeW> RECEIVE_COUNT_COMPARATOR =
599 //   new Comparator<CountNodeW>()
600 // {
601 //     public int compare( final CountNodeW n1, final CountNodeW n2 )
602 //     {
603 //         int signum = Long.signum( n2.receiveVolume() - n1.receiveVolume() );
604 //         if( signum == 0 ) signum = n1.email().compareTo( n2.email() );
605 //         return signum;
606 //     }
607 // };
608
609
610
611    /** An SQL order clause {@value} that sorts (1) numerically from highest to lowest
612      * receive count, and (2) lexically by email address.  The purpose of including the
613      * email address is to stabilize the order.
614      */
615    static final String RECEIVE_COUNT_ORDER_CLAUSE = "ORDER BY receiveVolume DESC, email";
616
617
618
619    /** The volume of votes received from other nodes.
620      *
621      *     @see #carryVolume
622      *     @see #holdVolume
623      */
624    public final long receiveVolume() { return receiveVolume; }
625
626
627        private long receiveVolume;
628
629
630
631 // /** Sets the state of this node from a vote.
632 //   */
633 // final void set( final Vote vote )
634 // {
635 //     assert vote.voterEmail().equals( email() );
636 //     setCandidateEmail( vote.getCandidateEmail() );
637 //     setTime( vote.getTime() );
638 // }
639
640
641
642    /** The table in which this node is stored, or null if this is a deserialized,
643      * read-only node.
644      */
645    public final CountTable.PollView tablePV() { return tablePV; }
646
647
648        private final transient CountTable.PollView tablePV;
649          // FIX get rid of this field, or call it tableOrNull() so client is forewarned
650
651
652
653    /** Traces the route that a vote would follow if it were cast from this node, without
654      * forcing the trace beyond uncast nodes.
655      *
656      *     @return array of nodes from the casting node (index 0) to the terminal node
657      *       (length-1).
658      */
659    public final CountNodeW[] trace() throws SQLException, XMLStreamException
660    {
661        return trace( /*toForce*/false );
662    }
663
664
665
666    /** Traces the route that a vote would follow if it were cast from this node.
667      *
668      *     @param toForce true to continue tracing beyond nodes that have yet {@linkplain
669      *       #isCast() to cast}; false to end the trace at the first such node.
670      *
671      *     @return array of nodes from the casting node (index 0) to the terminal node
672      *       (length-1).
673      */
674    final CountNodeW[] trace( final boolean toForce ) throws SQLException, XMLStreamException
675    {
676        if( tablePV == null ) throw newReadOnlyException(); // fail fast
677
678        final ArrayList<CountNodeW> nodeList = new ArrayList<CountNodeW>();
679        CountNodeW node = CountNodeW.this;
680        nodeList.add( node );
681        trace: for( ;; )
682        {
683            final String candidateEmail = node.getCandidateEmail();
684            if( candidateEmail == null ) break trace;
685
686            for( CountNodeW n: nodeList ) if( candidateEmail.equals( n.email() )) break trace;
687              // forestall any actual cycle
688
689            node = tablePV.getOrCreate( candidateEmail );
690            nodeList.add( node );
691            if( !toForce && !node.isCast() ) break trace; /* even if there's a valid
692              candidate to cast for.  This guard probably has no effect for current
693              clients; either toForce is true, or all nodes that can cast have */
694        }
695        return nodeList.toArray( new CountNodeW[nodeList.size()] );
696    }
697
698
699
700    /** The weight of original votes castable for a candidate by this node; either 0 or 1.
701      * The weight is 1 only if {@linkplain #getBar() no bar} is present and the node is
702      * {@linkplain #isPersonal() personal}.
703      *
704      *     @see #castVolume()
705      */
706    public final long voteWeight() { return bar == null && isPersonal? 1L:0L; }
707
708
709
710    /** Writes this node to the table if it has unwritten changes.
711      */
712    final void write() throws SQLException
713    {
714        if( tablePV == null ) throw newReadOnlyException(); // fail fast
715
716        if( !isChanged ) return;
717
718        tablePV.table().put( CountNodeW.this );
719        isChanged = false;
720    }
721
722
723
724   // - C o u n t - N o d e --------------------------------------------------------------
725
726
727    /** {@inheritDoc} This method is relatively slow in comparison to {@linkplain
728      * #getCandidateEmail() getCandidateEmail}.
729      */
730    public final String candidateName()
731    {
732        return candidateEmail == null? null: IDPair.toUsername( candidateEmail );
733    }
734
735
736
737    /** @see #setDartSector(int)
738      */
739    public final byte dartSector() { return dartSector; }
740
741
742        private byte dartSector;
743
744
745        /** Sets the dart sector occupied by this node.
746          *
747          *     @see #dartSector()
748          */
749        final void setDartSector( int _newSector )
750        {
751            final byte newSector = (byte)_newSector;
752            if( newSector == dartSector ) return;
753
754            if( newSector < 0 || newSector > DART_SECTOR_MAX ) throw new IllegalArgumentException();
755            dartSector = newSector;
756            isChanged = true;
757        }
758
759
760
761    public final Long directVoterCount() { return directVoterCount; }
762
763
764        private long directVoterCount;
765
766
767
768    /** @see #setDisplayTitle(String)
769      */
770    public final String displayTitle() { return displayTitle; }
771
772
773        private String displayTitle;
774
775
776        /** Sets the display title.
777          *
778          *     @see #displayTitle()
779          */
780        final void setDisplayTitle( final String newDisplayTitle )
781        {
782            if( ObjectX.nullEquals( newDisplayTitle, displayTitle )) return;
783
784            displayTitle = newDisplayTitle;
785            isChanged = true;
786        }
787
788
789
790    public final boolean isBaseCandidate() { return isCycler || isRootCandidate(); }
791
792
793
794    public final boolean isCandidate() { return directVoterCount > 0; }
795
796
797
798    public final boolean isCycler() { return isCycler; }
799
800
801        private boolean isCycler;
802
803
804
805    public final boolean isRootCandidate() { return !isCast && isCandidate(); }
806
807
808
809    /** @see #isCast()
810      */
811    public final boolean isVoter()
812    {
813        return candidateEmail != null && !candidateEmail.equals(email());
814    }
815
816
817
818    /** @see #person()
819      */
820    public String name() { return person.username(); }
821
822
823
824   // - O b j e c t ----------------------------------------------------------------------
825
826
827    public final @Override CountNodeW clone()
828    {
829        try
830        {
831            return (CountNodeW)super.clone();
832        }
833        catch( CloneNotSupportedException x ) { throw new RuntimeException( x ); } // never occurs
834    }
835
836
837
838    /** Returns true iff the o is a count node for the same person.
839      */
840    public final @Override boolean equals( Object o )
841    {
842        if( !( o instanceof CountNodeW )) return false;
843
844        final CountNodeW other = (CountNodeW)o;
845        return person.equals( other.person() );
846    }
847
848
849
850    /** Returns the person's {@linkplain #email() email address}.
851      */
852    public final @Override String toString() { return email(); }
853
854
855
856   // ====================================================================================
857
858
859    /** A routine that runs in the context of a count node.
860      */
861    public interface Runner
862    {
863
864       // - C o u n t - N o d e . R u n n e r --------------------------------------------
865
866
867        /** Runs this routine in the context of the specified count node.
868          */
869        public void run( final CountNodeW node );
870
871    }
872
873
874
875//// P r i v a t e ///////////////////////////////////////////////////////////////////////
876
877
878    /** Casts and carries down a cyclic trace that involves this node.  This is the most
879      * complicated case.  Carried votes are not held at any single terminal node, but
880      * rather are deposited here and there depending on where they entered the cycle.
881      *
882      * <p>Let the following denote a series of nodes N[i], where i = 0 to n-1, in two
883      * states B and C:</p><pre>
884      *
885      *          -> B[0]    B[1] -> B[2] -> ... -> B[n-1] --
886      *         |                                           |
887      *          - - - - - - - - - - - - - - - - - - - - - -
888      *
889      *
890      *          -> C[0] -> C[1] -> C[2] -> ... -> C[n-1] --
891      *         |                                           |
892      *          - - - - - - - - - - - - - - - - - - - - - -
893      *     </pre>
894      * <p>where:</p><pre>
895      *         -> is the route of votes within the series proper (other
896      *         votes may enter from outside the series, but their
897      *         entry is not shown here)
898      *
899      *         B[i] is the state of N[i] when N[0] has withheld its vote
900      *
901      *         C[i] is the state of N[i] when N[0] has cast for N[1],
902      *         thus causing a cyclic cascade
903      *
904      *         n is the number of nodes in the trace
905      *     </pre>
906      *
907      * <p>Given vote counts for B, calculate those for C.</p>
908      *
909      * <pre>  (1)
910      *         C[0].receive  =  B[0].receive
911      *     </pre>
912      *
913      * <p>Votes received by N[0] remain constant.  No vote can ever <em>actually</em>
914      * cycle, so the input to N[0] cannot increase when its output increases.</p>
915      *
916      * <pre>  (2)
917      *         C[i].receive  =  C[j].receive ,   for all i, j
918      *     </pre>
919      *
920      * <p>Equation 2 is the flood rule.  It states that all nodes in the cycle will
921      * receive the same volume of votes.  This follows from the fact that a vote entering
922      * the cycle will travel until just before it encounters the entry node again.
923      * Therefore it will be received by all nodes.  Likewise for casts originating within
924      * the cycle; they too are evenly distributed.</p>
925      *
926      * <p>From 1 and 2:</p>
927      *
928      * <pre>  (3)
929      *         C[i].receive  =  B[0].receive ,   for all i
930      *     </pre>
931      *
932      * <p>Also:</p>
933      *
934      * <pre>  (4)
935      *         C[i].hold  =  B[i+1].receive  -  B[i].receive ,   for i = 1...n-1
936      *     </pre>
937      *
938      * <p>Equation 4 states: except N[0], each node will come to hold whatever the
939      * following node had received before the cycle was formed, less whatever it itself
940      * had received.  In other words, a node will hold whatever its successor had
941      * introduced to the cycle.  This is guaranteed because each introduced vote must
942      * stop before it actually cycles.</p>
943      *
944      * <p>Known are C[i].receive (3) and C[i].hold (4), for i = 1...n-1.  C[i].carry may
945      * therefore be calculated:</p>
946      *
947      * <pre>  (5)
948      *         N.hold  =  N.receive  -  N.carry
949      *
950      *         C[i].carry  =  C[i].receive  -  C[i].hold ,   for all i
951      *     </pre>
952      *
953      * <p>Equation 5 states that votes are conserved.  Votes that are held are those
954      * received minus those carried to the candidate node.  It only remains to solve for
955      * C[0].</p>
956      *
957      * <pre>  (6)
958      *         C[0].hold  =  B[1].receive  +  B[1].cast
959      *     </pre>
960      *
961      * <p>Equation 6 is another instance of the rule (4) that a node holds whatever its
962      * successor had introduced into the cycle.</p>
963      *
964      * <p>Known are C[0].hold (6) and C[0].receive (1).  C[0].carry may therefore be
965      * calculated (5).</p>
966      *
967      *     @see #uncastCyclic(CountNodeW[])
968      *     @see <a href='http://reluk.ca/project/votorola/d/theory.xht#cycle' target='_top'
969      *                                                      >theory.xht#cycle</a>
970      */
971    @Warning( "non-API" ) final void castCyclic( final CountNodeW[] trace )
972    {
973        assert trace.length > 1;
974        assert trace[0].email().equals( trace[trace.length-1].getCandidateEmail() ): "trace is cyclic";
975        int i = 1;
976        CountNodeW node = trace[i];
977        ++node.directVoterCount; // node is immediate candidate
978        final long hold0 = node.receiveVolume + node.voteWeight(); // (6)
979        for( ;; )
980        {
981            assert node.isCast(); // trace is unforced
982            final CountNodeW sucessor;
983            if( i == trace.length - 1 ) sucessor = trace[0];
984            else sucessor = trace[i+1];
985            final long hold = sucessor.receiveVolume - node.receiveVolume; // (4)
986            node.receiveVolume = trace[0].receiveVolume; // (3)
987            node.carryVolume = node.receiveVolume - hold; // (5)
988            node.isChanged = true;
989            ++i;
990            if( i >= trace.length ) break;
991
992            node = trace[i];
993        }
994        carryVolume = receiveVolume - hold0; // (5), and receiveVolume unchanged (1)
995        isCast = true;
996        isCycler = true;
997        isChanged = true;
998    }
999
1000
1001
1002    /** Casts and carries down an acyclic trace.  A cycle may be present downstream in the
1003      * trace, but this node (topmost) is not part of it.
1004      *
1005      *     @see #uncastStraight(CountNodeW[])
1006      */
1007    @Warning( "non-API" ) final void castStraight( final CountNodeW[] trace )
1008    {
1009        assert trace.length > 1;
1010        assert !trace[0].email().equals( trace[trace.length-1].getCandidateEmail() ): "trace is acyclic";
1011        carryVolume = receiveVolume; // carried, no longer held
1012        flowAsStraight( trace, /*outVolume*/receiveVolume + voteWeight(), // send them all out
1013          /*isCycler*/false );
1014    }
1015
1016
1017
1018    /** Flows votes down a trace as though it were acyclic.  Either it really is acyclic,
1019      * or the outVolume is per {@linkplain #castSolo() castSolo}.
1020      */
1021    private final void flowAsStraight( final CountNodeW[] trace, final long outVolume,
1022      final boolean _isCycler )
1023    {
1024        isCast = true;
1025        isCycler = _isCycler;
1026        isChanged = true;
1027        int i = 1;
1028        CountNodeW node = trace[i];
1029        ++node.directVoterCount; // node is immediate candidate
1030        if( outVolume == 0L )
1031        {
1032            node.isChanged = true;
1033            return; // save time, no further effect is possible
1034        }
1035
1036        for( ;; )
1037        {
1038            node.receiveVolume += outVolume;
1039            node.isChanged = true;
1040            ++i;
1041            if( i >= trace.length ) break;
1042
1043            node.carryVolume += outVolume; // carry it onward to the end
1044            node = trace[i];
1045        }
1046    }
1047
1048
1049
1050    private boolean isChanged;
1051
1052
1053
1054    private static VotorolaRuntimeException newReadOnlyException()
1055    {
1056        return new VotorolaRuntimeException( "write attempt on deserialized (read only) node" );
1057    }
1058
1059
1060
1061    /** Withdraws cast and carried votes from a cyclic trace that involves this node.
1062      * This is the opposite of castCyclic, ({@linkplain #castCyclic(CountNodeW[]) q.v.}
1063      * for prior equations).  Again, let the following denote a series of nodes N[i],
1064      * where i = 0 to n-1, in two states C and B.<pre>
1065      *
1066      *          -> C[0] -> C[1] -> C[2] -> ... -> C[n-1] --
1067      *         |                                           |
1068      *          - - - - - - - - - - - - - - - - - - - - - -
1069      *
1070      *
1071      *          -> B[0]    B[1] -> B[2] -> ... -> B[n-1] --
1072      *         |                                           |
1073      *          - - - - - - - - - - - - - - - - - - - - - -
1074      *     </pre>
1075      *
1076      * <p>Given vote counts for C (when N[0] is cast and carried), calculate those for B
1077      * (when N[0] is withdrawn).</p>
1078      *
1079      * <pre>  (7)
1080      *         B[i].hold  =  0 ,   for i = 1...n-1
1081      *     </pre>
1082      *
1083      * <p>Equation 7 is true because all votes cascade to B[0].  The B series has no
1084      * cycle, therefore no other node holds votes.</p>
1085      *
1086      * <pre>  (8)
1087      *         C[i].carry  -  B[i].carry  =  sum( C[j].hold ) ,  or
1088      *
1089      *         B[i].carry  =  C[i].carry - sum( C[j].hold ) ,
1090      *
1091      *         for i = 1...n-1 ,   j = i+1...n-1
1092      *     </pre>
1093      *
1094      * <p>When N[0] was originally cast, the increase in carriage it caused at each node
1095      * had to be balanced by an increase in holds among that node's successors.  And
1096      * there could have been no increase in carriage at N[n-1] (1).  Therefore equation 8
1097      * must hold.</p>
1098      *
1099      * <p>Known are B[i].hold (7) and B[i].carry (8), for i = 1...n-1.  B[i].receive may
1100      * therefore be calculated (5).</p>
1101      *
1102      * <p>It only remains to solve for B[0].  But its vote is witheld, so there is no
1103      * carriage to other nodes:</p>
1104      *
1105      * <pre>  (9)
1106      *         B[0].carry  =  0
1107      *     </pre>
1108      *
1109      * <p>Known are B[0].carry (9) and B[0].receive (1). So B[0].hold may be calculated
1110      * (5).</p>
1111      *
1112      *     @see #castCyclic(CountNodeW[])
1113      */
1114    @Warning( "non-API" ) final void uncastCyclic( final CountNodeW[] trace )
1115    {
1116        assert trace.length > 1;
1117        assert trace[0].email().equals( trace[trace.length-1].getCandidateEmail() ): "trace is cyclic";
1118        long successorHoldSum = 0L; // so far
1119        for( int i = trace.length - 1;; --i )
1120        {
1121            final CountNodeW node = trace[i];
1122            final long wasHeld = node.holdVolume();
1123            node.carryVolume -= successorHoldSum; // (8)
1124            node.receiveVolume = node.carryVolume; // so now holding 0 (7)
1125            node.isChanged = true;
1126            successorHoldSum += wasHeld;
1127            if( i == 1 )
1128            {
1129                --node.directVoterCount; // node was immediate candidate
1130                assert node.directVoterCount >= 0L;
1131                break;
1132            }
1133        }
1134        carryVolume = 0L; // (9), and receiveVolume unchanged (1)
1135        isCast = false;
1136        isCycler = false;
1137        isChanged = true;
1138    }
1139
1140
1141
1142    /** Withdraws cast and carried votes from an acyclic trace.  A cycle may be present
1143      * downstream in the trace, but this node (topmost) is not part of it.
1144      *
1145      *     @see #castStraight(CountNodeW[])
1146      */
1147    @Warning( "non-API" ) final void uncastStraight( final CountNodeW[] trace )
1148    {
1149        assert trace.length > 1;
1150        assert !trace[0].email().equals( trace[trace.length-1].getCandidateEmail() ): "trace is acyclic";
1151        carryVolume = 0L; // held, no longer carried
1152        isCast = false;
1153        assert !isCycler;
1154        isChanged = true;
1155        int i = 1;
1156        CountNodeW node = trace[i];
1157        --node.directVoterCount; // node was immediate candidate
1158        assert node.directVoterCount >= 0L;
1159        final long withdrawVolume = receiveVolume + voteWeight(); // withdraw it all
1160        if( withdrawVolume == 0L )
1161        {
1162            node.isChanged = true;
1163            return; // save time, no further effect is possible
1164        }
1165
1166        for( ;; )
1167        {
1168            node.receiveVolume -= withdrawVolume;
1169            node.isChanged = true;
1170            ++i;
1171            if( i >= trace.length ) break;
1172
1173            node.carryVolume -= withdrawVolume; // withdraw it from onward to the end
1174            node = trace[i];
1175        }
1176    }
1177
1178
1179}