package 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. import java.io.*; import javax.xml.stream.*; import votorola.a.*; import votorola.g.io.*; import votorola.g.lang.*; import votorola.g.logging.*; /** The tallied results of a poll. */ public @ThreadSafe class Count implements Serializable { // cf. a/trust/NetworkTrace private static final long serialVersionUID = 10L; // depends not only on structure of this class, but also structure of relational CountTable /** Partially constructs a Count, for {@linkplain #init(CountTable) init} to finish. * * @see #candidateCount() * @see #rankCount() * @see #castVolume() */ Count( final String pollName, final ReadyDirectory readyDirectory, long _baseCandidateCount, long _candidateCount, long _castVolume, long _rankCount ) throws IOException { baseCandidateCount = _baseCandidateCount; candidateCount = _candidateCount; castVolume = _castVolume; rankCount = _rankCount; initTransients( pollName, readyDirectory ); } private final @ThreadRestricted("constructor") void initTransients( String _pollName, ReadyDirectory _readyDirectory ) throws IOException { // changing? keep in sync with copyTransients() pollName = _pollName; readyDirectory = _readyDirectory; final File file = new File( readyDirectory.snapDirectory(), OutputStore.SNAP_SUMMARY_FILENAME ); if( !file.isFile() ) { LoggerX.i(Count.class).info( "missing snapshot summary, assuming defaults: " + file ); return; } final BufferedReader in = new BufferedReader( new InputStreamReader( new FileInputStream( file ), "UTF-8" )); try { final XMLStreamReader r = OutputStore.newXMLStreamReader( in ); try { while( r.hasNext() ) { r.next(); if( r.isStartElement() && "snap".equals( r.getLocalName() )) { String name; String value; name = "msStart"; value = r.getAttributeValue( /*namespaceURI*/null, name ); if( value != null ) msStartSnap = Long.parseLong( value ); else LoggerX.i(Count.class).warning( "missing attribute '" + name + "' in snapshot summary, assuming defaults: " + file ); name = "msEnd"; value = r.getAttributeValue( /*namespaceURI*/null, name ); if( value != null ) msEndSnap = Long.parseLong( value ); else LoggerX.i(Count.class).warning( "missing attribute '" + name + "' in snapshot summary, assuming defaults: " + file ); break; } } } finally{ r.close(); } } catch( final XMLStreamException x ) { throw new RuntimeException( x ); } finally{ in.close(); } } /** Completes the construction of a new Count. * * @see #countTable() */ final @ThreadRestricted("constructor") void init( CountTable countTable ) { // changing? keep in sync with copyTransients() countTablePV = countTable.new PollView( pollName ); } /** Constructs a count as a copy of another. */ protected Count( final Count count ) { baseCandidateCount = count.baseCandidateCount; candidateCount = count.candidateCount; castVolume = count.castVolume; rankCount = count.rankCount; copyTransients( count ); } private final @ThreadRestricted("constructor") void copyTransients( final Count count ) { // changing? keep in sync with initTransients() and init() // initTransients // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - pollName = count.pollName; readyDirectory = count.readyDirectory; msStartSnap = count.msStartSnap; msEndSnap = count.msEndSnap; // init // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - countTablePV = count.countTablePV; } // ```````````````````````````````````````````````````````````````````````````````````` /** Returns true if this count is probably in sync with the serial file of the * specified ready directory; false if that file has since been modified or deleted. * Always returns false if the count was never originally deserialized from that * file path. * * @see #readObjectFromSerialFile(String,ReadyDirectory) */ final boolean isObjectReadFromSerialFile( final ReadyDirectory newReadyDirectory ) { if( readObjectFromSerialFile_modTime == 0L || !readyDirectory.equals( newReadyDirectory )) return false; final File serialFile = readyDirectory.newSerialFile( pollName ); return readObjectFromSerialFile_modTime == serialFile.lastModified(); // 0L if file does not exist } /** @throws FileNotFoundException if the count does not include the named poll. */ static Count readObjectFromSerialFile( final String pollName, final ReadyDirectory readyDirectory ) throws IOException { final File serialFile = readyDirectory.newSerialFile( pollName ); final Count count; try{ count = (Count)FileX.readObject( serialFile ); } catch( ClassNotFoundException x ) { throw new RuntimeException( x ); } count.initTransients( pollName, readyDirectory ); count.readObjectFromSerialFile_modTime = serialFile.lastModified(); return count; } private transient long readObjectFromSerialFile_modTime; // final after readObjectFromSerialFile final void writeObjectToSerialFile() throws IOException { final File serialFile = readyDirectory.newSerialFile( pollName ); FileX.ensuredirs( serialFile.getParentFile() ); FileX.writeObject( Count.this, serialFile ); } // ------------------------------------------------------------------------------------ /** The number of {@linkplain CountNode#isBaseCandidate() base candidate} nodes. */ public final long baseCandidateCount() { return baseCandidateCount; } private final long baseCandidateCount; /** The number of {@linkplain CountNode#isCandidate() candidate} nodes. */ public final long candidateCount() { return candidateCount; } private final long candidateCount; /** The total volume of votes cast. This is equal to the total volume {@linkplain * #holdVolume() held}. * * @see CountNodeW#castVolume() */ public long castVolume() { return castVolume; } private final long castVolume; /** The relational store of count nodes that (in part) backs this count, if known. It * is a table named "{@linkplain CountTable#SCHEMA_NAME * SCHEMA_NAME}.YYYY-MD-S-count_node-S", stored in the count * database. * * @return reference to relational store, or null if the count was not * initialized with a reference. * * @see votorola.a.VoteServer.Run#database() * @see #init(CountTable) */ public final CountTable countTable() { return countTablePV == null? null: countTablePV.table(); } /** The poll view of the count table, if known. * * @return the poll view, or null if the count was not initialized with a count * table. * * @see #init(CountTable) */ public final CountTable.PollView countTablePV() { return countTablePV; } private transient CountTable.PollView countTablePV; // final after init /** The total volume of votes held. This is equal to the total volume {@linkplain * #castVolume() cast}. * * @see CountNodeW#holdVolume() */ public final long holdVolume() { return castVolume(); } /** The time at which the snapshot of voter input was complete, in milliseconds since * the 'epoch'; or Long.MAX_VALUE if the time is unknown. * * @see System#currentTimeMillis() */ public long msEndSnap() { return msEndSnap; } private transient long msEndSnap = Long.MAX_VALUE; // final after init /** The time at which the snapshot of voter input commenced, in milliseconds since the * 'epoch'; or zero if the time is unknown. * * @see System#currentTimeMillis() */ public long msStartSnap() { return msStartSnap; } private transient long msStartSnap = 0L; // final after init /** The poll identifier for this count. */ public final String pollName() { return pollName; } private transient String pollName; // final after init /** The total number of {@linkplain CountNodeW#getRank() ranks}. * * @return count of 1 or larger, or 0 if there are no nodes at all. */ public final long rankCount() { return rankCount; } private final long rankCount; /** The file part of the backing for this count. */ public final ReadyDirectory readyDirectory() { return readyDirectory; } private transient ReadyDirectory readyDirectory; // final after init // ==================================================================================== /** API for all counts within the scope of a vote-server. * * @see VoteServer#scopeCount() */ public static @ThreadSafe final class VoteServerScope { /** Constructs a VoteServerScope. */ public @Warning( "non-API" ) VoteServerScope( final VoteServer vS ) { final String loc = "vocount" + File.separator; readyToReportLink = new File( vS.outDirectory(), loc + "_readyCount_report" ); snapToReportLink = new File( vS.outDirectory(), loc + "_snap_report" ); } // -------------------------------------------------------------------------------- /** The path of the _readyCount_report symbolic link. When this link * exists, it points to the ready directory of the currently reported count. * * @return abstract path (never null) of symbolic link. * * @see vocount */ public File readyToReportLink() { return readyToReportLink; } private final File readyToReportLink; /** The path of the _snap_report symbolic link. When this link * exists, it points to the snap directory of the currently reported count. This * is for informational and external use only, all code should instead use * {@linkplain #readyToReportLink() readyToReportLink}. * * @return abstract path (never null) of symbolic link. */ public File snapToReportLink() { return snapToReportLink; } private final File snapToReportLink; } }