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 extends T> defaultValueSupplier = null;
@Nullable private Describer super T> defaultValueDescriber = null;
@Nonnull private Function finalizer = Functions.identity();
@Nonnull private Predicate super T> 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* 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). *
* 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 extends T> 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 super T> 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("
*/
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 super T> 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 super T> 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 extends T> defaultValueSupplier(){ return defaultValueSupplier; }
@Nonnull final Function finalizer(){ return finalizer; }
@Nonnull final Predicate super T> 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 extends ParsedArguments> 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 super ParsedArguments> defaultValueDescriber)
{
throw new IllegalStateException();
}
/**
* @deprecated Commands can't be limited
*/
@Deprecated
@Override
public CommandBuilder limitTo(Predicate super ParsedArguments> 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, T> 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 extends ArgumentBuilder, T>, T> builder, final int arity)
{
super(new FixedArityParser(builder.internalParser(), arity));
init(builder, arity);
}
private ArityArgumentBuilder(final ArgumentBuilder extends ArgumentBuilder, T>, T> builder)
{
super(new VariableArityParser(builder.internalParser()));
setParameterArity(ParameterArity.VARIABLE_AMOUNT);
init(builder, 1);
}
private void init(final ArgumentBuilder extends ArgumentBuilder, T>, 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 extends ArgumentBuilder, T>, 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, V> valueBuilder;
private final StringParser keyParser;
private MapArgumentBuilder(final ArgumentBuilder, V> builder, StringParser keyParser)
{
this.valueBuilder = builder;
this.keyParser = keyParser;
copy(builder);
finalizeWith(Functions2.mapValueTransformer(builder.finalizer));
finalizeWith(Functions2.unmodifiableMap());
setAsPropertyMap();
}
@Override
InternalStringParser
© 2015 - 2025 Weber Informatics LLC | Privacy Policy