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 + "  ", "");
 }
Beispiel #2
0
 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());
   }
 }
  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());
              }
            }
          }
        }
      }
    }
  }