com.rathravane.till.console.ConsoleProgram Maven / Gradle / Ivy
Show all versions of silt Show documentation
/*
* Copyright 2006-2012, Rathravane LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rathravane.till.console;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rathravane.till.nv.rrNvReadable;
import com.rathravane.till.nv.rrNvWriteable;
import com.rathravane.till.nv.impl.nvEnvProperties;
import com.rathravane.till.nv.impl.nvInstallTypeWrapper;
import com.rathravane.till.nv.impl.nvReadableStack;
import com.rathravane.till.nv.impl.nvWriteableTable;
/**
* A console program runs on the command line.
*
* The console class expects the program's main() routine to call its
* runFromMain() method to start the system.
*
* @author peter
*/
public class ConsoleProgram
{
public static class usageException extends Exception
{
public usageException ( String correctUsage ) { super(correctUsage); }
public usageException ( Exception cause ) { super(cause); }
private static final long serialVersionUID = 1L;
}
public static class startupFailureException extends Exception
{
public startupFailureException ( Exception x ) { super(x); }
public startupFailureException ( String msg ) { super(msg); }
public startupFailureException ( String msg, Exception x ) { super(msg,x); }
private static final long serialVersionUID = 1L;
}
/**
* A looper is an object that is run repeatedly (in a loop). This class is
* what the main thread of the program does between startup and exit.
*
* @author peter
*
*/
public interface looper
{
/**
* setup the looper and return true to continue. Called once.
* @param prefs
* @param cmdLine
* @return true/false
*/
boolean setup ( rrNvReadable prefs, CmdLinePrefs cmdLine );
/**
* Run a loop iteration, return true to continue, false to exit. (Note
* that nothing requires this implementation to do a small amount of
* work vs. lengthy processing.)
* @return true to continue, false to exit
*/
boolean loop ( rrNvReadable prefs );
/**
* teardown the looper. called once.
* @param prefs
*/
void teardown ( rrNvReadable prefs );
}
protected CmdLineParser getCmdLineParser ()
{
return fCmdLineParser;
}
protected ConsoleProgram ()
{
fHostInfo = new nvWriteableTable ();
fDefaults = new nvWriteableTable ();
fCmdLineParser = new CmdLineParser ();
}
public void runFromMain ( String[] args ) throws Exception
{
// get setup
installShutdownHook ();
setupHostInfo ();
setupDefaults ( fDefaults );
setupOptions ( fCmdLineParser );
// parse the command line
final CmdLinePrefs cmdLine = fCmdLineParser.processArgs ( args );
// build a preferences stack
final nvReadableStack stack = new nvReadableStack ();
stack.push ( fHostInfo ); // sets 'hostname'
stack.push ( new nvEnvProperties() ); // makes system environment available
stack.push ( fDefaults ); // app defaults
stack.push ( cmdLine ); // settings from command line
// wrap the settings stack with the install-type suffix check,
// which lets you set -Drr.installation=foo on the Java cmd line
// and then have settings like mySetting[foo]=bar as specialization
// over mySetting=theUsualValue.
final nvInstallTypeWrapper wrapper = new nvInstallTypeWrapper ( stack );
// optionally load more configuration from the app. If provided,
// it fits in the stack below command line settings
final rrNvReadable config = loadAdditionalConfig ( wrapper );
if ( config != null )
{
stack.pushBelow ( config, cmdLine );
wrapper.rescan ();
}
// init and get the run loop
final looper l = init ( wrapper, cmdLine );
if ( l != null )
{
if ( l.setup ( wrapper, cmdLine ) )
{
while ( l.loop ( wrapper ) ) {}
l.teardown ( wrapper );
}
}
cleanup ();
}
/**
* Override this to handle an abrupt shutdown. This method is called when the system exits.
*/
protected void onShutdown () { }
/**
* Override this to setup default settings for the program.
* @param pt
*/
protected ConsoleProgram setupDefaults ( rrNvWriteable pt ) { return this; }
/**
* Override this to setup recognized command line options. Note that default values
* provided to the command line reader are NOT available from init's settings instance.
* That's because the settings system doesn't have a way to differentiate between having
* a key and having a key as a default value. When stacked, if the command line parser
* states that it has a key, then any explicit setting further down the stack will not
* be used.
* @param p
*/
protected ConsoleProgram setupOptions ( CmdLineParser p ) { return this; }
/**
* Override this to load additional configuration. If a non-null config is returned,
* it's inserted into the preferences stack between the default settings and the command line
* settings. That way, the command line arguments have precedence.
* @param currentPrefs
* @throws rrNvReadable.loadException
* @throws rrNvReadable.missingReqdSetting
*/
protected rrNvReadable loadAdditionalConfig ( rrNvReadable currentPrefs ) throws rrNvReadable.loadException, rrNvReadable.missingReqdSetting { return null; }
/**
* Init the program and return a loop instance if the program should continue. The base
* class returns null, so you have to override this to do anything beyond init.
* @param p settings
* @param cmdLine command line values
* @return non-null to continue, null to exit
* @throws rrNvReadable.missingReqdSetting
* @throws rrNvReadable.invalidSettingValue
*/
protected looper init ( rrNvReadable p, CmdLinePrefs cmdLine ) throws rrNvReadable.missingReqdSetting, rrNvReadable.invalidSettingValue, startupFailureException { return null; }
/**
* Override this to run any cleanup code after the main loop.
*/
protected void cleanup () {}
/**
* expand a file argument ("*" matches, etc.)
* @param arg
* @return
* @throws FileNotFoundException
*/
protected List expandFileArg ( String arg ) throws FileNotFoundException
{
final LinkedList fileList= new LinkedList ();
final File file = new File ( arg );
final File parentDir = file.getParentFile ();
if ( parentDir != null )
{
final String matchPart = file.getName ().replace ( "*", ".*" ); // cmd line regex to java regex
final Pattern p = Pattern.compile ( matchPart );
final File[] files = parentDir.listFiles ( new FilenameFilter ()
{
@Override
public boolean accept ( File dir, String name )
{
return p.matcher ( name ).matches ();
}
} );
if ( files != null )
{
for ( File f : files )
{
fileList.add ( f );
}
}
}
return fileList;
}
private final CmdLineParser fCmdLineParser;
private final nvWriteableTable fDefaults;
private final nvWriteableTable fHostInfo;
private static final Logger log = LoggerFactory.getLogger ( ConsoleProgram.class );
private void setupHostInfo ()
{
try
{
fHostInfo.set ( "hostname", InetAddress.getLocalHost().getHostName() );
}
catch ( UnknownHostException e )
{
log.warn ( "Couldn't establish hostname.", e );
}
}
private void installShutdownHook ()
{
Runtime.getRuntime ().addShutdownHook (
new Thread ()
{
@Override
public void run ()
{
onShutdown ();
}
}
);
}
}