001 /*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
005 *
006 * The contents of this file are subject to the terms of either the GNU
007 * General Public License Version 2 only ("GPL") or the Common Development
008 * and Distribution License("CDDL") (collectively, the "License"). You
009 * may not use this file except in compliance with the License. You can
010 * obtain a copy of the License at
011 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012 * or packager/legal/LICENSE.txt. See the License for the specific
013 * language governing permissions and limitations under the License.
014 *
015 * When distributing the software, include this License Header Notice in each
016 * file and include the License file at packager/legal/LICENSE.txt.
017 *
018 * GPL Classpath Exception:
019 * Oracle designates this particular file as subject to the "Classpath"
020 * exception as provided by Oracle in the GPL Version 2 section of the License
021 * file that accompanied this code.
022 *
023 * Modifications:
024 * If applicable, add the following below the License Header, with the fields
025 * enclosed by brackets [] replaced by your own identifying information:
026 * "Portions Copyright [year] [name of copyright owner]"
027 *
028 * Contributor(s):
029 * If you wish your version of this file to be governed by only the CDDL or
030 * only the GPL Version 2, indicate your decision by adding "[Contributor]
031 * elects to include this software in this distribution under the [CDDL or GPL
032 * Version 2] license." If you don't indicate a single choice of license, a
033 * recipient has the option to distribute your version of this file under
034 * either the CDDL, the GPL Version 2 or to extend the choice of license to
035 * its licensees as provided above. However, if you add GPL Version 2 code
036 * and therefore, elected the GPL Version 2 license, then the option applies
037 * only if the new code is made subject to such option by the copyright
038 * holder.
039 */
040
041 package com.sun.enterprise.admin.cli;
042
043 import java.io.*;
044 import java.util.*;
045 import org.glassfish.api.admin.*;
046 import org.glassfish.api.admin.CommandModel.ParamModel;
047 import com.sun.enterprise.admin.util.*;
048 import com.sun.enterprise.admin.util.CommandModelData.ParamModelData;
049 import com.sun.enterprise.universal.i18n.LocalStringsImpl;
050
051
052 /**
053 * The <code>Parser</code> object is used to parse the
054 * command line and verify that the command line is CLIP compliant.
055 */
056 public class Parser {
057 // MultiMap of options and values from command-line
058 private ParameterMap optionsMap = new ParameterMap();
059
060 // Array of operands from command-line
061 private List<String> operands = new ArrayList<String>();
062
063 // The valid options for the command we're parsing
064 private Collection<ParamModel> options;
065
066 // Ignore unknown options when parsing?
067 private boolean ignoreUnknown;
068
069 private static final LocalStringsImpl strings =
070 new LocalStringsImpl(Parser.class);
071
072 /*
073 * TODO:
074 * option types shouldn't be string literals here
075 */
076
077 /**
078 * Parse the given command line arguments
079 *
080 * @param args command line arguments
081 * @param start index in args to start parsing
082 * @param options the valid options to consider while parsing
083 * @param ignoreUnknown if true, unknown options are considered operands
084 * instead of generating an exception
085 * @throws CommandValidationException if command line parsing fails
086 */
087 public Parser(String[] args, int start,
088 Collection<ParamModel> options, boolean ignoreUnknown)
089 throws CommandValidationException {
090 this.options = options;
091 this.ignoreUnknown = ignoreUnknown;
092 parseCommandLine(args, start);
093 }
094
095 /**
096 * Parse the command line arguments according to CLIP.
097 *
098 * @param argv command line arguments
099 * @throws CommandValidationException if command line is invalid
100 */
101 private void parseCommandLine(final String[] argv, final int start)
102 throws CommandValidationException {
103
104 for (int si = start; si < argv.length; si++) {
105 String arg = argv[si];
106 if (arg.equals("--")) { // end of options
107 // if we're ignoring unknown options, we include this
108 // delimiter as an operand, it will be eliminated later
109 // when we process all remaining options
110 if (!ignoreUnknown)
111 si++;
112 while (si < argv.length)
113 operands.add(argv[si++]);
114 break;
115 }
116
117 // is it an operand or option value?
118 if (!arg.startsWith("-") || arg.length() <= 1) {
119 operands.add(arg);
120 if (ignoreUnknown)
121 continue;
122 si++;
123 while (si < argv.length)
124 operands.add(argv[si++]);
125 break;
126 }
127
128 // at this point it's got to be an option of some sort
129 ParamModel opt = null;
130 String name = null;
131 String value = null;
132 if (arg.charAt(1) == '-') { // long option
133 int ns = 2;
134 boolean sawno = false;
135 if (arg.startsWith("--no-")) {
136 sawno = true;
137 value = "false";
138 ns = 5; // skip prefix
139 }
140 // if of the form "--option=value", extract value
141 int ne = arg.indexOf('=');
142 if (ne < 0)
143 name = arg.substring(ns);
144 else {
145 if (value != null)
146 throw new CommandValidationException(
147 strings.get("parser.noValueAllowed", arg));
148 name = arg.substring(ns, ne);
149 value = arg.substring(ne + 1);
150 }
151 opt = lookupLongOption(name);
152 if (sawno && optionRequiresOperand(opt))
153 throw new CommandValidationException(
154 strings.get("parser.illegalNo", opt.getName()));
155 } else { // short option
156 /*
157 * possibilities are:
158 * -f
159 * -f value
160 * -f=value
161 * -fxyz (multiple single letter boolean options
162 * with no arguments)
163 */
164 if (arg.length() <= 2) { // one of the first two cases
165 opt = lookupShortOption(arg.charAt(1));
166 name = arg.substring(1);
167 } else { // one of the last two cases
168 if (arg.charAt(2) == '=') { // -f=value case
169 opt = lookupShortOption(arg.charAt(1));
170 value = arg.substring(3);
171 } else { // -fxyz case
172 for (int i = 1; i < arg.length(); i++) {
173 opt = lookupShortOption(arg.charAt(i));
174 if (opt == null) {
175 if (!ignoreUnknown)
176 throw new CommandValidationException(
177 strings.get("parser.invalidOption",
178 Character.toString(arg.charAt(i))));
179 // unknown option, skip all the rest
180 operands.add(arg);
181 break;
182 }
183 if (opt.getType() == Boolean.class ||
184 opt.getType() == boolean.class)
185 setOption(opt, "true");
186 else {
187 if (!ignoreUnknown)
188 throw new CommandValidationException(
189 strings.get("parser.nonbooleanNotAllowed",
190 Character.toString(arg.charAt(i)), arg));
191 // unknown option, skip all the rest
192 operands.add(arg);
193 break;
194 }
195 }
196 continue;
197 }
198 }
199 }
200
201 // is it a known option?
202 if (opt == null) {
203 if (!ignoreUnknown)
204 throw new CommandValidationException(
205 strings.get("parser.invalidOption", arg));
206 // unknown option, skip it
207 operands.add(arg);
208 continue;
209 }
210
211 // find option value, if needed
212 if (value == null) {
213 // if no valid options were specified, we use the next argument
214 // as an option as long as it doesn't look like an option
215 if (options == null) {
216 if (si + 1 < argv.length && !argv[si + 1].startsWith("-"))
217 value = argv[++si];
218 else
219 ((ParamModelData)opt).type = Boolean.class; // fake it
220 } else if (optionRequiresOperand(opt)) {
221 if (++si >= argv.length)
222 throw new CommandValidationException(
223 strings.get("parser.missingValue", name));
224 value = argv[si];
225 } else if (opt.getType() == Boolean.class ||
226 opt.getType() == boolean.class) {
227 /*
228 * If it's a boolean option, the following parameter
229 * might be the value for the option; peek ahead to
230 * see if it looks like a boolean value.
231 */
232 if (si + 1 < argv.length) {
233 String val = argv[si + 1];
234 if (val.equalsIgnoreCase("true") ||
235 val.equalsIgnoreCase("false")) {
236 // yup, it's a boolean value, consume it
237 si++;
238 value = val;
239 }
240 }
241 }
242 }
243 setOption(opt, value);
244 }
245 }
246
247 /**
248 * Returns a Map with all the options.
249 * The Map is indexed by the long name of the option.
250 *
251 * @return options
252 */
253 public ParameterMap getOptions() {
254 return optionsMap;
255 }
256
257 /**
258 * Returns the list of operands.
259 *
260 * @return list of operands
261 */
262 public List<String> getOperands() {
263 return operands;
264 }
265
266 public String toString() {
267 return "CLI parser: Options = " + optionsMap +
268 "; Operands = " + operands;
269 }
270
271 /**
272 * Get ParamModel for long option name.
273 */
274 private ParamModel lookupLongOption(String s) {
275 if (s == null || s.length() == 0)
276 return null;
277 // XXX - for now, fake it if no options
278 if (options == null) {
279 // no valid options specified so everything is valid
280 return new ParamModelData(s, String.class, true, null);
281 }
282 for (ParamModel od : options) {
283 if (od.getParam().primary())
284 continue;
285 if (s.equalsIgnoreCase(od.getName()))
286 return od;
287 if (s.equalsIgnoreCase(od.getParam().alias()))
288 return od;
289 }
290 return null;
291 }
292
293 /**
294 * Get ParamModel for short option name.
295 */
296 private ParamModel lookupShortOption(char c) {
297 // XXX - for now, fake it if no options
298 if (options == null)
299 return null;
300 String sc = Character.toString(c);
301 for (ParamModel od : options) {
302 if (od.getParam().shortName().equals(sc))
303 return od;
304 }
305 return null;
306 }
307
308 /**
309 * Does this option require an operand?
310 */
311 private static boolean optionRequiresOperand(ParamModel opt) {
312 return opt != null && opt.getType() != Boolean.class &&
313 opt.getType() != boolean.class;
314 }
315
316 /**
317 * Set the value for the option.
318 */
319 private void setOption(ParamModel opt, String value)
320 throws CommandValidationException {
321 String name = opt.getName();
322 // VERY basic validation
323 if (opt == null)
324 throw new NullPointerException("null option name");
325 if (value != null)
326 value = value.trim();
327
328 if (opt.getType() == File.class) {
329 File f = new File(value);
330 // allow the pseudo-file name of "-" to mean stdin
331 if (!value.equals("-") && !(f.isFile() || f.canRead())) {
332 // get a real exception for why it's no good
333 InputStream is = null;
334 try {
335 is = new FileInputStream(f);
336 } catch (IOException ioex) {
337 throw new CommandValidationException(
338 strings.get("parser.invalidFileEx",
339 name, ioex.toString()));
340 } finally {
341 if (is != null)
342 try {
343 is.close();
344 } catch (IOException cex) { }
345 }
346 throw new CommandValidationException(
347 strings.get("parser.invalidFile", name, value));
348 }
349 } else if (opt.getType() == Boolean.class ||
350 opt.getType() == boolean.class) {
351 if (value == null)
352 value = "true";
353 else if (!(value.toLowerCase(Locale.ENGLISH).equals("true") ||
354 value.toLowerCase(Locale.ENGLISH).equals("false")))
355 throw new CommandValidationException(
356 strings.get("parser.invalidBoolean", name, value));
357 } else if (opt.getParam().password())
358 throw new CommandValidationException(
359 strings.get("parser.passwordNotAllowed", opt.getName()));
360
361 if (!opt.getParam().multiple()) {
362 // repeats not allowed
363 if (optionsMap.containsKey(name)) {
364 throw new CommandValidationException(
365 strings.get("parser.noRepeats", name));
366 }
367 }
368
369 optionsMap.add(name, value);
370 }
371 }