001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.cli;
019    
020    import java.util.ArrayList;
021    import java.util.Arrays;
022    import java.util.Enumeration;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.ListIterator;
026    import java.util.Properties;
027    
028    /**
029     * <code>Parser</code> creates {@link CommandLine}s.
030     *
031     * @author John Keyes (john at integralsource.com)
032     * @version $Revision: 680644 $, $Date: 2008-07-29 01:13:48 -0700 (Tue, 29 Jul 2008) $
033     */
034    public abstract class Parser implements CommandLineParser
035    {
036        /** commandline instance */
037        protected CommandLine cmd;
038    
039        /** current Options */
040        private Options options;
041    
042        /** list of required options strings */
043        private List requiredOptions;
044    
045        protected void setOptions(final Options options)
046        {
047            this.options = options;
048            this.requiredOptions = new ArrayList(options.getRequiredOptions());
049        }
050    
051        protected Options getOptions()
052        {
053            return options;
054        }
055    
056        protected List getRequiredOptions()
057        {
058            return requiredOptions;
059        }
060    
061        /**
062         * Subclasses must implement this method to reduce
063         * the <code>arguments</code> that have been passed to the parse method.
064         *
065         * @param opts The Options to parse the arguments by.
066         * @param arguments The arguments that have to be flattened.
067         * @param stopAtNonOption specifies whether to stop
068         * flattening when a non option has been encountered
069         * @return a String array of the flattened arguments
070         */
071        protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption);
072    
073        /**
074         * Parses the specified <code>arguments</code> based
075         * on the specifed {@link Options}.
076         *
077         * @param options the <code>Options</code>
078         * @param arguments the <code>arguments</code>
079         * @return the <code>CommandLine</code>
080         * @throws ParseException if an error occurs when parsing the
081         * arguments.
082         */
083        public CommandLine parse(Options options, String[] arguments) throws ParseException
084        {
085            return parse(options, arguments, null, false);
086        }
087    
088        /**
089         * Parse the arguments according to the specified options and properties.
090         *
091         * @param options    the specified Options
092         * @param arguments  the command line arguments
093         * @param properties command line option name-value pairs
094         * @return the list of atomic option and value tokens
095         * @throws ParseException if there are any problems encountered
096         *                        while parsing the command line tokens.
097         *
098         * @since 1.1
099         */
100        public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
101        {
102            return parse(options, arguments, properties, false);
103        }
104    
105        /**
106         * Parses the specified <code>arguments</code>
107         * based on the specifed {@link Options}.
108         *
109         * @param options         the <code>Options</code>
110         * @param arguments       the <code>arguments</code>
111         * @param stopAtNonOption specifies whether to stop interpreting the
112         *                        arguments when a non option has been encountered
113         *                        and to add them to the CommandLines args list.
114         * @return the <code>CommandLine</code>
115         * @throws ParseException if an error occurs when parsing the arguments.
116         */
117        public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
118        {
119            return parse(options, arguments, null, stopAtNonOption);
120        }
121    
122        /**
123         * Parse the arguments according to the specified options and
124         * properties.
125         *
126         * @param options the specified Options
127         * @param arguments the command line arguments
128         * @param properties command line option name-value pairs
129         * @param stopAtNonOption stop parsing the arguments when the first
130         * non option is encountered.
131         *
132         * @return the list of atomic option and value tokens
133         *
134         * @throws ParseException if there are any problems encountered
135         * while parsing the command line tokens.
136         *
137         * @since 1.1
138         */
139        public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
140                throws ParseException
141        {
142            // clear out the data in options in case it's been used before (CLI-71)
143            for (Iterator it = options.helpOptions().iterator(); it.hasNext();)
144            {
145                Option opt = (Option) it.next();
146                opt.clearValues();
147            }
148    
149            // initialise members
150            setOptions(options);
151    
152            cmd = new CommandLine();
153    
154            boolean eatTheRest = false;
155    
156            if (arguments == null)
157            {
158                arguments = new String[0];
159            }
160    
161            List tokenList = Arrays.asList(flatten(getOptions(), arguments, stopAtNonOption));
162    
163            ListIterator iterator = tokenList.listIterator();
164    
165            // process each flattened token
166            while (iterator.hasNext())
167            {
168                String t = (String) iterator.next();
169    
170                // the value is the double-dash
171                if ("--".equals(t))
172                {
173                    eatTheRest = true;
174                }
175    
176                // the value is a single dash
177                else if ("-".equals(t))
178                {
179                    if (stopAtNonOption)
180                    {
181                        eatTheRest = true;
182                    }
183                    else
184                    {
185                        cmd.addArg(t);
186                    }
187                }
188    
189                // the value is an option
190                else if (t.startsWith("-"))
191                {
192                    if (stopAtNonOption && !getOptions().hasOption(t))
193                    {
194                        eatTheRest = true;
195                        cmd.addArg(t);
196                    }
197                    else
198                    {
199                        processOption(t, iterator);
200                    }
201                }
202    
203                // the value is an argument
204                else
205                {
206                    cmd.addArg(t);
207    
208                    if (stopAtNonOption)
209                    {
210                        eatTheRest = true;
211                    }
212                }
213    
214                // eat the remaining tokens
215                if (eatTheRest)
216                {
217                    while (iterator.hasNext())
218                    {
219                        String str = (String) iterator.next();
220    
221                        // ensure only one double-dash is added
222                        if (!"--".equals(str))
223                        {
224                            cmd.addArg(str);
225                        }
226                    }
227                }
228            }
229    
230            processProperties(properties);
231            checkRequiredOptions();
232    
233            return cmd;
234        }
235    
236        /**
237         * Sets the values of Options using the values in <code>properties</code>.
238         *
239         * @param properties The value properties to be processed.
240         */
241        protected void processProperties(Properties properties)
242        {
243            if (properties == null)
244            {
245                return;
246            }
247    
248            for (Enumeration e = properties.propertyNames(); e.hasMoreElements();)
249            {
250                String option = e.nextElement().toString();
251    
252                if (!cmd.hasOption(option))
253                {
254                    Option opt = getOptions().getOption(option);
255    
256                    // get the value from the properties instance
257                    String value = properties.getProperty(option);
258    
259                    if (opt.hasArg())
260                    {
261                        if (opt.getValues() == null || opt.getValues().length == 0)
262                        {
263                            try
264                            {
265                                opt.addValueForProcessing(value);
266                            }
267                            catch (RuntimeException exp)
268                            {
269                                // if we cannot add the value don't worry about it
270                            }
271                        }
272                    }
273                    else if (!("yes".equalsIgnoreCase(value)
274                            || "true".equalsIgnoreCase(value)
275                            || "1".equalsIgnoreCase(value)))
276                    {
277                        // if the value is not yes, true or 1 then don't add the
278                        // option to the CommandLine
279                        break;
280                    }
281    
282                    cmd.addOption(opt);
283                }
284            }
285        }
286    
287        /**
288         * Throws a {@link MissingOptionException} if all of the required options
289         * are not present.
290         *
291         * @throws MissingOptionException if any of the required Options
292         * are not present.
293         */
294        protected void checkRequiredOptions() throws MissingOptionException
295        {
296            // if there are required options that have not been processsed
297            if (!getRequiredOptions().isEmpty())
298            {
299                throw new MissingOptionException(getRequiredOptions());
300            }
301        }
302    
303        /**
304         * <p>Process the argument values for the specified Option
305         * <code>opt</code> using the values retrieved from the
306         * specified iterator <code>iter</code>.
307         *
308         * @param opt The current Option
309         * @param iter The iterator over the flattened command line
310         * Options.
311         *
312         * @throws ParseException if an argument value is required
313         * and it is has not been found.
314         */
315        public void processArgs(Option opt, ListIterator iter) throws ParseException
316        {
317            // loop until an option is found
318            while (iter.hasNext())
319            {
320                String str = (String) iter.next();
321    
322                // found an Option, not an argument
323                if (getOptions().hasOption(str) && str.startsWith("-"))
324                {
325                    iter.previous();
326                    break;
327                }
328    
329                // found a value
330                try
331                {
332                    opt.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(str));
333                }
334                catch (RuntimeException exp)
335                {
336                    iter.previous();
337                    break;
338                }
339            }
340    
341            if (opt.getValues() == null && !opt.hasOptionalArg())
342            {
343                throw new MissingArgumentException(opt);
344            }
345        }
346    
347        /**
348         * Process the Option specified by <code>arg</code> using the values
349         * retrieved from the specfied iterator <code>iter</code>.
350         *
351         * @param arg The String value representing an Option
352         * @param iter The iterator over the flattened command line arguments.
353         *
354         * @throws ParseException if <code>arg</code> does not represent an Option
355         */
356        protected void processOption(String arg, ListIterator iter) throws ParseException
357        {
358            boolean hasOption = getOptions().hasOption(arg);
359    
360            // if there is no option throw an UnrecognisedOptionException
361            if (!hasOption)
362            {
363                throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
364            }
365    
366            // get the option represented by arg
367            Option opt = (Option) getOptions().getOption(arg).clone();
368    
369            // if the option is a required option remove the option from
370            // the requiredOptions list
371            if (opt.isRequired())
372            {
373                getRequiredOptions().remove(opt.getKey());
374            }
375    
376            // if the option is in an OptionGroup make that option the selected
377            // option of the group
378            if (getOptions().getOptionGroup(opt) != null)
379            {
380                OptionGroup group = getOptions().getOptionGroup(opt);
381    
382                if (group.isRequired())
383                {
384                    getRequiredOptions().remove(group);
385                }
386    
387                group.setSelected(opt);
388            }
389    
390            // if the option takes an argument value
391            if (opt.hasArg())
392            {
393                processArgs(opt, iter);
394            }
395    
396            // set the option on the command line
397            cmd.addOption(opt);
398        }
399    }