8ec5113d5e8bf73553f39a022b7e447846e7f213
[debian/jabref.git] / src / java / gnu / dtools / ritopt / Options.java
1 package gnu.dtools.ritopt;
2
3 /**
4  * Options.java
5  *
6  * Version:
7  *    $Id: Options.java 1234 2005-07-30 14:49:05Z mortenalver $
8
9  */
10
11 import java.util.*;
12 import java.io.*;
13 import net.sf.jabref.*;
14
15 /**
16  * This class functions as a repository for options and their modules. It
17  * facilitates registration of options and modules, as well as processing of
18  * arguments.<p>
19  *
20  * Information such as help, usage, and versions are displayed
21  * when the respective --help and --version options are specified.
22  * The --menu option will invoke the built-in menu.<p>
23  *
24  * In the example below, the program processes three simple options.
25  *
26  * <pre>
27  * public class AboutMe {
28  *
29  *    private static StringOption name = new StringOption( "Ryan" );
30  *    private static IntOption age = new IntOption( 19 );
31  *    private static DoubleOption bankBalance = new DoubleOption( 15.15 );
32  *
33  *    public static void main( String args[] ) {
34  *       Options repo = new Options( "java AboutMe" );
35  *       repo.register( "name", 'n', name, "The person's name." );
36  *       repo.register( "age", 'a', age, "The person's age." );
37  *       repo.register( "balance", 'b', "The person's bank balance.",
38  *                       bankBalance );
39  *       repo.process( args );
40 g *       System.err.println( "" + name + ", age " + age + " has a " +
41  *                           " bank balance of " + bankBalance + "." );
42  *    }
43  * }
44  * </pre>
45  *
46  * <hr>
47  *
48  * <pre>
49  * Copyright (C) Damian Ryan Eads, 2001. All Rights Reserved.
50  *
51  * ritopt is free software; you can redistribute it and/or modify
52  * it under the terms of the GNU General Public License as published by
53  * the Free Software Foundation; either version 2 of the License, or
54  * (at your option) any later version.
55  *
56  * ritopt is distributed in the hope that it will be useful,
57  * but WITHOUT ANY WARRANTY; without even the implied warranty of
58  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
59  * GNU General Public License for more details.
60  *
61  * You should have received a copy of the GNU General Public License
62  * along with ritopt; if not, write to the Free Software
63  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
64  * </pre>
65  *
66  * @author Damian Eads
67  */
68
69 public class Options implements OptionRegistrar, OptionModuleRegistrar,
70                                 OptionListener {
71
72     /**
73      * The default verbosity.
74      */
75
76     public static final int DEFAULT_VERBOSITY = 3;
77
78     /**
79      * This boolean defines whether options are deprecated by default.
80      */
81
82     public static final boolean DEFAULT_DEPRECATED = false;
83
84     /**
85      * The default reason for deprecation.
86      */
87
88     public static final String DEFAULT_REASON = "No reason given.";
89
90     /**
91      * The default general module name.
92      */
93
94     public static final String DEFAULT_GENERAL_MODULE_NAME = "General";
95
96     /**
97      * This boolean defines whether usage should be displayed.
98      */
99
100     public static final boolean DEFAULT_DISPLAY_USAGE = false; // Mod. Morten A.
101
102     /**
103      * This boolean defines whether the menu should be used.
104      */
105
106     public static final boolean DEFAULT_USE_MENU = false; // Mod. Morten A.
107
108     /**
109      * The default program name that is display in the usage.
110      */
111
112     public static final String DEFAULT_PROGRAM_NAME = "java program";
113
114     /**
115      * The default option file.
116      */
117
118     public static final String DEFAULT_OPTION_FILENAME = "default.opt";
119
120     /**
121      * The current verbosity.
122      */
123
124     private int verbosity;
125
126     /**
127      * The program to display in the usage.
128      */
129
130     private String usageProgram;
131
132     /**
133      * The version to display in the usage.
134      */
135
136     private String version;
137
138     /**
139      * The default option filename if an option file is not specified.
140      */
141
142     private String defaultOptionFilename;
143
144     /**
145      * This flag defines whether to display usage when help is displayed.
146      */
147
148     private boolean displayUsage;
149
150     /**
151      * This boolean defines whether the menu should be used.
152      */
153
154     private boolean useMenu;
155
156     /**
157      * When this flag is true, debugging information is displayed.
158      */
159
160     private boolean debugFlag;
161
162     /**
163      * The current module being processed.
164      */
165
166     private OptionModule currentModule;
167
168     /**
169      * The general option module.
170      */
171
172     private OptionModule generalModule;
173
174     /**
175      * A map of option modules.
176      */
177
178     private java.util.HashMap modules;
179
180     /**
181      * The help method is invoked when this option is invoked.
182      */
183
184     private NotifyOption helpOption;
185
186     /**
187      * The built-in menu system is invoked when this option is invoked.
188      */
189
190     private NotifyOption menuOption;
191
192     /**
193      * Version information is displayed when this option is specified.
194      */
195
196     private NotifyOption versionOption;
197
198     /**
199      * An instance of the built-in menu.
200      */
201
202     private OptionMenu menu;
203
204     /**
205      * Create an option repository.
206      */
207
208     public Options() {
209         this( DEFAULT_PROGRAM_NAME );
210     }
211
212     /**
213      * Create an option repository and associated it with a program name.
214      *
215      * @param programName A program name like "java Balloons".
216      */
217
218     public Options( String programName ) {
219         verbosity = DEFAULT_VERBOSITY;
220         displayUsage = DEFAULT_DISPLAY_USAGE;
221         useMenu = DEFAULT_USE_MENU;
222         defaultOptionFilename = DEFAULT_OPTION_FILENAME;
223         usageProgram = programName;
224         modules = new HashMap();
225         menu = new OptionMenu( this );
226         helpOption = new NotifyOption( this, "help", "" );
227         versionOption = new NotifyOption( this, "version", "" );
228         version = "Version 1.0";
229         menuOption = new NotifyOption( menu, "menu", "" );
230         generalModule = new OptionModule( DEFAULT_GENERAL_MODULE_NAME );
231         currentModule = generalModule;
232
233         // Mod. Morten A. ------------------------------------------------
234         register( "version", 'v',
235                   "Displays version information.", versionOption );
236         /*register( "help", 'h', "Displays help for each option.", helpOption );
237         register( "menu", 'm', "Displays the built-in interactive menu.",
238                   menuOption );*/
239         // End mod. Morten A. ------------------------------------------------
240     }
241
242     /**
243      * Returns the help information as a string.
244      *
245      * @return The help information.
246      */
247
248     public String getHelp() {
249         String retval = (displayUsage ? getUsage() + "\n\n" : "" ) +
250             // Mod. Morten A.
251             //"Use --menu to invoke the interactive built-in menu.\n\n" +
252             Option.getHelpHeader() + "\n\n" + generalModule.getHelp();
253         Iterator it = modules.values().iterator();
254         while ( it.hasNext() ) {
255             OptionModule module = (OptionModule)it.next();
256             retval += "\n\nOption Listing for " + module.getName() + "\n";
257             retval += module.getHelp() + "\n";
258         }
259         return retval;
260     }
261
262     /**
263      * Returns usage information of this program.
264      *
265      * @return The usage information.
266      */
267
268     public String getUsage() {
269         return getUsageProgram()
270             + " @optionfile :module: OPTIONS ... :module: OPTIONS";
271     }
272
273     /**
274      * Returns the program name displayed in the usage.
275      *
276      * @param The program name.
277      */
278
279     public String getUsageProgram() {
280         return usageProgram;
281     }
282
283     /**
284      * Returns the version of the program.
285      *
286      * @param The version.
287      */
288
289     public String getVersion() {
290         return version;
291     }
292
293     /**
294      * Returns the option filename to load or write to if one is not
295      * specified.
296      *
297      * @return The default option filename.
298      */
299
300     public String getDefaultOptionFilename() {
301         return defaultOptionFilename;
302     }
303
304     /**
305      * Returns whether debugging information should be displayed.
306      *
307      * @return A boolean indicating whether to display help information.
308      */
309
310     public boolean getDebugFlag() {
311         return debugFlag;
312     }
313
314     /**
315      * Returns whether the help information should display usage.
316      *
317      * @return A boolean indicating whether help should display usage.
318      */
319
320     public boolean shouldDisplayUsage() {
321         return displayUsage;
322     }
323
324     /**
325      * Returns whether the built-in menu system can be invoked.
326      *
327      * @return A boolean indicating whether the build-in menu system
328      *         can be invoked.
329      */
330
331     public boolean shouldUseMenu() {
332         return useMenu;
333     }
334
335     /**
336      * Sets whether usage can be displayed.
337      *
338      * @param b     A boolean value indicating that usage can be displayed.
339      */
340
341     public void setDisplayUsage( boolean b ) {
342         displayUsage = b;
343     }
344
345     /**
346      * Sets whether the built-in menu system can be used.
347      *
348      * @param b      A boolean value indicating whether the built-in menu
349      *               system can be used.
350      */
351
352     public void setUseMenu( boolean b ) {
353         useMenu = b;
354     }
355
356     /**
357      * Sets the program to display when the usage is displayed.
358      *
359      * @param program The program displayed during usage.
360      */
361
362     public void setUsageProgram( String program ) {
363         usageProgram = program;
364     }
365
366     /**
367      * Sets the version of the program.
368      *
369      * @param version The version.
370      */
371
372     public void setVersion( String version ) {
373         this.version = version;
374     }
375
376     /**
377      * Sets the option file to use when an option file is not specified.
378      *
379      * @param fn      The filename of the default option file.
380      */
381
382     public void setDefaultOptionFilename( String fn ) {
383         defaultOptionFilename = fn;
384     }
385
386     /**
387      * Sets the debugging flag.
388      *
389      * @param flag    The value to set the debugging flag.
390
391     public void setDebugFlag( boolean flag ) {
392         debugFlag = flag;
393     }
394
395     /**
396      * Displays the program's help which includes a description of each
397      * option. The usage is display if the usage flag is set to true.
398      */
399
400     public void displayHelp() {
401         System.err.println( getHelp() );
402     }
403
404     /**
405      * Displays the version of the program.
406      */
407
408     public void displayVersion() {
409         System.err.println( getVersion() +" (build " +Globals.BUILD +")");
410     }
411
412     /**
413      * Register an option into the repository as a long option.
414      *
415      * @param longOption  The long option name.
416      * @param option      The option to register.
417      */
418
419     public void register( String longOption, Option option ) {
420         generalModule.register( longOption, option );
421     }
422
423     /**
424      * Register an option into the repository as a short option.
425      *
426      * @param shortOption The short option name.
427      * @param option      The option to register.
428      */
429
430     public void register( char shortOption, Option option ) {
431         generalModule.register( shortOption, option );
432     }
433
434     /**
435      * Register an option into the repository both as a short and long option.
436      *
437      * @param longOption  The long option name.
438      * @param shortOption The short option name.
439      * @param option      The option to register.
440      */
441
442     public void register( String longOption, char shortOption,
443                           Option option ) {
444         generalModule.register( longOption, shortOption, option );
445     }
446
447     /**
448      * Register an option into the repository both as a short and long option.
449      * Initialize its description with the description passed.
450      *
451      * @param longOption  The long option name.
452      * @param shortOption The short option name.
453      * @param description The description of the option.
454      * @param option      The option to register.
455      */
456
457     public void register( String longOption, char shortOption,
458                           String description, Option option ) {
459         generalModule.register( longOption, shortOption, description, option );
460     }
461
462     /**
463      * Register an option into the repository both as a short and long option.
464      * Initialize its description with the description passed.
465      *
466      * @param longOption  The long option name.
467      * @param shortOption The short option name.
468      * @param description The description of the option.
469      * @param option      The option to register.
470      * @param deprecated  A boolean indicating whether an option should
471      *                    be deprecated.
472      */
473
474     public void register( String longOption, char shortOption,
475                           String description, Option option,
476                           boolean deprecated ) {
477         generalModule.register( longOption, shortOption, description, option,
478                                 deprecated );
479     }
480
481     /**
482      * Register an option module based on its name.
483      *
484      * @param module The option module to register.
485      */
486
487     public void register( OptionModule module ) {
488         register( module.getName(), module );
489     }
490
491     /**
492      * Register an option module and associate it with the name passed.
493      *
494      * @param name   The name associated with the option module.
495      * @param module The option module to register.
496      */
497
498     public void register( String name, OptionModule module ) {
499         modules.put( name.toLowerCase(), module );
500     }
501
502     /**
503      * Process a string of values representing the invoked options. After
504      * all the options are processed, any leftover arguments are returned.
505      *
506      * @param args The arguments to process.
507      *
508      * @return The leftover arguments.
509      */
510
511     public String[] process( String args[] )
512     {
513         String []retval = new String[0];
514         try {
515             retval = processOptions( args );
516         }
517         catch ( OptionException e ) {
518             System.err.println( "Error: " + e.getMessage() );
519         }
520         /**
521         catch ( Exception e ) {
522             System.err.println( "Error: Unexpected Error in ritopt Processing." +
523                                 "Check syntax." );
524                                 }**/
525         return retval;
526     }
527
528     /**
529      * Retrieves an option module based on the name passed.
530      *
531      * @param name The name referring to the option module.
532      *
533      * @return The option module. Null is returned if the module does not
534      *         exist.
535      */
536
537     public OptionModule getModule( String name ) {
538         return (OptionModule)modules.get( name.toLowerCase() );
539     }
540
541     /**
542      * Returns a boolean indicating whether an option module exists.
543      *
544      * @param name The name referring to the option module.
545      *
546      * @return A boolean value indicating whether the module exists.
547      */
548
549     public boolean moduleExists( String name ) {
550         return getModule( name ) != null;
551     }
552
553     /**
554      * Receives NotifyOption events. If the event command equals "help"
555      * or "version", the appropriate display methods are invoked.
556      *
557      * @param event         The event object containing information about the
558      *                      invocation.
559      */
560
561     public void optionInvoked( OptionEvent event ) {
562         if ( event.getCommand().equals( "help" ) ) {
563             displayHelp();
564         }
565         else if ( event.getCommand().equals( "version" ) ) {
566             displayVersion();
567         }
568     }
569
570     /**
571      * Process a string representing the invoked options. This string
572      * gets split according to how they would be split when passed to
573      * a main method. The split array of options gets passed to a
574      * private method for processing. After all the options are processed,
575      * any leftover arguments are returned.
576      *
577      * @param str The arguments to process.
578      *
579      * @return The leftover arguments.
580      */
581
582     public String[] process( String str ) {
583         return process( split( str ) );
584     }
585
586     /**
587      * Splits a string representing command line arguments into several
588      * strings.
589      *
590      * @param split   The string to split.
591      *
592      * @return  The splitted string.
593      */
594
595     public String[] split( String str ) {
596         StringBuffer buf = new StringBuffer( str.length() );
597         java.util.List l = new java.util.ArrayList();
598         int scnt = Utility.count( str, '"' );
599         boolean q = false;
600         if ( ((double)scnt) / 2.0 != (double)(scnt / 2) ) {
601             throw new OptionProcessingException( "Expecting an end quote." );
602         }
603         for ( int n = 0; n < str.length(); n++ ) {
604             if ( str.charAt( n ) == '"' ) {
605                 q = !q;
606             }
607             else if ( str.charAt( n ) == ' ' && !q ) {
608                 l.add( buf.toString() );
609                 buf = new StringBuffer( str.length() );
610             }
611             else {
612                 buf.append( str.charAt( n ) );
613             }
614         }
615         if ( buf.length() != 0 ) {
616             l.add( buf.toString() );
617         }
618         Iterator it = l.iterator();
619         String retval[] = new String[ l.size() ];
620         int n = 0;
621         while ( it.hasNext() ) {
622             retval[ n++ ] = (String)it.next();
623         }
624         return retval;
625     }
626
627     /**
628      * Writes all options and their modules out to an options file.
629      *
630      * @param filename  The options filename to write.
631      */
632
633     public void writeOptionFile( String filename ) {
634         BufferedOutputStream writer = null;
635         String line = null;
636         Iterator it = null;
637         currentModule = generalModule;
638         try {
639             writer =
640                 new BufferedOutputStream( new FileOutputStream( filename ) );
641             PrintStream ps = new PrintStream( writer );
642             generalModule.writeFileToPrintStream( ps );
643             it = modules.values().iterator();
644             while ( it.hasNext() ) {
645                 OptionModule module = (OptionModule)it.next();
646                 module.writeFileToPrintStream( ps );
647             }
648         }
649         catch ( IOException e ) {
650             throw new OptionProcessingException( e.getMessage() );
651         }
652         finally {
653             try {
654                 if ( writer != null )
655                     writer.close();
656             }
657             catch( IOException e ) {
658                 throw new OptionProcessingException( e.getMessage() );
659             }
660         }
661     }
662
663     /**
664      * Loads all options and their modules from an options file.
665      *
666      * @param filename  The options filename to write.
667      */
668
669     public void loadOptionFile( String filename ) {
670         BufferedReader reader = null;
671         String line = null;
672         currentModule = generalModule;
673         try {
674             reader = new BufferedReader( new FileReader( filename ) );
675             while ( ( line = reader.readLine() ) != null ) {
676                 line = Utility.stripComments( line, '\"', ';' );
677                 process( line );
678             }
679         }
680         catch ( IOException e ) {
681             throw new OptionProcessingException( e.getMessage() );
682         }
683         finally {
684             try {
685                 if ( reader != null )
686                     reader.close();
687             }
688             catch( IOException e ) {
689                 throw new OptionProcessingException( e.getMessage() );
690             }
691         }
692     }
693
694     /**
695      * Processes an array of strings representing command line arguments.
696      *
697      * @param  The arguments to process.
698      *
699      * @return The leftover arguments.
700      */
701
702     private String[] processOptions( String args[] ) {
703         String retval[] = null;
704         String moduleName = "general";
705         String optionFile = "";
706         char shortOption = '\0';
707         String longOption = "";
708         for ( int n = 0; n < args.length && retval == null; n++ ) {
709             boolean moduleInvoked = false;
710             boolean shortOptionInvoked = false;
711             boolean longOptionInvoked = false;
712             boolean readOptionFileInvoked = false;
713             boolean writeOptionFileInvoked = false;
714             if ( args[ n ].length() >= 1 ) {
715                 char fc = args[ n ].charAt( 0 );
716                 moduleInvoked = fc == ':';
717                 readOptionFileInvoked = fc == '@';
718                 writeOptionFileInvoked = fc == '%';
719             }
720             if ( args[ n ].length() >= 2 ) {
721                 String s = args[ n ].substring( 0, 2 );
722                 shortOptionInvoked = ( !s.equals( "--" ) &&
723                            s.charAt( 0 ) == '-' );
724                 longOptionInvoked = ( s.equals( "--" ) );
725             }
726             if ( debugFlag ) {
727                 System.err.println( "Short Option: " + shortOptionInvoked );
728                 System.err.println( "Long Option: " + longOptionInvoked );
729                 System.err.println( "Module: " + moduleInvoked );
730                 System.err.println( "Load Option File: " +
731                                     readOptionFileInvoked );
732                 System.err.println( "Write Option File: "
733                                     + writeOptionFileInvoked );
734             }
735             if ( moduleInvoked ) {
736                 if (  args[ n ].charAt( args[ n ].length() - 1 ) != ':' ) {
737                     System.err.println( args[ n ] );
738                     throw new
739                         OptionProcessingException(
740                                                   "Module arguments must start"
741                                                   + " with : and end with :."
742                                                   );
743                 }
744                 else {
745                     moduleName = args[n].substring( 1,
746                                                     args[n].length() - 1
747                                                     ).toLowerCase();
748                     if ( moduleName.length() == 0
749                          || moduleName.equals( "general" ) ) {
750                         moduleName = "general";
751                         currentModule = generalModule;
752                     }
753                     else {
754                         currentModule = getModule( moduleName );
755                     }
756                     if ( currentModule == null )
757                         throw new OptionProcessingException( "Module '" +
758                                                              moduleName +
759                                                          "' does not exist." );
760                     if ( debugFlag ) {
761                         System.err.println( "Module: " + moduleName );
762                     }
763                 }
764                 moduleInvoked = false;
765             }
766             else if ( readOptionFileInvoked ) {
767                 optionFile = Utility.trim( args[ n ].substring( 1 ) );
768                 if ( optionFile.equals( "@" )
769                      || optionFile.length() == 0 )
770                     optionFile = defaultOptionFilename;
771                 if ( debugFlag ) {
772                     System.err.println( "Option file: '" + optionFile + "'." );
773                 }
774                 loadOptionFile( optionFile );
775             }
776             else if ( shortOptionInvoked ) {
777                 shortOption = args[ n ].charAt( 1 );
778                 if ( !Utility.isAlphaNumeric( shortOption ) ) {
779                     throw new OptionProcessingException(
780                       "A short option must be alphanumeric. -" + shortOption
781                       + " is not acceptable." );
782                 }
783                 if ( debugFlag ) {
784                     System.err.println( "Short option text: " + shortOption );
785                 }
786                 char delim = ( args[ n ].length() >= 3 ) ?
787                     args[ n ].charAt( 2 ) : '\0';
788                 if ( delim == '+' || delim == '-' ) {
789                     currentModule.action( shortOption, delim );
790                 }
791                 else if ( delim == '=' ) {
792                     currentModule.action( shortOption,
793                                           args[ n ].substring( 3 ) );
794                 }
795                 else if ( delim == '\0' ) {
796                     String dtext = "+";
797                     char dpeek = '\0';
798                     if ( n < args.length - 1 ) {
799                         dpeek = args[ n + 1 ].charAt( 0 );
800                         if ( !Utility.contains( args[ n + 1 ].charAt( 0 ),
801                                                 "-[@" ) ) {
802                             dtext = args[ n + 1 ];
803                             n++;
804                         }
805                     }
806                     currentModule.action( shortOption, dtext );
807                 }
808                 else if ( Utility.isAlphaNumeric( delim ) ) {
809                     for ( int j = 1; j < args[ n ].length(); j++ ) {
810                         if ( Utility.isAlphaNumeric( args[ n ].charAt( j ) ) ) {
811                             currentModule.action( shortOption, "+" );
812                         }
813                         else {
814                             throw new OptionProcessingException(
815                               "A short option must be alphanumeric. -"
816                               + shortOption + " is not acceptable." );
817                         }
818                     }
819                 }
820             }
821             else if ( longOptionInvoked ) {
822                 char lastchar = args[ n ].charAt( args[ n ].length() - 1 );
823                 int eqindex = args[ n ].indexOf( "=" );
824                 if ( eqindex != -1 ) {
825                     longOption = args[ n ].substring( 2, eqindex );
826                     String value = args[ n ].substring( eqindex + 1 );
827                     currentModule.action( longOption, value );
828                 }
829                 else if ( Utility.contains( lastchar, "+-" ) ) {
830                     longOption = args[ n ].substring( 2,
831                                                       args[ n ].length() - 1 );
832                     currentModule.action( longOption, lastchar );
833                 }
834                 else {
835                     longOption = args[ n ].substring( 2 );
836                     String dtext = "+";
837                     char dpeek = '\0';
838                     if ( n < args.length - 1 && args[ n + 1 ].length() > 0 ) {
839                         dpeek = args[ n + 1 ].charAt( 0 );
840                         if ( !Utility.contains( args[ n + 1 ].charAt( 0 ),
841                                                 "-[@" ) ) {
842                             dtext = args[ n + 1 ];
843                             n++;
844                         }
845                     }
846                     currentModule.action( longOption, dtext );
847                 }
848                 if ( debugFlag ) {
849                     System.err.println( "long option: " + longOption );
850                 }
851             }
852             else if ( writeOptionFileInvoked ) {
853                 optionFile = Utility.trim( args[ n ].substring( 1 ) );
854                 if ( optionFile.equals( "%" )
855                      || optionFile.length() == 0 )
856                     optionFile = defaultOptionFilename;
857                 if ( debugFlag ) {
858                     System.err.println( "Option file: '" + optionFile + "'." );
859                 }
860                 writeOptionFile( optionFile );
861             }
862             else {
863                 retval = new String[ args.length - n ];
864                 for ( int j = n; j < args.length; j++ ) {
865                     retval[ j - n ] = args[ j ];
866                 }
867             }
868         }
869         if ( retval == null ) retval = new String[ 0 ];
870         return retval;
871     }
872 }