OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / tools / gnu / classpath / tools / getopt / Parser.java
1 /* Parser.java - parse command line options
2  Copyright (C) 2006 Free Software Foundation, Inc.
3
4  This file is part of GNU Classpath.
5
6  GNU Classpath is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2, or (at your option)
9  any later version.
10
11  GNU Classpath is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  General Public License for more details.
15
16  You should have received a copy of the GNU General Public License
17  along with GNU Classpath; see the file COPYING.  If not, write to the
18  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  02110-1301 USA.
20
21  Linking this library statically or dynamically with other modules is
22  making a combined work based on this library.  Thus, the terms and
23  conditions of the GNU General Public License cover the whole
24  combination.
25
26  As a special exception, the copyright holders of this library give you
27  permission to link this library with independent modules to produce an
28  executable, regardless of the license terms of these independent
29  modules, and to copy and distribute the resulting executable under
30  terms of your choice, provided that you also meet, for each linked
31  independent module, the terms and conditions of the license of that
32  module.  An independent module is a module which is not derived from
33  or based on this library.  If you modify this library, you may extend
34  this exception to your version of the library, but you are not
35  obligated to do so.  If you do not wish to do so, delete this
36  exception statement from your version. */
37
38
39 package gnu.classpath.tools.getopt;
40
41 import java.io.PrintStream;
42 import java.text.BreakIterator;
43 import java.text.MessageFormat;
44 import java.util.ArrayList;
45 import java.util.Iterator;
46 import java.util.Locale;
47
48 /**
49  * An instance of this class is used to parse command-line options. It does "GNU
50  * style" argument recognition and also automatically handles "--help" and
51  * "--version" processing. It can also be put in "long option only" mode. In
52  * this mode long options are recognized with a single dash (as well as a double
53  * dash) and strings of options like "-abc" are never parsed as a collection of
54  * short options.
55  */
56 public class Parser
57 {
58   /** The maximum right column position. */
59   public static final int MAX_LINE_LENGTH = 80;
60
61   private String programName;
62
63   private String headerText;
64
65   private String footerText;
66
67   private boolean longOnly;
68
69   private ArrayList options = new ArrayList();
70
71   private ArrayList optionGroups = new ArrayList();
72
73   private OptionGroup defaultGroup = new OptionGroup();
74
75   private OptionGroup finalGroup;
76
77   // These are used while parsing.
78   private int currentIndex;
79
80   private String[] args;
81
82   /**
83    * Create a new parser. The program name is used when printing error messages.
84    * The version string is printed verbatim in response to "--version".
85    * 
86    * @param programName the name of the program
87    * @param versionString the program's version information
88    */
89   public Parser(String programName, String versionString)
90   {
91     this(programName, versionString, false);
92   }
93
94   /**
95    * Print a designated text to a {@link PrintStream}, eventually wrapping the
96    * lines of text so as to ensure that the width of each line does not overflow
97    * {@link #MAX_LINE_LENGTH} columns. The line-wrapping is done with a
98    * {@link BreakIterator} using the default {@link Locale}.
99    * <p>
100    * The text to print may contain <code>\n</code> characters. This method will
101    * force a line-break for each such character.
102    * 
103    * @param out the {@link PrintStream} destination of the formatted text.
104    * @param text the text to print.
105    * @see Parser#MAX_LINE_LENGTH
106    */
107   protected static void formatText(PrintStream out, String text)
108   {
109     formatText(out, text, Locale.getDefault());
110   }
111
112   /**
113    * Similar to the method with the same name and two arguments, except that the
114    * caller MUST specify a non-null {@link Locale} instance.
115    * <p>
116    * Print a designated text to a {@link PrintStream}, eventually wrapping the
117    * lines of text so as to ensure that the width of each line does not overflow
118    * {@link #MAX_LINE_LENGTH} columns. The line-wrapping is done with a
119    * {@link BreakIterator} using the designated {@link Locale}.
120    * <p>
121    * The text to print may contain <code>\n</code> characters. This method will
122    * force a line-break for each such character.
123    * 
124    * @param out the {@link PrintStream} destination of the formatted text.
125    * @param text the text to print.
126    * @param aLocale the {@link Locale} instance to use when constructing the
127    *          {@link BreakIterator}.
128    * @see Parser#MAX_LINE_LENGTH
129    */
130   protected static void formatText(PrintStream out, String text, Locale aLocale)
131   {
132     BreakIterator bit = BreakIterator.getLineInstance(aLocale);
133     String[] lines = text.split("\n"); //$NON-NLS-1$
134     for (int i = 0; i < lines.length; i++)
135       {
136         text = lines[i];
137         bit.setText(text);
138         int length = 0;
139         int finish;
140         int start = bit.first();
141         while ((finish = bit.next()) != BreakIterator.DONE)
142           {
143             String word = text.substring(start, finish);
144             length += word.length();
145             if (length >= MAX_LINE_LENGTH)
146               {
147                 out.println();
148                 length = word.length();
149               }
150             out.print(word);
151             start = finish;
152           }
153         out.println();
154       }
155   }
156
157   /**
158    * Create a new parser. The program name is used when printing error messages.
159    * The version string is printed verbatim in response to "--version".
160    * 
161    * @param programName the name of the program
162    * @param versionString the program's version information
163    * @param longOnly true if the parser should work in long-option-only mode
164    */
165   public Parser(String programName, final String versionString, boolean longOnly)
166   {
167     this.programName = programName;
168     this.longOnly = longOnly;
169
170     // Put standard options in their own section near the end.
171     finalGroup = new OptionGroup(Messages.getString("Parser.StdOptions")); //$NON-NLS-1$
172     finalGroup.add(new Option("help", Messages.getString("Parser.PrintHelp")) //$NON-NLS-1$ //$NON-NLS-2$
173     {
174       public void parsed(String argument) throws OptionException
175       {
176         printHelp(System.out);
177         System.exit(0);
178       }
179     });
180     finalGroup.add(new Option("version", Messages.getString("Parser.PrintVersion")) //$NON-NLS-1$ //$NON-NLS-2$
181     {
182       public void parsed(String argument) throws OptionException
183       {
184         System.out.println(versionString);
185         System.exit(0);
186       }
187     });
188     add(finalGroup);
189
190     add(defaultGroup);
191   }
192
193   /**
194    * Set the header text that is printed by --help.
195    * 
196    * @param headerText the header text
197    */
198   public void setHeader(String headerText)
199   {
200     this.headerText = headerText;
201   }
202
203   /**
204    * Set the footer text that is printed by --help.
205    * 
206    * @param footerText the footer text
207    */
208   public void setFooter(String footerText)
209   {
210     this.footerText = footerText;
211   }
212
213   /**
214    * Add an option to this parser. The option is added to the default option
215    * group; this affects where it is placed in the help output.
216    * 
217    * @param opt the option
218    */
219   public synchronized void add(Option opt)
220   {
221     options.add(opt);
222     defaultGroup.add(opt);
223   }
224
225   /**
226    * This is like {@link #add(Option)}, but adds the option to the "final"
227    * group.  This should be used sparingly, if at all; it is intended for
228    * other very generic options like --help or --version.
229    * @param opt the option to add
230    */
231   protected synchronized void addFinal(Option opt)
232   {
233     options.add(opt);
234     finalGroup.add(opt);
235   }
236
237   /**
238    * Add an option group to this parser. All the options in this group will be
239    * recognized by the parser.
240    * 
241    * @param group the option group
242    */
243   public synchronized void add(OptionGroup group)
244   {
245     options.addAll(group.options);
246     // This ensures that the final group always appears at the end
247     // of the options.
248     if (optionGroups.isEmpty())
249       optionGroups.add(group);
250     else
251       optionGroups.add(optionGroups.size() - 1, group);
252   }
253
254   public void printHelp()
255   {
256     this.printHelp(System.out);
257   }
258
259   void printHelp(PrintStream out)
260   {
261     if (headerText != null)
262       {
263         formatText(out, headerText);
264         out.println();
265       }
266
267     Iterator it = optionGroups.iterator();
268     while (it.hasNext())
269       {
270         OptionGroup group = (OptionGroup) it.next();
271         // An option group might be empty, in which case we don't
272         // want to print it..
273         if (! group.options.isEmpty())
274           {
275             group.printHelp(out, longOnly);
276             out.println();
277           }
278       }
279
280     if (footerText != null)
281       formatText(out, footerText);
282   }
283
284   /**
285    * This method can be overridden by subclassses to provide some option
286    * validation.  It is called by the parser after all options have been
287    * parsed.  If an option validation problem is encountered, this should
288    * throw an {@link OptionException} whose message should be shown to
289    * the user.
290    * <p>
291    * It is better to do validation here than after {@link #parse(String[])}
292    * returns, because the parser will print a message referring the
293    * user to the <code>--help</code> option.
294    * <p>
295    * The base implementation does nothing.
296    * 
297    * @throws OptionException the error encountered
298    */
299   protected void validate() throws OptionException
300   {
301     // Base implementation does nothing.
302   }
303
304   private String getArgument(String request) throws OptionException
305   {
306     ++currentIndex;
307     if (currentIndex >= args.length)
308       {
309         String message
310           = MessageFormat.format(Messages.getString("Parser.ArgReqd"), //$NON-NLS-1$
311                                  new Object[] { request });
312         throw new OptionException(request);
313       }
314     return args[currentIndex];
315   }
316
317   private void handleLongOption(String real, int index) throws OptionException
318   {
319     String option = real.substring(index);
320     String justName = option;
321     int eq = option.indexOf('=');
322     if (eq != -1)
323       justName = option.substring(0, eq);
324     boolean isPlainShort = justName.length() == 1;
325     char shortName = justName.charAt(0);
326     Option found = null;
327     for (int i = options.size() - 1; i >= 0; --i)
328       {
329         Option opt = (Option) options.get(i);
330         if (justName.equals(opt.getLongName()))
331           {
332             found = opt;
333             break;
334           }
335         if ((isPlainShort || opt.isJoined())
336             && opt.getShortName() == shortName)
337           {
338             if (! isPlainShort)
339               {
340                 // The rest of the option string is the argument.
341                 eq = 0;
342               }
343             found = opt;
344             break;
345           }
346       }
347     if (found == null)
348       {
349         String msg = MessageFormat.format(Messages.getString("Parser.Unrecognized"), //$NON-NLS-1$
350                                           new Object[] { real });
351         throw new OptionException(msg);
352       }
353     String argument = null;
354     if (found.getTakesArgument())
355       {
356         if (eq == -1)
357           argument = getArgument(real);
358         else
359           argument = option.substring(eq + 1);
360       }
361     else if (eq != - 1)
362       {
363         String msg
364           = MessageFormat.format(Messages.getString("Parser.NoArg"), //$NON-NLS-1$
365                                  new Object[] { real.substring(0, eq + index) });
366         throw new OptionException(msg);
367       }
368     found.parsed(argument);
369   }
370
371   private void handleShortOptions(String option) throws OptionException
372   {
373     for (int charIndex = 1; charIndex < option.length(); ++charIndex)
374       {
375         char optChar = option.charAt(charIndex);
376         Option found = null;
377         for (int i = options.size() - 1; i >= 0; --i)
378           {
379             Option opt = (Option) options.get(i);
380             if (optChar == opt.getShortName())
381               {
382                 found = opt;
383                 break;
384               }
385           }
386         if (found == null)
387           {
388             String msg = MessageFormat.format(Messages.getString("Parser.UnrecDash"), //$NON-NLS-1$
389                                               new Object[] { "" + optChar }); //$NON-NLS-1$
390             throw new OptionException(msg);
391           }
392         String argument = null;
393         if (found.getTakesArgument())
394           {
395             // If this is a joined short option, and there are more
396             // characters left in this argument, use those as the
397             // argument.
398             if (found.isJoined() && charIndex + 1 < option.length())
399               {
400                 argument = option.substring(charIndex + 1);
401                 charIndex = option.length();
402               }
403             else
404               argument = getArgument("-" + optChar); //$NON-NLS-1$
405           }
406         found.parsed(argument);
407       }
408   }
409
410   /**
411    * Parse a command line. Any files which are found will be passed to the file
412    * argument callback. This method will exit on error or when --help or
413    * --version is specified.
414    * 
415    * @param inArgs the command-line arguments
416    * @param files the file argument callback
417    */
418   public synchronized void parse(String[] inArgs, FileArgumentCallback files)
419   {
420     try
421       {
422         args = inArgs;
423         for (currentIndex = 0; currentIndex < args.length; ++currentIndex)
424           {
425             if (args[currentIndex].length() == 0
426                 || args[currentIndex].charAt(0) != '-'
427                 || "-".equals(args[currentIndex])) //$NON-NLS-1$
428               {
429                 files.notifyFile(args[currentIndex]);
430                 continue;
431               }
432             if ("--".equals(args[currentIndex])) //$NON-NLS-1$
433               break;
434             if (args[currentIndex].charAt(1) == '-')
435               handleLongOption(args[currentIndex], 2);
436             else if (longOnly)
437               handleLongOption(args[currentIndex], 1);
438             else
439               handleShortOptions(args[currentIndex]);
440           }
441         // Add remaining arguments to leftovers.
442         for (++currentIndex; currentIndex < args.length; ++currentIndex)
443           files.notifyFile(args[currentIndex]);
444         // See if something went wrong.
445         validate();
446       }
447     catch (OptionException err)
448       {
449         System.err.println(programName + ": " + err.getMessage()); //$NON-NLS-1$
450         String fmt;
451         if (longOnly)
452           fmt = Messages.getString("Parser.TryHelpShort"); //$NON-NLS-1$
453         else
454           fmt = Messages.getString("Parser.TryHelpLong"); //$NON-NLS-1$
455         String msg = MessageFormat.format(fmt, new Object[] { programName });
456         System.err.println(programName + ": " + msg); //$NON-NLS-1$
457         System.exit(1);
458       }
459   }
460
461   /**
462    * Parse a command line. Any files which are found will be returned. This
463    * method will exit on error or when --help or --version is specified.
464    * 
465    * @param inArgs the command-line arguments
466    */
467   public String[] parse(String[] inArgs)
468   {
469     final ArrayList fileResult = new ArrayList();
470     parse(inArgs, new FileArgumentCallback()
471     {
472       public void notifyFile(String fileArgument)
473       {
474         fileResult.add(fileArgument);
475       }
476     });
477     return (String[]) fileResult.toArray(new String[0]);
478   }
479 }