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 https://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 018package org.apache.commons.cli; 019 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Iterator; 023import java.util.List; 024 025import org.apache.commons.cli.help.OptionFormatter; 026 027/** 028 * The class PosixParser provides an implementation of the {@link Parser#flatten(Options,String[],boolean) flatten} 029 * method. 030 * 031 * @deprecated Since 1.3, use the {@link DefaultParser} instead. 032 */ 033@Deprecated 034public class PosixParser extends Parser { 035 036 /** Holder for flattened tokens. */ 037 private final List<String> tokens = new ArrayList<>(); 038 039 /** Specifies if bursting should continue.. */ 040 private boolean eatTheRest; 041 042 /** Holder for the current option */ 043 private Option currentOption; 044 045 /** The command line Options. */ 046 private Options options; 047 048 /** 049 * Constructs a new instance. 050 */ 051 public PosixParser() { 052 // empty 053 } 054 055 /** 056 * Adds the remaining tokens to the processed tokens list. 057 * 058 * @param iter An iterator over the remaining tokens. 059 */ 060 private void addRemaining(final Iterator<String> iter) { 061 if (eatTheRest) { 062 iter.forEachRemaining(tokens::add); 063 } 064 } 065 066 /** 067 * Breaks {@code token} into its constituent parts using the following algorithm. 068 * 069 * <ul> 070 * <li>ignore the first character ("<strong>-</strong>")</li> 071 * <li>for each remaining character check if an {@link Option} exists with that id.</li> 072 * <li>if an {@link Option} does exist then add that character prepended with "<strong>-</strong>" to the list of processed 073 * tokens.</li> 074 * <li>if the {@link Option} can have an argument value and there are remaining characters in the token then add the 075 * remaining characters as a token to the list of processed tokens.</li> 076 * <li>if an {@link Option} does <strong>NOT</strong> exist <strong>AND</strong> {@code stopAtNonOption} <strong>IS</strong> set then add the 077 * special token "<strong>--</strong>" followed by the remaining characters and also the remaining tokens directly to the 078 * processed tokens list.</li> 079 * <li>if an {@link Option} does <strong>NOT</strong> exist <strong>AND</strong> {@code stopAtNonOption} <strong>IS NOT</strong> set then add 080 * that character prepended with "<strong>-</strong>".</li> 081 * </ul> 082 * 083 * @param token The current token to be <strong>burst</strong>. 084 * @param stopAtNonOption Specifies whether to stop processing at the first non-Option encountered. 085 */ 086 protected void burstToken(final String token, final boolean stopAtNonOption) { 087 for (int i = 1; i < token.length(); i++) { 088 final String ch = String.valueOf(token.charAt(i)); 089 if (!options.hasOption(ch)) { 090 if (stopAtNonOption) { 091 processNonOptionToken(token.substring(i), true); 092 } else { 093 tokens.add(token); 094 } 095 break; 096 } 097 tokens.add(OptionFormatter.DEFAULT_OPT_PREFIX + ch); 098 currentOption = options.getOption(ch); 099 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}