001    package javafe;
002    
003    import java.io.*;
004    import java.util.ArrayList;
005    import java.util.StringTokenizer;
006    
007    import javafe.util.ErrorSet;
008    import javafe.util.UsageError;
009    import junitutils.Utils;
010    
011    /**
012     * This is the super-class of classes that hold the values of
013     * command-line options.  The options are separated from the Main
014     * class to improve modularity and to allow the options to be reset
015     * during a single execution of the main program.  Consequently, none
016     * of the options fields should be static.  The program may hold a
017     * static instance of an Options object if it wishes.
018     *
019     * <p> Derived classes should define overriding methods for
020     * <ul>
021     * <li> {@link #processOption(String, String[], int)},
022     * <li> {@link #showNonOptions()},
023     * <li> {@link #showOptions(boolean)}
024     * </ul>
025     */
026    
027    public class Options
028    {
029      /**
030       * Holds all the non-option arguments.
031       */
032      public ArrayList inputEntries; // elements are InputEntry
033    
034      /**
035       * Option to restrict output to error/caution/warning messages
036       * only - no progress or informational output.
037       */
038      public boolean quiet = false;
039        
040      /** 
041       * Option to generate lots of output indicating what is happening
042       * during execution.
043       */
044      public boolean v = false;
045    
046      /**
047       * When true, no variable output (e.g., execution time) is
048       * printed, so that output can be compared to an oracle output
049       * file.  Also, emit all paths for warnings, errors, etc. in
050       * canonical, machine-independent form.  Such output is strictly
051       * used for unit testing.  The canonical form of a path replaces
052       * all use of the slash ('/') and wack ('\') characters with bar
053       * ('|').
054       */
055      public boolean testMode = false;
056    
057      /**
058       * Option to turn off caution warnings.  This is used for Houdini
059       * where it is a pain to weed out the cautions from code where we
060       * are looking only at warnings.
061       */
062      public boolean noCautions = false;
063    
064      /** Option holding the current working directory. */
065      public String currentdir = System.getProperty("user.dir");
066    
067      /**
068       * Option holding the user-specified classpath.
069       */
070      public String userPath = null;
071    
072      /**
073       * Option holding the user-specified sourcepath.
074       */
075      public String userSourcePath = null;
076    
077      /**
078       * Option holding the user-specified boot classpath.
079       */
080      public String sysPath = null;
081    
082      /** True if we should simply issue a usage message and abort. */
083      public boolean issueUsage = false;
084    
085      // Note - the "-v" option is directly set in javafe.util.Info.on
086    
087      /**
088       * Are we parsing Java 1.4 source code (i.e., we must parse the
089       * new "assert" Java keyword).
090       *
091       * @design As Java evolves we'll likely have to change this to an
092       * enumeration type.
093       */
094      public boolean assertIsKeyword = false;
095    
096      /** 
097       * Java allows assertions to be enabled and disabled.  Replicate
098       * those options as well.
099       */
100      public boolean assertionsEnabled = false;
101    
102      /**
103       * Debugging flag used to turn on stack trace dumps when error
104       * messages are issued. (cf. javafe.util.ErrorSet)
105       */
106      public boolean showErrorLocation = false;
107    
108      /**
109       *    Flags to use or not use source or binary files.
110       */
111      static public final int PREFER_BINARY = 0;
112      static public final int PREFER_SOURCE = 1;
113      static public final int PREFER_RECENT = 2;
114      static public final int NEVER_BINARY = 3;
115      static public final int NEVER_SOURCE = 4;
116      public int fileOrigin = PREFER_RECENT;
117    
118      public Options() {
119        // Everything should be initialized to default values.
120        javafe.util.Info.on = false;
121      }
122    
123      /**
124       * Process tool options contained in <code>args</code>.
125       *
126       * @param args the command-line arguments that are being processed.
127       * @exception UsageError If the option is erroneous, throw an
128       * {@link UsageError} exception with a string describing the
129       * problem.
130       */
131      //@ requires \nonnullelements(args);
132      //@ ensures inputEntries != null;
133      public final void processOptions(String[] args) throws UsageError {
134        inputEntries = new ArrayList(args.length);
135        processOptionsLoop(args);
136      }
137    
138      //@ requires \nonnullelements(args);
139      //@ requires inputEntries != null;
140      protected final void processOptionsLoop(String[] args) throws UsageError {
141        int offset = 0;
142    
143        while (offset < args.length) {
144          String s = args[offset++];
145          if (s.length() == 0) {
146            // skip
147          } else if (s.charAt(0) == '-') {
148            offset = processOption(s, args, offset);
149          } else {
150            inputEntries.add(new UnknownInputEntry(s));
151          }
152        }
153      }
154    
155      /**
156       * Process next tool option.
157       *
158       * <p> This routine handles the standard front-end options, storing the
159       * resulting information in the preceding instance variables and
160       * <code>Info.on</code>.
161       *
162       * @design When this routine is overridden, the new method body should
163       * always end with <code>return super.processOption(option, args,
164       * offset)</code>.
165       *
166       * @param option the option currently being handled.  An option
167       * always starts with a '-' character, and the remaining
168       * command-line arguments (not counting <code>option</code>)
169       * (<code>args[offset]</code>,...,<code>args[args.length-1]</code>).
170       * @param args the command-line arguments that are being processed.
171       * @param offset the offset into the <code>args</args> array that
172       * indicates which option is currently being dealt with.
173       * @return The offset to any remaining command-line arguments
174       * should be returned.  (This allows the option to consume some or
175       * all of the following arguments.)
176       * @exception UsageError If the option is erroneous, throw an
177       * {@link UsageError} exception with a string describing the
178       * problem.
179       */
180      //@ requires option != null;
181      //@ requires \nonnullelements(args);
182      //@ requires 0 <= offset && offset <= args.length;
183      //@ ensures 0 <= \result && \result <= args.length;
184      public int processOption(String option, String[] args, int offset)
185        throws UsageError {
186        option = option.toLowerCase();
187        if (option.equals("-v") || 
188            option.equals("-verbose")) {
189          javafe.util.Info.on = true;
190          return offset;
191        } else if (option.equals("-q") || 
192                   option.equals("-quiet")) {
193          quiet = true;
194          return offset;
195        } else if (option.equals("-nocautions")) {
196          noCautions = true;
197          return offset;
198        } else if (option.equals("-sourcepath")) {
199          checkMoreArguments(option, args, offset);
200          userSourcePath = args[offset];
201          return offset + 1;
202        } else if (option.equals("-classpath") ||
203                   option.equals("-cp")) {
204          checkMoreArguments(option, args, offset);
205          userPath = args[offset];
206          return offset + 1;
207        } else if (option.equals("-bootclasspath")) {
208          checkMoreArguments(option, args, offset);
209          sysPath = args[offset];
210          return offset + 1;
211        } else if (option.equals("-currentdir")) {
212          checkMoreArguments(option, args, offset);
213          currentdir = args[offset];
214          return offset + 1;
215        } else if (option.equals("-package")) {
216          checkMoreArguments(option, args, offset);
217          inputEntries.add(new PackageInputEntry(args[offset]));
218          return offset + 1;
219        } else if (option.equals("-class")) {
220          checkMoreArguments(option, args, offset);
221          inputEntries.add(new ClassInputEntry(args[offset]));
222          return offset + 1;
223        } else if (option.equals("-dir")) {
224          checkMoreArguments(option, args, offset);
225          inputEntries.add(new DirInputEntry(args[offset]));
226          return offset + 1;
227        } else if (option.equals("-file")) {
228          checkMoreArguments(option, args, offset);
229          inputEntries.add(new FileInputEntry(args[offset]));
230          return offset + 1;
231        } else if (option.equals("-list")) {
232          checkMoreArguments(option, args, offset);
233          inputEntries.add(new ListInputEntry(args[offset]));
234          return offset + 1;
235        } else if (option.equals("-f")) {
236          checkMoreArguments(option, args, offset);
237          processFileOfArgs(args[offset]);
238          return offset + 1;
239        } else if (option.equals("-source")) {
240          if ((offset >= args.length) || (args[offset].charAt(0) == '-')) {
241            throw new UsageError(
242                                 "Option "
243                                 + option
244                                 + " requires one argument indicating Java source version\n"
245                                 + "(e.g., \"-source 1.4\")");
246          }
247          if (args[offset].equals("1.4"))
248            assertIsKeyword = true;
249          return offset + 1;
250        } else if (option.equals("-ea") || 
251                   option.equals("-enableassertions")) {
252          assertionsEnabled = true;
253          return offset;
254        } else if (option.equals("-da") || 
255                   option.equals("-disableassertions")) {
256          assertionsEnabled = false;
257          return offset;
258        } else if (option.equals("-h") ||
259                   option.equals("-help")) {
260          issueUsage = true;
261          return offset;
262        } else if (option.equals("-testmode")) {
263          testMode = true;
264          return offset;
265        } else if (option.equals("-showerrorlocation")) {
266          showErrorLocation = true;
267          return offset;
268        } else if (option.equals("-prefersource")) {
269          fileOrigin = PREFER_SOURCE;
270          return offset;
271        } else if (option.equals("-preferbinary")) {
272          fileOrigin = PREFER_BINARY;
273          return offset;
274        } else if (option.equals("-preferrecent")) {
275          fileOrigin = PREFER_RECENT;
276          return offset;
277        } else if (option.equals("-neverbinary")) {
278          fileOrigin = NEVER_BINARY;
279          return offset;
280        } else if (option.equals("-neversource")) {
281          fileOrigin = NEVER_SOURCE;
282          return offset;
283        } else if (option.equals("--")) {
284          while (offset < args.length) {
285            inputEntries.add(new UnknownInputEntry(args[offset++]));
286          }
287          return offset;
288        }
289    
290        // Pass on unrecognized options:
291    
292        // Derived classes will call:
293        // return super.processOption(option, args, offset);
294    
295        // Here we inline the error processing      
296        throw new UsageError("Unknown option: " + option);
297      }
298    
299      //@   protected normal_behavior
300      //@   requires offset < args.length;
301      //@ also protected exceptional_behavior
302      //@   requires offset >= args.length;
303      //@   signals (Exception e) e instanceof UsageError;
304      //@ pure
305      protected void checkMoreArguments(String option, String[] args, int offset)
306        throws UsageError {
307        if (offset >= args.length) {
308          throw new UsageError("Option " + option + " requires one argument");
309        }
310      }
311    
312      public void processFileOfArgs(String filename) throws UsageError {
313        try {
314          String[] sa = new String[20];
315          // more than most lines in the file will be, just for efficiency
316          BufferedReader r = null;
317          try { 
318            r = new BufferedReader(new FileReader(filename));
319            String s;
320            while ((s = r.readLine()) != null) {
321              sa = Utils.parseLine(s);
322              processOptionsLoop(sa);
323            }
324          } finally {
325            if (r != null) r.close();
326          }
327        } catch (IOException e) {
328          ErrorSet.error(
329                         "Failure while reading input arguments from file "
330                         + filename
331                         + ": "
332                         + e);
333        }
334      }
335    
336      /**
337       * Print our usage message to <code>System.err</code>.
338       *
339       * @param name the name of the tool whose options we are printing.
340       */
341      public void usage(String name) {
342        System.err.print(name + ": usage: " + name + " options* ");
343        System.err.print(showNonOptions());
344        System.err.println(" where options include:");
345        System.err.print(showOptions(javafe.util.Info.on));
346      }
347    
348      /**
349       * @return non-option usage information in a string.
350       */
351      public String showNonOptions() {
352        return "All switches are case-insensitive.";
353      }
354    
355      /**
356       * Return option information in a string where each line is
357       * preceeded by two blank spaces and followed by a line separator.
358       *
359       * @param all if true, then all options are printed, including
360       * experimental options; otherwise, just the options expected to
361       * be used by standard users are printed.
362       * @return a String containing all option information ready for
363       * output.
364       *
365       * @usage Each overriding method should first call
366       * <code>super.showOptions()</code>.
367       */
368      public String showOptions(boolean all) {
369        String result = showOptionArray(publicOptionData);
370        if (all) result += showOptionArray(privateOptionData);
371        return result;
372      }
373    
374      public String showOptionArray(String[][] data) {
375        StringBuffer sb = new StringBuffer();
376        for (int i = 0; i < data.length; ++i) {
377          sb.append(format(data[i]));
378        }
379        return sb.toString();
380      }
381    
382      public String format(String[] sa) {
383        int columns = Integer.getInteger("COLUMNS", 80).intValue();
384        StringBuffer sb = new StringBuffer("  " + sa[0] + " : ");
385        int current_column = 2 + sa[0].length() + 3;
386        // find the most words that fit into columns-(2 + sa[0].length + 3)
387        // for first line.  thereafter, find the most words that fit into
388        // (columns-8) lines until there is no remaining words.
389        StringTokenizer st = new StringTokenizer(sa[1]);
390        while (st.hasMoreTokens()) {
391          String word = st.nextToken();
392          if (current_column + word.length() < columns) {
393            sb.append(word + " ");
394            current_column += word.length() + 1;
395          }
396          else {
397            sb.append(eol + "\t" + word + " ");
398            current_column = 9 + word.length();
399          }
400        }
401        sb.append(eol);
402        return sb.toString();
403      }
404    
405      final String[][] publicOptionData = {
406        { "-Help, -h", 
407          "Prints this usage message and terminates (combine with -v to see private\n" + 
408          "\tswitches)" }, 
409        { "-Verbose, -v", 
410          "verbose mode" }, 
411        { "-Quiet, -q", 
412          "quiet mode (no informational messages)" }, 
413        { "-BootClassPath <classpath>",
414          "Directory path for specification and class files for the current JDK (default is the built-in classpath of your JDK); prepended to the current classpath.  Multiple uses of -BootClassPath are ignored; only final use of -BootClassPath is recognized, as in javac." },
415        { "-Class <fully.specified.classname>",
416          "Check the specified class; this option can be specified multiple times" },
417        { "-ClassPath <classpath>, -cp <classpath>",
418          "Directory path for class files (default is value of CLASSPATH).  Multiple uses of -ClassPath are ignored; only final use of -ClassPath is recognized, as in javac." },
419        { "-DisableAssertions, -da",
420          "Ignores all Java assert statements" },
421        { "-dir <directory>",
422          "Check all Java files in the specified directory" },
423        { "-EnableAssertions, -ea",
424          "Processes all Java assert statements" },
425        { "-f <file containing command-line arguments>",
426          "Path to a file containing command-line arguments that are inserted at this point in the command-line"},
427        { "-File <filename>",
428          "Check all classes in the specified file <filename>" },
429        { "-List <filename>",
430          "Check all classes listed in the text file <filename>; each classname in the file should be fully specified and end-of-line terminated" },
431        { "-NoCautions", 
432          "Does not print messages that are simply cautions" }, 
433        { "-Package <packagename>",
434          "Loads all the files in the named package" },
435        { "-Source <release>",
436          "Provide source compatibility with specified release" },
437        { "-SourcePath <classpath>",
438          "Directory path for source files (default is classpath).  Multiple uses of -SourcePath are ignored; only final use of -SourcePath is recognized, as in javac." },
439      };
440    
441      final String[][] privateOptionData = {
442        { "-CurrentDir <directory>",
443          "Specify the current working directory (REMOVE THIS OPTION?)" },
444        { "-TestMode",
445          "Replaces execution time by a constant string and path separators by `|'\n" + 
446          "so oracle files can be used in automated testing" },
447        { "-NeverBinary",
448          "Never read type information from a class files" },
449        { "-NeverSource",
450          "Never read type information from source files" },
451        { "-PreferBinary",
452          "Read type information from class files even if they are out-of-date" },
453        { "-PreferRecent",
454          "Read type information from either class or source files, whichever is\n" + 
455          "most recent" },
456        { "-PreferSource",
457          "Read type information from source if it is available, otherwise use\n" + 
458          "class files" },
459        { "-ShowErrorLocation",
460          "Dump a stacktrace to standard error when reporting a warning or error" },
461      };
462    
463      final static public String eol = System.getProperty("line.separator");
464    }