001package votorola.a.count; // Copyright 2007, 2009-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 javax.xml.stream.*; 005import votorola.a.*; 006import votorola.g.io.*; 007import votorola.g.lang.*; 008import votorola.g.logging.*; 009 010 011/** The tallied results of a poll. 012 */ 013public @ThreadSafe class Count implements Serializable 014{ 015 016 // cf. a/trust/NetworkTrace 017 018 private static final long serialVersionUID = 10L; 019 // depends not only on structure of this class, but also structure of relational CountTable 020 021 022 023 /** Partially constructs a Count, for {@linkplain #init(CountTable) init} to finish. 024 * 025 * @see #candidateCount() 026 * @see #rankCount() 027 * @see #castVolume() 028 */ 029 Count( final String pollName, final ReadyDirectory readyDirectory, long _baseCandidateCount, 030 long _candidateCount, long _castVolume, long _rankCount ) throws IOException 031 { 032 baseCandidateCount = _baseCandidateCount; 033 candidateCount = _candidateCount; 034 castVolume = _castVolume; 035 rankCount = _rankCount; 036 initTransients( pollName, readyDirectory ); 037 } 038 039 040 041 private final @ThreadRestricted("constructor") void initTransients( 042 String _pollName, ReadyDirectory _readyDirectory ) throws IOException 043 { 044 // changing? keep in sync with copyTransients() 045 pollName = _pollName; 046 readyDirectory = _readyDirectory; 047 final File file = new File( readyDirectory.snapDirectory(), 048 OutputStore.SNAP_SUMMARY_FILENAME ); 049 if( !file.isFile() ) 050 { 051 LoggerX.i(Count.class).info( "missing snapshot summary, assuming defaults: " + file ); 052 return; 053 } 054 055 final BufferedReader in = new BufferedReader( new InputStreamReader( 056 new FileInputStream( file ), "UTF-8" )); 057 try 058 { 059 final XMLStreamReader r = OutputStore.newXMLStreamReader( in ); 060 try 061 { 062 while( r.hasNext() ) 063 { 064 r.next(); 065 if( r.isStartElement() && "snap".equals( r.getLocalName() )) 066 { 067 String name; String value; 068 name = "msStart"; 069 value = r.getAttributeValue( /*namespaceURI*/null, name ); 070 if( value != null ) msStartSnap = Long.parseLong( value ); 071 else LoggerX.i(Count.class).warning( "missing attribute '" + name + "' in snapshot summary, assuming defaults: " + file ); 072 073 name = "msEnd"; 074 value = r.getAttributeValue( /*namespaceURI*/null, name ); 075 if( value != null ) msEndSnap = Long.parseLong( value ); 076 else LoggerX.i(Count.class).warning( "missing attribute '" + name + "' in snapshot summary, assuming defaults: " + file ); 077 078 break; 079 } 080 } 081 } 082 finally{ r.close(); } 083 } 084 catch( final XMLStreamException x ) { throw new RuntimeException( x ); } 085 finally{ in.close(); } 086 } 087 088 089 090 /** Completes the construction of a new Count. 091 * 092 * @see #countTable() 093 */ 094 final @ThreadRestricted("constructor") void init( CountTable countTable ) 095 { 096 // changing? keep in sync with copyTransients() 097 countTablePV = countTable.new PollView( pollName ); 098 } 099 100 101 102 /** Constructs a count as a copy of another. 103 */ 104 protected Count( final Count count ) 105 { 106 baseCandidateCount = count.baseCandidateCount; 107 candidateCount = count.candidateCount; 108 castVolume = count.castVolume; 109 rankCount = count.rankCount; 110 copyTransients( count ); 111 } 112 113 114 115 private final @ThreadRestricted("constructor") void copyTransients( final Count count ) 116 { 117 // changing? keep in sync with initTransients() and init() 118 119 // initTransients 120 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 121 pollName = count.pollName; 122 readyDirectory = count.readyDirectory; 123 msStartSnap = count.msStartSnap; 124 msEndSnap = count.msEndSnap; 125 126 // init 127 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 128 countTablePV = count.countTablePV; 129 } 130 131 132 133 // ```````````````````````````````````````````````````````````````````````````````````` 134 135 136 /** Returns true if this count is probably in sync with the serial file of the 137 * specified ready directory; false if that file has since been modified or deleted. 138 * Always returns false if the count was never originally deserialized from that 139 * file path. 140 * 141 * @see #readObjectFromSerialFile(String,ReadyDirectory) 142 */ 143 final boolean isObjectReadFromSerialFile( final ReadyDirectory newReadyDirectory ) 144 { 145 if( readObjectFromSerialFile_modTime == 0L 146 || !readyDirectory.equals( newReadyDirectory )) return false; 147 148 final File serialFile = readyDirectory.newSerialFile( pollName ); 149 return readObjectFromSerialFile_modTime == serialFile.lastModified(); 150 // 0L if file does not exist 151 } 152 153 154 155 /** @throws FileNotFoundException if the count does not include the named poll. 156 */ 157 static Count readObjectFromSerialFile( final String pollName, 158 final ReadyDirectory readyDirectory ) throws IOException 159 { 160 final File serialFile = readyDirectory.newSerialFile( pollName ); 161 final Count count; 162 try{ count = (Count)FileX.readObject( serialFile ); } 163 catch( ClassNotFoundException x ) { throw new RuntimeException( x ); } 164 165 count.initTransients( pollName, readyDirectory ); 166 count.readObjectFromSerialFile_modTime = serialFile.lastModified(); 167 return count; 168 } 169 170 171 private transient long readObjectFromSerialFile_modTime; 172 // final after readObjectFromSerialFile 173 174 175 176 final void writeObjectToSerialFile() throws IOException 177 { 178 final File serialFile = readyDirectory.newSerialFile( pollName ); 179 FileX.ensuredirs( serialFile.getParentFile() ); 180 FileX.writeObject( Count.this, serialFile ); 181 } 182 183 184 185 // ------------------------------------------------------------------------------------ 186 187 188 /** The number of {@linkplain CountNode#isBaseCandidate() base candidate} nodes. 189 */ 190 public final long baseCandidateCount() { return baseCandidateCount; } 191 192 193 private final long baseCandidateCount; 194 195 196 197 /** The number of {@linkplain CountNode#isCandidate() candidate} nodes. 198 */ 199 public final long candidateCount() { return candidateCount; } 200 201 202 private final long candidateCount; 203 204 205 206 /** The total volume of votes cast. This is equal to the total volume {@linkplain 207 * #holdVolume() held}. 208 * 209 * @see CountNodeW#castVolume() 210 */ 211 public long castVolume() { return castVolume; } 212 213 214 private final long castVolume; 215 216 217 218 /** The relational store of count nodes that (in part) backs this count, if known. It 219 * is a table named "{@linkplain CountTable#SCHEMA_NAME 220 * SCHEMA_NAME}.<var>YYYY-MD-S</var>-count_node-<var>S</var>", stored in the count 221 * database. 222 * 223 * @return reference to relational store, or null if the count was not 224 * initialized with a reference. 225 * 226 * @see votorola.a.VoteServer.Run#database() 227 * @see #init(CountTable) 228 */ 229 public final CountTable countTable() 230 { 231 return countTablePV == null? null: countTablePV.table(); 232 } 233 234 235 236 /** The poll view of the count table, if known. 237 * 238 * @return the poll view, or null if the count was not initialized with a count 239 * table. 240 * 241 * @see #init(CountTable) 242 */ 243 public final CountTable.PollView countTablePV() { return countTablePV; } 244 245 246 private transient CountTable.PollView countTablePV; // final after init 247 248 249 250 /** The total volume of votes held. This is equal to the total volume {@linkplain 251 * #castVolume() cast}. 252 * 253 * @see CountNodeW#holdVolume() 254 */ 255 public final long holdVolume() { return castVolume(); } 256 257 258 259 /** The time at which the snapshot of voter input was complete, in milliseconds since 260 * the 'epoch'; or Long.MAX_VALUE if the time is unknown. 261 * 262 * @see System#currentTimeMillis() 263 */ 264 public long msEndSnap() { return msEndSnap; } 265 266 267 private transient long msEndSnap = Long.MAX_VALUE; // final after init 268 269 270 271 /** The time at which the snapshot of voter input commenced, in milliseconds since the 272 * 'epoch'; or zero if the time is unknown. 273 * 274 * @see System#currentTimeMillis() 275 */ 276 public long msStartSnap() { return msStartSnap; } 277 278 279 private transient long msStartSnap = 0L; // final after init 280 281 282 283 /** The poll identifier for this count. 284 */ 285 public final String pollName() { return pollName; } 286 287 288 private transient String pollName; // final after init 289 290 291 292 /** The total number of {@linkplain CountNodeW#getRank() ranks}. 293 * 294 * @return count of 1 or larger, or 0 if there are no nodes at all. 295 */ 296 public final long rankCount() { return rankCount; } 297 298 299 private final long rankCount; 300 301 302 303 /** The file part of the backing for this count. 304 */ 305 public final ReadyDirectory readyDirectory() { return readyDirectory; } 306 307 308 private transient ReadyDirectory readyDirectory; // final after init 309 310 311 312 // ==================================================================================== 313 314 315 /** API for all counts within the scope of a vote-server. 316 * 317 * @see VoteServer#scopeCount() 318 */ 319 public static @ThreadSafe final class VoteServerScope 320 { 321 322 /** Constructs a VoteServerScope. 323 */ 324 public @Warning( "non-API" ) VoteServerScope( final VoteServer vS ) 325 { 326 final String loc = "vocount" + File.separator; 327 readyToReportLink = new File( vS.outDirectory(), loc + "_readyCount_report" ); 328 snapToReportLink = new File( vS.outDirectory(), loc + "_snap_report" ); 329 } 330 331 332 // -------------------------------------------------------------------------------- 333 334 335 /** The path of the <code>_readyCount_report</code> symbolic link. When this link 336 * exists, it points to the ready directory of the currently reported count. 337 * 338 * @return abstract path (never null) of symbolic link. 339 * 340 * @see <a href='../../../../../s/manual.xht#line-vocount'>vocount</a> 341 */ 342 public File readyToReportLink() { return readyToReportLink; } 343 344 345 private final File readyToReportLink; 346 347 348 /** The path of the <code>_snap_report</code> symbolic link. When this link 349 * exists, it points to the snap directory of the currently reported count. This 350 * is for informational and external use only, all code should instead use 351 * {@linkplain #readyToReportLink() readyToReportLink}. 352 * 353 * @return abstract path (never null) of symbolic link. 354 */ 355 public File snapToReportLink() { return snapToReportLink; } 356 357 358 private final File snapToReportLink; 359 360 } 361 362 363}