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

se.softhouse.jargo.ArgumentBuilder Maven / Gradle / Ivy

/* 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.Objects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Iterables.isEmpty;
import static java.util.Collections.emptyList;
import static se.softhouse.common.guavaextensions.Functions2.listTransformer;
import static se.softhouse.common.guavaextensions.Predicates2.listPredicate;
import static se.softhouse.common.strings.Descriptions.EMPTY_STRING;
import static se.softhouse.common.strings.Descriptions.withString;
import static se.softhouse.jargo.StringParsers.optionParser;
import static se.softhouse.jargo.StringParsers.stringParser;

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.OverridingMethodsMustInvokeSuper;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;

import se.softhouse.common.guavaextensions.Functions2;
import se.softhouse.common.guavaextensions.Suppliers2;
import se.softhouse.common.strings.Describer;
import se.softhouse.common.strings.Describers;
import se.softhouse.common.strings.Description;
import se.softhouse.jargo.Argument.ParameterArity;
import se.softhouse.jargo.ForwardingStringParser.SimpleForwardingStringParser;
import se.softhouse.jargo.StringParsers.FixedArityParser;
import se.softhouse.jargo.StringParsers.InternalStringParser;
import se.softhouse.jargo.StringParsers.KeyValueParser;
import se.softhouse.jargo.StringParsers.RepeatedArgumentParser;
import se.softhouse.jargo.StringParsers.StringParserBridge;
import se.softhouse.jargo.StringParsers.StringSplitterParser;
import se.softhouse.jargo.StringParsers.VariableArityParser;
import se.softhouse.jargo.internal.Texts.ProgrammaticErrors;
import se.softhouse.jargo.internal.Texts.UserErrors;

import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.Ranges;

/**
 * 
 * Responsible for configuring and building {@link Argument} instances.
 * Example builders can be created through the {@link Arguments}.
 * 
 * Note:The code examples (for each method) assumes that all methods in {@link Arguments} have been statically imported.
 * 
 * Note:Some methods needs to be called in a specific order
 * (to make the generic type system produce the correct type) and to guide the
 * caller, such invalid orders are documented with {@link Deprecated}. If those warnings
 * are ignored {@link IllegalStateException} will be thrown at the offending call.
 * 
 * @param  the type of the subclass extending this class.
 * 		Concept borrowed from: Ansgar.Konermann's blog
 * 		The pattern also resembles the Curiously recurring template pattern
 * @param  the type of arguments the built {@link Argument} instance should handle,
 * 	such as {@link Integer} in the case of {@link Arguments#integerArgument(String...)}
 * 
*/ @NotThreadSafe public abstract class ArgumentBuilder, T> { static final String DEFAULT_SEPARATOR = " "; // ArgumentSetting variables, think about #copy() when adding new ones @Nonnull private List names = emptyList(); @Nonnull private Description description = EMPTY_STRING; private boolean required = false; @Nonnull private String separator = DEFAULT_SEPARATOR; @Nonnull private Optional localeOverride = Optional.absent(); private boolean ignoreCase = false; private boolean isPropertyMap = false; private boolean isAllowedToRepeat = false; @Nonnull private Optional metaDescription = Optional.absent(); private boolean hideFromUsage = false; private ParameterArity parameterArity = ParameterArity.AT_LEAST_ONE_ARGUMENT; // Members that uses the T type, think about // ListArgumentBuilder#copyAsListBuilder() when adding new ones @Nullable private Supplier defaultValueSupplier = null; @Nullable private Describer defaultValueDescriber = null; @Nonnull private Function finalizer = Functions.identity(); @Nonnull private Predicate limiter = Predicates.alwaysTrue(); @Nullable private final InternalStringParser internalStringParser; @Nonnull private final SELF myself; /** * Creates a default {@link ArgumentBuilder} that produces arguments that: *
    *
  • doesn't have any {@link #names(String...)}
  • *
  • has an empty {@link #description(String)}
  • *
  • isn't {@link #required()}
  • *
  • uses {@link StringParser#defaultValue()} on {@link #parser()} to produce default values
  • *
  • uses {@link Object#toString()} to describe the default value
  • *
  • uses {@link StringParser#metaDescription()} on {@link #parser()} to produce meta * descriptions
  • *
  • doesn't have any {@link #limitTo(Predicate)} set
  • *
* Typically invoked implicitly by subclasses. */ protected ArgumentBuilder() { this(null); } ArgumentBuilder(final InternalStringParser stringParser) { this.internalStringParser = stringParser; myself = self(); } // SELF is passed in by subclasses as a type-variable, so type-safety // is up to them @SuppressWarnings("unchecked") private SELF self() { return (SELF) this; } /** * Returns an {@link Immutable} {@link Argument} which can be passed to * {@link CommandLineParser#withArguments(Argument...)}
* When the parsing is done the parsed value for this * argument can be fetched with {@link ParsedArguments#get(Argument)}. * * @throws IllegalStateException if it's not possible to construct an {@link Argument} with the * current settings */ @CheckReturnValue @Nonnull public final Argument build() { return new Argument(this); } /** *
	 * Parses command line arguments and returns the value of the argument built
	 * by {@link #build()}, providing a simple one liner shortcut when faced with only one argument.
	 * 
* *
	 * 
	 * String[] args = {"--listen-port", "8090"};
	 * 
	 * int port = integerArgument("-p", "--listen-port").defaultValue(8080).description("The port clients should connect to.").parse(args);
	 * assertThat(port).isEqualTo(8090);
	 * 
	 * 
* * This is a shorthand method that should be used if only one {@link Argument} is expected as it * will throw if unexpected arguments are encountered. If several arguments are expected use * {@link CommandLineParser#withArguments(Argument...)} instead. * * @param actualArguments the arguments from the command line * @return the parsed value from the {@code actualArguments} * @throws ArgumentException if actualArguments isn't compatible with this * argument *
*/ @Nullable public final T parse(String ... actualArguments) throws ArgumentException { return build().parse(actualArguments); } /** * Returns a usage string for the argument that this builder builds. * Should only be used if one argument is supported, * otherwise the {@link CommandLineParser#usage()} method should be used instead. */ public final Usage usage() { return build().usage(); } /** *
	 * Returns a customized parser that the {@link Argument} will use to parse values.
	 * 
	 * This is a suitable place to verify the configuration of your parser.
	 * 
	 * If your {@link StringParser} doesn't support any configuration you can use
	 * {@link Arguments#withParser(StringParser)} directly instead of subclassing
	 * {@link ArgumentBuilder}
	 * 
	 * @return the {@link StringParser} that performs the actual parsing of an argument value
	 * @throws IllegalStateException if the parser have been configured wrongly
	 * 
*/ @Nonnull protected abstract StringParser parser(); @Nonnull InternalStringParser internalParser() { StringParser parser = parser(); if(parser != InternalArgumentBuilder.MARKER) return new StringParserBridge(parser); return internalStringParser; } /** * Specifies the argument names that triggers the argument being built. As commands sometimes * gets long and hard to understand it's recommended to also support long named arguments, * making the commands even longer but more readable instead
* Note: If you choose to use multiple {@link #required()} indexed arguments all of them * must have unique {@link #metaDescription(String)}s. This ensures that error messages better * can point out erroneous arguments * * @param argumentNames
    *
  • "-o" for a short named option/argument
  • *
  • "--option-name" for a long named option/argument
  • *
  • "-o", "--option-name" to give the user both choices
  • *
  • zero elements: makes this an indexed argument, meaning that the argument must * be given at the same position on the command line as it is given to * {@link CommandLineParser#withArguments(Argument...)} (not counting named * arguments).
  • *
* @return this builder */ public SELF names(final String ... argumentNames) { names = copyOf(argumentNames); return myself; } /** * Works just like {@link #names(String...)} but it takes an {@link Iterable} instead. * * @param argumentNames the list to use as argument names * @return this builder */ public SELF names(final Iterable argumentNames) { names = copyOf(argumentNames); return myself; } /** * Ignores the case of the argument names set by {@link ArgumentBuilder#names(String...)} * * @return this builder */ public final SELF ignoreCase() { ignoreCase = true; return myself; } /** * Uses {@code localeToUseForThisArgument} to handle this argument with instead of the one * passed to {@link CommandLineParser#locale(Locale)}, allowing different {@link Argument}s to * use different {@link Locale}s. * * @return this builder */ public final SELF locale(Locale localeToUseForThisArgument) { this.localeOverride = Optional.of(localeToUseForThisArgument); return myself; } /** * TODO: support resource bundles with i18n texts */ /** *
	 * Sets {@code theDescription} of what this argument does/means. Printed with
	 * {@link CommandLineParser#usage()}.
	 * 
	 * For instance, in:
	 * -l, --enable-logging      Output debug information to standard out
	 *                           Default: disabled
	 * "Output debug information to standard out" is {@code theDescription}
	 * @return this builder
	 * 
*/ public final SELF description(final String theDescription) { description = withString(theDescription); return myself; } /** *
	 * {@link Description} version of {@link #description(String)}.
	 * @return this builder
	 * 
*/ public final SELF description(final Description theDescription) { description = checkNotNull(theDescription); return myself; } /** *
	 * Makes {@link CommandLineParser#parse(String...)} throw
	 * {@link ArgumentException} if this argument isn't given.
	 * It's however preferred to use {@link #defaultValue(Object)} instead.
	 * 
	 * The {@link Argument#toString()} will be used to print each missing argument.
	 * 
	 * @return this builder
	 * @throws IllegalStateException if {@link #defaultValue(Object)} (or
	 *             {@link #defaultValueSupplier(Supplier)}) has been
	 *             called, because these two methods are mutually exclusive
	 * 
*/ public SELF required() { checkState(defaultValueSupplier == null, ProgrammaticErrors.DEFAULT_VALUE_AND_REQUIRED); required = true; return myself; } /** *
	 * Sets a default value to use for this argument. Overrides {@link StringParser#defaultValue()} which is used by default.
	 * Returned by {@link ParsedArguments#get(Argument)} when no argument was given.
	 * To create default values lazily see {@link ArgumentBuilder#defaultValueSupplier(Supplier)}.
	 * 
	 * Mutability:Remember that as {@link Argument} is {@link Immutable}
	 * this value should be so too if multiple argument parsings is going to take place.
	 * If mutability is wanted {@link ArgumentBuilder#defaultValueSupplier(Supplier)} should be used instead.
	 * 
	 * @return this builder
	 * @throws IllegalStateException if {@link #required()} has been called,
	 * because these two methods are mutually exclusive
	 */
	public SELF defaultValue(@Nullable final T value)
	{
		checkState(!required, ProgrammaticErrors.DEFAULT_VALUE_AND_REQUIRED);
		defaultValueSupplier = Suppliers.ofInstance(value);
		return myself;
	}

	/**
	 * 
	 * Sets a supplier that can supply default values in the absence of this argument
	 * 
	 * Note: Even if {@link #limitTo(Predicate)} is used, the {@link Supplier#get()} isn't called
	 * until the default value is actually needed ({@link ParsedArguments#get(Argument)}. If the
	 * default value is deemed non-allowed at that point an {@link IllegalStateException} is thrown.
	 * 
	 * Note: May be removed in the future if Guava is removed as a dependency
	 * 
	 * Wrap your supplier with {@link Suppliers#memoize(Supplier)} if you want to cache created values.
	 * 
	 * @return this builder
	 * @throws IllegalStateException if {@link #required()} has been called,
	 * because these two methods are mutually exclusive
	 */
	@Beta
	public SELF defaultValueSupplier(final Supplier aDefaultValueSupplier)
	{
		checkState(!required, ProgrammaticErrors.DEFAULT_VALUE_AND_REQUIRED);
		defaultValueSupplier = checkNotNull(aDefaultValueSupplier);
		return myself;
	}

	/**
	 * Provides a way to give the usage texts a better explanation of a default
	 * value than {@link Object#toString()} provides. Always prints {@code aDescription} regardless
	 * of what the default value is.
	 * 
	 * @param aDescription the description
	 * @return this builder
	 * @see Describers#withConstantString(String)
	 */
	public SELF defaultValueDescription(final String aDescription)
	{
		this.defaultValueDescriber = Describers.withConstantString(aDescription);
		return myself;
	}

	/**
	 * {@link Describer} version of {@link #defaultValueDescription(String)}
	 * 
	 * @param describer a describer
	 * @return this builder
	 */
	public SELF defaultValueDescriber(final Describer describer)
	{
		this.defaultValueDescriber = checkNotNull(describer);
		return myself;
	}

	/**
	 * 
	 * By default {@link StringParser}s provides a meta description (by implementing {@link StringParser#metaDescription()}
	 * that describes the type of data they expect. For instance, if you're writing a music player,
	 * a user of your application would (most probably) rather see:
	 * 
	 * --track-nr <track nr>    The track number to play (using {@code  metaDescription("") })
	 * 
	 * instead of
	 * 
	 * --track-nr <integer>     The track number to play (the default provided by {@link StringParser#metaDescription()})
	 * 
	 * So when using general data type parsers such as {@link StringParsers#integerParser()} you're better of if
	 * you provide a meta description that explains what the {@code integer} represents in the context of this argument.
	 * 
	 * Note: empty meta descriptions aren't allowed
	 * Note: the surrounding < & > aren't enforced or added automatically but it's preferred
	 * to have them because it makes a clear distinction between {@link #names(String...) argument names} and their parameters.
	 * 
	 * @param aMetaDescription "<track nr>" in the above example
	 * @return this builder
	 * @throws IllegalArgumentException if aMetaDescription is empty
	 * 
*/ public final SELF metaDescription(final String aMetaDescription) { checkArgument(aMetaDescription.length() > 0, ProgrammaticErrors.INVALID_META_DESCRIPTION); this.metaDescription = Optional.of(aMetaDescription); return myself; } /** * Hides this argument so that it's not displayed in the usage texts.
* It's recommended that hidden arguments have a reasonable {@link #defaultValue(Object)} and * aren't {@link #required()}, in fact this is recommended for all arguments. * * @return this builder */ public final SELF hideFromUsage() { this.hideFromUsage = true; return myself; } /** * For instance, "=" in --author=jjonsson * * @param aSeparator the character that separates the argument name and * argument value, defaults to a space * @return this builder */ public SELF separator(final String aSeparator) { separator = checkNotNull(aSeparator); return myself; } /** *
	 * Limits values parsed so that they conform to some specific rule.
	 * Only values for which {@link Predicate#apply(Object)} returns true, will be accepted.
	 * Other values will cause an {@link ArgumentException}.
	 * For example {@link Ranges#closed(Comparable, Comparable)} only allows values within a range.
	 * 
	 * To override the default error message that is generated with {@link UserErrors#DISALLOWED_VALUE}
	 * you can throw {@link IllegalArgumentException} from {@link Predicate#apply(Object)}. The detail
	 * message of that exception will be used by {@link ArgumentException#getMessageAndUsage()}.
	 * When this is needed it's generally recommended to write a parser of its own instead.
	 * 
	 * Note:{@link Object#toString() toString()} on {@code aLimiter} will replace {@link StringParser#descriptionOfValidValues(Locale)} in the usage
	 * 
	 * Note:The validity of any {@link #defaultValueSupplier(Supplier) default value} isn't checked until
	 * it's actually needed when {@link ParsedArguments#get(Argument)} is called. This is so
	 * because {@link Supplier#get()} (or {@link StringParser#defaultValue()}) could take an arbitrary long time.
	 * 
* * @param aLimiter a limiter * @return this builder */ @Beta public SELF limitTo(Predicate aLimiter) { limiter = checkNotNull(aLimiter); return myself; } /** *
	 * Makes this argument handle "property like" arguments:
	 * -Dproperty_name=value
	 * where
	 * "-D" is the string supplied to {@link #names(String...)},
	 * "property_name" is the {@link String} key in the resulting {@link Map},
	 * "=" is the {@link #separator(String)} (set to "=" if it hasn't been overridden already),
	 * "value" is decoded by the previously set {@link StringParser}.
	 * 
	 * 
	 * Tip: You can pass {@link #defaultValueDescriber(Describer)} a
	 * {@link Describers#mapDescriber(Map)} to describe each property that you
	 * support (given that you've used {@link ArgumentBuilder#defaultValue(Object)}
	 * to use sane defaults for your properties).
	 * 
	 * @return a new (more specific) builder
	 * @throws IllegalStateException if {@link #defaultValue(Object)} has been set before {@link #asPropertyMap()} is called
	 * 
	 * @see #asKeyValuesWithKeyParser(StringParser) to use other types as keys than {@link String}
	 * 
*/ @CheckReturnValue public final MapArgumentBuilder asPropertyMap() { checkState(defaultValueSupplier == null, ProgrammaticErrors.INVALID_CALL_ORDER, "defaultValue", "asPropertyMap"); return new MapArgumentBuilder(this, stringParser()); } /** *
	 * Makes this argument handle properties like arguments:
	 * -Dproperty_name=value
	 * where "-D" is one of the strings supplied to {@link #names(String...)},
	 * "property_name" is decoded by {@code keyParser} and
	 * "value" is decoded by the {@link StringParser} previously passed to the constructor.
	 * 
	 * For example:
	 * 
* *
	 * 
	 * Map<Integer, Integer> numberMap = Arguments.integerArgument("-N")
	 * 						.asKeyValuesWithKeyParser(StringParsers.integerParser())
	 * 						.parse("-N1=5", "-N2=10");
	 * assertThat(numberMap.get(1)).isEqualTo(5);
	 * 
	 * 
* * For this to work correctly it's paramount that {@code K} implements a * proper {@link Object#hashCode()} because it's going to be used a key in a {@link Map}. * * @return a new (more specific) builder * @throws IllegalStateException if a {@link #defaultValueDescriber(Describer)} or * {@link #defaultValue(Object)} has been set as they have no place in a property * map. */ @CheckReturnValue public final MapArgumentBuilder asKeyValuesWithKeyParser(StringParser keyParser) { checkState(defaultValueSupplier == null, ProgrammaticErrors.INVALID_CALL_ORDER, "defaultValue", "asKeyValuesWithKeyParser"); return new MapArgumentBuilder(this, checkNotNull(keyParser)); } /** *
	 * When given a "," this allows for
	 * arguments such as:
	 * -numbers 1,2,3
	 * where the resulting {@code List} would contain 1, 2 & 3.
	 * 
	 * Doesn't allow empty lists.
	 * 
* * @param valueSeparator the string to split the input with * @return a new (more specific) builder */ @CheckReturnValue public SplitterArgumentBuilder splitWith(final String valueSeparator) { return new SplitterArgumentBuilder(this, valueSeparator); } /** * Useful for handling a variable amount of parameters in the end of a * command. Uses this argument to parse values but assumes that all the following * parameters are of the same type, integer in the following example: * *
	 * 
	 * String[] threeArgs = {"--numbers", "1", "2", "3"};
	 * List<Integer> numbers = integerArgument("--numbers").variableArity().parse(threeArgs);
	 * assertThat(numbers).isEqualTo(asList(1, 2, 3));
	 * 
	 * String[] twoArgs = {"--numbers", "1", "2"};
	 * List<Integer> numbers = integerArgument("--numbers").variableArity().parse(twoArgs);
	 * assertThat(numbers).isEqualTo(asList(1, 2));
	 * 
	 * 
* * Note: if {@link #defaultValue(Object) default value} has been set before * {@link #variableArity()} is called the {@link #defaultValue(Object) default value} will be a * one element list with that value in it, otherwise it will be empty. * * @return a new (more specific) builder */ @CheckReturnValue public ArityArgumentBuilder variableArity() { return new ArityArgumentBuilder(this); } /** *
	 * Uses this argument to parse values but assumes that {@code numberOfParameters} of the
	 * following parameters are of the same type.
	 * 
	 * {@link Integer} in the following example:
	 * 
* *
	 * 
	 * List<Integer> numbers = integerArgument("--numbers").arity(2).parse("--numbers", "1", "2");
	 * assertThat(numbers).isEqualTo(asList(1, 2));
	 * 
	 * 
* * Note:If the argument isn't {@link #required()} the default value * will be a list that contains {@code numberOfParameters} elements * of {@link StringParser#defaultValue()}, in the above example that would be two zeros. * If this isn't wanted use {@link #defaultValue(Object)} to override it. * * @return a new (more specific) builder * @throws IllegalArgumentException if {@code numberOfParameters} is less than 2 */ @CheckReturnValue public ArityArgumentBuilder arity(final int numberOfParameters) { checkArgument(numberOfParameters > 1, ProgrammaticErrors.TO_SMALL_ARITY, numberOfParameters); return new ArityArgumentBuilder(this, numberOfParameters); } /** *
	 * Makes it possible to enter several values for the same argument. Such as this:
	 * 
	 * 
	 * 
	 * String[] arguments = {"--number", "1", "--number", "2"};
	 * List<Integer> numbers = integerArgument("--number").repeated().parse(arguments);
	 * assertThat(numbers).isEqualTo(asList(1, 2));
	 * 
	 * 
* * If you want to combine {@link #repeated()} with a specific {@link #arity(int)} then call * {@link #arity(int)} before calling {@link #repeated()}: * *
	 * 
	 * String[] arguments = "--numbers", "1", "2", "--numbers", "3", "4"};
	 * List<List<Integer>> numberLists =
	 * integerArgument("--numbers").arity(2).repeated().parse(arguments);
	 * assertThat(numberLists).isEqualTo(asList(asList(1, 2), asList(3, 4)));
	 * 
	 * 
* * For repeated values in a property map such as this: * *
	 * 
	 * String[] arguments = "-Nnumber=1", "-Nnumber=2"};
	 * Map<String, List<Integer>> numberMap = integerArgument("-N").repeated().asPropertyMap().parse(arguments);
	 * assertThat(numberMap.get("number")).isEqualTo(asList(1, 2));
	 * 
	 * 
* * {@link #repeated()} should be called before {@link #asPropertyMap()}. * For arguments without a name (indexed arguments) use {@link #variableArity()} instead. * * @return a new (more specific) builder *
*/ @CheckReturnValue public RepeatedArgumentBuilder repeated() { return new RepeatedArgumentBuilder(this); } @Override public String toString() { return toStringHelper(this).omitNullValues().add("names", names).add("description", description).add("metaDescription", metaDescription) .add("hideFromUsage", hideFromUsage).add("ignoreCase", ignoreCase).add("limiter", limiter).add("required", required) .add("separator", separator).add("defaultValueDescriber", defaultValueDescriber).add("localeOverride", localeOverride) .add("defaultValueSupplier", defaultValueSupplier).add("internalStringParser", internalStringParser).toString(); } /** *
	 * Copies all values from the given copy into this one, except for:
	 * {@link #parser()}, {@link #defaultValueSupplier()} & {@link #defaultValueDescriber()}
	 * as they may change between different builders
	 * (e.g the default value for Argument<Boolean> and Argument<List<Boolean> are not compatible)
	 * @param copy the ArgumentBuilder to copy from
	 */
	@OverridingMethodsMustInvokeSuper
	void copy(final ArgumentBuilder copy)
	{
		this.names = copy.names;
		this.description = copy.description;
		this.required = copy.required;
		this.separator = copy.separator;
		this.localeOverride = copy.localeOverride;
		this.ignoreCase = copy.ignoreCase;
		this.isAllowedToRepeat = copy.isAllowedToRepeat;
		this.metaDescription = copy.metaDescription;
		this.hideFromUsage = copy.hideFromUsage;
	}

	/**
	 * 
	 * {@code aFinalizer} is called after {@link StringParser#parse(String, Locale)}
	 * but before any predicates given to {@link #limitTo(Predicate)} are tested.
	 * 
	 * Is used internally to finalize values produced by {@link StringParser#parse(String, Locale)}.
	 * 
	 * For example {@link RepeatedArgumentParser} uses this to make the resulting {@link List} {@link Immutable}.
	 * 
	 * For regular {@link StringParser}s it's recommended to use {@link SimpleForwardingStringParser}
	 * and decorate your {@link StringParser} with any finalization there instead.
	 * 
	 * Note: If {@link #finalizeWith(Function)} have been called before,
	 * the given {@code aFinalizer} will be run after that finalizer.
	 * 
	 * 
* * @param aFinalizer a finalizer * @return this builder */ final SELF finalizeWith(Function aFinalizer) { finalizer = Functions2.compound(finalizer, checkNotNull(aFinalizer)); return myself; } final void allowRepeatedArguments() { isAllowedToRepeat = true; } final void setAsPropertyMap() { isPropertyMap = true; } final void setParameterArity(ParameterArity arity) { parameterArity = arity; } /** * @formatter.off */ @Nonnull final List names(){ return names; } @Nullable Describer defaultValueDescriber(){ return defaultValueDescriber; } @Nonnull Description description(){ return description; } final boolean isRequired(){ return required; } @Nullable final String separator(){ return separator; } @Nullable final Optional localeOverride(){ return localeOverride; } final boolean isIgnoringCase(){ return ignoreCase; } final boolean isPropertyMap(){ return isPropertyMap; } final ParameterArity parameterArity(){ return parameterArity; } final boolean isAllowedToRepeat(){ return isAllowedToRepeat; } @Nullable final Optional metaDescription(){ return metaDescription; } final boolean isHiddenFromUsage(){ return hideFromUsage; } @Nullable final Supplier defaultValueSupplier(){ return defaultValueSupplier; } @Nonnull final Function finalizer(){ return finalizer; } @Nonnull final Predicate limiter(){ return limiter; } /** * @formatter.on */ /** * A very simple version of an {@link ArgumentBuilder}, it's exposed mainly to * lessen the exposure of the {@code SELF_TYPE} argument of the {@link ArgumentBuilder}. */ @NotThreadSafe public static final class DefaultArgumentBuilder extends ArgumentBuilder, T> { private final StringParser parser; DefaultArgumentBuilder(final StringParser aParser) { parser = aParser; } @Override protected StringParser parser() { return parser; } } // Non-Interesting builders below, most declarations under here handles // (by deprecating) invalid invariants between different argument properties private abstract static class InternalArgumentBuilder, T> extends ArgumentBuilder { /** * Only used to flag that this builder is an internal one, not used for parsing */ static final StringParser MARKER = new StringParserBridge(StringParsers.stringParser()); InternalArgumentBuilder() { } InternalArgumentBuilder(InternalStringParser parser) { super(parser); } @SuppressWarnings("unchecked") @Override protected StringParser parser() { return (StringParser) MARKER; } } /** * Builder for {@link Command}s. Created with {@link Arguments#command(Command)}. */ @NotThreadSafe public static final class CommandBuilder extends InternalArgumentBuilder { CommandBuilder(final Command command) { super(command); } /** * @deprecated Commands shouldn't be required */ @Deprecated @Override public CommandBuilder required() { throw new IllegalStateException(""); } /** * @deprecated Commands shouldn't have default values */ @Deprecated @Override public CommandBuilder defaultValue(ParsedArguments defaultValue) { throw new IllegalStateException(); } /** * @deprecated Commands shouldn't have default values */ @Deprecated @Override public CommandBuilder defaultValueSupplier(Supplier defaultValueSupplier) { throw new IllegalStateException(); } /** * @deprecated Commands can't have default values, so no description can be useful */ @Deprecated @Override public CommandBuilder defaultValueDescription(String defaultValueDescription) { throw new IllegalStateException(); } /** * @deprecated Commands can't have default values, so no description can be useful */ @Deprecated @Override public CommandBuilder defaultValueDescriber(Describer defaultValueDescriber) { throw new IllegalStateException(); } /** * @deprecated Commands can't be limited */ @Deprecated @Override public CommandBuilder limitTo(Predicate limiter) { throw new IllegalStateException(); } /** * @deprecated Commands can't be split */ @Deprecated @Override public SplitterArgumentBuilder splitWith(String valueSeparator) { throw new IllegalStateException(); } /** * @deprecated pass any {@link Argument}s to your {@link Command} to * {@link Command#Command(Argument...)} */ @Deprecated @Override public ArityArgumentBuilder arity(int arity) { throw new IllegalStateException(); } /** * @deprecated pass any {@link Argument}s to your {@link Command} to * {@link Command#Command(Argument...)} */ @Deprecated @Override public ArityArgumentBuilder variableArity() { throw new IllegalStateException(); } } private static class ListArgumentBuilder, T> extends InternalArgumentBuilder> { ListArgumentBuilder(InternalStringParser> parser) { super(parser); } void copyAsListBuilder(ArgumentBuilder builder, int nrOfElementsInDefaultValue) { finalizeWith(listTransformer(builder.finalizer)); finalizeWith(Functions2.unmodifiableList()); limitTo(listPredicate(builder.limiter)); if(builder.defaultValueSupplier != null) { defaultValueSupplier(Suppliers2.ofRepeatedElements(builder.defaultValueSupplier, nrOfElementsInDefaultValue)); } if(builder.defaultValueDescriber != null) { defaultValueDescriber(Describers.listDescriber(builder.defaultValueDescriber)); } // TODO: should meta description be expanded to ? }; } /** * An intermediate builder used by {@link #arity(int)} and {@link #variableArity()}. It's mainly * used to switch the T argument of the previous builder to List and to indicate invalid call * orders. */ @NotThreadSafe public static final class ArityArgumentBuilder extends ListArgumentBuilder, T> { private ArityArgumentBuilder(final ArgumentBuilder, T> builder, final int arity) { super(new FixedArityParser(builder.internalParser(), arity)); init(builder, arity); } private ArityArgumentBuilder(final ArgumentBuilder, T> builder) { super(new VariableArityParser(builder.internalParser())); setParameterArity(ParameterArity.VARIABLE_AMOUNT); init(builder, 1); } private void init(final ArgumentBuilder, T> builder, int nrOfElementsInDefaultValue) { copy(builder); copyAsListBuilder(builder, nrOfElementsInDefaultValue); } /** * @deprecated *
		 * This doesn't work with {@link ArgumentBuilder#arity(int)} or {@link ArgumentBuilder#variableArity()}
		 * I.e --foo 1,2 3,4
		 * is currently unsupported
		 * 
*/ @Deprecated @Override public SplitterArgumentBuilder> splitWith(final String valueSeparator) { throw new IllegalStateException("splitWith(...) doesn't work with arity/variableArity()"); } } /** * An intermediate builder used by {@link #repeated()}. It's mainly used to switch the T * argument of the previous builder to List and to indicate invalid call orders. */ @NotThreadSafe public static final class RepeatedArgumentBuilder extends ListArgumentBuilder, T> { private RepeatedArgumentBuilder(final ArgumentBuilder, T> builder) { super(new RepeatedArgumentParser(builder.internalParser())); copy(builder); copyAsListBuilder(builder, 1); allowRepeatedArguments(); } /** * @deprecated this method should be called before {@link #repeated()} */ @Deprecated @Override public ArityArgumentBuilder> arity(final int numberOfParameters) { throw new IllegalStateException("Programmer Error. Call arity(...) before repeated()"); } /** * @deprecated this method should be called before {@link #repeated()} */ @Override @Deprecated public ArityArgumentBuilder> variableArity() { throw new IllegalStateException("Programmer Error. Call variableArity(...) before repeated()"); } /** * @deprecated call {@link #splitWith(String)} before {@link #repeated()} */ @Deprecated @Override public SplitterArgumentBuilder> splitWith(final String valueSeparator) { throw new IllegalStateException("call splitWith(String) before repeated()"); } } /** * An intermediate builder used by {@link Arguments#optionArgument(String, String...)} */ @NotThreadSafe public static final class OptionArgumentBuilder extends InternalArgumentBuilder { OptionArgumentBuilder() { defaultValue(false); setParameterArity(ParameterArity.NO_ARGUMENTS); } @Override InternalStringParser internalParser() { return optionParser(defaultValueSupplier().get()); } @Override public OptionArgumentBuilder defaultValue(@Nonnull Boolean value) { checkNotNull(value, ProgrammaticErrors.OPTION_DOES_NOT_ALLOW_NULL_AS_DEFAULT); return super.defaultValue(value); } @Override public OptionArgumentBuilder names(Iterable argumentNames) { checkArgument(!isEmpty(argumentNames), ProgrammaticErrors.OPTIONS_REQUIRES_AT_LEAST_ONE_NAME); return super.names(argumentNames); } @Override public OptionArgumentBuilder names(String ... argumentNames) { checkArgument(argumentNames.length >= 1, ProgrammaticErrors.OPTIONS_REQUIRES_AT_LEAST_ONE_NAME); return super.names(argumentNames); } /** * @deprecated an optional flag can't be required */ @Deprecated @Override public OptionArgumentBuilder required() { throw new IllegalStateException("An optional flag can't be requried"); } /** * @deprecated a separator is useless since an optional flag can't be * assigned a value */ @Deprecated @Override public OptionArgumentBuilder separator(final String aSeparator) { throw new IllegalStateException("A seperator for an optional flag isn't supported as an optional flag can't be assigned a value"); } /** * @deprecated an optional flag can only have an arity of zero */ @Deprecated @Override public ArityArgumentBuilder arity(final int numberOfParameters) { throw new IllegalStateException("An optional flag can't have any other arity than zero"); } /** * @deprecated an optional flag can only have an arity of zero */ @Deprecated @Override public ArityArgumentBuilder variableArity() { throw new IllegalStateException("An optional flag can't have any other arity than zero"); } /** * @deprecated an optional flag can't be split by anything */ @Deprecated @Override public SplitterArgumentBuilder splitWith(final String valueSeparator) { throw new IllegalStateException("An optional flag can't be split as it has no value that is parsed"); } } /** * An intermediate builder used by {@link #asPropertyMap()}. It's mainly used to switch the T * argument of the previous builder to Map and to indicate invalid call orders. */ @NotThreadSafe public static final class MapArgumentBuilder extends InternalArgumentBuilder, Map> { private final ArgumentBuilder valueBuilder; private final StringParser keyParser; private MapArgumentBuilder(final ArgumentBuilder builder, StringParser keyParser) { this.valueBuilder = builder; this.keyParser = keyParser; copy(builder); finalizeWith(Functions2.mapValueTransformer(builder.finalizer)); finalizeWith(Functions2.unmodifiableMap()); setAsPropertyMap(); } @Override InternalStringParser> internalParser() { checkState(names().size() > 0, ProgrammaticErrors.NO_NAME_FOR_PROPERTY_MAP); if(separator().equals(DEFAULT_SEPARATOR)) { separator("="); } else { checkState(separator().length() > 0, ProgrammaticErrors.EMPTY_SEPARATOR); } return new KeyValueParser(keyParser, valueBuilder.internalParser(), valueBuilder.limiter, defaultValueSupplier()); } // A Describer is also a describer for a V @SuppressWarnings("unchecked") @Override Describer> defaultValueDescriber() { Describer> overriddenDescriber = super.defaultValueDescriber(); if(overriddenDescriber != null) return overriddenDescriber; Describer valueDescriber = valueBuilder.defaultValueDescriber(); if(valueDescriber != null) { Describer mapDescriber = Describers.mapDescriber(valueDescriber, separator()); return (Describer>) mapDescriber; } return Describers.mapDescriber(Describers.toStringDescriber(), separator()); } /** * @deprecated because {@link #repeated()} should be called before {@link #asPropertyMap()} */ @Deprecated @Override public RepeatedArgumentBuilder> repeated() { throw new IllegalStateException("You'll need to call repeated before asPropertyMap"); } /** * @deprecated because {@link #splitWith(String)} should be * called before {@link #asPropertyMap()}. * This is to make generic work its magic and produce the * correct type, for example {@code Map>}. */ @Deprecated @Override public SplitterArgumentBuilder> splitWith(final String valueSeparator) { throw new IllegalStateException("You'll need to call splitWith before asPropertyMap"); } /** * @deprecated because {@link #arity(int)} should be * called before {@link #asPropertyMap()}. */ @Deprecated @Override public ArityArgumentBuilder> arity(final int nrOfParameters) { throw new IllegalStateException("You'll need to call arity before asPropertyMap"); } /** * @deprecated because {@link #asPropertyMap()} is already of variable arity */ @Deprecated @Override public ArityArgumentBuilder> variableArity() { throw new IllegalStateException("asPropertyMap is already of variable arity"); } } /** * An intermediate builder used by {@link #splitWith(String)}. It's mainly used to switch the T * argument of the previous builder to List and to indicate invalid call orders. */ @NotThreadSafe public static final class SplitterArgumentBuilder extends ListArgumentBuilder, T> { private SplitterArgumentBuilder(final ArgumentBuilder builder, final String valueSeparator) { super(new StringSplitterParser(valueSeparator, builder.internalParser())); copy(builder); copyAsListBuilder(builder, 1); } /** * @deprecated you can't use both {@link #splitWith(String)} and {@link #arity(int)} */ @Deprecated @Override public ArityArgumentBuilder> arity(final int numberOfParameters) { throw new IllegalStateException("You can't use both splitWith and arity"); } /** * @deprecated you can't use both {@link #splitWith(String)} and {@link #variableArity()} */ @Deprecated @Override public ArityArgumentBuilder> variableArity() { throw new IllegalStateException("You can't use both splitWith and variableArity"); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy