package 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. import java.io.*; import java.util.*; import java.text.*; import java.util.regex.*; import javax.xml.stream.*; import votorola.g.io.*; import votorola.g.lang.*; import votorola.g.sql.*; /** Output store utilities. The output store contains service output, such as periodic * snapshots of voter input, trust network traces and poll results. It includes both * file data and relational data. * * @see VoteServer#outDirectory() */ public @ThreadSafe final class OutputStore { private OutputStore() {} /** Answers whether the specified string matches S_PATTERN. * * @param string the string, which may be empty, or null * * @see #S_PATTERN */ public static boolean isS( final String string ) { if( string == null ) return false; return S_PATTERN.matcher( string ).matches(); } /** Answers whether the specified string matches Y4MDS_PATTERN. * * @param string the string, which may be empty, or null * * @see #Y4MDS_PATTERN */ public static boolean isY4MDS( final String string ) { if( string == null ) return false; return Y4MDS_PATTERN.matcher( string ).matches(); } /** Creates a unique directory named baseName-S, where -S is in the form of S_PATTERN. * Also creates super-directories, as necessary. * * @see #S_PATTERN */ public static File mkdirsS( File parentDirectory, String baseName ) throws IOException { assert !baseName.contains( Character.toString(SUFFIX_DELIMITER)); // suffix alone contains dashes, per suffix() return mkdirsS_unguarded( parentDirectory, baseName ); } private static File mkdirsS_unguarded( final File parentDirectory, String baseName ) throws IOException { final String baseNameDelimited = baseName + SUFFIX_DELIMITER; for( int s = 1;; ++s ) { final String sString = Integer.toString( s, SUFFIX_RADIX ); final File d = new File( parentDirectory, baseNameDelimited + sString ); if( d.exists() ) continue; FileX.ensuredirs( d ); return d; } } /** Creates a unique directory named baseName-YYYY-MD-S, where -YYYY-MD-S is in the * form of Y4MDS_PATTERN. Also creates super-directories, as necessary. * * @see #Y4MDS_PATTERN */ public static File mkdirsY4MDS( final File parentDirectory, final String baseName ) throws IOException { assert !baseName.contains( Character.toString(SUFFIX_DELIMITER)); // suffix alone contains dashes, per suffix() final Calendar calendar = new GregorianCalendar(); return mkdirsS_unguarded( parentDirectory, baseName + SUFFIX_DELIMITER + calendar.get( Calendar.YEAR ) + SUFFIX_DELIMITER + Integer.toString( calendar.get(Calendar.MONTH) + 1, SUFFIX_RADIX ) + Integer.toString( calendar.get(Calendar.DAY_OF_MONTH), SUFFIX_RADIX )); } /** Constructs a stream reader configured to read the snapshot summary file, and such. * * @see #SNAP_SUMMARY_FILENAME */ public static XMLStreamReader newXMLStreamReader( final Reader reader ) throws XMLStreamException { return XMLColumnAppender.newStreamReader( reader ); } /** The pattern of the filename suffix "-S"; where S is a serial number encoded in * radix 36. * * @see #isS(String) * @see #mkdirsS(File,String) */ public static final Pattern S_PATTERN = Pattern.compile( "^-[1-9a-z][0-9a-z]*$" ); // - S /** Clears the calendar and sets the date specified by the {@linkplain #Y4MDS_PATTERN * name pattern} of the snap directory. * * @return the same calendar. */ public static Calendar setNominalDate( final Calendar calendar, final File snapDirectory ) { final Matcher m = Y4MDS_PATTERN.matcher( suffix( snapDirectory.getName() )); if( !m.matches() ) throw new IllegalArgumentException( "not Y4MDS: " + snapDirectory ); calendar.clear(); calendar.set( /*year*/Integer.parseInt(m.group(1)), /*month*/Integer.parseInt(m.group(2),SUFFIX_RADIX) - 1, /*day*/Integer.parseInt(m.group(3),SUFFIX_RADIX) ); return calendar; } /** A file filter that accepts only directories that are named in the pattern of * snapshot directories. * * @see vocount * @see votrace */ public static final FileFilter SNAP_DIRECTORY_FILTER = new FileFilter() { public boolean accept( final File file ) { final String name = file.getName(); return file.isDirectory() && name.startsWith( "snap-" ) && isY4MDS( suffix( name )); } }; /** The name of the summary file in a snapshot directory. */ public static final String SNAP_SUMMARY_FILENAME = "_summary.xml"; /** Returns the suffix of the specified file name, including the preceding delimiter. * It is not guaranteed to be in any particular format. * * @param fileName without parent path * @return the suffix, or null if there is none * * @see #SUFFIX_DELIMITER */ public static String suffix( final String fileName ) { assert !fileName.contains( File.separator ); int d = fileName.indexOf( SUFFIX_DELIMITER ); if( d == -1 ) return null; return fileName.substring( d ); } /** The delimiter that marks the beginning of every filename suffix. * * @see #suffix(String) * @see #Y4MDS_PATTERN */ public static final char SUFFIX_DELIMITER = '-'; /** The pattern of the filename suffix "-YYYY-MD-S", where YYYY-MD encodes the nominal * date and S is a serial number. All numbers are encoded in radix 36, except YYYY, * which is plain radix 10. The resulting match (if any) is split into groups YYYY * (1), M (2), D (3) and S (4). * * @see #isY4MDS(String) * @see #mkdirsY4MDS(File,String) */ public static final Pattern Y4MDS_PATTERN = Pattern.compile( "^-([0-9]{4})-([1-9a-c])([1-9a-v])-([1-9a-z][0-9a-z]*)$" ); //- YYYY - M D - S // ==================================================================================== /** The path to a snap/readyTrace, snap/readyCount or other output record. It is * guaranteed to be in canonical form at the time of construction. * * @see snap/readyTrace record * @see snap/readyCount record */ public static @ThreadSafe abstract class ReadyDirectory extends File { /** Constructs a ReadyDirectory from a abstract (File) pathname. * * @param file the abstract pathname, which will be converted to canonical * form if it is non-canonical. * * @throws FileNotFoundException if no directory exists at the specified * pathname. */ protected ReadyDirectory( final File file ) throws IOException { super( file.getCanonicalPath() ); if( !isDirectory() ) throw new FileNotFoundException( getPath() ); } /** The number of distinct status flags, STATUS_FLAG_*. */ public static final int STATUS_FLAGS_COUNT; /** The flag character for a ready directory that is mounted. */ public static final char STATUS_FLAG_MOUNTED; /** The flag character for a ready directory that is currently reported. */ public static final char STATUS_FLAG_REPORTED; static { int count = 0; STATUS_FLAG_MOUNTED = 'M'; ++count; STATUS_FLAG_REPORTED = 'R'; ++count; STATUS_FLAGS_COUNT = count; } // - 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 ---------------------- /** Answers whether the store is nominally mounted. */ public abstract boolean isMounted(); /** The parent directory, containing the entire snapshot. */ public final File snapDirectory() { return getParentFile(); } /** Prints to standard output the status flags of this ready directory, its * nominal path, and a new line. * * @param flagB a buffer for storing the flag characters, * which is not cleared herein * @param nominalReadyDirectory the nominal path of this ready directory, * which may differ from the canonical path * @param readyDirectoryToReportOrNull the currently reported ready directory, * if any is currently reported */ public final void statusPrintln( final StringBuilder flagB, final File nominalReadyDirectory, final File readyDirectoryToReportOrNull ) { if( isMounted() ) flagB.append( STATUS_FLAG_MOUNTED ); if( equals( readyDirectoryToReportOrNull )) flagB.append( STATUS_FLAG_REPORTED ); statusPrintln( flagB, nominalReadyDirectory ); } /** Prints to standard output the status flags of a directory, its nominal path, * and a new line. * * @param flagB a buffer of flag characters * @param nominalDirectory the nominal path of the directory, * which may differ from its canonical path */ public static void statusPrintln( final StringBuilder flagB, final File nominalDirectory ) { System.out.print( flagB ); for( int c = flagB.length(); c < STATUS_FLAGS_COUNT; ++c ) System.out.print( ' ' ); // pad flags System.out.print( ' ' ); System.out.println( nominalDirectory ); } /** Returns a short identifier intended for presenting to users, using a separator of * " / " (space, slash, space). It includes the names of the parent snap directory, * and this ready directory. */ 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 /** Returns a short identifier intended for presenting to users. It includes the * names of the parent snap directory, and this ready directory. * * @param separator the string to separate parent and child file names */ public final String toUIString( final String separator ) { return snapDirectory().getName() + separator + getName(); } } //// P r i v a t e /////////////////////////////////////////////////////////////////////// // private static final NumberFormat twoDigitFormatter = NumberFormat.getInstance(); // to date-name files so they sort alphanumeric = chronological // static // { // twoDigitFormatter.setMinimumIntegerDigits( 2 ); // } private static final int SUFFIX_RADIX = 36; static { assert Character.MAX_RADIX >= SUFFIX_RADIX; } // else Integer.toString silently defaults to a lower radix }