001: /*
002: Copyright 2004-2007 Paul R. Holser, Jr. All rights reserved.
003: Licensed under the Academic Free License version 3.0
004: */
005:
006: package joptsimple;
007:
008: import java.lang.reflect.Constructor;
009: import java.lang.reflect.Member;
010: import java.lang.reflect.Method;
011:
012: /**
013: * <p>Specification of an option that accepts an argument.</p>
014: *
015: * <p>Instances are returned from {@link OptionSpecBuilder} methods to allow the formation
016: * of parser directives as sentences in a domain-specific language. For example:</p>
017: *
018: * <pre>
019: * OptionParser parser = new OptionParser();
020: * parser.accepts( "c" ).withRequiredArg().<strong>ofType( Integer.class )</strong>;
021: * </pre>
022: *
023: * <p>If no methods are invoked on an instance of this class, then that instance's option
024: * will treat its argument as a {@link String}.</p>
025: *
026: * @since 1.0
027: * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
028: * @version $Id: ArgumentAcceptingOptionSpec.java,v 1.31 2007/04/10 20:06:25 pholser Exp $
029: */
030: public abstract class ArgumentAcceptingOptionSpec extends OptionSpec {
031: private final boolean argumentRequired;
032: private Member converter;
033: private String argumentDescription = "";
034:
035: protected ArgumentAcceptingOptionSpec(String option,
036: boolean argumentRequired) {
037: super (option);
038:
039: this .argumentRequired = argumentRequired;
040: }
041:
042: protected ArgumentAcceptingOptionSpec(String option,
043: boolean argumentRequired, String description) {
044:
045: super (option, description);
046:
047: this .argumentRequired = argumentRequired;
048: }
049:
050: /**
051: * <p>Specifies a type to which arguments of this spec's option are to be converted.
052: * </p>
053: *
054: * <p>JOpt Simple accepts types that have either:</p>
055: *
056: * <ol>
057: * <li>a public static method called <code>valueOf</code> which accepts a single
058: * argument of type {@link String} and whose return type is the same as the class
059: * on which the method is declared. The <code>java.lang</code> primitive wrapper
060: * classes have such methods.</li>
061: *
062: * <li>a constructor which accepts a single argument of type {@link String}.</li>
063: * </ol>
064: *
065: * <p>This class converts arguments using those methods in that order; that is,
066: * <code>valueOf</code> would be invoked before a one-{@link String}-arg constructor
067: * would.</p>
068: *
069: * @param argumentType desired type of arguments to this spec's option
070: * @throws NullPointerException if the type is <code>null</code>
071: * @throws IllegalArgumentException if the type does not have the standard conversion
072: * methods
073: */
074: public void ofType(Class argumentType) {
075: converter = Reflection.findConverter(argumentType);
076: }
077:
078: /**
079: * Specifies a description for the argument of the option that this spec represents.
080: * This description is used when generating help information about the parser.
081: *
082: * @param description describes the nature of the argument of this spec's
083: * option
084: * @return self, so that the caller can add clauses to the domain-specific sentence
085: */
086: public final ArgumentAcceptingOptionSpec describedAs(
087: String description) {
088: argumentDescription = description;
089:
090: return this ;
091: }
092:
093: /**
094: * {@inheritDoc}
095: */
096: public boolean equals(Object that) {
097: if (!super .equals(that))
098: return false;
099:
100: ArgumentAcceptingOptionSpec other = (ArgumentAcceptingOptionSpec) that;
101: return requiresArgument() == other.requiresArgument();
102: }
103:
104: /**
105: * {@inheritDoc}
106: */
107: public int hashCode() {
108: return super .hashCode() ^ (argumentRequired ? 0 : 1);
109: }
110:
111: /**
112: * {@inheritDoc}
113: */
114: public String toString() {
115: return super .toString() + ", arg "
116: + (requiresArgument() ? "required" : "optional");
117: }
118:
119: final void handleOption(OptionParser parser,
120: ArgumentList arguments, OptionSet detectedOptions,
121: String detectedArgument) {
122:
123: if (isNullOrEmpty(detectedArgument))
124: handleOptionFurther(parser, arguments, detectedOptions);
125: else
126: detectedOptions.addWithArgument(option(),
127: convert(detectedArgument));
128: }
129:
130: /**
131: * Provides additional handling of this option when detected.
132: *
133: * TODO: need a better name here.
134: *
135: * @param parser an option parser
136: * @param arguments an argument list
137: * @param detectedOptions set of options detected in the argument list so far
138: */
139: protected abstract void handleOptionFurther(OptionParser parser,
140: ArgumentList arguments, OptionSet detectedOptions);
141:
142: /**
143: * Converts the given argument to the type specified in this spec, if one was given.
144: *
145: * @param argument the argument to convert
146: * @return the converted argument, or itself if no conversion was specified
147: */
148: protected final Object convert(String argument) {
149: if (converter == null)
150: return argument;
151:
152: Object[] args = new Object[] { argument };
153:
154: try {
155: if (converter instanceof Constructor)
156: return Reflection.invokeQuietly(
157: (Constructor) converter, args);
158:
159: return Reflection.invokeQuietly((Method) converter, args);
160: } catch (ReflectionException ignored) {
161: throw new OptionArgumentConversionException(option(),
162: argument, converter.getDeclaringClass());
163: }
164: }
165:
166: /**
167: * Tells whether the given argument can be successfully converted to the type
168: * specified in this spec, if one was given.
169: *
170: * @param argument the argument to convert
171: * @return <code>true</code> if conversion would be successful
172: */
173: protected boolean canConvertArgument(String argument) {
174: try {
175: convert(argument);
176: return true;
177: } catch (OptionException ignored) {
178: return false;
179: }
180: }
181:
182: /**
183: * Tells whether this option spec converts its arguments to a number type.
184: *
185: * @return whether this option spec converts its arguments to a number type
186: */
187: protected boolean isArgumentOfNumberType() {
188: if (converter == null)
189: return false;
190:
191: return Number.class.isAssignableFrom(converter
192: .getDeclaringClass());
193: }
194:
195: boolean acceptsArguments() {
196: return true;
197: }
198:
199: boolean requiresArgument() {
200: return argumentRequired;
201: }
202:
203: private static boolean isNullOrEmpty(String s) {
204: return s == null || "".equals(s.trim());
205: }
206:
207: String argumentDescription() {
208: return argumentDescription;
209: }
210:
211: Class argumentType() {
212: return converter == null ? String.class : converter
213: .getDeclaringClass();
214: }
215: }
|