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}