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}