/* 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 se.softhouse.common.strings.StringsUtil.numberToPositionalString; import java.io.Serializable; import java.util.Collection; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import se.softhouse.common.strings.Describable; import se.softhouse.common.strings.Describables; import se.softhouse.common.strings.Describables.SerializableDescription; import se.softhouse.jargo.CommandLineParserInstance.ArgumentIterator; import se.softhouse.jargo.internal.Texts.UserErrors; /** * Gives you static access for creating {@link ArgumentException}s.<br> * Remember that created exceptions are simply returned <b>not thrown</b>. */ @Immutable public final class ArgumentExceptions { private ArgumentExceptions() { } /** * The most simple version of {@link ArgumentException}s, it simply prints the result of * {@link #toString()} for {@code message} as the message. Use * {@link #withMessage(Object, Throwable)} if you have a cause. */ @CheckReturnValue @Nonnull public static ArgumentException withMessage(final Object message) { return new SimpleArgumentException(Describables.toString(message)); } /** * Like {@link #withMessage(Object)} but with a {@code cause} */ @CheckReturnValue @Nonnull public static ArgumentException withMessage(final Object message, Throwable cause) { return new SimpleArgumentException(Describables.toString(message)).andCause(cause); } /** * {@link Describable} based version of {@link #withMessage(Object)} */ @CheckReturnValue @Nonnull public static ArgumentException withMessage(Describable message) { return new SimpleArgumentException(message); } /** * Like {@link #withMessage(Describable)} but with a {@code cause} */ @CheckReturnValue @Nonnull public static ArgumentException withMessage(Describable message, Throwable cause) { return new SimpleArgumentException(message).andCause(cause); } private static final class SimpleArgumentException extends ArgumentException { private final SerializableDescription message; private SimpleArgumentException(final Describable message) { this.message = Describables.asSerializable(message); } @Override protected String getMessage(String argumentNameOrcommandName) { return message.description(); } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } /** * Converts any {@link Throwable} into an {@link ArgumentException}. * Uses the detail message of {@code exceptionToWrap} as it's own detail message. * {@code exceptionToWrap} is also set as the cause of the created exception. */ @CheckReturnValue @Nonnull public static ArgumentException wrapException(final Throwable exceptionToWrap) { return new WrappedArgumentException(exceptionToWrap); } private static final class WrappedArgumentException extends ArgumentException { private final Throwable wrappedException; WrappedArgumentException(final Throwable wrappedException) { this.wrappedException = checkNotNull(wrappedException); initCause(wrappedException); } @Override protected String getMessage(String argumentNameOrcommandName) { return wrappedException.getMessage(); } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } /** * Thrown when {@link ArgumentBuilder#required()} has been specified but the * argument wasn't found in the input arguments * * @param missingArguments a collection of all the missing arguments */ @CheckReturnValue @Nonnull static ArgumentException forMissingArguments(final Collection<Argument<?>> missingArguments) { return new MissingRequiredArgumentException(missingArguments); } private static final class MissingRequiredArgumentException extends ArgumentException { private final String missingArguments; private MissingRequiredArgumentException(final Collection<Argument<?>> missingArguments) { this.missingArguments = missingArguments.toString(); } @Override protected String getMessage(String argumentNameOrcommandName) { if(isCausedByCommand(argumentNameOrcommandName)) return String.format(UserErrors.MISSING_COMMAND_ARGUMENTS, argumentNameOrcommandName, missingArguments); return String.format(UserErrors.MISSING_REQUIRED_ARGUMENTS, missingArguments); } private boolean isCausedByCommand(@Nullable String argumentNameOrcommandName) { return argumentNameOrcommandName != null; } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } /** * Used when * "--number 1" is expected but * "--number 1 --number 2" is given * * @param unhandledArgument the argument --number in this case */ @CheckReturnValue @Nonnull static ArgumentException forUnallowedRepetitionArgument(final String unhandledArgument) { checkNotNull(unhandledArgument); return new SimpleArgumentException(Describables.format(UserErrors.DISALLOWED_REPETITION, unhandledArgument)); } /** * Used when * "-p 8080" is expected but * "-p" is given <br> * Prints "Missing <Integer> parameter for -p" * * @param argumentWithMissingParameter the -p argument in this case */ @CheckReturnValue @Nonnull static ArgumentException forMissingParameter(Argument<?> argumentWithMissingParameter) { return new MissingParameterException(argumentWithMissingParameter); } static final class MissingParameterException extends ArgumentException { private final String parameterDescription; private MissingParameterException(Argument<?> argumentWithMissingParameter) { this.parameterDescription = argumentWithMissingParameter.metaDescriptionInRightColumn(); } @Override protected String getMessage(String argumentNameOrcommandName) { return String.format(UserErrors.MISSING_PARAMETER, parameterDescription, argumentNameOrcommandName); } String parameterDescription() { return parameterDescription; } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } /** * Used when * "-p 8080 7080" is expected but * "-p 8080" is given<br> * Prints "Missing second <Integer> parameter for -p" * * @param cause with a {@link MissingParameterException#parameterDescription() * parameterDescription} such as "<Integer>" in this case * @param missingIndex 1 in this case */ @CheckReturnValue @Nonnull static ArgumentException forMissingNthParameter(MissingParameterException cause, int missingIndex) { return new MissingNthParameterException(cause.parameterDescription(), missingIndex).andCause(cause); } private static final class MissingNthParameterException extends ArgumentException { private final String parameterDescription; private final int missingIndex; private MissingNthParameterException(String parameterDescription, int missingIndex) { this.parameterDescription = parameterDescription; this.missingIndex = missingIndex; } @Override protected String getMessage(String argumentNameOrCommandName) { return String.format( UserErrors.MISSING_NTH_PARAMETER, numberToPositionalString(missingIndex + 1), parameterDescription, argumentNameOrCommandName); } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } /** * Used when * "1 2" is expected but * "1 2 3" is given<br> * Prints "Unexpected argument: 3, previous argument: 2" * * @param arguments used to print the unexpected argument, 3 in this case, 2 is also printed to * further pinpoint where 3 is situated */ @Nonnull static ArgumentException forUnexpectedArgument(final ArgumentIterator arguments) { String unexpectedArgument = arguments.previous(); String previousArgument = null; if(arguments.hasPrevious()) { previousArgument = arguments.previous(); } return new UnexpectedArgumentException(unexpectedArgument, previousArgument); } static final class UnexpectedArgumentException extends ArgumentException { private final String unexpectedArgument; private final String previousArgument; private UnexpectedArgumentException(final String unexpectedArgument, @Nullable final String previousArgument) { this.unexpectedArgument = unexpectedArgument; this.previousArgument = previousArgument; } @Override protected String getMessage(String argumentNameOrcommandName) { String message = "Unexpected argument: " + unexpectedArgument; if(previousArgument != null) { message += ", previous argument: " + previousArgument; } return message; } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; } }