private String debugState(final String prefix) { return prefix + "Parameter: " + parameter + "\n" + prefix + "Options: " + FList.mkString(options, "\n" + prefix + " ", ",\n" + prefix + " ", "") + "\n" + prefix + "Commands: " + FList.mkString(commands, "\n" + prefix + " ", ",\n" + prefix + " ", "") + "\n" + prefix + "ResourceBundle: " + resourceBundle + "\n" + prefix + "Locale: " + (resourceBundle == null ? null : resourceBundle.getLocale()) + "\n" + prefix + "CmdOptionHandlers: " + FList.mkString( handlerRegistry.entrySet(), "\n" + prefix + " ", "\n" + prefix + " ", ""); }
public void applyParams( final Object config, final AccessibleObject element, final String[] args, final String optionName) throws CmdOptionHandlerException { try { if (element instanceof Field) { final Field field = (Field) element; final Class<? extends Enum> type = (Class<? extends Enum>) field.getType(); final Enum<?> value = Enum.valueOf(type, args[0]); field.set(config, value); } else if (element instanceof Method) { final Method method = (Method) element; final Class<? extends Enum> type = (Class<? extends Enum>) method.getParameterTypes()[0]; final Enum<?> value = Enum.valueOf(type, args[0]); method.invoke(config, value); } } catch (final IllegalArgumentException e) { final Class<? extends Enum> type; if (element instanceof Field) { type = (Class<? extends Enum>) ((Field) element).getType(); } else if (element instanceof Method) { type = (Class<? extends Enum>) ((Method) element).getParameterTypes()[0]; } else { type = Enum.class; } final I18n i18n = I18nFactory.getI18n(EnumHandler.class); final PreparedI18n msg = i18n.preparetr( "Invalid enum value: \"{0}\". Supported values: {1}", args[0], FList.mkString(EnumSet.allOf(type), ", ")); throw new CmdOptionHandlerException(msg.notr(), e, msg.tr()); } catch (final Exception e) { // TODO better message final I18n i18n = I18nFactory.getI18n(EnumHandler.class); final PreparedI18n msg = i18n.preparetr( "Could not apply parameters: {0} to field/method {1}", Arrays.toString(args), element); throw new CmdOptionHandlerException(msg.notr(), e, msg.tr()); } }
/** * Create a new commandline parser instance and scan all given object for supported options, * parameters and commands using the pre-registered default handlers. Please note that if you want * to use a custom set of option handlers, you should not give your config objects here but use * the {@link #addObject(Object...)} method after you registered the desired set of handlers. * * @param objects The configuration objects containing supported annotations. */ public CmdlineParser(final Object... objects) { parent = null; programName = "<main class>"; usageFormatter = new DefaultUsageFormatter(true, 80, new TtyLineLengthDetector()); // ensure order by using a LinkedHashMap handlerRegistry = new LinkedHashMap<Class<? extends CmdOptionHandler>, CmdOptionHandler>(); FList.foreach( defaultHandlers(), new Procedure1<CmdOptionHandler>() { @Override public void apply(final CmdOptionHandler h) { registerHandler(h); } }); addObject(objects); }
protected void scanOptions(final Object object) { final Class<?> class1 = object.getClass(); final List<Field> fields = new LinkedList<Field>(); final List<Method> privateMethods = new LinkedList<Method>(); final List<Method> otherPackageNonPrivateMethods = new LinkedList<Method>(); final List<Method> currentPackageNonPrivateMethods = new LinkedList<Method>(); Class<?> parentClass = class1; while (parentClass != null && !parentClass.equals(Object.class)) { // We cannot override fields in child classes, so we simple collect // all fields we found fields.addAll(Arrays.asList(parentClass.getDeclaredFields())); // for methods, we need to respect overridden methods when // inspecting the parent classes for (final Method method : parentClass.getDeclaredMethods()) { if (isPrivate(method)) { privateMethods.add(method); } else if (isPublicOrProtected(method)) { if (!containsMethod(otherPackageNonPrivateMethods, method) && !containsMethod(currentPackageNonPrivateMethods, method)) { currentPackageNonPrivateMethods.add(method); } } else if (isPackagePrivate(method)) { // if (!containsMethod(publicOrProtectedMethods, method)) { // method not overloaded if (isPackagePrivate(method)) { if (!containsMethod(currentPackageNonPrivateMethods, method)) { currentPackageNonPrivateMethods.add(method); } } } } final Package pack = parentClass.getPackage(); parentClass = parentClass.getSuperclass(); if ((pack == null && parentClass.getPackage() != null) || (pack != null && !pack.equals(parentClass.getPackage()))) { otherPackageNonPrivateMethods.addAll(currentPackageNonPrivateMethods); currentPackageNonPrivateMethods.clear(); } } // inspect elements final Set<AccessibleObject> elements = new LinkedHashSet<AccessibleObject>(); elements.addAll(fields); elements.addAll(privateMethods); elements.addAll(otherPackageNonPrivateMethods); elements.addAll(currentPackageNonPrivateMethods); for (final AccessibleObject element : elements) { if (element instanceof Field && element.getAnnotation(CmdOptionDelegate.class) != null) { debug("Found delegate object at: {0}", element); try { final boolean origAccessibleFlag = element.isAccessible(); if (!origAccessibleFlag) { element.setAccessible(true); } final Object delegate = ((Field) element).get(object); if (!origAccessibleFlag) { // do not leave doors open element.setAccessible(origAccessibleFlag); } if (delegate != null) { scanOptions(delegate); } } catch (final IllegalArgumentException e) { debug("Could not scan delegate object at: {0}", element); } catch (final IllegalAccessException e) { debug("Could not scan delegate object at: {0}", element); } continue; } final CmdOption anno = element.getAnnotation(CmdOption.class); if (anno == null) { continue; } if (element instanceof Field && Modifier.isFinal(((Field) element).getModifiers())) { debug("Detected option on final field: {0}", element); // continue; } final String[] names = anno.names(); final CmdOptionHandler handler = findHandler(element, anno.args().length, anno.handler()); if (handler == null) { final PreparedI18n msg = i18n.preparetr( "No suitable handler found for option(s): {0} ({1} argument(s))", FList.mkString(anno.names(), ","), anno.args().length); throw new CmdlineParserException(msg.notr(), msg.tr()); } if (names == null || names.length == 0) { // No names means this is the ONLY parameter if (parameter != null) { final PreparedI18n msg = i18n.preparetr( "More than one parameter definition found. First definition: {0} Second definition: {1}", parameter.getElement(), element); throw new CmdlineParserException(msg.notr(), msg.tr()); } // TODO: should we ignore the help parameter? final OptionHandle paramHandle = new OptionHandle( new String[] {}, anno.description(), handler, object, element, anno.args(), anno.minCount(), anno.maxCount(), false /* * cannot * be * a * help * option */, anno.hidden(), anno.requires(), anno.conflictsWith()); if (paramHandle.getArgsCount() <= 0) { final PreparedI18n msg = i18n.preparetr("Parameter definition must support at least on argument."); throw new CmdlineParserException(msg.notr(), msg.tr()); } parameter = paramHandle; } else { final OptionHandle option = new OptionHandle( names, anno.description(), handler, object, element, anno.args(), anno.minCount(), anno.maxCount(), anno.isHelp(), anno.hidden(), anno.requires(), anno.conflictsWith()); for (final String name : names) { if (quickCommandMap.containsKey(name) || quickOptionMap.containsKey(name)) { final PreparedI18n msg = i18n.preparetr( "Duplicate command/option name \"{0}\" found in: {1}", name, element); throw new CmdlineParserException(msg.notr(), msg.tr()); } quickOptionMap.put(name, option); } options.add(option); } } }
public void parse( final boolean dryrun, final boolean detectHelpAndSkipValidation, String... cmdline) { if (log.isDebugEnabled()) { log.debug( "About to start parsing. dryrun: " + dryrun + ", detectHelpAndSkipValidation: " + detectHelpAndSkipValidation + ", state: " + debugState(" ")); } if (defaultCommandName != null && !quickCommandMap.containsKey(defaultCommandName)) { final PreparedI18n msg = i18n.preparetr("Default command \"{0}\" is not a known command.", defaultCommandName); throw new CmdlineParserException(msg.notr(), msg.tr()); } // Avoid null access cmdline = cmdline == null ? new String[] {} : cmdline; if (argsFromFilePrefix.isDefined()) { cmdline = FList.flatMap( cmdline, new F1<String, List<String>>() { @Override public List<String> apply(final String arg) { if (arg.startsWith(argsFromFilePrefix.get())) { debug("Expanding {0} into argument list", arg); final File file = new File(arg.substring(1)); if (file.exists() && file.isFile()) { try { final BufferedReader reader = new BufferedReader(new FileReader(file)); final List<String> args = new LinkedList<String>(); String line; while ((line = reader.readLine()) != null) { // if (line.trim().length() > 0) { args.add(line); // } } reader.close(); return args; } catch (final FileNotFoundException e) { final PreparedI18n msg = i18n.preparetr("File referenced via {0} does not exist.", arg); throw new CmdlineParserException(msg.notr(), e, msg.tr()); } catch (final IOException e) { final PreparedI18n msg = i18n.preparetr("File referenced via {0} could not be read.", arg); throw new CmdlineParserException(msg.notr(), e, msg.tr()); } } else { final PreparedI18n msg = i18n.preparetr("File referenced via {0} does not exist.", arg); throw new CmdlineParserException(msg.notr(), msg.tr()); } } else { return Arrays.asList(arg); } } }) .toArray(new String[0]); } if (!dryrun) { debug("Parsing..."); // Check without applying anything parse(true, detectHelpAndSkipValidation, cmdline); } if (dryrun) { validateOptions(); } // Should be set to false, if an stopOption was found and parsing of // options is no longer allowed boolean parseOptions = true; final String stopOption = "--"; // optionCount counts the occurrence for each option handle in the // cmdline final Map<OptionHandle, Integer> optionCount = new LinkedHashMap<OptionHandle, Integer>(); for (final OptionHandle option : options) { optionCount.put(option, 0); } if (parameter != null) { optionCount.put(parameter, 0); } boolean helpDetected = false; // Actually iterate over the command line elements for (int index = 0; index < cmdline.length; ++index) { final String param = cmdline[index]; if (parseOptions && stopOption.equals(param)) { parseOptions = false; } else if (debugAllowed && param.equals("--CMDOPTION_DEBUG")) { if (!debugMode) { debugMode = true; debug("Enabled debug mode\n" + debugState("")); } } else if (parseOptions && quickOptionMap.containsKey(param)) { // Found an option final OptionHandle optionHandle = quickOptionMap.get(param); optionCount.put(optionHandle, optionCount.get(optionHandle) + 1); if (optionHandle.isHelp()) { debug("Detected a help request through: " + param); helpDetected = true; } if (cmdline.length <= index + optionHandle.getArgsCount()) { final PreparedI18n msg = i18n.preparetr( "Missing arguments(s): {0}. Option \"{1}\" requires {2} arguments, but you gave {3}.", FList.mkString( Arrays.asList(optionHandle.getArgs()) .subList(cmdline.length - index - 1, optionHandle.getArgsCount()), ", "), param, optionHandle.getArgsCount(), cmdline.length - index - 1); throw new CmdlineParserException(msg.notr(), msg.tr()); } // slurp next cmdline arguments into option arguments final String[] optionArgs = Arrays.copyOfRange(cmdline, index + 1, index + 1 + optionHandle.getArgsCount()); index += optionHandle.getArgsCount(); final AccessibleObject element = optionHandle.getElement(); final CmdOptionHandler handler = optionHandle.getCmdOptionHandler(); if (!dryrun) { try { final boolean origAccessibleFlag = element.isAccessible(); if (!origAccessibleFlag) { element.setAccessible(true); } handler.applyParams(optionHandle.getObject(), element, optionArgs, param); if (!origAccessibleFlag) { // do not leave doors open element.setAccessible(origAccessibleFlag); } } catch (final CmdOptionHandlerException e) { throw new CmdlineParserException(e.getMessage(), e, e.getLocalizedMessage()); } catch (final Exception e) { final PreparedI18n msg = i18n.preparetr( "Could not apply parameters {0} to field/method {1}", Arrays.toString(optionArgs), element); throw new CmdlineParserException(msg.notr(), e, msg.tr()); } } } else if (parseOptions && quickCommandMap.containsKey(param)) { // Found a command final CommandHandle commandHandle = quickCommandMap.get(param); if (!dryrun) { parsedCommandName = param; } // Delegate parsing of the rest of the cmdline to the command commandHandle .getCmdlineParser() .parse( dryrun, detectHelpAndSkipValidation, Arrays.copyOfRange(cmdline, index + 1, cmdline.length)); // Stop parsing break; } else if (parameter == null && defaultCommandName != null && quickCommandMap.containsKey(defaultCommandName)) { // Assume a default command inserted here debug( "Unsupported option '" + param + "' found, assuming default command: " + defaultCommandName); final CommandHandle commandHandle = quickCommandMap.get(defaultCommandName); if (!dryrun) { parsedCommandName = defaultCommandName; } // Delegate parsing of the rest of the cmdline to the command commandHandle .getCmdlineParser() .parse( dryrun, detectHelpAndSkipValidation, Arrays.copyOfRange(cmdline, index, cmdline.length)); // Stop parsing break; } else if (parameter != null) { // Found a parameter optionCount.put(parameter, optionCount.get(parameter) + 1); if (cmdline.length <= index + parameter.getArgsCount() - 1) { final int countOfGivenParams = cmdline.length - index; final PreparedI18n msg = i18n.preparetr( "Missing arguments: {0} Parameter requires {1} arguments, but you gave {2}.", Arrays.asList(parameter.getArgs()) .subList(countOfGivenParams, parameter.getArgsCount()), parameter.getArgsCount(), countOfGivenParams); throw new CmdlineParserException(msg.notr(), msg.tr()); } // slurp next cmdline arguments into option arguments final String[] optionArgs = Arrays.copyOfRange(cmdline, index, index + parameter.getArgsCount()); // -1, because index gets increased by one at end of for-loop index += parameter.getArgsCount() - 1; final AccessibleObject element = parameter.getElement(); final CmdOptionHandler handler = parameter.getCmdOptionHandler(); if (!dryrun) { try { debug("Apply main parameter from parameters: {0}", FList.mkString(optionArgs, ", ")); final boolean origAccessibleFlag = element.isAccessible(); if (!origAccessibleFlag) { element.setAccessible(true); } handler.applyParams(parameter.getObject(), element, optionArgs, param); if (!origAccessibleFlag) { // do not leave doors open element.setAccessible(origAccessibleFlag); } } catch (final CmdOptionHandlerException e) { throw new CmdlineParserException(e.getMessage(), e, e.getLocalizedMessage()); } catch (final Exception e) { final PreparedI18n msg = i18n.preparetr( "Could not apply parameters {0} to field/method {1}", Arrays.toString(optionArgs), element); throw new CmdlineParserException(msg.notr(), e, msg.tr()); } } } else { final PreparedI18n msg = i18n.preparetr("Unsupported option or parameter found: {0}", param); throw new CmdlineParserException(msg.notr(), msg.tr()); } } if (!detectHelpAndSkipValidation || !helpDetected) { // Validate optionCount matches allowed for (final Entry<OptionHandle, Integer> optionC : optionCount.entrySet()) { final OptionHandle option = optionC.getKey(); final Integer count = optionC.getValue(); if (count < option.getMinCount() || (option.getMaxCount() > 0 && count > option.getMaxCount())) { final PreparedI18n rangeMsg; if (option.getMaxCount() < 0) { rangeMsg = i18n.preparetr("at least {0}", option.getMinCount()); } else { if (option.getMinCount() == option.getMaxCount()) { rangeMsg = i18n.preparetr("exactly {0}", option.getMinCount()); } else { rangeMsg = i18n.preparetr("between {0} and {1}", option.getMinCount(), option.getMaxCount()); } } final String msg; final Object[] msgArgs; final Object[] msgArgsTr; if (option.getNames() == null || option.getNames().length == 0) { msg = I18n.marktr( "Main parameter \"{0}\" was given {1} times, but must be given {2} times"); msgArgs = new Object[] {FList.mkString(option.getArgs(), " "), count, rangeMsg.notr()}; msgArgsTr = new Object[] {FList.mkString(option.getArgs(), " "), count, rangeMsg.tr()}; } else { msg = I18n.marktr("Option \"{0}\" was given {1} times, but must be given {2} times"); msgArgs = new Object[] {option.getNames()[0], count, rangeMsg.notr()}; msgArgsTr = new Object[] {option.getNames()[0], count, rangeMsg.tr()}; } throw new CmdlineParserException( MessageFormat.format(msg, msgArgs), i18n.tr(msg, msgArgsTr)); } } // Validate required options because of 'required' attribute in // other options for (final Entry<OptionHandle, Integer> optionC : optionCount.entrySet()) { if (optionC.getValue() > 0) { final OptionHandle calledOption = optionC.getKey(); for (final String required : calledOption.getRequires()) { // check, of an option was called with that name, if // not, this is an error final OptionHandle reqOptionHandle = quickOptionMap.get(required); if (reqOptionHandle == null) { // required option does not exists, error // TODO: error } else { final Integer reqOptionCount = optionCount.get(reqOptionHandle); if (reqOptionCount == null || reqOptionCount.intValue() <= 0) { // required option was not called, this is an // error final PreparedI18n msg = i18n.preparetr( "When using option \"{0}\" also option \"{1}\" must be given.", calledOption.getNames()[0], required); throw new CmdlineParserException(msg.notr(), msg.tr()); } } } for (final String conflict : calledOption.getConflictsWith()) { // check, of an option was called with that name, if // not, this is an error final OptionHandle conflictOptionHandle = quickOptionMap.get(conflict); if (conflictOptionHandle == null) { // conflicting option does not exists, error // TODO: error } else { final Integer conflictOptionCount = optionCount.get(conflictOptionHandle); if (conflictOptionCount != null && conflictOptionCount.intValue() > 0) { // conflicting option was called, this is an // conflict final PreparedI18n msg = i18n.preparetr( "Options \"{0}\" and \"{1}\" cannot be used at the same time.", calledOption.getNames()[0], conflict); throw new CmdlineParserException(msg.notr(), msg.tr()); } } } } } } }