package gnu.dtools.ritopt; /** * Options.java * * Version: * $Id: Options.java 2488 2007-11-14 00:25:31Z coezbek $ */ import java.io.*; import java.util.HashMap; import java.util.Iterator; import net.sf.jabref.Globals; /** * This class functions as a repository for options and their modules. It * facilitates registration of options and modules, as well as processing of * arguments.<p> * * Information such as help, usage, and versions are displayed * when the respective --help and --version options are specified. * The --menu option will invoke the built-in menu.<p> * * In the example below, the program processes three simple options. * * <pre> * public class AboutMe { * * private static StringOption name = new StringOption( "Ryan" ); * private static IntOption age = new IntOption( 19 ); * private static DoubleOption bankBalance = new DoubleOption( 15.15 ); * * public static void main( String args[] ) { * Options repo = new Options( "java AboutMe" ); * repo.register( "name", 'n', name, "The person's name." ); * repo.register( "age", 'a', age, "The person's age." ); * repo.register( "balance", 'b', "The person's bank balance.", * bankBalance ); * repo.process( args ); g * System.err.println( "" + name + ", age " + age + " has a " + * " bank balance of " + bankBalance + "." ); * } * } * </pre> * * <hr> * * <pre> * Copyright (C) Damian Ryan Eads, 2001. All Rights Reserved. * * ritopt is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * ritopt is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ritopt; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * </pre> * * @author Damian Eads */ public class Options implements OptionRegistrar, OptionModuleRegistrar, OptionListener { /** * The default verbosity. */ public static final int DEFAULT_VERBOSITY = 3; /** * This boolean defines whether options are deprecated by default. */ public static final boolean DEFAULT_DEPRECATED = false; /** * The default reason for deprecation. */ public static final String DEFAULT_REASON = "No reason given."; /** * The default general module name. */ public static final String DEFAULT_GENERAL_MODULE_NAME = "General"; /** * This boolean defines whether usage should be displayed. */ public static final boolean DEFAULT_DISPLAY_USAGE = false; // Mod. Morten A. /** * This boolean defines whether the menu should be used. */ public static final boolean DEFAULT_USE_MENU = false; // Mod. Morten A. /** * The default program name that is display in the usage. */ public static final String DEFAULT_PROGRAM_NAME = "java program"; /** * The default option file. */ public static final String DEFAULT_OPTION_FILENAME = "default.opt"; /** * The program to display in the usage. */ private String usageProgram; /** * The version to display in the usage. */ private String version; /** * The default option filename if an option file is not specified. */ private String defaultOptionFilename; /** * This flag defines whether to display usage when help is displayed. */ private boolean displayUsage; /** * This boolean defines whether the menu should be used. */ private boolean useMenu; /** * When this flag is true, debugging information is displayed. */ private boolean debugFlag; /** * The current module being processed. */ private OptionModule currentModule; /** * The general option module. */ private OptionModule generalModule; /** * A map of option modules. */ private java.util.HashMap<String, OptionModule> modules; /** * Version information is displayed when this option is specified. */ private NotifyOption versionOption; /** * Create an option repository. */ public Options() { this( DEFAULT_PROGRAM_NAME ); } /** * Create an option repository and associated it with a program name. * * @param programName A program name like "java Balloons". */ public Options( String programName ) { displayUsage = DEFAULT_DISPLAY_USAGE; useMenu = DEFAULT_USE_MENU; defaultOptionFilename = DEFAULT_OPTION_FILENAME; usageProgram = programName; modules = new HashMap<String, OptionModule>(); versionOption = new NotifyOption( this, "version", "" ); version = "Version 1.0"; generalModule = new OptionModule( DEFAULT_GENERAL_MODULE_NAME ); currentModule = generalModule; // Mod. Morten A. ------------------------------------------------ register( "version", 'v', "Displays version information.", versionOption ); /*register( "help", 'h', "Displays help for each option.", helpOption ); register( "menu", 'm', "Displays the built-in interactive menu.", menuOption );*/ // End mod. Morten A. ------------------------------------------------ } /** * Returns the help information as a string. * * @return The help information. */ public String getHelp() { String retval = (displayUsage ? getUsage() + "\n\n" : "" ) + // Mod. Morten A. //"Use --menu to invoke the interactive built-in menu.\n\n" + Option.getHelpHeader() + "\n\n" + generalModule.getHelp(); Iterator<OptionModule> it = modules.values().iterator(); while ( it.hasNext() ) { OptionModule module = it.next(); retval += "\n\nOption Listing for " + module.getName() + "\n"; retval += module.getHelp() + "\n"; } return retval; } /** * Returns usage information of this program. * * @return The usage information. */ public String getUsage() { return getUsageProgram() + " @optionfile :module: OPTIONS ... :module: OPTIONS"; } /** * Returns the program name displayed in the usage. * * @returns The program name. */ public String getUsageProgram() { return usageProgram; } /** * Returns the version of the program. * * @returns The version. */ public String getVersion() { return version; } /** * Returns the option filename to load or write to if one is not * specified. * * @return The default option filename. */ public String getDefaultOptionFilename() { return defaultOptionFilename; } /** * Returns whether debugging information should be displayed. * * @return A boolean indicating whether to display help information. */ public boolean getDebugFlag() { return debugFlag; } /** * Returns whether the help information should display usage. * * @return A boolean indicating whether help should display usage. */ public boolean shouldDisplayUsage() { return displayUsage; } /** * Returns whether the built-in menu system can be invoked. * * @return A boolean indicating whether the build-in menu system * can be invoked. */ public boolean shouldUseMenu() { return useMenu; } /** * Sets whether usage can be displayed. * * @param b A boolean value indicating that usage can be displayed. */ public void setDisplayUsage( boolean b ) { displayUsage = b; } /** * Sets whether the built-in menu system can be used. * * @param b A boolean value indicating whether the built-in menu * system can be used. */ public void setUseMenu( boolean b ) { useMenu = b; } /** * Sets the program to display when the usage is displayed. * * @param program The program displayed during usage. */ public void setUsageProgram( String program ) { usageProgram = program; } /** * Sets the version of the program. * * @param version The version. */ public void setVersion( String version ) { this.version = version; } /** * Sets the option file to use when an option file is not specified. * * @param fn The filename of the default option file. */ public void setDefaultOptionFilename( String fn ) { defaultOptionFilename = fn; } /** * Displays the program's help which includes a description of each * option. The usage is display if the usage flag is set to true. */ public void displayHelp() { System.err.println( getHelp() ); } /** * Displays the version of the program. */ public void displayVersion() { System.err.println( getVersion() +" (build " +Globals.BUILD +")"); } /** * Register an option into the repository as a long option. * * @param longOption The long option name. * @param option The option to register. */ public void register( String longOption, Option option ) { generalModule.register( longOption, option ); } /** * Register an option into the repository as a short option. * * @param shortOption The short option name. * @param option The option to register. */ public void register( char shortOption, Option option ) { generalModule.register( shortOption, option ); } /** * Register an option into the repository both as a short and long option. * * @param longOption The long option name. * @param shortOption The short option name. * @param option The option to register. */ public void register( String longOption, char shortOption, Option option ) { generalModule.register( longOption, shortOption, option ); } /** * Register an option into the repository both as a short and long option. * Initialize its description with the description passed. * * @param longOption The long option name. * @param shortOption The short option name. * @param description The description of the option. * @param option The option to register. */ public void register( String longOption, char shortOption, String description, Option option ) { generalModule.register( longOption, shortOption, description, option ); } /** * Register an option into the repository both as a short and long option. * Initialize its description with the description passed. * * @param longOption The long option name. * @param shortOption The short option name. * @param description The description of the option. * @param option The option to register. * @param deprecated A boolean indicating whether an option should * be deprecated. */ public void register( String longOption, char shortOption, String description, Option option, boolean deprecated ) { generalModule.register( longOption, shortOption, description, option, deprecated ); } /** * Register an option module based on its name. * * @param module The option module to register. */ public void register( OptionModule module ) { register( module.getName(), module ); } /** * Register an option module and associate it with the name passed. * * @param name The name associated with the option module. * @param module The option module to register. */ public void register( String name, OptionModule module ) { modules.put( name.toLowerCase(), module ); } /** * Process a string of values representing the invoked options. After * all the options are processed, any leftover arguments are returned. * * @param args The arguments to process. * * @return The leftover arguments. */ public String[] process( String args[] ) { String []retval = new String[0]; try { retval = processOptions( args ); } catch ( OptionException e ) { System.err.println( "Error: " + e.getMessage() ); } /** catch ( Exception e ) { System.err.println( "Error: Unexpected Error in ritopt Processing." + "Check syntax." ); }**/ return retval; } /** * Retrieves an option module based on the name passed. * * @param name The name referring to the option module. * * @return The option module. Null is returned if the module does not * exist. */ public OptionModule getModule( String name ) { return modules.get( name.toLowerCase() ); } /** * Returns a boolean indicating whether an option module exists. * * @param name The name referring to the option module. * * @return A boolean value indicating whether the module exists. */ public boolean moduleExists( String name ) { return getModule( name ) != null; } /** * Receives NotifyOption events. If the event command equals "help" * or "version", the appropriate display methods are invoked. * * @param event The event object containing information about the * invocation. */ public void optionInvoked( OptionEvent event ) { if ( event.getCommand().equals( "help" ) ) { displayHelp(); } else if ( event.getCommand().equals( "version" ) ) { displayVersion(); } } /** * Process a string representing the invoked options. This string * gets split according to how they would be split when passed to * a main method. The split array of options gets passed to a * private method for processing. After all the options are processed, * any leftover arguments are returned. * * @param str The arguments to process. * * @return The leftover arguments. */ public String[] process( String str ) { return process( split( str ) ); } /** * Splits a string representing command line arguments into several * strings. * * @param str The string to split. * * @return The splitted string. */ public String[] split( String str ) { StringBuffer buf = new StringBuffer( str.length() ); java.util.List<String> l = new java.util.ArrayList<String>(); int scnt = Utility.count( str, '"' ); boolean q = false; if ( (scnt) / 2.0 != (scnt / 2) ) { throw new OptionProcessingException( "Expecting an end quote." ); } for ( int n = 0; n < str.length(); n++ ) { if ( str.charAt( n ) == '"' ) { q = !q; } else if ( str.charAt( n ) == ' ' && !q ) { l.add( buf.toString() ); buf = new StringBuffer( str.length() ); } else { buf.append( str.charAt( n ) ); } } if ( buf.length() != 0 ) { l.add( buf.toString() ); } Iterator<String> it = l.iterator(); String retval[] = new String[ l.size() ]; int n = 0; while ( it.hasNext() ) { retval[ n++ ] = it.next(); } return retval; } /** * Writes all options and their modules out to an options file. * * @param filename The options filename to write. */ public void writeOptionFile( String filename ) { BufferedOutputStream writer = null; Iterator<OptionModule> it = null; currentModule = generalModule; try { writer = new BufferedOutputStream( new FileOutputStream( filename ) ); PrintStream ps = new PrintStream( writer ); generalModule.writeFileToPrintStream( ps ); it = modules.values().iterator(); while ( it.hasNext() ) { OptionModule module = it.next(); module.writeFileToPrintStream( ps ); } } catch ( IOException e ) { throw new OptionProcessingException( e.getMessage() ); } finally { try { if ( writer != null ) writer.close(); } catch( IOException e ) { throw new OptionProcessingException( e.getMessage() ); } } } /** * Loads all options and their modules from an options file. * * @param filename The options filename to write. */ public void loadOptionFile( String filename ) { BufferedReader reader = null; String line = null; currentModule = generalModule; try { reader = new BufferedReader( new FileReader( filename ) ); while ( ( line = reader.readLine() ) != null ) { line = Utility.stripComments( line, '\"', ';' ); process( line ); } } catch ( IOException e ) { throw new OptionProcessingException( e.getMessage() ); } finally { try { if ( reader != null ) reader.close(); } catch( IOException e ) { throw new OptionProcessingException( e.getMessage() ); } } } /** * Processes an array of strings representing command line arguments. * * @param args arguments to process. * * @return The leftover arguments. */ private String[] processOptions( String args[] ) { String retval[] = null; String moduleName = "general"; String optionFile = ""; char shortOption = '\0'; String longOption = ""; for ( int n = 0; n < args.length && retval == null; n++ ) { boolean moduleInvoked = false; boolean shortOptionInvoked = false; boolean longOptionInvoked = false; boolean readOptionFileInvoked = false; boolean writeOptionFileInvoked = false; if ( args[ n ].length() >= 1 ) { char fc = args[ n ].charAt( 0 ); moduleInvoked = fc == ':'; readOptionFileInvoked = fc == '@'; writeOptionFileInvoked = fc == '%'; } if ( args[ n ].length() >= 2 ) { String s = args[ n ].substring( 0, 2 ); shortOptionInvoked = ( !s.equals( "--" ) && s.charAt( 0 ) == '-' ); longOptionInvoked = ( s.equals( "--" ) ); } if ( debugFlag ) { System.err.println( "Short Option: " + shortOptionInvoked ); System.err.println( "Long Option: " + longOptionInvoked ); System.err.println( "Module: " + moduleInvoked ); System.err.println( "Load Option File: " + readOptionFileInvoked ); System.err.println( "Write Option File: " + writeOptionFileInvoked ); } if ( moduleInvoked ) { if ( args[ n ].charAt( args[ n ].length() - 1 ) != ':' ) { System.err.println( args[ n ] ); throw new OptionProcessingException( "Module arguments must start" + " with : and end with :." ); } else { moduleName = args[n].substring( 1, args[n].length() - 1 ).toLowerCase(); if ( moduleName.length() == 0 || moduleName.equals( "general" ) ) { moduleName = "general"; currentModule = generalModule; } else { currentModule = getModule( moduleName ); } if ( currentModule == null ) throw new OptionProcessingException( "Module '" + moduleName + "' does not exist." ); if ( debugFlag ) { System.err.println( "Module: " + moduleName ); } } moduleInvoked = false; } else if ( readOptionFileInvoked ) { optionFile = Utility.trim( args[ n ].substring( 1 ) ); if ( optionFile.equals( "@" ) || optionFile.length() == 0 ) optionFile = defaultOptionFilename; if ( debugFlag ) { System.err.println( "Option file: '" + optionFile + "'." ); } loadOptionFile( optionFile ); } else if ( shortOptionInvoked ) { shortOption = args[ n ].charAt( 1 ); if ( !Utility.isAlphaNumeric( shortOption ) ) { throw new OptionProcessingException( "A short option must be alphanumeric. -" + shortOption + " is not acceptable." ); } if ( debugFlag ) { System.err.println( "Short option text: " + shortOption ); } char delim = ( args[ n ].length() >= 3 ) ? args[ n ].charAt( 2 ) : '\0'; if ( delim == '+' || delim == '-' ) { currentModule.action( shortOption, delim ); } else if ( delim == '=' ) { currentModule.action( shortOption, args[ n ].substring( 3 ) ); } else if ( delim == '\0' ) { String dtext = "+"; if ( n < args.length - 1 ) { if ( !Utility.contains( args[ n + 1 ].charAt( 0 ), "-[@" ) ) { dtext = args[ n + 1 ]; n++; } } currentModule.action( shortOption, dtext ); } else if ( Utility.isAlphaNumeric( delim ) ) { for ( int j = 1; j < args[ n ].length(); j++ ) { if ( Utility.isAlphaNumeric( args[ n ].charAt( j ) ) ) { currentModule.action( shortOption, "+" ); } else { throw new OptionProcessingException( "A short option must be alphanumeric. -" + shortOption + " is not acceptable." ); } } } } else if ( longOptionInvoked ) { char lastchar = args[ n ].charAt( args[ n ].length() - 1 ); int eqindex = args[ n ].indexOf( "=" ); if ( eqindex != -1 ) { longOption = args[ n ].substring( 2, eqindex ); String value = args[ n ].substring( eqindex + 1 ); currentModule.action( longOption, value ); } else if ( Utility.contains( lastchar, "+-" ) ) { longOption = args[ n ].substring( 2, args[ n ].length() - 1 ); currentModule.action( longOption, lastchar ); } else { longOption = args[ n ].substring( 2 ); String dtext = "+"; if ( n < args.length - 1 && args[ n + 1 ].length() > 0 ) { if ( !Utility.contains( args[ n + 1 ].charAt( 0 ), "-[@" ) ) { dtext = args[ n + 1 ]; n++; } } currentModule.action( longOption, dtext ); } if ( debugFlag ) { System.err.println( "long option: " + longOption ); } } else if ( writeOptionFileInvoked ) { optionFile = Utility.trim( args[ n ].substring( 1 ) ); if ( optionFile.equals( "%" ) || optionFile.length() == 0 ) optionFile = defaultOptionFilename; if ( debugFlag ) { System.err.println( "Option file: '" + optionFile + "'." ); } writeOptionFile( optionFile ); } else { retval = new String[ args.length - n ]; for ( int j = n; j < args.length; j++ ) { retval[ j - n ] = args[ j ]; } } } if ( retval == null ) retval = new String[ 0 ]; return retval; } }