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