1 /*--------------------------------------------------------------------------
2 * Copyright 2008 Taro L. Saito
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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 //--------------------------------------
20 // Since: Oct 27, 2008 11:11:28 AM
24 //--------------------------------------
25 package org.xerial.util.opt;
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;
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;
42 * A command-line option and argument parser
47 public class OptionParser {
48 private final OptionSchema schema;
49 private final Object optionHolder;
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>();
56 public <T> OptionParser(T optionHolder) {
57 this.optionHolder = optionHolder;
58 schema = OptionSchema.newOptionSchema(optionHolder);
61 public <T> OptionParser(Class<T> optionHolderType) {
63 this.optionHolder = TypeInfo.createInstance(optionHolderType);
65 catch (BeanException e) {
66 throw new XerialError(XerialErrorCode.INVALID_ARGUMENT, e);
69 schema = OptionSchema.newOptionSchema(optionHolder);
72 @SuppressWarnings("unchecked")
73 public <T> T getOptionHolder() {
74 return (T) optionHolder;
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: "
88 public void printUsage() {
89 printUsage(System.out);
92 public void printUsage(Writer out) {
93 assert schema != null;
94 schema.printUsage(out);
97 public void printUsage(OutputStream out) {
98 assert schema != null;
99 schema.printUsage(out);
102 public String getUsage() {
103 StringWriter buf = new StringWriter();
105 return buf.toString();
109 * Return the unused option arguments after the invocation of the
110 * {@link #parse(String[])} method
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);
122 * Parse the command-line arguments and bind them to the field value of the
126 * @param ignoreUnknownOption
127 * when true, the option parser ignore unknown commands. When
128 * false, OptionParserException will be thrown when unknown
130 * @throws OptionParserException
132 public void parse(String[] args, boolean ignoreUnknownOption) throws OptionParserException {
133 setIgnoreUnknownOption(ignoreUnknownOption);
138 * Parse the command-line arguments and bind them to the field value of the
142 * @throws OptionParserException
144 public void parse(String[] args) throws OptionParserException {
146 unusedArgument.clear();
147 activatedOption.clear();
148 activatedArgument.clear();
150 // initialize collections in the OptionHolder
151 for (OptionItem each : schema.getOptionItemList()) {
152 each.initialize(optionHolder);
154 for (ArgumentItem each : schema.getArgumentItemList()) {
155 each.initialize(optionHolder);
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];
163 if (currentArg.startsWith("--")) {
165 int splitPos = currentArg.indexOf('=');
166 if (splitPos == -1) {
168 String longOptionName = currentArg.substring(2);
169 OptionItem optionItem = findOptionItem(schema, longOptionName);
170 if (optionItem == null) {
171 unusedArgument.add(currentArg);
175 if (optionItem.needsArgument())
176 throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR,
177 "parameter value is required for --" + longOptionName);
179 setOption(optionItem, "true");
181 if (!optionItem.takesMultipleArguments()
182 && activatedOption.contains(optionItem.getOption()))
183 throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION,
184 optionItem.getOption().toString());
186 activatedOption.add(optionItem.getOption());
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);
198 if (!optionItem.needsArgument()) {
199 throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR,
200 "syntax error --" + longOptionName);
203 setOption(optionItem, value);
204 if (!optionItem.takesMultipleArguments()
205 && activatedOption.contains(optionItem.getOption()))
206 throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION,
207 optionItem.getOption().toString());
209 activatedOption.add(optionItem.getOption());
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);
224 if (optionItem.needsArgument()) {
225 if (shortOptionList.length() != 1)
226 throw new OptionParserException(
227 XerialErrorCode.SYNTAX_ERROR,
230 "short name option -%s with an arguments must be a single notation",
233 setOption(optionItem, args[++index]);
236 setOption(optionItem, "true");
238 if (!optionItem.takesMultipleArguments()
239 && activatedOption.contains(optionItem.getOption()))
240 throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION,
241 optionItem.getOption().toString());
243 activatedOption.add(optionItem.getOption());
248 ArgumentItem argItem = schema.getArgument(argIndex);
249 if (argItem == null) {
250 if (ignoreUnknownOption) {
251 unusedArgument.add(currentArg);
255 throw new OptionParserException(XerialErrorCode.SYNTAX_ERROR,
256 "unused argument: " + currentArg);
260 argItem.set(optionHolder, currentArg);
262 catch (XerialException e) {
263 throw new OptionParserException(XerialErrorCode.INVALID_ARGUMENT, e
267 if (!argItem.takesMultipleArguments()
268 && activatedArgument.contains(argItem.getArgumentDescriptor()))
269 throw new OptionParserException(XerialErrorCode.DUPLICATE_OPTION, argItem
270 .getArgumentDescriptor().toString());
272 activatedArgument.add(argItem.getArgumentDescriptor());
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
287 private void setOption(OptionItem item, String value) throws OptionParserException {
289 item.setOption(optionHolder, value);
291 catch (XerialException e) {
292 if (BeanErrorCode.class.isInstance(e.getErrorCode())) {
293 BeanErrorCode be = BeanErrorCode.class.cast(e.getErrorCode());
296 throw new OptionParserException(XerialErrorCode.INVALID_ARGUMENT, String
297 .format("cannot set %s to %s", value, item.toString()));
299 throw new OptionParserException(e.getErrorCode(), e.getMessage());
303 throw new OptionParserException(e.getErrorCode(), e.getMessage());
308 * Set this true when ignoring unknown options and arguments that match the
313 public void setIgnoreUnknownOption(boolean ignore) {
314 ignoreUnknownOption = ignore;