All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.rinde.rinsim.cli.ArgumentParser Maven / Gradle / Ivy

There is a newer version: 4.4.6
Show newest version
/*
 * Copyright (C) 2011-2018 Rinde R.S. van Lon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.rinde.rinsim.cli;

import static com.google.common.base.Verify.verifyNotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import com.github.rinde.rinsim.cli.CliException.CauseType;
import com.github.rinde.rinsim.cli.Option.OptionArg;
import com.google.common.base.Enums;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;

/**
 * An argument parser is responsible for converting strings into the expected
 * type. If the parsing fails, a {@link CliException} is thrown. This class
 * contains a number of {@link ArgumentParser}s for common types.
 * @author Rinde van Lon
 * @param  The argument type.
 */
public abstract class ArgumentParser {
  /**
   * Separator of argument lists.
   */
  public static final char ARG_LIST_SEPARATOR = ',';

  /**
   * {@link Splitter} using {@link #ARG_LIST_SEPARATOR}.
   */
  public static final Splitter ARG_LIST_SPLITTER =
    Splitter.on(ARG_LIST_SEPARATOR);

  private static final ArgumentParser BOOLEAN = new BooleanParser();
  private static final ArgumentParser LONG =
    asParser("long", Longs.stringConverter());
  private static final ArgumentParser> LONG_LIST =
    asListParser("long list", Longs.stringConverter());
  private static final ArgumentParser INTEGER =
    asParser("int", Ints.stringConverter());
  private static final ArgumentParser> INTEGER_LIST =
    asListParser("int list", Ints.stringConverter());
  private static final ArgumentParser DOUBLE =
    asParser("double", Doubles.stringConverter());
  private static final ArgumentParser> DOUBLE_LIST =
    asListParser("double list", Doubles.stringConverter());
  private static final ArgumentParser STRING =
    asParser("string", Functions.identity());
  private static final ArgumentParser> STRING_LIST =
    asListParser("string list", Functions.identity());

  private final String name;

  ArgumentParser(String nm) {
    name = nm;
  }

  abstract V parse(OptionArg option, String arg);

  String name() {
    return name;
  }

  /**
   * @return {@link Integer} parser.
   */
  public static ArgumentParser intParser() {
    return INTEGER;
  }

  /**
   * @return List of {@link Integer}s parser.
   */
  public static ArgumentParser> intListParser() {
    return INTEGER_LIST;
  }

  /**
   * A prefixed int list allows arguments such as: c0,..,c4, this
   * generates a list containing the following five elements
   * c0,c1,c2,c3,c4.
   * @param prefix The prefix string.
   * @return A new argument parser with the specified prefix.
   */
  public static ArgumentParser> prefixedIntList(String prefix) {
    return new PrefixedIntListParser(prefix);
  }

  /**
   * @return {@link Double} parser.
   */
  public static ArgumentParser doubleParser() {
    return DOUBLE;
  }

  /**
   * @return List of {@link Double}s parser.
   */
  public static ArgumentParser> doubleListParser() {
    return DOUBLE_LIST;
  }

  /**
   * @return {@link String} parser.
   */
  public static ArgumentParser stringParser() {
    return STRING;
  }

  /**
   * @return List of {@link String}s parser.
   */
  public static ArgumentParser> stringListParser() {
    return STRING_LIST;
  }

  /**
   * @return {@link Boolean} parser.
   */
  public static ArgumentParser booleanParser() {
    return BOOLEAN;
  }

  /**
   * @return {@link Long} parser.
   */
  public static ArgumentParser longParser() {
    return LONG;
  }

  /**
   * @return List of {@link Long}s parser.
   */
  public static ArgumentParser> longListParser() {
    return LONG_LIST;
  }

  /**
   * Create a parser for {@link Enum}s.
   * @param name The name for the value of the option (typically the enum name).
   * @param enumClass The class of the enum.
   * @param  The class of the enum.
   * @return A new {@link ArgumentParser} for instances of the specified enum.
   */
  public static > ArgumentParser enumParser(String name,
      Class enumClass) {
    return asParser(name, Enums.stringConverter(enumClass));
  }

  /**
   * Create a parser for lists of {@link Enum}s.
   * @param name The name for the values of the option (typically the enum name
   *          with 'list' appended).
   * @param enumClass The class of the enum.
   * @param  The class of the enum.
   * @return A new {@link ArgumentParser} for lists of instances of the
   *         specified enum.
   */
  public static > ArgumentParser> enumListParser(
      String name, Class enumClass) {
    return asListParser(name, Enums.stringConverter(enumClass));
  }

  /**
   * Constructs a new {@link ArgumentParser} based on the specified
   * {@link Function}.
   * @param name The name of the value that is expected for the option.
   * @param func A function that converts string to the expected type.
   * @param  The type that it is expected.
   * @return A new instance.
   */
  public static  ArgumentParser asParser(String name,
      Function func) {
    return new FunctionToParserAdapter<>(name, func);
  }

  /**
   * Constructs a new {@link ArgumentParser} for lists based on the specified
   * {@link Function}.
   * @param name The name of the value that is expected for the option.
   * @param func A function that converts string to the expected type.
   * @param  The type that it is expected.
   * @return A new instance.
   */
  public static  ArgumentParser> asListParser(String name,
      Function func) {
    return new FunctionToListParserAdapter<>(name, func);
  }

  static CliException convertIAE(Option option, IllegalArgumentException e,
      String value, String argName) {

    return new CliException(
      String.format("The option %s expects a %s, found '%s'.",
        option, argName, value),
      e, CauseType.INVALID_ARG_FORMAT, option);
  }

  static class BooleanParser extends ArgumentParser {
    BooleanParser() {
      super("boolean");
    }

    @Override
    Boolean parse(OptionArg option, String arg) {
      if ("T".equalsIgnoreCase(arg)
        || "true".equalsIgnoreCase(arg)
        || "1".equals(arg)) {
        return true;
      } else if ("F".equalsIgnoreCase(arg)
        || "false".equalsIgnoreCase(arg)
        || "0".equals(arg)) {
        return false;
      }
      throw new CliException(
        "Expected a boolean but found: '" + arg + "'.",
        CauseType.INVALID_ARG_FORMAT,
        option);
    }
  }

  static class FunctionToParserAdapter extends ArgumentParser {
    private final Function converter;

    FunctionToParserAdapter(String nm, Function conv) {
      super(nm);
      converter = conv;
    }

    @Override
    T parse(OptionArg option, String arg) {
      try {
        return verifyNotNull(converter.apply(arg),
          "Converter should never return null.");
      } catch (final IllegalArgumentException e) {
        throw convertIAE(option, e, arg, name());
      }
    }
  }

  static class FunctionToListParserAdapter>
      extends ArgumentParser> {
    private final Function converter;

    FunctionToListParserAdapter(String nm, Function conv) {
      super(nm);
      converter = conv;
    }

    @Override
    List parse(OptionArg> option, String arg) {
      final Iterable strings = ARG_LIST_SPLITTER.split(arg);
      try {
        return ImmutableList.copyOf(Iterables.transform(strings, converter));
      } catch (final IllegalArgumentException e) {
        throw convertIAE(option, e, arg, name());
      }
    }

  }

  static class PrefixedIntListParser extends ArgumentParser> {
    private final String prefix;
    private final Pattern pattern;

    PrefixedIntListParser(String prefx) {
      super("prefixed int list");
      prefix = prefx;
      pattern = Pattern.compile(prefix + "\\d+");
    }

    @Override
    List parse(OptionArg> option, String value) {
      // can not be empty
      final List list = Splitter.on(ARG_LIST_SEPARATOR)
        .splitToList(value);

      final PeekingIterator it = Iterators
        .peekingIterator(list.iterator());

      final List generatedList = new ArrayList<>();
      while (it.hasNext()) {
        final String cur = it.next();
        if ("..".equals(cur)) {
          CliException.checkArgFormat(!generatedList.isEmpty(),
            option,
            "'..' cannot be the first item in the list.");
          CliException.checkArgFormat(it.hasNext(),
            option,
            "After '..' at least one more item is expected.");

          final String prev = generatedList.get(generatedList.size() - 1);
          final int prevNum = Integer.parseInt(prev.substring(prefix.length()));

          final String next = it.peek();
          checkItemFormat(option, next);
          final int nextNum = Integer.parseInt(next.substring(prefix.length()));

          CliException.checkArgFormat(prevNum + 1 < nextNum,
            option,
            "The items adjacent to '..' must be >= 0 and at least one apart. "
              + "Found '%s' and '%s'.",
            prevNum,
            nextNum);

          for (int i = prevNum + 1; i < nextNum; i++) {
            generatedList.add(prefix + Integer.toString(i));
          }
        } else {
          checkItemFormat(option, cur);
          generatedList.add(cur);
        }
      }
      return generatedList;
    }

    void checkItemFormat(Option opt, String str) {
      CliException.checkArgFormat(pattern.matcher(str).matches(),
        opt,
        "'%s' does not match expected pattern: '%s'",
        str,
        pattern.pattern());
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy