OSDN Git Service

git-svn-id: http://www.xerial.org/svn/project/XerialJ/trunk/xerial-core@3498 ae02f08e...
[xerial/xerial-core.git] / src / main / java / org / xerial / util / opt / OptionParser.java
1 /*--------------------------------------------------------------------------
2  *  Copyright 2008 Taro L. Saito
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  *--------------------------------------------------------------------------*/
16 //--------------------------------------
17 // XerialJ
18 //
19 // OptionParser.java
20 // Since: Oct 27, 2008 11:11:28 AM
21 //
22 // $URL$
23 // $Author$
24 //--------------------------------------
25 package org.xerial.util.opt;
26
27 import java.io.OutputStream;
28 import java.io.StringWriter;
29 import java.io.Writer;
30 import java.util.ArrayList;
31 import java.util.HashSet;
32 import java.util.List;
33
34 import org.xerial.core.XerialError;
35 import org.xerial.core.XerialErrorCode;
36 import org.xerial.core.XerialException;
37 import org.xerial.util.bean.BeanErrorCode;
38 import org.xerial.util.bean.BeanException;
39 import org.xerial.util.bean.TypeInfo;
40
41 /**
42  * A command-line option and argument parser
43  * 
44  * @author leo
45  * 
46  */
47 public class OptionParser {
48     private final OptionSchema schema;
49     private final Object optionHolder;
50
51     private boolean ignoreUnknownOption = false;
52     private HashSet<Option> activatedOption = new HashSet<Option>();
53     private HashSet<Argument> activatedArgument = new HashSet<Argument>();
54     private List<String> unusedArgument = new ArrayList<String>();
55
56     public <T> OptionParser(T optionHolder) {
57         this.optionHolder = optionHolder;
58         schema = OptionSchema.newOptionSchema(optionHolder);
59     }
60
61     public <T> OptionParser(Class<T> optionHolderType) {
62         try {
63             this.optionHolder = TypeInfo.createInstance(optionHolderType);
64         }
65         catch (BeanException e) {
66             throw new XerialError(XerialErrorCode.INVALID_ARGUMENT, e);
67         }
68
69         schema = OptionSchema.newOptionSchema(optionHolder);
70     }
71
72     @SuppressWarnings("unchecked")
73     public <T> T getOptionHolder() {
74         return (T) optionHolder;
75     }
76
77     OptionItem findOptionItem(OptionSchema schema, String optionName) throws OptionParserException {
78         OptionItem optionItem = schema.getOption(optionName);
79         if (optionItem == null) {
80             if (!ignoreUnknownOption) {
81                 throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR, "unknown option: "
82                         + optionName);
83             }
84         }
85         return optionItem;
86     }
87
88     public void printUsage() {
89         printUsage(System.out);
90     }
91
92     public void printUsage(Writer out) {
93         assert schema != null;
94         schema.printUsage(out);
95     }
96
97     public void printUsage(OutputStream out) {
98         assert schema != null;
99         schema.printUsage(out);
100     }
101
102     public String getUsage() {
103         StringWriter buf = new StringWriter();
104         printUsage(buf);
105         return buf.toString();
106     }
107
108     /**
109      * Return the unused option arguments after the invocation of the
110      * {@link #parse(String[])} method
111      * 
112      * @return
113      */
114     public String[] getUnusedArguments() {
115         String[] result = new String[unusedArgument.size()];
116         for (int i = 0; i < unusedArgument.size(); ++i)
117             result[i] = unusedArgument.get(i);
118         return result;
119     }
120
121     /**
122      * Parse the command-line arguments and bind them to the field value of the
123      * optionHolder
124      * 
125      * @param args
126      * @param ignoreUnknownOption
127      *            when true, the option parser ignore unknown commands. When
128      *            false, OptionParserException will be thrown when unknown
129      *            options are found.
130      * @throws OptionParserException
131      */
132     public void parse(String[] args, boolean ignoreUnknownOption) throws OptionParserException {
133         setIgnoreUnknownOption(ignoreUnknownOption);
134         parse(args);
135     }
136
137     /**
138      * Parse the command-line arguments and bind them to the field value of the
139      * optionHolder
140      * 
141      * @param args
142      * @throws OptionParserException
143      */
144     public void parse(String[] args) throws OptionParserException {
145         // clear
146         unusedArgument.clear();
147         activatedOption.clear();
148         activatedArgument.clear();
149
150         // initialize collections in the OptionHolder
151         for (OptionItem each : schema.getOptionItemList()) {
152             each.initialize(optionHolder);
153         }
154         for (ArgumentItem each : schema.getArgumentItemList()) {
155             each.initialize(optionHolder);
156         }
157
158         int index = 0; // index in the args array
159         int argIndex = 0; // argument index
160         for (; index < args.length; index++) {
161             String currentArg = args[index];
162
163             if (currentArg.startsWith("--")) {
164                 // long name option
165                 int splitPos = currentArg.indexOf('=');
166                 if (splitPos == -1) {
167                     // no value is found
168                     String longOptionName = currentArg.substring(2);
169                     OptionItem optionItem = findOptionItem(schema, longOptionName);
170                     if (optionItem == null) {
171                         unusedArgument.add(currentArg);
172                         continue;
173                     }
174
175                     if (optionItem.needsArgument())
176                         throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR,
177                                 "parameter value is required for --" + longOptionName);
178
179                     setOption(optionItem, "true");
180
181                     if (!optionItem.takesMultipleArguments()
182                             && activatedOption.contains(optionItem.getOption()))
183                         throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION,
184                                 optionItem.getOption().toString());
185
186                     activatedOption.add(optionItem.getOption());
187                 }
188                 else {
189                     // option is a (key, value) pair
190                     String longOptionName = currentArg.substring(2, splitPos);
191                     String value = currentArg.substring(splitPos + 1);
192                     OptionItem optionItem = findOptionItem(schema, longOptionName);
193                     if (optionItem == null) {
194                         unusedArgument.add(currentArg);
195                         continue;
196                     }
197
198                     if (!optionItem.needsArgument()) {
199                         throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR,
200                                 "syntax error --" + longOptionName);
201                     }
202
203                     setOption(optionItem, value);
204                     if (!optionItem.takesMultipleArguments()
205                             && activatedOption.contains(optionItem.getOption()))
206                         throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION,
207                                 optionItem.getOption().toString());
208
209                     activatedOption.add(optionItem.getOption());
210                 }
211
212             }
213             else if (currentArg.startsWith("-")) {
214                 // option with a leading hyphen (e.g. "-txvf" is equivalent to "-t", "-x", "-v" and "-f")
215                 String shortOptionList = currentArg.substring(1);
216                 for (int i = 0; i < shortOptionList.length(); i++) {
217                     String shortOptionName = shortOptionList.substring(i, i + 1);
218                     OptionItem optionItem = findOptionItem(schema, shortOptionName);
219                     if (optionItem == null) {
220                         unusedArgument.add(currentArg);
221                         continue;
222                     }
223
224                     if (optionItem.needsArgument()) {
225                         if (shortOptionList.length() != 1)
226                             throw new OptionParserException(
227                                     XerialErrorCode.SYNTAX_ERROR,
228                                     String
229                                             .format(
230                                                     "short name option -%s with an arguments must be a single notation",
231                                                     shortOptionName));
232
233                         setOption(optionItem, args[++index]);
234                     }
235                     else
236                         setOption(optionItem, "true");
237
238                     if (!optionItem.takesMultipleArguments()
239                             && activatedOption.contains(optionItem.getOption()))
240                         throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION,
241                                 optionItem.getOption().toString());
242
243                     activatedOption.add(optionItem.getOption());
244                 }
245             }
246             else {
247                 // general argument
248                 ArgumentItem argItem = schema.getArgument(argIndex);
249                 if (argItem == null) {
250                     if (ignoreUnknownOption) {
251                         unusedArgument.add(currentArg);
252                         continue;
253                     }
254                     else
255                         throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR,
256                                 "unused argument: " + currentArg);
257                 }
258
259                 try {
260                     argItem.set(optionHolder, currentArg);
261                 }
262                 catch (XerialException e) {
263                     throw new OptionParserException(XerialErrorCode.INVALID_ARGUMENT, e
264                             .getMessage());
265                 }
266
267                 if (!argItem.takesMultipleArguments()
268                         && activatedArgument.contains(argItem.getArgumentDescriptor()))
269                     throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION, argItem
270                             .getArgumentDescriptor().toString());
271
272                 activatedArgument.add(argItem.getArgumentDescriptor());
273                 argIndex++;
274             }
275
276         }
277
278         // verify missing options & arguments
279         for (ArgumentItem argItem : schema.getArgumentItemList()) {
280             if (argItem.getArgumentDescriptor().required()
281                     && !activatedArgument.contains(argItem.getArgumentDescriptor()))
282                 throw new OptionParserException(XerialErrorCode.MISSING_ARGUMENT, argItem
283                         .toString());
284         }
285     }
286
287     private void setOption(OptionItem item, String value) throws OptionParserException {
288         try {
289             item.setOption(optionHolder, value);
290         }
291         catch (XerialException e) {
292             if (BeanErrorCode.class.isInstance(e.getErrorCode())) {
293                 BeanErrorCode be = BeanErrorCode.class.cast(e.getErrorCode());
294                 switch (be) {
295                 case InvalidFormat:
296                     throw new OptionParserException(XerialErrorCode.INVALID_ARGUMENT, String
297                             .format("cannot set %s to %s", value, item.toString()));
298                 default:
299                     throw new OptionParserException(e.getErrorCode(), e.getMessage());
300                 }
301             }
302             else
303                 throw new OptionParserException(e.getErrorCode(), e.getMessage());
304         }
305     }
306
307     /**
308      * Set this true when ignoring unknown options and arguments that match the
309      * input arguments
310      * 
311      * @param ignore
312      */
313     public void setIgnoreUnknownOption(boolean ignore) {
314         ignoreUnknownOption = ignore;
315     }
316
317 }