/* Copyright 2013 Jonatan Jönsson * * 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 se.softhouse.jargo; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.repeat; import static com.google.common.collect.Lists.newArrayListWithCapacity; import static java.util.Collections.emptyList; import static se.softhouse.common.strings.Describables.format; import static se.softhouse.jargo.ArgumentExceptions.forMissingNthParameter; import static se.softhouse.jargo.ArgumentExceptions.forMissingParameter; import static se.softhouse.jargo.ArgumentExceptions.withMessage; import static se.softhouse.jargo.ArgumentExceptions.wrapException; import java.io.File; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import se.softhouse.common.numbers.NumberType; import se.softhouse.common.strings.StringBuilders; import se.softhouse.jargo.Argument.ParameterArity; import se.softhouse.jargo.ArgumentExceptions.MissingParameterException; import se.softhouse.jargo.CommandLineParserInstance.ArgumentIterator; import se.softhouse.jargo.internal.Texts.UserErrors; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.base.Supplier; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * <pre> * Gives you static access to implementations of the {@link StringParser} interface. * All methods return {@link Immutable} parsers. * Most methods here even return the same instance for every call. * If you want to customize one of these parsers you can use a {@link ForwardingStringParser}. * * By {@link StringParser#defaultValue() default} most parsers return <code>zero</code> or otherwise * sane defaults. The only exception being {@link #enumParser(Class)} which returns null instead * of the first enum constant available. * </pre> */ @Immutable public final class StringParsers { private StringParsers() { } /** * The simplest possible parser, it simply returns the strings it's given to parse. */ @CheckReturnValue @Nonnull public static StringParser<String> stringParser() { return StringStringParser.STRING; } @VisibleForTesting enum StringStringParser implements StringParser<String> { /** * Simply returns the strings it's given to parse */ STRING { @Override public String parse(String value, Locale locale) throws ArgumentException { return value; } }; // Put other StringParser<String> parsers here @Override public String descriptionOfValidValues(Locale locale) { return "any string"; } @Override public String defaultValue() { return ""; } @Override public String metaDescription() { return "<string>"; } } /** * <pre> * A parser that parse strings with {@link Boolean#valueOf(String)} * The {@link StringParser#defaultValue() default value} is <code>false</code> * </pre> */ @CheckReturnValue @Nonnull public static StringParser<Boolean> booleanParser() { return BooleanParser.INSTANCE; } private static final class BooleanParser implements StringParser<Boolean> { private static final BooleanParser INSTANCE = new BooleanParser(); @Override public Boolean parse(final String value, Locale locale) { return Boolean.valueOf(value); } @Override public String descriptionOfValidValues(Locale locale) { return "true or false"; } @Override public Boolean defaultValue() { return false; } @Override public String metaDescription() { return "<boolean>"; } } /** * A parser that creates {@link File}s using {@link File#File(String)} for input strings.<br> * The {@link StringParser#defaultValue() default value} is a {@link File} representing the * current working directory (cwd). */ @CheckReturnValue @Nonnull public static StringParser<File> fileParser() { return FileParser.INSTANCE; } private static final class FileParser implements StringParser<File> { private static final FileParser INSTANCE = new FileParser(); @Override public File parse(final String value, Locale locale) { return new File(value); } @Override public String descriptionOfValidValues(Locale locale) { return "a file path"; } @Override public File defaultValue() { return new File("."); } @Override public String metaDescription() { return "<path>"; } } /** * <pre> * A parser that returns the first character in a {@link String} as a {@link Character} and that * throws {@link ArgumentException} for any given {@link String} with more than * one {@link Character}. * The {@link StringParser#defaultValue()} is the {@link Ascii#NUL} character. * </pre> */ @CheckReturnValue @Nonnull public static StringParser<Character> charParser() { return CharParser.INSTANCE; } private static final class CharParser implements StringParser<Character> { private static final CharParser INSTANCE = new CharParser(); @Override public Character parse(final String value, Locale locale) throws ArgumentException { if(value.length() != 1) throw withMessage(format(UserErrors.INVALID_CHAR, value)); return value.charAt(0); } @Override public String descriptionOfValidValues(Locale locale) { return "any unicode character"; } @Override public Character defaultValue() { return 0; } @Override public String metaDescription() { return "<character>"; } } /** * <pre> * A parser that uses {@link Enum#valueOf(Class, String)} to {@link StringParser#parse(String, Locale) parse} input strings. * * <b>Case sensitivity note:</b> First a direct match with the input value in upper case is * made, if that fails a direct match without converting the case is made, * if that also fails an {@link ArgumentException} is thrown. This order of execution is * based on the fact that users typically don't upper case their input while * <nobr><a href="http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-135099.html#367">java naming conventions</a> * recommends upper case for enum constants.</nobr> * </pre> * * <b>Default value:</b> <code>null</code> is used as {@link StringParser#defaultValue()} and * not any of the enum values in {@code enumToHandle} * * @param enumToHandle the {@link Class} literal for the {@link Enum} to parse strings into * @return a {@link StringParser} that parses strings into enum values of the type {@code T} */ @CheckReturnValue @Nonnull public static <T extends Enum<T>> StringParser<T> enumParser(final Class<T> enumToHandle) { return new EnumParser<T>(enumToHandle); } private static final class EnumParser<E extends Enum<E>> implements StringParser<E> { private final Class<E> enumType; private EnumParser(final Class<E> enumToHandle) { enumType = checkNotNull(enumToHandle); } @Override public E parse(final String value, final Locale locale) throws ArgumentException { try { return Enum.valueOf(enumType, value.toUpperCase(Locale.US)); } catch(IllegalArgumentException noEnumFoundWithUpperCase) { try { // Try an exact match just in case an enum value doesn't follow java naming // guidelines return Enum.valueOf(enumType, value); } catch(IllegalArgumentException noEnumFoundWithExactMatch) { throw withMessage(format(UserErrors.INVALID_ENUM_VALUE, value, new Object(){ @Override public String toString() { // Lazily call this as it's going over all enum values and converting // them to strings return descriptionOfValidValues(locale); } }), noEnumFoundWithExactMatch); } } } @Override public String descriptionOfValidValues(Locale locale) { E[] enumValues = enumType.getEnumConstants(); StringBuilder values = StringBuilders.withExpectedSize(enumValues.length * AVERAGE_ENUM_NAME_LENGTH); values.append('{'); Joiner.on(" | ").appendTo(values, enumValues); values.append('}'); return values.toString(); } private static final int AVERAGE_ENUM_NAME_LENGTH = 10; @Override public E defaultValue() { return null; } @Override public String metaDescription() { return "<" + enumType.getSimpleName() + ">"; } } /** * Parses {@link String}s into {@link Byte}s using {@link NumberType#parse(String, Locale)} */ @CheckReturnValue @Nonnull public static StringParser<Byte> byteParser() { return NumberParser.BYTE; } /** * Parses {@link String}s into {@link Short}s using {@link NumberType#parse(String, Locale)} */ @CheckReturnValue @Nonnull public static StringParser<Short> shortParser() { return NumberParser.SHORT; } /** * Parses {@link String}s into {@link Integer}s using {@link NumberType#parse(String, Locale)} */ @CheckReturnValue @Nonnull public static StringParser<Integer> integerParser() { return NumberParser.INTEGER; } /** * Parses {@link String}s into {@link Long}s using {@link NumberType#parse(String, Locale)} */ @CheckReturnValue @Nonnull public static StringParser<Long> longParser() { return NumberParser.LONG; } /** * Parses {@link String}s into {@link BigInteger}s using * {@link NumberType#parse(String, Locale)} */ @CheckReturnValue @Nonnull public static StringParser<BigInteger> bigIntegerParser() { return NumberParser.BIG_INTEGER; } /** * Parses {@link String}s into {@link BigDecimal}s using * {@link NumberType#parse(String, Locale)} */ @CheckReturnValue @Nonnull public static StringParser<BigDecimal> bigDecimalParser() { return NumberParser.BIG_DECIMAL; } private static final class NumberParser<N extends Number & Comparable<N>> implements StringParser<N> { private static final StringParser<Byte> BYTE = new NumberParser<Byte>(NumberType.BYTE); private static final StringParser<Short> SHORT = new NumberParser<Short>(NumberType.SHORT); private static final StringParser<Integer> INTEGER = new NumberParser<Integer>(NumberType.INTEGER); private static final StringParser<Long> LONG = new NumberParser<Long>(NumberType.LONG); private static final StringParser<BigInteger> BIG_INTEGER = new NumberParser<BigInteger>(NumberType.BIG_INTEGER); private static final StringParser<BigDecimal> BIG_DECIMAL = new NumberParser<BigDecimal>(NumberType.BIG_DECIMAL); private final NumberType<N> type; private NumberParser(NumberType<N> type) { this.type = type; } @Override public N parse(String argument, Locale locale) throws ArgumentException { try { return type.parse(argument, locale); } catch(IllegalArgumentException invalidNumber) { throw wrapException(invalidNumber); } } @Override public String descriptionOfValidValues(Locale locale) { return type.descriptionOfValidValues(locale); } @Override public N defaultValue() { return type.defaultValue(); } @Override public String metaDescription() { return '<' + type.name() + '>'; } @Override public String toString() { return descriptionOfValidValues(Locale.US); } } /** * <pre> * A {@link Function} that uses {@link StringParser#parse(String, Locale) parse} for input {@link String}s. * </pre> * * For example: * * <pre class="prettyprint"> * <code class="language-java"> * List<Integer> result = Lists.transform(asList("1", "3", "2"), asFunction(integerParser())); * </code> * </pre> * * <b>Note:</b>This method may be removed in the future if Guava is removed as a dependency.<br> * <b>Note:</b>The parser will pass the {@link Locale#getDefault()} to * {@link StringParser#parse(String, Locale) parse}. Use * {@link StringParsers#asFunction(StringParser, Locale)} to specify a specific {@link Locale} * * @param parser the parser to expose as a {@link Function} * @return {@code parser} exposed in a {@link Function} that throws {@link ArgumentException} * when given faulty values. */ @Nonnull @CheckReturnValue @Beta public static <T> Function<String, T> asFunction(final StringParser<T> parser) { return asFunction(parser, Locale.getDefault()); } /** * {@link Locale} version of {@link #asFunction(StringParser)} */ @Nonnull @CheckReturnValue @Beta public static <T> Function<String, T> asFunction(final StringParser<T> parser, final Locale localeToUse) { checkNotNull(parser); checkNotNull(localeToUse); return new Function<String, T>(){ @Override public T apply(@Nonnull String input) { return parser.parse(input, localeToUse); } }; } /** * <pre> * Makes it possible to convert several (or zero) {@link String}s into a single {@code T} value. * For a simpler one use {@link StringParser}. * * {@link Argument} is passed to the functions that produces text for the usage, * it can't be a member of this class because one parser can be referenced * from multiple different {@link Argument}s so this is extrinsic state. * * @param <T> the type this parser parses strings into * </pre> */ abstract static class InternalStringParser<T> { /** * @param arguments the arguments given from the command line where * {@link ArgumentIterator#next()} points to the parameter * for a named argument, for an indexed argument it points to the single unnamed * argument * @param previousOccurance the previously parsed value for this * argument if it appears several times, otherwise null * @param argumentSettings argument settings for this parser, * can be used for providing good exception messages. * @param locale the locale to parse strings with, may matter when parsing numbers, dates * etc * @return the parsed value * @throws ArgumentException if an error occurred while parsing the value */ abstract T parse(final ArgumentIterator arguments, @Nullable final T previousOccurance, final Argument<?> argumentSettings, Locale locale) throws ArgumentException; /** * Describes the values this parser accepts * * @return a description string to show in usage texts */ @Nonnull abstract String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale); /** * If you can provide a suitable value do so, it will look much better * in the usage texts and providing sane defaults makes your program/code easier to use, * otherwise return {@code null} */ @Nullable abstract T defaultValue(); /** * Returns a description of {@code value}, or null if no description is * needed */ @Nullable String describeValue(@Nullable T value) { return String.valueOf(value); } @Nonnull abstract String metaDescription(Argument<?> argumentSettings); String metaDescriptionInLeftColumn(Argument<?> argumentSettings) { return metaDescription(argumentSettings); } String metaDescriptionInRightColumn(Argument<?> argumentSettings) { return metaDescription(argumentSettings); } /** * Returns the {@link ParameterArity} * {@link #parse(ArgumentIterator, Object, Argument, Locale) parse} expects. */ ParameterArity parameterArity() { return ParameterArity.AT_LEAST_ONE_ARGUMENT; } } @CheckReturnValue @Nonnull static InternalStringParser<Boolean> optionParser(final boolean defaultValue) { if(defaultValue) return OptionParser.DEFAULT_TRUE; return OptionParser.DEFAULT_FALSE; } /** * Inherits from InternalStringParser because implementing StringParser * would make it require a parameter which an Option doesn't. */ static final class OptionParser extends InternalStringParser<Boolean> { private static final OptionParser DEFAULT_FALSE = new OptionParser(false); private static final OptionParser DEFAULT_TRUE = new OptionParser(true); private final Boolean defaultValue; private OptionParser(final boolean defaultValue) { this.defaultValue = defaultValue; } @Override Boolean parse(ArgumentIterator arguments, Boolean previousOccurance, Argument<?> argumentSettings, Locale locale) throws ArgumentException { return !defaultValue; } @Override public Boolean defaultValue() { return defaultValue; } /** * Only the existence of the flag matters, no specific value. * Use {@link ArgumentBuilder#description(String)} to describe this * argument. */ @Override public String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale) { return ""; } @Override String metaDescription(Argument<?> argumentSettings) { // Options don't have parameters so there's no parameter to explain return ""; } @Override ParameterArity parameterArity() { return ParameterArity.NO_ARGUMENTS; } } static final class HelpParser extends InternalStringParser<String> { static final HelpParser INSTANCE = new HelpParser(); @Override String parse(ArgumentIterator arguments, String previousOccurance, Argument<?> argumentSettings, Locale locale) throws ArgumentException { throw arguments.currentParser().helpFor(arguments, locale); } @Override String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale) { return "an argument to print help for"; } @Override String defaultValue() { return "If no specific parameter is given the whole usage text is given"; } @Override String metaDescription(Argument<?> argumentSettings) { return "<argument-to-print-help-for>"; } } /** * Runs a {@link Runnable} when {@link StringParser#parse(String, Locale) parse} is invoked. */ static final class RunnableParser extends InternalStringParser<Object> { final Runnable target; RunnableParser(Runnable target) { this.target = target; } @Override Object parse(ArgumentIterator arguments, Object previousOccurance, Argument<?> argumentSettings, Locale locale) throws ArgumentException { target.run(); return null; } @Override String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale) { return ""; } @Override Object defaultValue() { return null; } @Override String metaDescription(Argument<?> argumentSettings) { return ""; } } /** * Base class for {@link StringParser}s that uses a sub parser to parse element values and puts * them into a {@link List} */ abstract static class ListParser<T> extends InternalStringParser<List<T>> { private final InternalStringParser<T> elementParser; private ListParser(final InternalStringParser<T> elementParser) { this.elementParser = elementParser; } protected final InternalStringParser<T> elementParser() { return elementParser; } @Override public String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale) { return elementParser.descriptionOfValidValues(argumentSettings, locale); } @Override String metaDescription(Argument<?> argumentSettings) { return elementParser.metaDescription(argumentSettings); } @Override public List<T> defaultValue() { return emptyList(); } @Override String describeValue(List<T> value) { if(value == null) return "null"; if(value.isEmpty()) return "Empty list"; Iterator<T> values = value.iterator(); String firstValue = String.valueOf(values.next()); StringBuilder sb = StringBuilders.withExpectedSize(value.size() * firstValue.length()); sb.append(firstValue); while(values.hasNext()) { sb.append(", ").append(String.valueOf(values.next())); } return sb.toString(); } } /** * Implements {@link ArgumentBuilder#arity(int)} */ static final class FixedArityParser<T> extends ListParser<T> { private final int arity; FixedArityParser(final InternalStringParser<T> parser, final int arity) { super(parser); this.arity = arity; } @Override List<T> parse(final ArgumentIterator arguments, final List<T> list, final Argument<?> argumentSettings, Locale locale) throws ArgumentException { List<T> parsedArguments = newArrayListWithCapacity(arity); for(int i = 0; i < arity; i++) { try { T parsedValue = elementParser().parse(arguments, null, argumentSettings, locale); parsedArguments.add(parsedValue); } catch(MissingParameterException exception) { // Wrap exception to more clearly specify which parameter that is missing throw forMissingNthParameter(exception, i); } } return parsedArguments; } @Override public List<T> defaultValue() { T defaultValue = elementParser().defaultValue(); List<T> listFilledWithDefaultValues = newArrayListWithCapacity(arity); for(int i = 0; i < arity; i++) { listFilledWithDefaultValues.add(defaultValue); } return listFilledWithDefaultValues; } @Override String metaDescriptionInLeftColumn(Argument<?> argumentSettings) { String metaDescriptionForValue = metaDescription(argumentSettings); return metaDescriptionForValue + repeat(" " + metaDescriptionForValue, arity - 1); } } /** * Implements {@link ArgumentBuilder#variableArity()} */ static final class VariableArityParser<T> extends ListParser<T> { VariableArityParser(final InternalStringParser<T> parser) { super(parser); } @Override List<T> parse(final ArgumentIterator arguments, final List<T> list, final Argument<?> argumentSettings, Locale locale) throws ArgumentException { List<T> parsedArguments = newArrayListWithCapacity(arguments.nrOfRemainingArguments()); while(arguments.hasNext()) { T parsedValue = elementParser().parse(arguments, null, argumentSettings, locale); parsedArguments.add(parsedValue); } return parsedArguments; } @Override String metaDescriptionInLeftColumn(Argument<?> argumentSettings) { String metaDescriptionForValue = metaDescription(argumentSettings); return metaDescriptionForValue + " ..."; } @Override ParameterArity parameterArity() { return ParameterArity.VARIABLE_AMOUNT; } } /** * Implements {@link ArgumentBuilder#splitWith(String)}. * * @param <T> the type that's separated by the {@code valueSeparator} */ static final class StringSplitterParser<T> extends ListParser<T> { @Nonnull private final String valueSeparator; @Nonnull private final Splitter splitter; StringSplitterParser(final String valueSeparator, final InternalStringParser<T> parser) { super(parser); this.valueSeparator = valueSeparator; this.splitter = Splitter.on(valueSeparator).omitEmptyStrings().trimResults(); } @Override List<T> parse(final ArgumentIterator arguments, final List<T> oldValue, final Argument<?> argumentSettings, Locale locale) throws ArgumentException { if(!arguments.hasNext()) throw forMissingParameter(argumentSettings); String values = arguments.next(); List<T> result = new ArrayList<T>(); for(String value : splitter.split(values)) { ArgumentIterator argument = ArgumentIterator.forArguments(Arrays.asList(value)); T parsedValue = elementParser().parse(argument, null, argumentSettings, locale); result.add(parsedValue); } return result; } @Override String metaDescriptionInLeftColumn(Argument<?> argumentSettings) { String metaDescriptionForValue = metaDescription(argumentSettings); return metaDescriptionForValue + valueSeparator + metaDescriptionForValue + valueSeparator + "..."; } } /** * Implements {@link ArgumentBuilder#repeated()}. * * @param <T> type of the repeated values (such as {@link Integer} for {@link #integerParser()} */ static final class RepeatedArgumentParser<T> extends ListParser<T> { RepeatedArgumentParser(final InternalStringParser<T> parser) { super(parser); } @Override List<T> parse(final ArgumentIterator arguments, List<T> previouslyCreatedList, final Argument<?> argumentSettings, Locale locale) throws ArgumentException { T parsedValue = elementParser().parse(arguments, null, argumentSettings, locale); List<T> listToStoreRepeatedValuesIn = previouslyCreatedList; if(listToStoreRepeatedValuesIn == null) { listToStoreRepeatedValuesIn = Lists.newLinkedList(); } listToStoreRepeatedValuesIn.add(parsedValue); return listToStoreRepeatedValuesIn; } } /** * Implements {@link ArgumentBuilder#asPropertyMap()} & * {@link ArgumentBuilder#asKeyValuesWithKeyParser(StringParser)}. * * @param <K> the type of key in the resulting map * @param <V> the type of values in the resulting map */ @VisibleForTesting static final class KeyValueParser<K, V> extends InternalStringParser<Map<K, V>> { @Nonnull private final InternalStringParser<V> valueParser; @Nonnull private final StringParser<K> keyParser; @Nonnull private final Predicate<? super V> valueLimiter; @Nonnull private final Supplier<? extends Map<K, V>> defaultMap; KeyValueParser(StringParser<K> keyParser, InternalStringParser<V> valueParser, Predicate<? super V> valueLimiter, @Nullable Supplier<? extends Map<K, V>> defaultMap, @Nullable final Supplier<? extends V> defaultValue) { this.valueParser = valueParser; this.keyParser = keyParser; this.valueLimiter = valueLimiter; if(defaultMap == null) { this.defaultMap = new Supplier<Map<K, V>>(){ @Override public Map<K, V> get() { if(defaultValue != null) return new LinkedHashMap<K, V>(){ private static final long serialVersionUID = 1L; @Override public V get(Object key) { if(super.containsKey(key)) return super.get(key); return defaultValue.get(); } }; return Maps.newLinkedHashMap(); } }; } else { this.defaultMap = defaultMap; } } @Override Map<K, V> parse(final ArgumentIterator arguments, Map<K, V> previousMap, final Argument<?> argumentSettings, Locale locale) throws ArgumentException { Map<K, V> map = previousMap; if(map == null) { map = defaultValue(); } String keyValue = arguments.next(); String key = getKey(keyValue, argumentSettings); K parsedKey = keyParser.parse(key, locale); V oldValue = map.get(parsedKey); // Hide what we just did to the parser that handles the "value" if(valueParser.parameterArity() != ParameterArity.NO_ARGUMENTS) { arguments.setNextArgumentTo(getValue(key, keyValue, argumentSettings)); } V parsedValue = valueParser.parse(arguments, oldValue, argumentSettings, locale); try { if(!valueLimiter.apply(parsedValue)) throw withMessage(format(UserErrors.DISALLOWED_PROPERTY_VALUE, parsedKey, parsedValue, valueLimiter)); } catch(IllegalArgumentException e) { throw wrapException(e); } map.put(parsedKey, parsedValue); return map; } /** * Fetch "key" from "key=value" */ private String getKey(String keyValue, Argument<?> argumentSettings) throws ArgumentException { if(valueParser.parameterArity() == ParameterArity.NO_ARGUMENTS) return keyValue;// Consume the whole string as the key as there's no value to parse String separator = argumentSettings.separator(); int keyEndIndex = keyValue.indexOf(separator); if(keyEndIndex == -1) throw withMessage(format(UserErrors.MISSING_KEY_VALUE_SEPARATOR, argumentSettings, keyValue, separator)); return keyValue.substring(0, keyEndIndex); } /** * Fetch "value" from "key=value" */ private String getValue(String key, String keyValue, Argument<?> argumentSettings) { return keyValue.substring(key.length() + argumentSettings.separator().length()); } @Override public String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale) { String keyMeta = '"' + keyParser.metaDescription() + '"'; String valueMeta = '"' + valueParser.metaDescription(argumentSettings) + '"'; String keyDescription = keyParser.descriptionOfValidValues(locale); String valueDescription; if(valueLimiter != Predicates.alwaysTrue()) { valueDescription = valueLimiter.toString(); } else { valueDescription = valueParser.descriptionOfValidValues(argumentSettings, locale); } return "where " + keyMeta + " is " + keyDescription + " and " + valueMeta + " is " + valueDescription; } @Override public Map<K, V> defaultValue() { return defaultMap.get(); } @Override String metaDescription(Argument<?> argumentSettings) { String keyMeta = keyParser.metaDescription(); String separator = argumentSettings.separator(); String valueMeta = valueParser.metaDescription(argumentSettings); return keyMeta + separator + valueMeta; } } static final class StringParserBridge<T> extends InternalStringParser<T> implements StringParser<T> { private final StringParser<T> stringParser; /** * A bridge between the {@link StringParser} & {@link InternalStringParser} interfaces. * * @param parserToBridge the {@link StringParser} to expose as a * {@link InternalStringParser} */ StringParserBridge(StringParser<T> parserToBridge) { stringParser = parserToBridge; } @Override T parse(ArgumentIterator arguments, T previousOccurance, Argument<?> argumentSettings, Locale locale) throws ArgumentException { if(!arguments.hasNext()) throw forMissingParameter(argumentSettings); return parse(arguments.next(), locale); } @Override String descriptionOfValidValues(Argument<?> argumentSettings, Locale locale) { return descriptionOfValidValues(locale); } @Override String metaDescription(Argument<?> argumentSettings) { return metaDescription(); } @Override public T parse(String argument, Locale locale) throws ArgumentException { return stringParser.parse(argument, locale); } @Override public String descriptionOfValidValues(Locale locale) { return stringParser.descriptionOfValidValues(locale); } @Override public String metaDescription() { return stringParser.metaDescription(); } @Override public T defaultValue() { return stringParser.defaultValue(); } @Override public String toString() { return descriptionOfValidValues(Locale.US); } } }