| GetOptions.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 as published by
5 * the Free Software Foundation. This program is distributed in the hope
6 * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * Copyright: 2008
18 * The copyright to this program is held by it's authors.
19 *
20 * ID: $Id: org.eclipse.jdt.ui.prefs 1178 2006-11-06 12:48:02Z dmsmith $
21 */
22
23 package org.crosswire.common.options;
24
25 import java.util.ArrayList;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29
30 /**
31 * GetOptions parses an argument list for requested arguments given by an
32 * OptionList.<br/>
33 *
34 * This supports short and long options:<br/>
35 * Short Options have the following characteristics.
36 * <ul>
37 * <li>A single dash, '-', starts a flag or a flag sequence. An example of a
38 * flag is '-c' and a flag sequence is '-xyz'.</li>
39 * <li>A flag may have a required argument. The flag may or may not be separated
40 * by a space from it's argument. For example, both -fbar and -f bar are
41 * acceptable.</li>
42 * <li>A flag may have an optional argument. The flag must not be separated by a
43 * space from it's optional argument. For example, -fbar is acceptable provides
44 * bar as the argument, but -f bar has bar as a non-option argument.</li>
45 * <li>These rules can combine. For example, -xyzfoo can be the same as -x -y -z
46 * foo</li>
47 * <li>If an Option expects an argument, then that argument can have a leading
48 * '-'. That is, if -x requires an option then the argument -y can be given as
49 * -x-y or -x -y.</li>
50 * </ul>
51 *
52 * Long Options have the following characteristics:
53 * <ul>
54 * <li>A double dash '--' starts a single flag. For example --print. Note, a
55 * long option is typically descriptive, but can be a single character.</li>
56 * <li>An argument may be given in one of two ways --file=filename or --file
57 * filename. That is, separated by an '=' sign or whitespace.</li>
58 * <li>
59 * <ul>
60 * Note:
61 * <ul>
62 * <li>Options can be repeated. What that means is up to the program.</li>
63 * <li>The '--' sequence terminates argument processing.</li>
64 * <li>A '-' by itself is not a flag.</li>
65 * <li>Unrecognized flags are an error.</li>
66 * <li>Unrecognized arguments are moved after the processed flags.</li>
67 * </ul>
68 *
69 * @see gnu.lgpl.License for license details.<br>
70 * The copyright to this program is held by it's authors.
71 * @author DM Smith [dmsmith555 at yahoo dot com]
72 */
73 public class GetOptions {
74 public GetOptions(String programName, String[] args, OptionList programOptions) {
75 this.programName = programName;
76 this.args = args.clone();
77 this.programOptions = programOptions;
78 // Initially, we have not started to process an argument
79 this.nonOptionArgs = new ArrayList<String>();
80 this.suppliedOptions = new LinkedHashMap<Option, String>();
81
82 parse();
83 }
84
85 /**
86 * @return the programName
87 */
88 public String getProgramName() {
89 return programName;
90 }
91
92 /**
93 * @param programName
94 * the programName to set
95 */
96 public void setProgramName(String programName) {
97 this.programName = programName;
98 }
99
100 private void parse() {
101 int nargs = args.length;
102 int skip = 0;
103 for (int i = 0; i < nargs; i += 1 + skip) {
104 skip = 0;
105 String nextArg = args[i];
106 // All options are 2 or more characters long and begin with a '-'.
107 // If this is a non-option then note it and advance
108 if (nextArg.length() < 2 || nextArg.charAt(0) != '-') {
109 nonOptionArgs.add(nextArg);
110 continue;
111 }
112
113 // If we are at the end of all options, '--', we need to skip this
114 // and copy what follows to the end
115 if ("--".equals(nextArg)) {
116 for (int j = i + 1; j < nargs; j++) {
117 nonOptionArgs.add(args[j]);
118 }
119 return;
120 }
121
122 // At this point we are on a short option, a short option sequence
123 // or a long option.
124 // Invariant: the length > 1.
125 if (nextArg.charAt(1) == '-') {
126 // Process a long argument
127 // This can be of the form --flag or --flag argument or
128 // --flag=argument
129 int equalPos = nextArg.indexOf('=');
130 String flag = (equalPos != -1) ? nextArg.substring(2, equalPos) : nextArg.substring(2);
131 List<Option> opts = programOptions.getLongOptions(flag);
132 int count = opts.size();
133 if (count == 0) {
134 throw new IllegalArgumentException("Illegal option --" + flag);
135 }
136 if (count > 1) {
137 throw new IllegalArgumentException("Ambiguous option --" + flag);
138 }
139 Option option = opts.get(0);
140 if (option.getArgumentType().equals(ArgumentType.NO_ARGUMENT)) {
141 // Add option with null argument to options
142 suppliedOptions.put(option, null);
143 continue;
144 }
145 // An argument is allowed or required
146 if (equalPos != -1) {
147 // Add option with argument to options
148 // Check for empty argument
149 String argument = (equalPos + 1 < nextArg.length()) ? nextArg.substring(equalPos + 1) : "";
150 suppliedOptions.put(option, argument);
151 continue;
152 }
153 // An argument is required, so take the next one.
154 if (option.getArgumentType().equals(ArgumentType.REQUIRED_ARGUMENT)) {
155 if (i + 1 < nargs) {
156 // Add option with following argument to options
157 String argument = args[i];
158 skip = 1;
159 suppliedOptions.put(option, argument);
160 continue;
161 }
162 throw new IllegalArgumentException("Option missing required argument");
163 }
164 } else {
165 // Process a short argument or short argument sequence
166
167 // for each letter after the '-'
168 int shortSeqSize = nextArg.length();
169 for (int j = 1; j < shortSeqSize; j++) {
170 char curChar = nextArg.charAt(j);
171 Option option = programOptions.getShortOption(curChar);
172 if (option == null) {
173 throw new IllegalArgumentException("Illegal option -" + curChar);
174 }
175 if (option.getArgumentType().equals(ArgumentType.NO_ARGUMENT)) {
176 // Add option with null argument to options
177 suppliedOptions.put(option, null);
178 continue;
179 }
180 // This option allows or requires an argument
181 if (j < shortSeqSize) {
182 // since there is stuff that follows the flag, it is the
183 // argument.
184 String argument = nextArg.substring(j + 1);
185 suppliedOptions.put(option, argument);
186 continue;
187 }
188 if (option.getArgumentType().equals(ArgumentType.REQUIRED_ARGUMENT)) {
189 if (i + 1 < nargs) {
190 // Add option with following argument to options
191 String argument = args[i];
192 skip = 1;
193 suppliedOptions.put(option, argument);
194 continue;
195 }
196 throw new IllegalArgumentException("Option missing required argument");
197 }
198 }
199 }
200 }
201 }
202
203 /**
204 * Swap adjacent blocks in an array.
205 *
206 * @param array
207 * The array to modify in place
208 * @param firstStart
209 * the index of the start of the first block
210 * @param firstEnd
211 * the index of the end of the first block
212 * @param secondEnd
213 * the index of the end of the second block. Note: the start of
214 * the second block is firstEnd + 1
215 */
216 public static void swap(Object[] array, int firstStart, int firstEnd, int secondEnd) {
217 // Note: this is currently unused.
218 // If we implement the traditional GNU extensions GetOpts interface we
219 // will need it.
220 // We copy the smaller block to the longer block.
221
222 // The performance of this is linear with respect to the size of the
223 // larger block.
224 // If the blocks are equal the number of swaps is equal to the "larger"
225 // block size otherwise it is one greater.
226
227 // if the first block is smaller we start at the start of both and swap
228 // Otherwise we start at the end of both and swap from the end to the
229 // start
230
231 // Set variables for the second block to be larger
232 int sourcePos = firstStart;
233 int destPos = firstEnd + 1;
234 int increment = 1;
235 int destStop = secondEnd;
236 int firstSize = firstEnd - firstStart + 1;
237 int secondSize = secondEnd - firstEnd;
238 int swapCount = secondSize + 1;
239
240 if (firstSize > secondSize) {
241 // first block is bigger or equal
242 sourcePos = secondEnd;
243 destPos = firstEnd;
244 destStop = firstStart;
245 increment = -1;
246 swapCount = firstSize + 1;
247 }
248
249 if (firstSize == secondSize) {
250 swapCount--;
251 }
252
253 while (swapCount-- > 0) {
254 Object temp = array[destPos];
255 array[destPos] = array[sourcePos];
256 array[sourcePos] = temp;
257 if (sourcePos != destStop) {
258 sourcePos += increment;
259 }
260 if (destPos != destStop) {
261 destPos += increment;
262 }
263
264 }
265 }
266
267 // public static void main(String[] args)
268 // {
269 // String[] a = {"a","b","c","d","e"};
270 // swap(a, 0, 2, 4);
271 // swap(a, 0, 1, 4);
272 // swap(a, 0, 0, 1);
273 // swap(a, 0, 0, 1);
274 // swap(a, 1, 2, 4);
275 // swap(a, 1, 2, 4);
276 // }
277
278 private String programName;
279 private String[] args;
280 private OptionList programOptions;
281
282 /**
283 * The position in the array that is currently being studied.
284 */
285 private List<String> nonOptionArgs;
286 private Map<Option, String> suppliedOptions;
287 }
288