001package votorola.a; // Copyright 2007-2010, 2012, 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.util.*;
005import java.text.*;
006import java.util.regex.*;
007import javax.xml.stream.*;
008import votorola.g.io.*;
009import votorola.g.lang.*;
010import votorola.g.sql.*;
011
012
013/** Output store utilities.  The output store contains service output, such as periodic
014  * snapshots of voter input, trust network traces and poll results.  It includes both
015  * file data and relational data.
016  *
017  *     @see VoteServer#outDirectory()
018  */
019public @ThreadSafe final class OutputStore
020{
021
022    private OutputStore() {}
023
024
025
026    /** Answers whether the specified string matches S_PATTERN.
027      *
028      *     @param string the string, which may be empty, or null
029      *
030      *     @see #S_PATTERN
031      */
032    public static boolean isS( final String string )
033    {
034        if( string == null ) return false;
035
036        return S_PATTERN.matcher( string ).matches();
037    }
038
039
040
041    /** Answers whether the specified string matches Y4MDS_PATTERN.
042      *
043      *     @param string the string, which may be empty, or null
044      *
045      *     @see #Y4MDS_PATTERN
046      */
047    public static boolean isY4MDS( final String string )
048    {
049        if( string == null ) return false;
050
051        return Y4MDS_PATTERN.matcher( string ).matches();
052    }
053
054
055
056    /** Creates a unique directory named baseName-S, where -S is in the form of S_PATTERN.
057      * Also creates super-directories, as necessary.
058      *
059      *     @see #S_PATTERN
060      */
061    public static File mkdirsS( File parentDirectory, String baseName ) throws IOException
062    {
063        assert !baseName.contains( Character.toString(SUFFIX_DELIMITER)); // suffix alone contains dashes, per suffix()
064        return mkdirsS_unguarded( parentDirectory, baseName );
065    }
066
067
068
069        private static File mkdirsS_unguarded( final File parentDirectory, String baseName )
070          throws IOException
071        {
072            final String baseNameDelimited = baseName + SUFFIX_DELIMITER;
073            for( int s = 1;; ++s )
074            {
075                final String sString = Integer.toString( s, SUFFIX_RADIX );
076                final File d = new File( parentDirectory, baseNameDelimited + sString );
077                if( d.exists() ) continue;
078
079                FileX.ensuredirs( d );
080                return d;
081            }
082        }
083
084
085
086    /** Creates a unique directory named baseName-YYYY-MD-S, where -YYYY-MD-S is in the
087      * form of Y4MDS_PATTERN.  Also creates super-directories, as necessary.
088      *
089      *     @see #Y4MDS_PATTERN
090      */
091    public static File mkdirsY4MDS( final File parentDirectory, final String baseName )
092      throws IOException
093    {
094        assert !baseName.contains( Character.toString(SUFFIX_DELIMITER)); // suffix alone contains dashes, per suffix()
095        final Calendar calendar = new GregorianCalendar();
096        return mkdirsS_unguarded( parentDirectory, baseName
097         + SUFFIX_DELIMITER
098         + calendar.get( Calendar.YEAR )
099         + SUFFIX_DELIMITER
100         + Integer.toString( calendar.get(Calendar.MONTH) + 1, SUFFIX_RADIX )
101         + Integer.toString( calendar.get(Calendar.DAY_OF_MONTH), SUFFIX_RADIX ));
102    }
103
104
105
106    /** Constructs a stream reader configured to read the snapshot summary file, and such.
107      *
108      *     @see #SNAP_SUMMARY_FILENAME
109      */
110    public static XMLStreamReader newXMLStreamReader( final Reader reader )
111      throws XMLStreamException
112    {
113        return XMLColumnAppender.newStreamReader( reader );
114    }
115
116
117
118    /** The pattern of the filename suffix "-S"; where S is a serial number encoded in
119      * radix 36.
120      *
121      *     @see #isS(String)
122      *     @see #mkdirsS(File,String)
123      */
124    public static final Pattern S_PATTERN = Pattern.compile( "^-[1-9a-z][0-9a-z]*$" );
125      //                                                       -   S
126
127
128
129    /** Clears the calendar and sets the date specified by the {@linkplain #Y4MDS_PATTERN
130      * name pattern} of the snap directory.
131      *
132      *     @return the same calendar.
133      */
134    public static Calendar setNominalDate( final Calendar calendar, final File snapDirectory )
135    {
136        final Matcher m = Y4MDS_PATTERN.matcher( suffix( snapDirectory.getName() ));
137        if( !m.matches() ) throw new IllegalArgumentException( "not Y4MDS: " + snapDirectory );
138
139        calendar.clear();
140        calendar.set( /*year*/Integer.parseInt(m.group(1)),
141          /*month*/Integer.parseInt(m.group(2),SUFFIX_RADIX) - 1,
142          /*day*/Integer.parseInt(m.group(3),SUFFIX_RADIX) );
143        return calendar;
144    }
145
146
147
148    /** A file filter that accepts only directories that are named in the pattern of
149      * snapshot directories.
150      *
151      *     @see <a href='../../../../s/manual.xht#line-vocount'>vocount</a>
152      *     @see <a href='../../../../s/manual.xht#line-votrace'>votrace</a>
153      */
154    public static final FileFilter SNAP_DIRECTORY_FILTER = new FileFilter()
155    {
156        public boolean accept( final File file )
157        {
158            final String name = file.getName();
159            return file.isDirectory() && name.startsWith( "snap-" )
160              && isY4MDS( suffix( name ));
161        }
162    };
163
164
165
166    /** The name of the summary file in a snapshot directory.
167      */
168    public static final String SNAP_SUMMARY_FILENAME = "_summary.xml";
169
170
171
172    /** Returns the suffix of the specified file name, including the preceding delimiter.
173      * It is not guaranteed to be in any particular format.
174      *
175      *     @param fileName without parent path
176      *     @return the suffix, or null if there is none
177      *
178      *     @see #SUFFIX_DELIMITER
179      */
180    public static String suffix( final String fileName )
181    {
182        assert !fileName.contains( File.separator );
183        int d = fileName.indexOf( SUFFIX_DELIMITER );
184        if( d == -1 ) return null;
185
186        return fileName.substring( d );
187    }
188
189
190
191    /** The delimiter that marks the beginning of every filename suffix.
192      *
193      *     @see #suffix(String)
194      *     @see #Y4MDS_PATTERN
195      */
196    public static final char SUFFIX_DELIMITER = '-';
197
198
199
200    /** The pattern of the filename suffix "-YYYY-MD-S", where YYYY-MD encodes the nominal
201      * date and S is a serial number.  All numbers are encoded in radix 36, except YYYY,
202      * which is plain radix 10.  The resulting match (if any) is split into groups YYYY
203      * (1), M (2), D (3) and S (4).
204      *
205      *     @see #isY4MDS(String)
206      *     @see #mkdirsY4MDS(File,String)
207      */
208    public static final Pattern Y4MDS_PATTERN = Pattern.compile(
209      "^-([0-9]{4})-([1-9a-c])([1-9a-v])-([1-9a-z][0-9a-z]*)$" );
210      //- YYYY     - M         D        - S
211
212
213
214
215   // ====================================================================================
216
217
218    /** The path to a snap/readyTrace, snap/readyCount or other output record.  It is
219      * guaranteed to be in canonical form at the time of construction.
220      *
221      *     @see <a href='../../../../s/manual.xht#line-votrace'>snap/readyTrace record</a>
222      *     @see <a href='../../../../s/manual.xht#line-vocount'>snap/readyCount record</a>
223      */
224    public static @ThreadSafe abstract class ReadyDirectory extends File
225    {
226
227        /** Constructs a ReadyDirectory from a abstract (File) pathname.
228          *
229          *     @param file the abstract pathname, which will be converted to canonical
230          *       form if it is non-canonical.
231          *
232          *     @throws FileNotFoundException if no directory exists at the specified
233          *       pathname.
234          */
235        protected ReadyDirectory( final File file ) throws IOException
236        {
237            super( file.getCanonicalPath() );
238            if( !isDirectory() ) throw new FileNotFoundException( getPath() );
239        }
240
241
242        /** The number of distinct status flags, <code>STATUS_FLAG_*</code>.
243          */
244        public static final int STATUS_FLAGS_COUNT;
245
246
247            /** The flag character for a ready directory that is mounted.
248              */
249            public static final char STATUS_FLAG_MOUNTED;
250
251
252            /** The flag character for a ready directory that is currently reported.
253              */
254            public static final char STATUS_FLAG_REPORTED;
255
256
257            static
258            {
259                int count = 0;
260                STATUS_FLAG_MOUNTED = 'M'; ++count;
261                STATUS_FLAG_REPORTED = 'R'; ++count;
262                STATUS_FLAGS_COUNT = count;
263            }
264
265
266       // - O u t p u t - S t o r e . R e a d y - D i r e c t o r y ----------------------
267
268
269        /** Answers whether the store is nominally mounted.
270          */
271        public abstract boolean isMounted();
272
273
274        /** The parent directory, containing the entire snapshot.
275          */
276        public final File snapDirectory() { return getParentFile(); }
277
278
279        /** Prints to standard output the status flags of this ready directory, its
280          * nominal path, and a new line.
281          *
282          *     @param flagB a buffer for storing the flag characters,
283          *       which is not cleared herein
284          *     @param nominalReadyDirectory the nominal path of this ready directory,
285          *       which may differ from the canonical path
286          *     @param readyDirectoryToReportOrNull the currently reported ready directory,
287          *       if any is currently reported
288          */
289        public final void statusPrintln( final StringBuilder flagB,
290          final File nominalReadyDirectory, final File readyDirectoryToReportOrNull )
291        {
292            if( isMounted() ) flagB.append( STATUS_FLAG_MOUNTED );
293
294            if( equals( readyDirectoryToReportOrNull )) flagB.append( STATUS_FLAG_REPORTED );
295
296            statusPrintln( flagB, nominalReadyDirectory );
297        }
298
299
300        /** Prints to standard output the status flags of a directory, its nominal path,
301          * and a new line.
302          *
303          *     @param flagB a buffer of flag characters
304          *     @param nominalDirectory the nominal path of the directory,
305          *       which may differ from its canonical path
306          */
307        public static void statusPrintln( final StringBuilder flagB, final File nominalDirectory )
308        {
309            System.out.print( flagB );
310            for( int c = flagB.length(); c < STATUS_FLAGS_COUNT; ++c ) System.out.print( ' ' ); // pad flags
311            System.out.print( ' ' );
312            System.out.println( nominalDirectory );
313        }
314
315
316        /** Returns a short identifier intended for presenting to users, using a separator of
317          * " / " (space, slash, space).  It includes the names of the parent snap directory,
318          * and this ready directory.
319          */
320        public final String toUIString() { return toUIString( " / " ); } // idea was probably to allow it to wrap in the command-line interface; but the problem should be be solved there, not here
321
322
323        /** Returns a short identifier intended for presenting to users.  It includes the
324          * names of the parent snap directory, and this ready directory.
325          *
326          *     @param separator the string to separate parent and child file names
327          */
328        public final String toUIString( final String separator )
329        {
330            return snapDirectory().getName() + separator + getName();
331        }
332
333
334    }
335
336
337
338//// P r i v a t e ///////////////////////////////////////////////////////////////////////
339
340
341 // private static final NumberFormat twoDigitFormatter = NumberFormat.getInstance(); // to date-name files so they sort alphanumeric = chronological
342 // static
343 // {
344 //     twoDigitFormatter.setMinimumIntegerDigits( 2 );
345 // }
346
347
348
349    private static final int SUFFIX_RADIX = 36;
350
351
352        static { assert Character.MAX_RADIX >= SUFFIX_RADIX; } // else Integer.toString silently defaults to a lower radix
353
354
355}