/* 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.NEWLINE; import java.io.Serializable; import javax.annotation.concurrent.NotThreadSafe; import se.softhouse.common.strings.StringsUtil; import se.softhouse.jargo.internal.Texts.ProgrammaticErrors; import se.softhouse.jargo.internal.Texts.UsageTexts; /** * Indicates that something went wrong in a {@link CommandLineParser#parse(String...) parsing}. * Typical causes include: * <ul> * <li>Missing parameters</li> * <li>Unknown arguments, if it's {@link StringsUtil#closestMatches(String, Iterable, int) close * enough} to a known argument the error message will contain suggestions</li> * <li>Missing required arguments</li> * <li>Invalid arguments, thrown from {@link StringParser#parse(String, java.util.Locale) parse}</li> * <li>Repetition of argument that hasn't specified {@link ArgumentBuilder#repeated() repeated}</li> * </ul> * The typical remedy action is to present {@link #getMessageAndUsage()} to the user so he is * informed about what he did wrong. <br> * As all {@link Exception}s should be, {@link ArgumentException} is {@link Serializable} so usage * is available after deserialization. */ @NotThreadSafe public abstract class ArgumentException extends RuntimeException { /** * The {@link Usage} explaining how to avoid this exception */ private Usage usage = null; /** * The used name, one of the strings passed to {@link ArgumentBuilder#names(String...)} */ private String usedArgumentName = null; /** * The first name passed to {@link ArgumentBuilder#names(String...)}. Used to print a reference * to the explanation of the erroneous argument. Used instead of usedArgumentName as the first * name is the leftmost in the listings. */ private String usageReferenceName = null; protected ArgumentException() { } /** * Alias for {@link #initCause(Throwable)}.<br> * Added simply because {@code withMessage("Message").andCause(exception)} flows better. */ public final ArgumentException andCause(Throwable cause) { initCause(checkNotNull(cause)); return this; } /** * Returns usage detailing how to use the {@link CommandLineParser} that caused this * {@link ArgumentException exception}, prepended with an error message detailing the erroneous * argument and, if applicable, a reference to the usage where the user can read about * acceptable input. */ public final Usage getMessageAndUsage() { // TODO(jontejj): jack into the uncaughtExceptionHandler and remove stacktraces? Potentially // very annoying feature... String message = getMessage(usedArgumentName) + usageReference() + NEWLINE + NEWLINE; return getUsage().withErrorMessage(message); } /** * Returns why this exception occurred. * * @param referenceName the argument name that was used on the command line. * If indexed the {@link ArgumentBuilder#metaDescription(String)} is given. * Furthermore if the argument is both indexed and part of a {@link Command} the * command name used to trigger the command is given. Alas never null. */ protected abstract String getMessage(String referenceName); /** * <pre> * {@inheritDoc} * * Marked as final as the {@link #getMessage(String)} should be implemented instead * </pre> * * @see #getMessageAndUsage() */ @Override public final String getMessage() { return getMessage(usedArgumentName); } /** * Returns the usage explaining how to use the {@link CommandLineParser} that * caused this exception. */ private Usage getUsage() { checkNotNull(usage, ProgrammaticErrors.NO_USAGE_AVAILABLE); return usage; } final ArgumentException withUsage(final Usage theUsage) { if(usage != null) return this; // Don't overwrite if the user has specified a custom Usage usage = theUsage; return this; } final ArgumentException withUsedArgumentName(String argumentNameThatTriggeredMe) { usedArgumentName = argumentNameThatTriggeredMe; return this; } /** * By default ". See usage for --author for proper values." is appended to the end of the error * message if {@link #getMessageAndUsage()} is used. This method overrides that to be * {@code usageReference} instead. */ void withUsageReference(final String usageReference) { usageReferenceName = checkNotNull(usageReference); } ArgumentException withUsageReference(final Argument<?> usageReference) { if(usageReferenceName != null) return this; // Don't overwrite if the user has specified a custom reference usageReferenceName = String.format(UsageTexts.USAGE_REFERENCE, usageReference); return this; } private String usageReference() { return usageReferenceName; } /** * For {@link Serializable} */ private static final long serialVersionUID = 1L; }