1 /* Parser.java - parse command line options
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
39 package gnu.classpath.tools.getopt;
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;
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
58 /** The maximum right column position. */
59 public static final int MAX_LINE_LENGTH = 80;
61 private String programName;
63 private String headerText;
65 private String footerText;
67 private boolean longOnly;
69 private ArrayList options = new ArrayList();
71 private ArrayList optionGroups = new ArrayList();
73 private OptionGroup defaultGroup = new OptionGroup();
75 private OptionGroup finalGroup;
77 // These are used while parsing.
78 private int currentIndex;
80 private String[] args;
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".
86 * @param programName the name of the program
87 * @param versionString the program's version information
89 public Parser(String programName, String versionString)
91 this(programName, versionString, false);
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}.
100 * The text to print may contain <code>\n</code> characters. This method will
101 * force a line-break for each such character.
103 * @param out the {@link PrintStream} destination of the formatted text.
104 * @param text the text to print.
105 * @see Parser#MAX_LINE_LENGTH
107 protected static void formatText(PrintStream out, String text)
109 formatText(out, text, Locale.getDefault());
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.
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}.
121 * The text to print may contain <code>\n</code> characters. This method will
122 * force a line-break for each such character.
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
130 protected static void formatText(PrintStream out, String text, Locale aLocale)
132 BreakIterator bit = BreakIterator.getLineInstance(aLocale);
133 String[] lines = text.split("\n"); //$NON-NLS-1$
134 for (int i = 0; i < lines.length; i++)
140 int start = bit.first();
141 while ((finish = bit.next()) != BreakIterator.DONE)
143 String word = text.substring(start, finish);
144 length += word.length();
145 if (length >= MAX_LINE_LENGTH)
148 length = word.length();
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".
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
165 public Parser(String programName, final String versionString, boolean longOnly)
167 this.programName = programName;
168 this.longOnly = longOnly;
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$
174 public void parsed(String argument) throws OptionException
176 printHelp(System.out);
180 finalGroup.add(new Option("version", Messages.getString("Parser.PrintVersion")) //$NON-NLS-1$ //$NON-NLS-2$
182 public void parsed(String argument) throws OptionException
184 System.out.println(versionString);
194 * Set the header text that is printed by --help.
196 * @param headerText the header text
198 public void setHeader(String headerText)
200 this.headerText = headerText;
204 * Set the footer text that is printed by --help.
206 * @param footerText the footer text
208 public void setFooter(String footerText)
210 this.footerText = footerText;
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.
217 * @param opt the option
219 public synchronized void add(Option opt)
222 defaultGroup.add(opt);
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
231 protected synchronized void addFinal(Option opt)
238 * Add an option group to this parser. All the options in this group will be
239 * recognized by the parser.
241 * @param group the option group
243 public synchronized void add(OptionGroup group)
245 options.addAll(group.options);
246 // This ensures that the final group always appears at the end
248 if (optionGroups.isEmpty())
249 optionGroups.add(group);
251 optionGroups.add(optionGroups.size() - 1, group);
254 public void printHelp()
256 this.printHelp(System.out);
259 void printHelp(PrintStream out)
261 if (headerText != null)
263 formatText(out, headerText);
267 Iterator it = optionGroups.iterator();
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())
275 group.printHelp(out, longOnly);
280 if (footerText != null)
281 formatText(out, footerText);
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
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.
295 * The base implementation does nothing.
297 * @throws OptionException the error encountered
299 protected void validate() throws OptionException
301 // Base implementation does nothing.
304 private String getArgument(String request) throws OptionException
307 if (currentIndex >= args.length)
310 = MessageFormat.format(Messages.getString("Parser.ArgReqd"), //$NON-NLS-1$
311 new Object[] { request });
312 throw new OptionException(request);
314 return args[currentIndex];
317 private void handleLongOption(String real, int index) throws OptionException
319 String option = real.substring(index);
320 String justName = option;
321 int eq = option.indexOf('=');
323 justName = option.substring(0, eq);
324 boolean isPlainShort = justName.length() == 1;
325 char shortName = justName.charAt(0);
327 for (int i = options.size() - 1; i >= 0; --i)
329 Option opt = (Option) options.get(i);
330 if (justName.equals(opt.getLongName()))
335 if ((isPlainShort || opt.isJoined())
336 && opt.getShortName() == shortName)
340 // The rest of the option string is the argument.
349 String msg = MessageFormat.format(Messages.getString("Parser.Unrecognized"), //$NON-NLS-1$
350 new Object[] { real });
351 throw new OptionException(msg);
353 String argument = null;
354 if (found.getTakesArgument())
357 argument = getArgument(real);
359 argument = option.substring(eq + 1);
364 = MessageFormat.format(Messages.getString("Parser.NoArg"), //$NON-NLS-1$
365 new Object[] { real.substring(0, eq + index) });
366 throw new OptionException(msg);
368 found.parsed(argument);
371 private void handleShortOptions(String option) throws OptionException
373 for (int charIndex = 1; charIndex < option.length(); ++charIndex)
375 char optChar = option.charAt(charIndex);
377 for (int i = options.size() - 1; i >= 0; --i)
379 Option opt = (Option) options.get(i);
380 if (optChar == opt.getShortName())
388 String msg = MessageFormat.format(Messages.getString("Parser.UnrecDash"), //$NON-NLS-1$
389 new Object[] { "" + optChar }); //$NON-NLS-1$
390 throw new OptionException(msg);
392 String argument = null;
393 if (found.getTakesArgument())
395 // If this is a joined short option, and there are more
396 // characters left in this argument, use those as the
398 if (found.isJoined() && charIndex + 1 < option.length())
400 argument = option.substring(charIndex + 1);
401 charIndex = option.length();
404 argument = getArgument("-" + optChar); //$NON-NLS-1$
406 found.parsed(argument);
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.
415 * @param inArgs the command-line arguments
416 * @param files the file argument callback
418 public synchronized void parse(String[] inArgs, FileArgumentCallback files)
423 for (currentIndex = 0; currentIndex < args.length; ++currentIndex)
425 if (args[currentIndex].length() == 0
426 || args[currentIndex].charAt(0) != '-'
427 || "-".equals(args[currentIndex])) //$NON-NLS-1$
429 files.notifyFile(args[currentIndex]);
432 if ("--".equals(args[currentIndex])) //$NON-NLS-1$
434 if (args[currentIndex].charAt(1) == '-')
435 handleLongOption(args[currentIndex], 2);
437 handleLongOption(args[currentIndex], 1);
439 handleShortOptions(args[currentIndex]);
441 // Add remaining arguments to leftovers.
442 for (++currentIndex; currentIndex < args.length; ++currentIndex)
443 files.notifyFile(args[currentIndex]);
444 // See if something went wrong.
447 catch (OptionException err)
449 System.err.println(programName + ": " + err.getMessage()); //$NON-NLS-1$
452 fmt = Messages.getString("Parser.TryHelpShort"); //$NON-NLS-1$
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$
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.
465 * @param inArgs the command-line arguments
467 public String[] parse(String[] inArgs)
469 final ArrayList fileResult = new ArrayList();
470 parse(inArgs, new FileArgumentCallback()
472 public void notifyFile(String fileArgument)
474 fileResult.add(fileArgument);
477 return (String[]) fileResult.toArray(new String[0]);