Parser.java

  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.       http://www.apache.org/licenses/LICENSE-2.0

  9.   Unless required by applicable law or agreed to in writing, software
  10.   distributed under the License is distributed on an "AS IS" BASIS,
  11.   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12.   See the License for the specific language governing permissions and
  13.   limitations under the License.
  14.  */

  15. package org.apache.commons.cli;

  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.Enumeration;
  19. import java.util.List;
  20. import java.util.ListIterator;
  21. import java.util.Properties;

  22. /**
  23.  * {@code Parser} creates {@link CommandLine}s.
  24.  *
  25.  * @deprecated since 1.3, the two-pass parsing with the flatten method is not enough flexible to handle complex cases
  26.  */
  27. @Deprecated
  28. public abstract class Parser implements CommandLineParser {
  29.     /** CommandLine instance */
  30.     protected CommandLine cmd;

  31.     /** Current Options */
  32.     private Options options;

  33.     /** List of required options strings */
  34.     private List requiredOptions;

  35.     /**
  36.      * Throws a {@link MissingOptionException} if all of the required options are not present.
  37.      *
  38.      * @throws MissingOptionException if any of the required Options are not present.
  39.      */
  40.     protected void checkRequiredOptions() throws MissingOptionException {
  41.         // if there are required options that have not been processed
  42.         if (!getRequiredOptions().isEmpty()) {
  43.             throw new MissingOptionException(getRequiredOptions());
  44.         }
  45.     }

  46.     /**
  47.      * Subclasses must implement this method to reduce the {@code arguments} that have been passed to the parse method.
  48.      *
  49.      * @param opts The Options to parse the arguments by.
  50.      * @param arguments The arguments that have to be flattened.
  51.      * @param stopAtNonOption specifies whether to stop flattening when a non option has been encountered
  52.      * @return a String array of the flattened arguments
  53.      * @throws ParseException if there are any problems encountered while parsing the command line tokens.
  54.      */
  55.     protected abstract String[] flatten(Options opts, String[] arguments, boolean stopAtNonOption) throws ParseException;

  56.     /**
  57.      * Gets the options.
  58.      *
  59.      * @return the options.
  60.      */
  61.     protected Options getOptions() {
  62.         return options;
  63.     }

  64.     /**
  65.      * Gets the required options.
  66.      *
  67.      * @return the required options.
  68.      */
  69.     protected List getRequiredOptions() {
  70.         return requiredOptions;
  71.     }

  72.     /**
  73.      * Parses the specified {@code arguments} based on the specified {@link Options}.
  74.      *
  75.      * @param options the {@code Options}
  76.      * @param arguments the {@code arguments}
  77.      * @return the {@code CommandLine}
  78.      * @throws ParseException if there are any problems encountered while parsing the command line tokens.
  79.      */
  80.     @Override
  81.     public CommandLine parse(final Options options, final String[] arguments) throws ParseException {
  82.         return parse(options, arguments, null, false);
  83.     }

  84.     /**
  85.      * Parses the specified {@code arguments} based on the specified {@link Options}.
  86.      *
  87.      * @param options the {@code Options}
  88.      * @param arguments the {@code arguments}
  89.      * @param stopAtNonOption if {@code true} an unrecognized argument stops the parsing and the remaining arguments
  90.      *        are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a
  91.      *        ParseException.
  92.      * @return the {@code CommandLine}
  93.      * @throws ParseException if an error occurs when parsing the arguments.
  94.      */
  95.     @Override
  96.     public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
  97.         return parse(options, arguments, null, stopAtNonOption);
  98.     }

  99.     /**
  100.      * Parse the arguments according to the specified options and properties.
  101.      *
  102.      * @param options the specified Options
  103.      * @param arguments the command line arguments
  104.      * @param properties command line option name-value pairs
  105.      * @return the list of atomic option and value tokens
  106.      * @throws ParseException if there are any problems encountered while parsing the command line tokens.
  107.      * @since 1.1
  108.      */
  109.     public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException {
  110.         return parse(options, arguments, properties, false);
  111.     }

  112.     /**
  113.      * Parse the arguments according to the specified options and properties.
  114.      *
  115.      * @param options the specified Options
  116.      * @param arguments the command line arguments
  117.      * @param properties command line option name-value pairs
  118.      * @param stopAtNonOption if {@code true} an unrecognized argument stops the parsing and the remaining arguments
  119.      *        are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a
  120.      *        ParseException.
  121.      * @return the list of atomic option and value tokens
  122.      * @throws ParseException if there are any problems encountered while parsing the command line tokens.
  123.      * @since 1.1
  124.      */
  125.     public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption)
  126.             throws ParseException {
  127.         // clear out the data in options in case it's been used before (CLI-71)
  128.         for (final Option opt : options.helpOptions()) {
  129.             opt.clearValues();
  130.         }
  131.         // clear the data from the groups
  132.         for (final OptionGroup group : options.getOptionGroups()) {
  133.             group.setSelected(null);
  134.         }
  135.         // initialize members
  136.         setOptions(options);
  137.         cmd = CommandLine.builder().build();
  138.         boolean eatTheRest = false;
  139.         final List<String> tokenList = Arrays.asList(flatten(getOptions(), arguments == null ? new String[0] : arguments, stopAtNonOption));
  140.         final ListIterator<String> iterator = tokenList.listIterator();
  141.         // process each flattened token
  142.         while (iterator.hasNext()) {
  143.             final String token = iterator.next();
  144.             if (token != null) {
  145.                 // the value is the double-dash
  146.                 if ("--".equals(token)) {
  147.                     eatTheRest = true;
  148.                 } else if ("-".equals(token)) {
  149.                     // the value is a single dash
  150.                     if (stopAtNonOption) {
  151.                         eatTheRest = true;
  152.                     } else {
  153.                         cmd.addArg(token);
  154.                     }
  155.                 } else if (token.startsWith("-")) {
  156.                     // the value is an option
  157.                     if (stopAtNonOption && !getOptions().hasOption(token)) {
  158.                         eatTheRest = true;
  159.                         cmd.addArg(token);
  160.                     } else {
  161.                         processOption(token, iterator);
  162.                     }
  163.                 } else {
  164.                     // the value is an argument
  165.                     cmd.addArg(token);
  166.                     if (stopAtNonOption) {
  167.                         eatTheRest = true;
  168.                     }
  169.                 }
  170.                 // eat the remaining tokens
  171.                 if (eatTheRest) {
  172.                     while (iterator.hasNext()) {
  173.                         final String str = iterator.next();
  174.                         // ensure only one double-dash is added
  175.                         if (!"--".equals(str)) {
  176.                             cmd.addArg(str);
  177.                         }
  178.                     }
  179.                 }
  180.             }
  181.         }
  182.         processProperties(properties);
  183.         checkRequiredOptions();
  184.         return cmd;
  185.     }

  186.     /**
  187.      * Process the argument values for the specified Option {@code opt} using the values retrieved from the specified
  188.      * iterator {@code iter}.
  189.      *
  190.      * @param opt The current Option
  191.      * @param iter The iterator over the flattened command line Options.
  192.      * @throws ParseException if an argument value is required and it is has not been found.
  193.      */
  194.     public void processArgs(final Option opt, final ListIterator<String> iter) throws ParseException {
  195.         // loop until an option is found
  196.         while (iter.hasNext()) {
  197.             final String str = iter.next();
  198.             // found an Option, not an argument
  199.             if (getOptions().hasOption(str) && str.startsWith("-")) {
  200.                 iter.previous();
  201.                 break;
  202.             }
  203.             // found a value
  204.             try {
  205.                 opt.processValue(Util.stripLeadingAndTrailingQuotes(str));
  206.             } catch (final RuntimeException exp) {
  207.                 iter.previous();
  208.                 break;
  209.             }
  210.         }
  211.         if (opt.getValues() == null && !opt.hasOptionalArg()) {
  212.             throw new MissingArgumentException(opt);
  213.         }
  214.     }

  215.     /**
  216.      * Process the Option specified by {@code arg} using the values retrieved from the specified iterator
  217.      * {@code iter}.
  218.      *
  219.      * @param arg The String value representing an Option
  220.      * @param iter The iterator over the flattened command line arguments.
  221.      * @throws ParseException if {@code arg} does not represent an Option
  222.      */
  223.     protected void processOption(final String arg, final ListIterator<String> iter) throws ParseException {
  224.         final boolean hasOption = getOptions().hasOption(arg);
  225.         // if there is no option throw an UnrecognizedOptionException
  226.         if (!hasOption) {
  227.             throw new UnrecognizedOptionException("Unrecognized option: " + arg, arg);
  228.         }
  229.         // get the option represented by arg
  230.         final Option opt = (Option) getOptions().getOption(arg).clone();
  231.         // update the required options and groups
  232.         updateRequiredOptions(opt);
  233.         // if the option takes an argument value
  234.         if (opt.hasArg()) {
  235.             processArgs(opt, iter);
  236.         }
  237.         // set the option on the command line
  238.         cmd.addOption(opt);
  239.     }

  240.     /**
  241.      * Sets the values of Options using the values in {@code properties}.
  242.      *
  243.      * @param properties The value properties to be processed.
  244.      * @throws ParseException if there are any problems encountered while processing the properties.
  245.      */
  246.     protected void processProperties(final Properties properties) throws ParseException {
  247.         if (properties == null) {
  248.             return;
  249.         }
  250.         for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
  251.             final String option = e.nextElement().toString();
  252.             final Option opt = options.getOption(option);
  253.             if (opt == null) {
  254.                 throw new UnrecognizedOptionException("Default option wasn't defined", option);
  255.             }
  256.             // if the option is part of a group, check if another option of the group has been selected
  257.             final OptionGroup group = options.getOptionGroup(opt);
  258.             final boolean selected = group != null && group.isSelected();
  259.             if (!cmd.hasOption(option) && !selected) {
  260.                 // get the value from the properties instance
  261.                 final String value = properties.getProperty(option);
  262.                 if (opt.hasArg()) {
  263.                     if (Util.isEmpty(opt.getValues())) {
  264.                         try {
  265.                             opt.processValue(value);
  266.                         } catch (final RuntimeException exp) { // NOPMD
  267.                             // if we cannot add the value don't worry about it
  268.                         }
  269.                     }
  270.                 } else if (!("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value))) {
  271.                     // if the value is not yes, true or 1 then don't add the
  272.                     // option to the CommandLine
  273.                     continue;
  274.                 }
  275.                 cmd.addOption(opt);
  276.                 updateRequiredOptions(opt);
  277.             }
  278.         }
  279.     }

  280.     /**
  281.      * Sets the options.
  282.      *
  283.      * @param options the options.
  284.      */
  285.     protected void setOptions(final Options options) {
  286.         this.options = options;
  287.         this.requiredOptions = new ArrayList<>(options.getRequiredOptions());
  288.     }

  289.     /**
  290.      * Removes the option or its group from the list of expected elements.
  291.      *
  292.      * @param opt
  293.      */
  294.     private void updateRequiredOptions(final Option opt) throws ParseException {
  295.         // if the option is a required option remove the option from
  296.         // the requiredOptions list
  297.         if (opt.isRequired()) {
  298.             getRequiredOptions().remove(opt.getKey());
  299.         }
  300.         // if the option is in an OptionGroup make that option the selected
  301.         // option of the group
  302.         if (getOptions().getOptionGroup(opt) != null) {
  303.             final OptionGroup group = getOptions().getOptionGroup(opt);
  304.             if (group.isRequired()) {
  305.                 getRequiredOptions().remove(group);
  306.             }
  307.             group.setSelected(opt);
  308.         }
  309.     }

  310. }