001package votorola.s.line; // Copyright 2008-2011, 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 java.net.*; 005import java.util.*; 006import java.util.logging.*; 007import java.util.regex.*; 008import javax.mail.internet.*; 009import javax.xml.stream.*; 010import votorola.a.*; 011import votorola.a.response.line.*; 012import votorola.a.trust.*; 013import votorola.a.voter.*; 014import votorola.g.*; 015import votorola.g.hold.*; 016import votorola.g.io.*; 017import votorola.g.lang.*; 018import votorola.g.logging.*; 019import votorola.g.net.*; 020import votorola.g.option.*; 021import votorola.g.script.*; 022 023 024/** Main class of the executable <code>votrace</code> - a tool to trace a neigbourhood 025 * trust network and compile a residential register. 026 * 027 * @see <a href='../../../../../s/manual.xht#line-votrace' target='_top'>votrace</a> 028 */ 029public final class VOTrace extends ResultsCompiler 030{ 031 032 // cf. a/count/VOCount 033 034 035 /** Runs the tool from the command line. 036 * 037 * @param argv command line argument array 038 */ 039 public static void main( final String[] argv ) 040 { 041 logger.info( "votrace is running with arguments " + Arrays.toString( argv )); 042 new VOTrace().run( argv ); 043 } 044 045 046 047//// P r i v a t e /////////////////////////////////////////////////////////////////////// 048 049 050 private static final Logger logger = LoggerX.i( VOTrace.class ); 051 052 053 054 private static ArrayList<File> nominalReadyDirectories( final File votraceDirectory ) 055 { 056 final ArrayList<File> list = new ArrayList<File>(); 057 final File[] snapDirectoryArray = FileX.listFilesNoNull( votraceDirectory, 058 OutputStore.SNAP_DIRECTORY_FILTER ); 059 for( final File snapDirectory: snapDirectoryArray ) 060 { 061 final File[] readyDirectoryArray = FileX.listFilesNoNull( snapDirectory, 062 ReadyDirectory.READY_DIRECTORY_FILTER ); 063 for( final File readyDirectory: readyDirectoryArray ) list.add( readyDirectory ); 064 } 065 // Collections.sort( list ); // sorts on pathnames, with '/' messing up the order 066 //// no need, it's fine on Linux 067 return list; 068 } 069 070 071 072 private void run( final String[] argv ) 073 { 074 final Map<String,Option> optionMap = compileBaseOptions(); 075 { 076 final String name = "churn"; 077 optionMap.put( name, new Option( name, Option.NO_ARGUMENT )); 078 } 079 080 final int aFirstNonOption = GetoptX.parse( "votrace", argv, optionMap ); 081 if( optionMap.get("help").hasOccured() ) 082 { 083 System.out.print 084 ( 085 "Usage: votrace [--verbose] [--etc[=ready|mount|report|umount]] [--churn]\n" + 086 " or votrace snap [--verbose] [--etc[=ready|mount|report|umount]] [--churn]\n" + 087 " votrace ready [--verbose] [--etc[=mount|report|umount]]\n" + 088 " ~/votorola/out/votrace/snap-YYYY-MD-S\n" + 089 " votrace mount [--verbose] [--etc[=report|umount]]\n" + 090 " [~/votorola/out/votrace/snap-YYYY-MD-S/readyTrace-S]\n" + 091 " votrace report [--verbose] [--etc]\n" + 092 " [~/votorola/out/votrace/snap-YYYY-MD-S/readyTrace-S]\n" + 093 " votrace umount [--verbose]\n" + 094 " [~/votorola/out/votrace/snap-OLD-YYYY-MD-S/readyTrace-OLD-S]\n" + 095 " or votrace ureport [--verbose]\n" + 096 " or votrace status\n" + 097 "Trace a neigbourhood trust network and compile a residential register.\n" 098 ); 099 return; 100 } 101 102 final int argCount = argv.length - aFirstNonOption; 103 final Action action; 104 final String argument; 105 if( argCount == 0 ) 106 { 107 action = Action.snap; 108 argument = null; 109 final Option etc = optionMap.get( "etc" ); 110 if( !etc.hasOccured() ) etc.addOccurence( Action.END_ACTION_LAST.name() ); 111 } 112 else if( argCount > 2 ) 113 { 114 System.err.println( "votrace: too many arguments" ); 115 System.err.println( GetoptX.createHelpPrompt( "votrace" )); 116 System.exit( 1 ); return; // redundant return, to prevent compiler warnings 117 } 118 else 119 { 120 final String actionArg = argv[aFirstNonOption]; 121 try 122 { 123 action = Action.valueOf( actionArg ); 124 } 125 catch( IllegalArgumentException x ) 126 { 127 System.err.println( "votrace: unrecognized action argument: " + actionArg ); 128 System.err.println( GetoptX.createHelpPrompt( "votrace" )); 129 System.exit( 1 ); return; // redundant return, to prevent compiler warnings 130 } 131 132 if( argCount == 1 ) argument = null; 133 else 134 { 135 assert argCount == 2; 136 argument = argv[aFirstNonOption + 1]; 137 } 138 } 139 run( action, argument, optionMap ); 140 } 141 142 143 144 private void run( final Action action, String argument, final Map<String,Option> optionMap ) 145 { 146 final boolean isEtc = action.isEtc( "votrace", optionMap ); 147 final boolean isVerbose = optionMap.get("verbose").hasOccured(); 148 try 149 { 150 final VoteServer.Run vsRun = vsRun(); 151 final VoteServer vS = vsRun.voteServer(); 152 final JavaScriptIncluder configScript = new JavaScriptIncluder( new File( 153 vsRun.trustserver().serviceDirectory(), "votrace.js" )); 154 155 // 1. Snap 156 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 157 if( action == Action.snap ) 158 { 159 if( argument != null ) 160 { 161 System.err.println( "votrace: too many arguments" ); 162 System.err.println( GetoptX.createHelpPrompt( "votrace" )); 163 System.exit( 1 ); 164 } 165 166 // timestamp santity check 167 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 168 final File votraceDirectory = new File( vS.outDirectory(), "votrace" ); 169 final long msStart = System.currentTimeMillis(); 170 for( final File d: FileX.listFilesNoNull( votraceDirectory, 171 OutputStore.SNAP_DIRECTORY_FILTER )) 172 { 173 if( d.lastModified() > msStart ) 174 { 175 System.err.println( 176 "votrace, warning: existing snap directory timestamped in future: " + d ); 177 // Problematic because the default for the 'ready' action 178 // depends on timestamps. 179 } 180 } 181 182 // churn 183 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 184 if( optionMap.get("churn").hasOccured() ) vS.pollwiki().cache().churn(); 185 186 // snap 187 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 188 final File snapDirectory = OutputStore.mkdirsY4MDS( votraceDirectory, "snap" ); 189 final File inRegFile = new File( snapDirectory, "_in_registration.xml" ); 190 191 final Spool outSpool = new Spool1(); 192 try 193 { 194 final BufferedWriter w = new BufferedWriter( 195 new OutputStreamWriter( new FileOutputStream( inRegFile ), "UTF-8" )); 196 outSpool.add( new Hold() 197 { 198 public void release() { try{ w.close(); } catch( IOException x ) { throw new RuntimeException( x ); }} 199 }); 200 w.append( "<?xml version='1.0' encoding='UTF-8'?> <!-- -*-coding: utf-8;-*- -->\n" 201 + "<registrantInput>\n" ); 202 outSpool.add( new Hold() 203 { 204 public void release() { try{ w.append( "\t</registrantInput>\n"); } catch( IOException x ) { throw new RuntimeException( x ); }} 205 }); 206 207 queryChain: for( String queryContinuation = "";; ) 208 { 209 final Spool inSpool = new Spool1(); 210 try 211 { 212 final HttpURLConnection http; 213 { 214 // http://www.mediawiki.org/wiki/API:Query_-_Lists#categorymembers_.2F_cm 215 final URI s = vS.pollwiki().scriptURI(); 216 final URI queryURI = new URI( 217 s.getScheme(), s.getAuthority(), s.getPath() + "/api.php", 218 /*query*/"action=query&list=categorymembers&cmlimit=500&cmnamespace=2&cmprop=title&cmtitle=Category:Registrant&format=xml" 219 + queryContinuation, 220 /*fragment*/null ); 221 logger.fine( "querying streetwiki " + queryURI ); 222 http = (HttpURLConnection)( queryURI.toURL().openConnection() ); 223 } 224 URLConnectionX.connect( http ); 225 inSpool.add( new Hold() 226 { 227 public void release() { http.disconnect(); } 228 }); 229 230 final InputStream in = http.getInputStream(); 231 inSpool.add( new Hold() 232 { 233 public void release() { try{ in.close(); } catch( IOException x ) { throw new RuntimeException( x ); }} 234 }); 235 236 final XMLStreamReader r = MediaWiki.newXMLStreamReader( in, inSpool ); 237 queryContinuation = null; 238 while( r.hasNext() ) 239 { 240 r.next(); 241 if( r.isStartElement() && "cm".equals( r.getLocalName() )) 242 { 243 final Registration reg; 244 { 245 final String ti = r.getAttributeValue( /*ns*/null, "title" ); 246 final MatchResult m = MediaWiki.parsePageNameS( ti ); 247 if( m == null ) 248 { 249 throw new VotorolaRuntimeException( 250 "malformed page name '" + ti + "'" ); 251 } 252 253 final String username = m.group( 2 ); 254 try 255 { 256 reg = new Registration( IDPair.fromUsername( username )); 257 } 258 catch( final AddressException x ) 259 { 260 logger.log( LoggerX.CONFIG, /*message*/"malformed username, ignoring registrant: " + username, x ); 261 continue; 262 } 263 } 264 265 final Registration.SnapshotContext rSC = 266 new Registration.SnapshotContext( vS, reg ); 267 configScript.invokeKnownFunction( "snapRegistration", rSC ); 268 try 269 { 270 reg.toXML( new VoterInputTable.XMLColumnAppender( w )); 271 } 272 catch( VoterInputTable.BadInputException x ) 273 { 274 logger.log( LoggerX.CONFIG, /*message*/"unable to serialize snapshot, ignoring registrant: " + reg.registrant().username(), x ); 275 } 276 } 277 else if( r.isStartElement() 278 && "categorymembers".equals( r.getLocalName() )) 279 { 280 final String cmcontinue = r.getAttributeValue( 281 /*ns*/null, "cmcontinue" ); 282 if( cmcontinue != null ) // also serves to gaurd against clobbering. Up to two elements are expected with this same name, only one of which has the sought for attribute. 283 { 284 queryContinuation = "&cmcontinue=" + cmcontinue; 285 } 286 } 287 else if( r.isStartElement() && "error".equals( r.getLocalName() )) 288 { 289 throw new MediaWiki.APIError( r ); 290 } 291 } 292 if( queryContinuation == null ) break queryChain; 293 } 294 finally{ inSpool.unwind(); } 295 } 296 } 297 finally{ outSpool.unwind(); } 298 299 if( !isEtc || isVerbose ) System.out.println( snapDirectory ); 300 } 301 302 // 2. Ready 303 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 304 else if( action == Action.ready ) 305 { 306 final File votraceDirectory = new File( vS.outDirectory(), "votrace" ); 307 if( argument == null ) 308 { 309 File snapDirectory = null; // till found 310 for( final File d: FileX.listFilesNoNull( votraceDirectory, 311 OutputStore.SNAP_DIRECTORY_FILTER )) 312 { 313 if( snapDirectory == null 314 || d.lastModified() > snapDirectory.lastModified() ) snapDirectory = d; 315 } 316 317 if( snapDirectory == null ) 318 { 319 System.err.println( 320 "votrace: nothing to ready, no snap directory exists\n" 321 + " try: votrace snap" ); 322 System.exit( 1 ); 323 } 324 325 argument = snapDirectory.getPath(); 326 if( isVerbose ) 327 { 328 System.out.println( "readying most recently modified snap directory: " 329 + argument ); 330 } 331 } 332 333 // timestamp santity check 334 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 335 final long msStart = System.currentTimeMillis(); 336 for( final File r: nominalReadyDirectories( votraceDirectory )) 337 { 338 if( r.lastModified() > msStart ) 339 { 340 System.err.println( 341 "votrace, warning: existing record timestamped in future: " + r ); 342 // Problematic because the default for the 'mount' action 343 // depends on timestamps. 344 } 345 } 346 347 // ready 348 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 349 final File snapDirectory = new File( argument ); 350 final ReadyDirectory r = ReadyDirectory.createReadyDirectory( snapDirectory ); 351 if( !isEtc || isVerbose ) System.out.println( r ); 352 } 353 354 // 3. Mount 355 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 356 else if( action == Action.mount ) 357 { 358 final File votraceDirectory = new File( vS.outDirectory(), "votrace" ); 359 final ArrayList<File> readyDirectoryList = 360 nominalReadyDirectories( votraceDirectory ); 361 if( argument == null ) 362 { 363 ReadyDirectory readyDirectory = null; // till found 364 File nominalReadyDirectory = null; 365 for( final File d: readyDirectoryList ) 366 { 367 final ReadyDirectory r = new ReadyDirectory( d ); // as such 368 if( readyDirectory == null || r.lastModified() 369 > readyDirectory.lastModified() ) 370 { 371 readyDirectory = r; 372 nominalReadyDirectory = d; 373 } 374 } 375 376 if( readyDirectory == null ) 377 { 378 System.err.println( 379 "votrace: nothing to mount, no snap/readyTrace record exists\n" 380 + " try: votrace ready" ); 381 System.exit( 1 ); 382 } 383 384 if( readyDirectory.isMounted() ) 385 { 386 System.err.println( 387 "votrace: already mounted most recently modified record: " 388 + nominalReadyDirectory ); 389 System.exit( 1 ); 390 } 391 392 argument = nominalReadyDirectory.getPath(); 393 if( isVerbose ) 394 { 395 System.out.println( "mounting most recently modified record: " + argument ); 396 } 397 } 398 399 final String nominalReadyPath = argument; 400 final ReadyDirectory readyDirectory = new ReadyDirectory( nominalReadyPath ); 401 if( readyDirectory.isMounted() ) 402 { 403 System.err.println( "votrace: already mounted: " + nominalReadyPath ); 404 System.exit( 1 ); 405 } 406 407 // timestamp santity check 408 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 409 final long msStart = System.currentTimeMillis(); 410 for( final File d: readyDirectoryList ) 411 { 412 final ReadyDirectory r = new ReadyDirectory( d ); // as such 413 if( !r.isMounted() ) continue; 414 415 if( r.mountedDirectory().lastModified() > msStart ) 416 { 417 System.err.println( 418 "votrace, warning: existing mount timestamped in future: " 419 + r.mountedDirectory() ); 420 // Problematic because the default for the 'report' action 421 // depends on timestamps. 422 } 423 } 424 425 // mount 426 // ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` 427 readyDirectory.mount( vsRun.trustserver(), isVerbose ); 428 } 429 430 // 4. Report 431 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 432 else if( action == Action.report ) 433 { 434 final NetworkTrace.VoteServerScope scopeTrace = vS.scopeTrace(); 435 final File readyToReportLink = scopeTrace.readyToReportLink(); 436 if( argument == null ) 437 { 438 final File votraceDirectory = new File( vS.outDirectory(), "votrace" ); 439 ReadyDirectory readyDirectory = null; // till found 440 File nominalReadyDirectory = null; 441 for( final File d: nominalReadyDirectories( votraceDirectory )) 442 { 443 final ReadyDirectory r = new ReadyDirectory( d ); // as such 444 if( !r.isMounted() ) continue; 445 446 if( readyDirectory == null || r.mountedDirectory().lastModified() 447 > readyDirectory.mountedDirectory().lastModified() ) 448 { 449 readyDirectory = r; 450 nominalReadyDirectory = d; 451 } 452 } 453 if( readyDirectory == null ) 454 { 455 System.err.println( "votrace: nothing to report, no trace is mounted" ); 456 System.exit( 1 ); 457 } 458 459 if( readyToReportLink.exists() 460 && readyDirectory.equals( readyToReportLink.getCanonicalFile() )) 461 { 462 System.err.println( 463 "votrace: already reporting most recently mounted record: " 464 + nominalReadyDirectory ); 465 System.exit( 1 ); 466 } 467 468 argument = nominalReadyDirectory.getPath(); 469 if( isVerbose ) 470 { 471 System.out.println( "reporting most recently mounted record: " + argument ); 472 } 473 } 474 final String nominalReadyPath = argument; 475 final ReadyDirectory readyDirectory = new ReadyDirectory( nominalReadyPath ); 476 if( readyToReportLink.exists() 477 && readyDirectory.equals( readyToReportLink.getCanonicalFile() )) 478 { 479 System.err.println( "votrace: already reported: " + nominalReadyPath ); 480 System.exit( 1 ); 481 } 482 483 if( !readyDirectory.isMounted() ) 484 { 485 System.err.println( "votrace: cannot report, trace is not mounted\n" 486 + " try: votrace mount " + nominalReadyPath ); 487 System.exit( 1 ); 488 } 489 490 FileX.symlink( readyDirectory.getPath(), readyToReportLink.getPath() ); 491 FileX.symlink( readyDirectory.snapDirectory().getName(), 492 scopeTrace.snapToReportLink().getPath() ); 493 } 494 495 // 5. Umount 496 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 497 else if( action == Action.umount ) 498 { 499 final File readyToReportLink = vS.scopeTrace().readyToReportLink(); 500 if( argument == null ) // unmount all 501 { 502 final File votraceDirectory = new File( vS.outDirectory(), "votrace" ); 503 for( final File nominalReadyDirectory: 504 nominalReadyDirectories( votraceDirectory )) 505 { 506 final ReadyDirectory r = new ReadyDirectory( nominalReadyDirectory ); // as such 507 if( !r.isMounted() ) continue; 508 509 argument = nominalReadyDirectory.getPath(); 510 if( r.equals( readyToReportLink.getCanonicalFile() )) 511 { 512 if( isVerbose ) 513 { 514 System.out.println( "skipping unmount of reported trace " 515 + argument ); 516 } 517 continue; 518 } 519 520 if( isVerbose ) System.out.println( "unmounting " + argument ); 521 run( action, argument, optionMap ); 522 } 523 return; 524 } 525 else // unmount one 526 { 527 final String nominalReadyPath = argument; 528 final ReadyDirectory readyDirectory = new ReadyDirectory( nominalReadyPath ); 529 if( readyToReportLink.exists() 530 && readyDirectory.equals( readyToReportLink.getCanonicalFile() )) 531 { 532 System.err.println( "votrace: cannot unmount, trace is currently reported\n" 533 + " try: votrace ureport" ); 534 System.exit( 1 ); 535 } 536 537 if( !readyDirectory.unmount( vsRun )) 538 { 539 System.err.println( "votrace: not mounted: " + nominalReadyPath ); 540 System.exit( 1 ); 541 } 542 } 543 544 } 545 546 // *. Ureport 547 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 548 else if( action == Action.ureport ) 549 { 550 if( argument != null ) 551 { 552 System.err.println( "votrace: too many arguments" ); 553 System.err.println( GetoptX.createHelpPrompt( "votrace" )); 554 System.exit( 1 ); 555 } 556 557 final NetworkTrace.VoteServerScope scopeTrace = vS.scopeTrace(); 558 final File readyToReportLink = scopeTrace.readyToReportLink(); 559 if( !readyToReportLink.exists() ) 560 { 561 System.err.println( "votrace: nothing to unreport" ); 562 System.exit( 1 ); 563 } 564 565 if( isVerbose ) 566 { 567 System.out.println( "unreporting " + readyToReportLink.getCanonicalFile() ); // difficult to recover the nominal path in this case, because getCanonicalFile() is the only easy way to de-reference the link 568 } 569 if( !readyToReportLink.delete() ) 570 { 571 System.err.println( 572 "votrace: unable to delete report link, please delete it manually: " 573 + readyToReportLink ); 574 } 575 final File snapToReportLink = scopeTrace.snapToReportLink(); 576 if( !snapToReportLink.delete() ) 577 { 578 System.err.println( 579 "votrace: unable to delete snap link, please delete it manually: " 580 + snapToReportLink ); 581 } 582 } 583 584 // *. Status 585 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 586 else if( action == Action.status ) 587 { 588 if( argument != null ) 589 { 590 System.err.println( "votrace: too many arguments" ); 591 System.err.println( GetoptX.createHelpPrompt( "votrace" )); 592 System.exit( 1 ); 593 } 594 595 final File votraceDirectory = new File( vS.outDirectory(), "votrace" ); 596 final File readyDirectoryToReportOrNull; 597 { 598 final File readyToReportLink = vS.scopeTrace().readyToReportLink(); 599 readyDirectoryToReportOrNull = readyToReportLink.exists()? 600 readyToReportLink.getCanonicalFile(): null; 601 } 602 final File[] snapDirectoryArray = FileX.listFilesNoNull( votraceDirectory, 603 OutputStore.SNAP_DIRECTORY_FILTER ); 604 Arrays.sort( snapDirectoryArray ); 605 final StringBuilder flagB = new StringBuilder( ReadyDirectory.STATUS_FLAGS_COUNT ); 606 for( final File snapDirectory: snapDirectoryArray ) 607 { 608 final File[] readyDirectoryArray = FileX.listFilesNoNull( snapDirectory, 609 ReadyDirectory.READY_DIRECTORY_FILTER ); 610 if( readyDirectoryArray.length == 0 ) // unreadied snap 611 { 612 ReadyDirectory.statusPrintln( StringBuilderX.clear(flagB), 613 snapDirectory ); 614 continue; 615 } 616 617 Arrays.sort( readyDirectoryArray ); 618 for( final File nominalReadyDirectory: readyDirectoryArray ) 619 { 620 final ReadyDirectory readyDirectory = 621 new ReadyDirectory( nominalReadyDirectory ); // as such 622 readyDirectory.statusPrintln( StringBuilderX.clear(flagB), 623 nominalReadyDirectory, readyDirectoryToReportOrNull ); 624 } 625 } 626 } 627 628 // - - - 629 else assert false; 630 if( isEtc ) 631 { 632 run( /*next*/Action.values()[action.ordinal() + 1], /*argument*/null, optionMap ); 633 } 634 } 635 catch( RuntimeException x ) { throw x; } 636 catch( Exception x ) 637 { 638 System.err.print( "votrace: fatal error" + Votorola.unmessagedDetails(x) + ": " ); 639 // System.err.println( x ); 640 x.printStackTrace( System.err ); 641 System.exit( 1 ); 642 } 643 } 644 645 646}