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         https://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  
18  package org.apache.commons.cli;
19  
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Iterator;
23  import java.util.List;
24  
25  import org.apache.commons.cli.help.OptionFormatter;
26  
27  /**
28   * The class PosixParser provides an implementation of the {@link Parser#flatten(Options,String[],boolean) flatten}
29   * method.
30   *
31   * @deprecated Since 1.3, use the {@link DefaultParser} instead.
32   */
33  @Deprecated
34  public class PosixParser extends Parser {
35  
36      /** Holder for flattened tokens. */
37      private final List<String> tokens = new ArrayList<>();
38  
39      /** Specifies if bursting should continue.. */
40      private boolean eatTheRest;
41  
42      /** Holder for the current option */
43      private Option currentOption;
44  
45      /** The command line Options. */
46      private Options options;
47  
48      /**
49       * Constructs a new instance.
50       */
51      public PosixParser() {
52          // empty
53      }
54  
55      /**
56       * Adds the remaining tokens to the processed tokens list.
57       *
58       * @param iter An iterator over the remaining tokens.
59       */
60      private void addRemaining(final Iterator<String> iter) {
61          if (eatTheRest) {
62              iter.forEachRemaining(tokens::add);
63          }
64      }
65  
66      /**
67       * Breaks {@code token} into its constituent parts using the following algorithm.
68       *
69       * <ul>
70       * <li>ignore the first character ("<strong>-</strong>")</li>
71       * <li>for each remaining character check if an {@link Option} exists with that id.</li>
72       * <li>if an {@link Option} does exist then add that character prepended with "<strong>-</strong>" to the list of processed
73       * tokens.</li>
74       * <li>if the {@link Option} can have an argument value and there are remaining characters in the token then add the
75       * remaining characters as a token to the list of processed tokens.</li>
76       * <li>if an {@link Option} does <strong>NOT</strong> exist <strong>AND</strong> {@code stopAtNonOption} <strong>IS</strong> set then add the
77       * special token "<strong>--</strong>" followed by the remaining characters and also the remaining tokens directly to the
78       * processed tokens list.</li>
79       * <li>if an {@link Option} does <strong>NOT</strong> exist <strong>AND</strong> {@code stopAtNonOption} <strong>IS NOT</strong> set then add
80       * that character prepended with "<strong>-</strong>".</li>
81       * </ul>
82       *
83       * @param token The current token to be <strong>burst</strong>.
84       * @param stopAtNonOption Specifies whether to stop processing at the first non-Option encountered.
85       */
86      protected void burstToken(final String token, final boolean stopAtNonOption) {
87          for (int i = 1; i < token.length(); i++) {
88              final String ch = String.valueOf(token.charAt(i));
89              if (!options.hasOption(ch)) {
90                  if (stopAtNonOption) {
91                      processNonOptionToken(token.substring(i), true);
92                  } else {
93                      tokens.add(token);
94                  }
95                  break;
96              }
97              tokens.add(OptionFormatter.DEFAULT_OPT_PREFIX + ch);
98              currentOption = options.getOption(ch);
99              if (currentOption.hasArg() && token.length() != i + 1) {
100                 tokens.add(token.substring(i + 1));
101                 break;
102             }
103         }
104     }
105 
106     /**
107      * <p>
108      * An implementation of {@link Parser}'s abstract {@link Parser#flatten(Options,String[],boolean) flatten} method.
109      * </p>
110      *
111      * <p>
112      * The following are the rules used by this flatten method.
113      * </p>
114      * <ol>
115      * <li>if {@code stopAtNonOption} is <strong>true</strong> then do not burst anymore of {@code arguments} entries, just
116      * add each successive entry without further processing. Otherwise, ignore {@code stopAtNonOption}.</li>
117      * <li>if the current {@code arguments} entry is "<strong>--</strong>" just add the entry to the list of processed
118      * tokens</li>
119      * <li>if the current {@code arguments} entry is "<strong>-</strong>" just add the entry to the list of processed tokens</li>
120      * <li>if the current {@code arguments} entry is two characters in length and the first character is "<strong>-</strong>"
121      * then check if this is a valid {@link Option} id. If it is a valid id, then add the entry to the list of processed
122      * tokens and set the current {@link Option} member. If it is not a valid id and {@code stopAtNonOption} is true,
123      * then the remaining entries are copied to the list of processed tokens. Otherwise, the current entry is ignored.</li>
124      * <li>if the current {@code arguments} entry is more than two characters in length and the first character is
125      * "<strong>-</strong>" then we need to burst the entry to determine its constituents. For more information on the bursting
126      * algorithm see {@link PosixParser#burstToken(String, boolean) burstToken}.</li>
127      * <li>if the current {@code arguments} entry is not handled by any of the previous rules, then the entry is added
128      * to the list of processed tokens.</li>
129      * </ol>
130      *
131      * @param options The command line {@link Options}.
132      * @param arguments The command line arguments to be parsed.
133      * @param stopAtNonOption Specifies whether to stop flattening when an non option is found.
134      * @return The flattened {@code arguments} String array.
135      */
136     @Override
137     protected String[] flatten(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
138         init();
139         this.options = options;
140         // an iterator for the command line tokens
141         final Iterator<String> iter = Arrays.asList(arguments).iterator();
142         // process each command line token
143         while (iter.hasNext()) {
144             // get the next command line token
145             final String token = iter.next();
146             if (token != null) {
147                 // single or double hyphen
148                 if (OptionFormatter.DEFAULT_OPT_PREFIX.equals(token) || OptionFormatter.DEFAULT_LONG_OPT_PREFIX.equals(token)) {
149                     tokens.add(token);
150                 } else if (token.startsWith(OptionFormatter.DEFAULT_LONG_OPT_PREFIX)) {
151                     // handle long option --foo or --foo=bar
152                     final int pos = DefaultParser.indexOfEqual(token);
153                     final String opt = pos == -1 ? token : token.substring(0, pos); // --foo
154                     final List<String> matchingOpts = options.getMatchingOptions(opt);
155                     if (matchingOpts.isEmpty()) {
156                         processNonOptionToken(token, stopAtNonOption);
157                     } else if (matchingOpts.size() > 1) {
158                         throw new AmbiguousOptionException(opt, matchingOpts);
159                     } else {
160                         currentOption = options.getOption(matchingOpts.get(0));
161                         tokens.add(OptionFormatter.DEFAULT_LONG_OPT_PREFIX + currentOption.getLongOpt());
162                         if (pos != -1) {
163                             tokens.add(token.substring(pos + 1));
164                         }
165                     }
166                 } else if (token.startsWith(OptionFormatter.DEFAULT_OPT_PREFIX)) {
167                     if (token.length() == 2 || options.hasOption(token)) {
168                         processOptionToken(token, stopAtNonOption);
169                     } else if (!options.getMatchingOptions(token).isEmpty()) {
170                         final List<String> matchingOpts = options.getMatchingOptions(token);
171                         if (matchingOpts.size() > 1) {
172                             throw new AmbiguousOptionException(token, matchingOpts);
173                         }
174                         final Option opt = options.getOption(matchingOpts.get(0));
175                         processOptionToken(OptionFormatter.DEFAULT_OPT_PREFIX + opt.getLongOpt(), stopAtNonOption);
176                     }
177                     // requires bursting
178                     else {
179                         burstToken(token, stopAtNonOption);
180                     }
181                 } else {
182                     processNonOptionToken(token, stopAtNonOption);
183                 }
184             }
185             addRemaining(iter);
186         }
187         return tokens.toArray(Util.EMPTY_STRING_ARRAY);
188     }
189 
190     /**
191      * Resets the members to their original state i.e. remove all of {@code tokens} entries and set
192      * {@code eatTheRest} to false.
193      */
194     private void init() {
195         eatTheRest = false;
196         tokens.clear();
197     }
198 
199     /**
200      * Add the special token "<strong>--</strong>" and the current {@code value} to the processed tokens list. Then add all the
201      * remaining {@code argument} values to the processed tokens list.
202      *
203      * @param value The current token.
204      */
205     private void processNonOptionToken(final String value, final boolean stopAtNonOption) {
206         if (stopAtNonOption && (currentOption == null || !currentOption.hasArg())) {
207             eatTheRest = true;
208             tokens.add(OptionFormatter.DEFAULT_LONG_OPT_PREFIX);
209         }
210         tokens.add(value);
211     }
212 
213     /**
214      * <p>
215      * If an {@link Option} exists for {@code token} then add the token to the processed list.
216      * </p>
217      *
218      * <p>
219      * If an {@link Option} does not exist and {@code stopAtNonOption} is set then add the remaining tokens to the
220      * processed tokens list directly.
221      * </p>
222      *
223      * @param token The current option token.
224      * @param stopAtNonOption Specifies whether flattening should halt at the first non option.
225      */
226     private void processOptionToken(final String token, final boolean stopAtNonOption) {
227         if (stopAtNonOption && !options.hasOption(token)) {
228             eatTheRest = true;
229         }
230         if (options.hasOption(token)) {
231             currentOption = options.getOption(token);
232         }
233         tokens.add(token);
234     }
235 }