1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 *
26 * ident	"%Z%%M%	%I%	%E% SMI"
27 */
28
29/* Copyright (c) 1988 AT&T */
30/* All Rights Reserved */
31
32import java.io.StringWriter;
33import java.io.PrintWriter;
34
35/**
36 * A Java port of Solaris {@code lib/libc/port/gen/getopt.c}, which is a
37 * port of System V UNIX getopt.  See <b>getopt(3C)</b> and SUS/XPG
38 * getopt() for function definition and requirements. Unlike that
39 * definition, this implementation moves non-options to the end of the
40 * argv array rather than quitting at the first non-option.
41 */
42public class Getopt {
43    static final int EOF = -1;
44
45    private String progname;
46    private String[] args;
47    private int argc;
48    private String optstring;
49    private int optind = 0; // args index
50    private int optopt = 0;
51    private String optarg = null;
52    private boolean opterr = true;
53
54    /*
55     * _sp is required to keep state between successive calls to
56     * getopt() while extracting aggregated short-options (ie: -abcd).
57     */
58    private int _sp = 1;
59
60    /**
61     * Creates a {Code Getopt} instance to parse the given command-line
62     * arguments. Modifies the given args array by swapping the
63     * positions of non-options and options so that non-options appear
64     * at the end of the array.
65     */
66    public Getopt(String programName, String[] args,
67	    String optionString)
68    {
69	progname = programName;
70	// No defensive copy; Getopt is expected to modify the given
71	// args array
72	this.args = args;
73	argc = this.args.length;
74	optstring = optionString;
75	validate();
76    }
77
78    private void
79    validate()
80    {
81	if (progname == null) {
82	    throw new NullPointerException("program name is null");
83	}
84	int i = 0;
85	for (String s : args) {
86	    if (s == null) {
87		throw new NullPointerException("null arg at index " + i);
88	    }
89	    ++i;
90	}
91	if (optstring == null) {
92	    throw new NullPointerException("option string is null");
93	}
94    }
95
96    private static class StringRef {
97	private String s;
98
99	public String
100	get()
101	{
102	    return s;
103	}
104
105	public StringRef
106	set(String value)
107	{
108	    s = value;
109	    return this;
110	}
111    }
112
113    /*
114     * Generalized error processing method. If the optstr parameter is
115     * null, the character c is converted to a string and displayed
116     * instead.
117     */
118    void
119    err(String format, char c, String optstr)
120    {
121	if (opterr && optstring.charAt(0) != ':') {
122	    StringWriter w = new StringWriter();
123	    PrintWriter p = new PrintWriter(w);
124	    p.printf(format, progname, (optstr == null ?
125		    Character.toString(c) : optstr.substring(2)));
126	    System.err.println(w.toString());
127	}
128    }
129
130    /*
131     * Determine if the specified character (c) is present in the string
132     * (optstring) as a regular, single character option. If the option
133     * is found, return an index into optstring where the short-option
134     * character is found, otherwise return -1. The characters ':' and
135     * '(' are not allowed.
136     */
137    static int
138    parseshort(String optstring, char c)
139    {
140	if (c == ':' || c == '(') {
141	    return -1;
142	}
143
144	int ch;
145	int len = optstring.length();
146	for (int i = 0; i < len; ++i) {
147	    ch = optstring.charAt(i);
148	    if (ch == c) {
149		return i;
150	    }
151
152	    while (i < len && ch == '(') {
153		for (++i; i < len && (ch = optstring.charAt(i)) != ')'; ++i);
154	    }
155	}
156
157	return -1;
158    }
159
160    /**
161     * Determine if the specified string (opt) is present in the string
162     * (optstring) as a long-option contained within parenthesis. If the
163     * long-option specifies option-argument, return a reference to it
164     * in longoptarg.  Otherwise set the longoptarg reference to null.
165     * If the option is found, return an index into optstring at the
166     * position of the short-option character associated with the
167     * long-option; otherwise return -1.
168     *
169     * @param optstring	the entire optstring passed to the {@code
170     * Getopt} constructor
171     * @param opt the long option read from the command line
172     * @param longoptarg the value of the option is returned in this
173     * parameter, if an option exists. Possible return values in
174     * longoptarg are:
175     * <ul>
176     * <li><b>NULL:</b> No argument was found</li>
177     * <li><b>empty string (""):</b> Argument was explicitly left empty
178     * by the user (e.g., --option= )</li>
179     * <li><b>valid string:</b> Argument found on the command line</li>
180     * </ul>
181     * @return index to equivalent short-option in optstring, or -1 if
182     * option not found in optstring.
183     */
184    static int
185    parselong(String optstring, String opt, StringRef longoptarg)
186    {
187	int cp; // index into optstring, beginning of one option spec
188	int ip; // index into optstring, traverses every char
189	char ic; // optstring char
190	int il; // optstring length
191	int op;	// index into opt
192	char oc; // opt char
193	int ol; // opt length
194	boolean	match; // true if opt is matching part of optstring
195
196	longoptarg.set(null);
197	cp = ip = 0;
198	il = optstring.length();
199	ol = opt.length();
200	do {
201	    ic = optstring.charAt(ip);
202	    if (ic != '(' && ++ip == il)
203		break;
204	    ic = optstring.charAt(ip);
205	    if (ic == ':' && ++ip == il)
206		break;
207	    ic = optstring.charAt(ip);
208	    while (ic == '(') {
209		if (++ip == il)
210		    break;
211		op = 0;
212		match = true;
213		while (ip < il && (ic = optstring.charAt(ip)) != ')' &&
214			op < ol) {
215		    oc = opt.charAt(op++);
216		    match = (ic == oc && match);
217		    ++ip;
218		}
219
220		if (match && ip < il && ic == ')' && (op >= ol ||
221			opt.charAt(op) == '=')) {
222		    if (op < ol && opt.charAt(op) == '=') {
223			/* may be an empty string - OK */
224			longoptarg.set(opt.substring(op + 1));
225		    } else {
226			longoptarg.set(null);
227		    }
228		    return cp;
229		}
230		if (ip < il && ic == ')' && ++ip == il)
231		    break;
232		ic = optstring.charAt(ip);
233	    }
234	    cp = ip;
235	    /*
236	     * Handle double-colon in optstring ("a::(longa)") The old
237	     * getopt() accepts it and treats it as a required argument.
238	     */
239	    while ((cp > 0) && (cp < il) && (optstring.charAt(cp) == ':')) {
240		--cp;
241	    }
242	} while (cp < il);
243	return -1;
244    }
245
246    /**
247     * Get the current option value.
248     */
249    public String
250    getOptarg()
251    {
252	return optarg;
253    }
254
255    /**
256     * Get the index of the next option to be parsed.
257     */
258    public int
259    getOptind()
260    {
261	return optind;
262    }
263
264    /**
265     * Gets the command-line arguments.
266     */
267    public String[]
268    getArgv()
269    {
270	// No defensive copy: Getopt is expected to modify the given
271	// args array.
272	return args;
273    }
274
275    /**
276     * Gets the aggregated short option that just failed. Since long
277     * options can't be aggregated, a failed long option can be obtained
278     * by {@code getArgv()[getOptind() - 1]}.
279     */
280    public int
281    getOptopt()
282    {
283	return optopt;
284    }
285
286    /**
287     * Set to {@code false} to suppress diagnostic messages to stderr.
288     */
289    public void
290    setOpterr(boolean err)
291    {
292	opterr = err;
293    }
294
295    /**
296     * Gets the next option character, or -1 if there are no more
297     * options. If getopt() encounters a short-option character or a
298     * long-option string not described in the {@code optionString}
299     * argument to the constructor, it returns the question-mark (?)
300     * character. If it detects a missing option-argument, it also
301     * returns the question-mark (?) character, unless the first
302     * character of the {@code optionString} argument was a colon (:),
303     * in which case getopt() returns the colon (:) character.
304     * <p>
305     * This implementation swaps the positions of options and
306     * non-options in the given argv array.
307     */
308    public int
309    getopt()
310    {
311	char c;
312	int cp;
313	boolean longopt;
314	StringRef longoptarg = new StringRef();
315
316	/*
317	 * Has the end of the options been encountered?  The following
318	 * implements the SUS requirements:
319	 *
320	 * If, when getopt() is called:
321	 *	- the first character of argv[optind] is not '-'
322	 *	- argv[optind] is the string "-"
323	 * getopt() returns -1 without changing optind if
324	 *	- argv[optind] is the string "--"
325	 * getopt() returns -1 after incrementing optind
326	 */
327	if (_sp == 1) {
328	    boolean nonOption;
329	    do {
330		nonOption = false;
331		if (optind >= argc || args[optind].equals("-")) {
332		    return EOF;
333		} else if (args[optind].equals("--")) {
334		    ++optind;
335		    return EOF;
336		} else if (args[optind].charAt(0) != '-') {
337		    // non-option: here we deviate from the SUS requirements
338		    // by not quitting, and instead move non-options to the
339		    // end of the args array
340		    nonOption = true;
341		    String tmp = args[optind];
342		    if (optind + 1 < args.length) {
343			System.arraycopy(args, optind + 1, args, optind,
344				args.length - (optind + 1));
345			args[args.length - 1] = tmp;
346		    }
347		    --argc;
348		}
349	    } while (nonOption);
350	}
351
352	/*
353	 * Getting this far indicates that an option has been encountered.
354	 * Note that the syntax of optstring applies special meanings to
355	 * the characters ':' and '(', so they are not permissible as
356	 * option letters. A special meaning is also applied to the ')'
357	 * character, but its meaning can be determined from context.
358	 * Note that the specification only requires that the alnum
359	 * characters be accepted.
360	 *
361	 * If the second character of the argument is a '-' this must be
362	 * a long-option, otherwise it must be a short option.  Scan for
363	 * the option in optstring by the appropriate algorithm. Either
364	 * scan will return an index to the short-option character in
365	 * optstring if the option is found and -1 otherwise.
366	 *
367	 * For an unrecognized long-option, optopt will equal 0, but
368	 * since long-options can't aggregate the failing option can be
369	 * identified by argv[optind-1].
370	 */
371	optopt = c = args[optind].charAt(_sp);
372	optarg = null;
373	longopt = (_sp == 1 && c == '-');
374	if (!(longopt
375		? ((cp = parselong(optstring, args[optind].substring(2),
376		longoptarg)) != -1)
377		: ((cp = parseshort(optstring, c)) != -1))) {
378	    err("%s: illegal option -- %s", c,
379		    (longopt ? args[optind] : null));
380	    /*
381	     * Note: When the long option is unrecognized, optopt will
382	     * be '-' here, which matches the specification.
383	     */
384	    if (args[optind].length() == ++_sp || longopt) {
385		++optind;
386		_sp = 1;
387	    }
388	    return '?';
389	}
390	optopt = c = optstring.charAt(cp);
391
392	/*
393	 * A valid option has been identified.  If it should have an
394	 * option-argument, process that now.  SUS defines the setting
395	 * of optarg as follows:
396	 *
397	 *   1.	If the option was the last character in an element of
398	 *   argv, then optarg contains the next element of argv, and
399	 *   optind is incremented by 2. If the resulting value of
400	 *   optind is not less than argc, this indicates a missing
401	 *   option-argument, and getopt() returns an error indication.
402	 *
403	 *   2.	Otherwise, optarg points to the string following the
404	 *   option character in that element of argv, and optind is
405	 *   incremented by 1.
406	 *
407	 * The second clause allows -abcd (where b requires an
408	 * option-argument) to be interpreted as "-a -b cd".
409	 *
410	 * Note that the option-argument can legally be an empty string,
411	 * such as:
412	 * 	command --option= operand
413	 * which explicitly sets the value of --option to nil
414	 */
415	if (cp + 1 < optstring.length() && optstring.charAt(cp + 1) == ':') {
416	    // The option takes an argument
417	    if (!longopt && ((_sp + 1) < args[optind].length())) {
418		optarg = args[optind++].substring(_sp + 1);
419	    } else if (longopt && (longoptarg.get() != null)) {
420		/*
421		 * The option argument was explicitly set to the empty
422		 * string on the command line (--option=)
423		 */
424		optind++;
425		optarg = longoptarg.get();
426	    } else if (++optind >= argc) {
427		err("%s: option requires an argument -- %s", c,
428			(longopt ? args[optind - 1] : null));
429		_sp = 1;
430		optarg = null;
431		return (optstring.charAt(0) == ':' ? ':' : '?');
432	    } else
433		optarg = args[optind++];
434		_sp = 1;
435	    } else {
436		// The option does NOT take an argument
437		if (longopt && (longoptarg.get() != null)) {
438		// User supplied an arg to an option that takes none
439		err("%s: option doesn't take an argument -- %s", (char)0,
440			(longopt ? args[optind] : null));
441		optarg = longoptarg.set(null).get();
442		c = '?';
443	    }
444
445	    if (longopt || args[optind].length() == ++_sp) {
446		_sp = 1;
447		++optind;
448	    }
449	    optarg = null;
450	}
451	return (c);
452    }
453}
454