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}