View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.cli2.option;
18  
19  import java.util.Collections;
20  import java.util.Comparator;
21  import java.util.List;
22  import java.util.ListIterator;
23  import java.util.Set;
24  import java.util.StringTokenizer;
25  
26  import org.apache.commons.cli2.Argument;
27  import org.apache.commons.cli2.DisplaySetting;
28  import org.apache.commons.cli2.HelpLine;
29  import org.apache.commons.cli2.Option;
30  import org.apache.commons.cli2.OptionException;
31  import org.apache.commons.cli2.WriteableCommandLine;
32  import org.apache.commons.cli2.resource.ResourceConstants;
33  import org.apache.commons.cli2.resource.ResourceHelper;
34  import org.apache.commons.cli2.validation.InvalidArgumentException;
35  import org.apache.commons.cli2.validation.Validator;
36  
37  /**
38   * An implementation of an Argument.
39   */
40  public class ArgumentImpl
41      extends OptionImpl implements Argument {
42      private static final char NUL = '\0';
43  
44      /**
45       * The default value for the initial separator char.
46       */
47      public static final char DEFAULT_INITIAL_SEPARATOR = NUL;
48  
49      /**
50       * The default value for the subsequent separator char.
51       */
52      public static final char DEFAULT_SUBSEQUENT_SEPARATOR = NUL;
53  
54      /**
55       * The default token to indicate that remaining arguments should be consumed
56       * as values.
57       */
58      public static final String DEFAULT_CONSUME_REMAINING = "--";
59      private final String name;
60      private final String description;
61      private final int minimum;
62      private final int maximum;
63      private final char initialSeparator;
64      private final char subsequentSeparator;
65      private final boolean subsequentSplit;
66      private final Validator validator;
67      private final String consumeRemaining;
68      private final List defaultValues;
69      private final ResourceHelper resources = ResourceHelper.getResourceHelper();
70  
71      /**
72       * Creates a new Argument instance.
73       *
74       * @param name
75       *            The name of the argument
76       * @param description
77       *            A description of the argument
78       * @param minimum
79       *            The minimum number of values needed to be valid
80       * @param maximum
81       *            The maximum number of values allowed to be valid
82       * @param initialSeparator
83       *            The char separating option from value
84       * @param subsequentSeparator
85       *            The char separating values from each other
86       * @param validator
87       *            The object responsible for validating the values
88       * @param consumeRemaining
89       *            The String used for the "consuming option" group
90       * @param valueDefaults
91       *            The values to be used if none are specified.
92       * @param id
93       *            The id of the option, 0 implies automatic assignment.
94       *
95       * @see OptionImpl#OptionImpl(int,boolean)
96       */
97      public ArgumentImpl(final String name,
98                          final String description,
99                          final int minimum,
100                         final int maximum,
101                         final char initialSeparator,
102                         final char subsequentSeparator,
103                         final Validator validator,
104                         final String consumeRemaining,
105                         final List valueDefaults,
106                         final int id) {
107         super(id, false);
108 
109         this.name = (name == null) ? "arg" : name;
110         this.description = description;
111         this.minimum = minimum;
112         this.maximum = maximum;
113         this.initialSeparator = initialSeparator;
114         this.subsequentSeparator = subsequentSeparator;
115         this.subsequentSplit = subsequentSeparator != NUL;
116         this.validator = validator;
117         this.consumeRemaining = consumeRemaining;
118         this.defaultValues = valueDefaults;
119 
120         if (minimum > maximum) {
121             throw new IllegalArgumentException(resources.getMessage(ResourceConstants.ARGUMENT_MIN_EXCEEDS_MAX));
122         }
123 
124         if ((valueDefaults != null) && (valueDefaults.size() > 0)) {
125             if (valueDefaults.size() < minimum) {
126                 throw new IllegalArgumentException(resources.getMessage(ResourceConstants.ARGUMENT_TOO_FEW_DEFAULTS));
127             }
128 
129             if (valueDefaults.size() > maximum) {
130                 throw new IllegalArgumentException(resources.getMessage(ResourceConstants.ARGUMENT_TOO_MANY_DEFAULTS));
131             }
132         }
133     }
134 
135     public String getPreferredName() {
136         return name;
137     }
138 
139     public void processValues(final WriteableCommandLine commandLine,
140                               final ListIterator arguments,
141                               final Option option)
142         throws OptionException {
143         // count of arguments processed for this option.
144         int argumentCount = commandLine.getUndefaultedValues(option).size();
145 
146         while (arguments.hasNext() && (argumentCount < maximum)) {
147             final String allValuesQuoted = (String) arguments.next();
148             final String allValues = stripBoundaryQuotes(allValuesQuoted);
149 
150             // should we ignore things that look like options?
151             if (allValuesQuoted.equals(consumeRemaining)) {
152                 while (arguments.hasNext() && (argumentCount < maximum)) {
153                     ++argumentCount;
154                     commandLine.addValue(option, arguments.next());
155                 }
156             }
157             // does it look like an option?
158             else if (commandLine.looksLikeOption(allValuesQuoted)) {
159                 arguments.previous();
160 
161                 break;
162             }
163             // should we split the string up?
164             else if (subsequentSplit) {
165                 final StringTokenizer values =
166                     new StringTokenizer(allValues, String.valueOf(subsequentSeparator));
167 
168                 arguments.remove();
169 
170                 while (values.hasMoreTokens() && (argumentCount < maximum)) {
171                     ++argumentCount;
172 
173                     final String token = values.nextToken();
174                     commandLine.addValue(option, token);
175                     arguments.add(token);
176                 }
177 
178                 if (values.hasMoreTokens()) {
179                     throw new OptionException(option, ResourceConstants.ARGUMENT_UNEXPECTED_VALUE,
180                                               values.nextToken());
181                 }
182             }
183             // it must be a value as it is
184             else {
185                 ++argumentCount;
186                 commandLine.addValue(option, allValues);
187             }
188         }
189     }
190 
191     public boolean canProcess(final WriteableCommandLine commandLine,
192                               final String arg) {
193         return true;
194     }
195 
196     public Set getPrefixes() {
197         return Collections.EMPTY_SET;
198     }
199 
200     public void process(WriteableCommandLine commandLine,
201                         ListIterator args)
202         throws OptionException {
203         processValues(commandLine, args, this);
204     }
205 
206     public char getInitialSeparator() {
207         return this.initialSeparator;
208     }
209 
210     public char getSubsequentSeparator() {
211         return this.subsequentSeparator;
212     }
213 
214     public Set getTriggers() {
215         return Collections.EMPTY_SET;
216     }
217 
218     public String getConsumeRemaining() {
219         return this.consumeRemaining;
220     }
221 
222     public List getDefaultValues() {
223         return this.defaultValues;
224     }
225 
226     public Validator getValidator() {
227         return this.validator;
228     }
229 
230     public void validate(final WriteableCommandLine commandLine)
231         throws OptionException {
232         validate(commandLine, this);
233     }
234 
235     public void validate(final WriteableCommandLine commandLine,
236                          final Option option)
237         throws OptionException {
238         final List values = commandLine.getValues(option);
239 
240         if (values.size() < minimum) {
241             throw new OptionException(option, ResourceConstants.ARGUMENT_MISSING_VALUES);
242         }
243 
244         if (values.size() > maximum) {
245             throw new OptionException(option, ResourceConstants.ARGUMENT_UNEXPECTED_VALUE,
246                                       (String) values.get(maximum));
247         }
248 
249         if (validator != null) {
250             try {
251                 validator.validate(values);
252             } catch (InvalidArgumentException ive) {
253                 throw new OptionException(option, ResourceConstants.ARGUMENT_UNEXPECTED_VALUE,
254                                           ive.getMessage());
255             }
256         }
257     }
258 
259     public void appendUsage(final StringBuffer buffer,
260                             final Set helpSettings,
261                             final Comparator comp) {
262         // do we display the outer optionality
263         final boolean optional = helpSettings.contains(DisplaySetting.DISPLAY_OPTIONAL);
264 
265         // allow numbering if multiple args
266         final boolean numbered =
267             (maximum > 1) && helpSettings.contains(DisplaySetting.DISPLAY_ARGUMENT_NUMBERED);
268 
269         final boolean bracketed = helpSettings.contains(DisplaySetting.DISPLAY_ARGUMENT_BRACKETED);
270 
271         // if infinite args are allowed then crop the list
272         final int max = (maximum == Integer.MAX_VALUE) ? 2 : maximum;
273 
274         int i = 0;
275 
276         // for each argument
277         while (i < max) {
278             // if we're past the first add a space
279             if (i > 0) {
280                 buffer.append(' ');
281             }
282 
283             // if the next arg is optional
284             if ((i >= minimum) && (optional || (i > 0))) {
285                 buffer.append('[');
286             }
287 
288             if (bracketed) {
289                 buffer.append('<');
290             }
291 
292             // add name
293             buffer.append(name);
294             ++i;
295 
296             // if numbering
297             if (numbered) {
298                 buffer.append(i);
299             }
300 
301             if (bracketed) {
302                 buffer.append('>');
303             }
304         }
305 
306         // if infinite args are allowed
307         if (maximum == Integer.MAX_VALUE) {
308             // append elipsis
309             buffer.append(" ...");
310         }
311 
312         // for each argument
313         while (i > 0) {
314             --i;
315 
316             // if the next arg is optional
317             if ((i >= minimum) && (optional || (i > 0))) {
318                 buffer.append(']');
319             }
320         }
321     }
322 
323     public String getDescription() {
324         return description;
325     }
326 
327     public List helpLines(final int depth,
328                           final Set helpSettings,
329                           final Comparator comp) {
330         final HelpLine helpLine = new HelpLineImpl(this, depth);
331 
332         return Collections.singletonList(helpLine);
333     }
334 
335     public int getMaximum() {
336         return maximum;
337     }
338 
339     public int getMinimum() {
340         return minimum;
341     }
342 
343     /**
344      * If there are any leading or trailing quotes remove them from the
345      * specified token.
346      *
347      * @param token
348      *            the token to strip leading and trailing quotes
349      *
350      * @return String the possibly modified token
351      */
352     public String stripBoundaryQuotes(String token) {
353         if (!token.startsWith("\"") || !token.endsWith("\"")) {
354             return token;
355         }
356 
357         token = token.substring(1, token.length() - 1);
358 
359         return token;
360     }
361 
362     public boolean isRequired() {
363         return getMinimum() > 0;
364     }
365 
366     public void defaults(final WriteableCommandLine commandLine) {
367         super.defaults(commandLine);
368         defaultValues(commandLine, this);
369     }
370 
371     public void defaultValues(final WriteableCommandLine commandLine,
372                               final Option option) {
373         commandLine.setDefaultValues(option, defaultValues);
374     }
375 }