001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. 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
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.api.sendopts;
043:
044: import java.io.File;
045: import java.io.InputStream;
046: import java.io.OutputStream;
047: import java.io.PrintWriter;
048: import java.util.ArrayList;
049: import java.util.Collections;
050: import java.util.HashMap;
051: import java.util.HashSet;
052: import java.util.LinkedHashMap;
053: import java.util.LinkedHashSet;
054: import java.util.Locale;
055: import java.util.Map;
056: import org.netbeans.modules.sendopts.OptionImpl;
057: import org.netbeans.spi.sendopts.Env;
058: import org.netbeans.spi.sendopts.Option;
059: import org.netbeans.spi.sendopts.OptionProcessor;
060: import org.openide.util.Lookup;
061: import org.openide.util.NbBundle;
062:
063: /**
064: * A class for clients that have an array of strings and want to process
065: * it - e.g. parse it and also invoke registered {@link OptionProcessor}s.
066: *
067: * @author Jaroslav Tulach
068: */
069: public final class CommandLine {
070: /** internal errors of CommandLine start here and end here + 100 */
071: private static final int ERROR_BASE = 50345;
072:
073: /** Use factory methods to create the line. */
074: CommandLine() {
075: }
076:
077: /** Getter for the default command line processor in the system. List
078: * of {@link OptionProcessor}s is taken from default
079: * <a href="@org-openide-util@/org/openide/util/Lookup.html">Lookup.getDefault</code>.
080: */
081: public static CommandLine getDefault() {
082: return new CommandLine();
083: }
084:
085: /** Process the array of arguments and invoke associated {@link OptionProcessor}s.
086: *
087: * @param args the array of strings to process
088: * @exception CommandException if processing is not possible or failed
089: */
090: public void process(String[] args) throws CommandException {
091: process(args, null, null, null, null);
092: }
093:
094: /** Process the array of arguments and invoke associated {@link OptionProcessor}s.
095: *
096: * @param args the array of strings to process
097: * @param is the input stream that processors can read
098: * @param os the output stream that processors can write to
099: * @param err the output stream that processors can send error messages to
100: * @param currentDir directory that processors should use as current user dir
101: * @exception CommandException if processing is not possible or failed
102: */
103: public void process(String[] args, InputStream is, OutputStream os,
104: OutputStream err, File currentDir) throws CommandException {
105: if (is == null) {
106: is = System.in;
107: }
108: if (os == null) {
109: os = System.out;
110: }
111: if (err == null) {
112: err = System.err;
113: }
114: if (currentDir == null) {
115: currentDir = new File(System.getProperty("user.dir")); // NOI18N
116: }
117: Env env = OptionImpl.Trampoline.DEFAULT.create(is, os, err,
118: currentDir);
119:
120: ArrayList<String> additionalParams = new ArrayList<String>();
121: ArrayList<OptionImpl> opts = new ArrayList<OptionImpl>();
122: OptionImpl acceptsAdons = null;
123:
124: OptionImpl[] mainOptions = getOptions();
125: LinkedHashSet<OptionImpl> allOptions = new LinkedHashSet<OptionImpl>();
126: for (int i = 0; i < mainOptions.length; i++) {
127: mainOptions[i] = mainOptions[i].addWorkingCopy(allOptions);
128: }
129: OptionImpl[] arr = allOptions.toArray(new OptionImpl[0]);
130:
131: boolean optionMode = true;
132: ARGS: for (int i = 0; i < args.length; i++) {
133: if (args[i] == null) {
134: continue ARGS;
135: }
136:
137: if (optionMode) {
138: if (args[i].startsWith("--")) {
139: if (args[i].length() == 2) {
140: optionMode = false;
141: continue ARGS;
142: }
143:
144: String text = args[i].substring(2);
145: String value = null;
146: int textEqual = text.indexOf('=');
147: if (textEqual >= 0) {
148: // strip the name of the option
149: value = text.substring(textEqual + 1);
150: text = text.substring(0, textEqual);
151: }
152: OptionImpl opt = findByLongName(text, arr);
153: if (opt == null) {
154: throw new CommandException(args[i],
155: ERROR_BASE + 1);
156: }
157: if (opt.getArgumentType() == 1 && value == null) {
158: // read next value from the argument
159: for (;;) {
160: if (++i == args.length) {
161: throw new CommandException(
162: NbBundle
163: .getMessage(
164: CommandLine.class,
165: "MSG_MissingArgument",
166: "--"
167: + opt
168: .getLongName()),
169: ERROR_BASE + 2); // NOI18N
170: }
171:
172: if (args[i].equals("--")) {
173: optionMode = false;
174: continue;
175: }
176:
177: if (optionMode && args[i].startsWith("-")) {
178: throw new CommandException(
179: NbBundle
180: .getMessage(
181: CommandLine.class,
182: "MSG_MissingArgument",
183: "--"
184: + opt
185: .getLongName()),
186: ERROR_BASE + 2); // NOI18N
187: }
188:
189: break;
190: }
191:
192: value = args[i];
193: }
194:
195: if (value != null) {
196: if (opt.getArgumentType() != 1
197: && opt.getArgumentType() != 2) {
198: throw new CommandException("Option " + opt
199: + " cannot have value " + value,
200: ERROR_BASE + 2);
201: }
202:
203: opt.associateValue(value);
204: }
205:
206: if (opt.getArgumentType() == 3) {
207: if (acceptsAdons != null) {
208: String oName1 = findOptionName(
209: acceptsAdons, args);
210: String oName2 = findOptionName(opt, args);
211: String msg = NbBundle.getMessage(
212: CommandLine.class,
213: "MSG_CannotTogether", oName1,
214: oName2); // NOI18N
215: throw new CommandException(msg,
216: ERROR_BASE + 3);
217: }
218: acceptsAdons = opt;
219: }
220:
221: opts.add(opt);
222: continue ARGS;
223: } else if (args[i].startsWith("-")
224: && args[i].length() > 1) {
225: for (int j = 1; j < args[i].length(); j++) {
226: char ch = args[i].charAt(j);
227: OptionImpl opt = findByShortName(ch, arr);
228: if (opt == null) {
229: throw new CommandException(
230: "Unknown option " + args[i],
231: ERROR_BASE + 1);
232: }
233: if (args[i].length() == j + 1
234: && opt.getArgumentType() == 1) {
235: throw new CommandException(NbBundle
236: .getMessage(CommandLine.class,
237: "MSG_MissingArgument",
238: args[i]), ERROR_BASE + 2);
239: }
240:
241: if (args[i].length() > j
242: && (opt.getArgumentType() == 1 || opt
243: .getArgumentType() == 2)) {
244: opt
245: .associateValue(args[i]
246: .substring(j + 1));
247: j = args[i].length();
248: }
249: if (opt.getArgumentType() == 3) {
250: if (acceptsAdons != null) {
251: String oName1 = findOptionName(
252: acceptsAdons, args);
253: String oName2 = findOptionName(opt,
254: args);
255: String msg = NbBundle.getMessage(
256: CommandLine.class,
257: "MSG_CannotTogether", oName1,
258: oName2); // NOI18N
259: throw new CommandException(msg,
260: ERROR_BASE + 3);
261: }
262: acceptsAdons = opt;
263: }
264: opts.add(opt);
265: }
266: continue ARGS;
267: }
268: }
269:
270: additionalParams.add(args[i]);
271: }
272:
273: if (acceptsAdons == null && !additionalParams.isEmpty()) {
274: for (int i = 0; i < arr.length; i++) {
275: if (arr[i].getArgumentType() == 4) {
276: if (acceptsAdons != null) {
277: throw new CommandException(
278: "There cannot be two default options: "
279: + acceptsAdons + " and "
280: + arr[i], ERROR_BASE + 3);
281: }
282: acceptsAdons = arr[i];
283: opts.add(acceptsAdons);
284: }
285: }
286: if (acceptsAdons == null) {
287: throw new CommandException(
288: "There are params but noone wants to proces them: "
289: + additionalParams, ERROR_BASE + 2);
290: }
291:
292: }
293:
294: OptionImpl.Appearance[] postProcess = new OptionImpl.Appearance[mainOptions.length];
295: {
296: HashSet<OptionImpl> used = new HashSet<OptionImpl>(opts);
297: for (int i = 0; i < mainOptions.length; i++) {
298: OptionImpl.Appearance res = mainOptions[i]
299: .checkConsistent(used);
300: postProcess[i] = res;
301: if (res.isThere()) {
302: mainOptions[i].markConsistent(res);
303: }
304: /*
305: if (res.isError()) {
306: throw new CommandException(res.errorMessage(args), ERROR_BASE + 4);
307: }
308: */
309: }
310: }
311:
312: {
313: HashSet<OptionImpl> used = new HashSet<OptionImpl>(opts);
314: for (int i = 0; i < mainOptions.length; i++) {
315: if (postProcess[i].isError()) {
316: OptionImpl error = mainOptions[i]
317: .findNotUsedOption(used);
318: if (error != null) {
319: throw new CommandException(postProcess[i]
320: .errorMessage(args), ERROR_BASE + 4);
321: }
322: }
323: }
324: }
325:
326: Map<OptionProcessor, Map<Option, String[]>> providers = new LinkedHashMap<OptionProcessor, Map<Option, String[]>>();
327: {
328: for (int i = 0; i < mainOptions.length; i++) {
329: if (postProcess[i].isThere()) {
330: Map<Option, String[]> param = providers
331: .get(mainOptions[i].getProvider());
332: if (param == null) {
333: param = new HashMap<Option, String[]>();
334: providers.put(mainOptions[i].getProvider(),
335: param);
336: }
337: mainOptions[i].process(additionalParams
338: .toArray(new String[0]), param);
339: }
340: }
341: }
342:
343: for (Map.Entry<OptionProcessor, Map<Option, String[]>> pair : providers
344: .entrySet()) {
345: OptionImpl.Trampoline.DEFAULT.process(pair.getKey(), env,
346: pair.getValue());
347: }
348: }
349:
350: /** Prints the usage information about options provided by associated
351: * {@link OptionProcessor}s.
352: *
353: * @param w the writer to output usage info to
354: * @since 1.7
355: */
356: public void usage(PrintWriter w) {
357: OptionImpl[] mainOptions = getOptions();
358: LinkedHashSet<OptionImpl> allOptions = new LinkedHashSet<OptionImpl>();
359: for (int i = 0; i < mainOptions.length; i++) {
360: mainOptions[i].addWorkingCopy(allOptions);
361: }
362: OptionImpl[] arr = allOptions.toArray(new OptionImpl[0]);
363:
364: int max = 25;
365: String[] prefixes = new String[arr.length];
366: for (int i = 0; i < arr.length; i++) {
367: StringBuffer sb = new StringBuffer();
368:
369: String ownDisplay = OptionImpl.Trampoline.DEFAULT
370: .getDisplayName(arr[i].getOption(), Locale
371: .getDefault());
372: if (ownDisplay != null) {
373: sb.append(ownDisplay);
374: } else {
375: String sep = "";
376: if (arr[i].getShortName() != -1) {
377: sb.append('-');
378: sb.append((char) arr[i].getShortName());
379: sep = ", ";
380: }
381: if (arr[i].getLongName() != null) {
382: sb.append(sep);
383: sb.append("--"); // NOI18N
384: sb.append(arr[i].getLongName());
385: } else {
386: if (sep.length() == 0) {
387: continue;
388: }
389: }
390:
391: switch (arr[i].getArgumentType()) {
392: case 0:
393: break;
394: case 1:
395: sb.append(' ');
396: sb.append(NbBundle.getMessage(CommandLine.class,
397: "MSG_OneArg")); // NOI18N
398: break;
399: case 2:
400: sb.append(' ');
401: sb.append(NbBundle.getMessage(CommandLine.class,
402: "MSG_OptionalArg")); // NOI18N
403: break;
404: case 3:
405: sb.append(' ');
406: sb.append(NbBundle.getMessage(CommandLine.class,
407: "MSG_AddionalArgs")); // NOI18N
408: break;
409: default:
410: assert false;
411: }
412: }
413:
414: if (sb.length() > max) {
415: max = sb.length();
416: }
417:
418: prefixes[i] = sb.toString();
419: }
420:
421: for (int i = 0; i < arr.length; i++) {
422: if (prefixes[i] != null) {
423: w.print(" "); // NOI18N
424: w.print(prefixes[i]);
425: for (int j = prefixes[i].length(); j < max; j++) {
426: w.print(' ');
427: }
428: w.print(' ');
429: arr[i].usage(w, max);
430: w.println();
431: }
432: }
433:
434: w.flush();
435: }
436:
437: private OptionImpl[] getOptions() {
438: ArrayList<OptionImpl> arr = new ArrayList<OptionImpl>();
439:
440: for (OptionProcessor p : Lookup.getDefault().lookupAll(
441: OptionProcessor.class)) {
442: org.netbeans.spi.sendopts.Option[] all = OptionImpl.Trampoline.DEFAULT
443: .getOptions(p);
444: for (int i = 0; i < all.length; i++) {
445: arr.add(OptionImpl.cloneImpl(OptionImpl.find(all[i]),
446: all[i], p));
447: }
448: }
449:
450: return arr.toArray(new OptionImpl[0]);
451: }
452:
453: private OptionImpl findByLongName(String lng, OptionImpl[] arr) {
454: boolean abbrev = false;
455: OptionImpl best = null;
456: for (int i = 0; i < arr.length; i++) {
457: String on = arr[i].getLongName();
458: if (on == null) {
459: continue;
460: }
461: if (lng.equals(on)) {
462: return arr[i];
463: }
464: if (on.startsWith(lng)) {
465: abbrev = best == null;
466: best = arr[i];
467: }
468: }
469:
470: return abbrev ? best : null;
471: }
472:
473: private OptionImpl findByShortName(char ch, OptionImpl[] arr) {
474: for (int i = 0; i < arr.length; i++) {
475: if (ch == arr[i].getShortName()) {
476: return arr[i];
477: }
478: }
479: return null;
480: }
481:
482: private static String findOptionName(OptionImpl opt, String[] args) {
483: for (int i = 0; i < args.length; i++) {
484: if (!args[i].startsWith("-")) {
485: continue;
486: }
487:
488: if (args[i].startsWith("--")) {
489: String text = args[i].substring(2);
490: int textEqual = text.indexOf('=');
491: if (textEqual >= 0) {
492: // strip the name of the option
493: text = text.substring(0, textEqual);
494: }
495: if (text.startsWith(opt.getLongName())) {
496: return args[i];
497: }
498: } else {
499: if (opt.getShortName() == args[i].charAt(1)) {
500: return "-" + (char) opt.getShortName();
501: }
502: }
503: }
504:
505: return opt.toString();
506: }
507:
508: }
|