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.spi.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.Arrays;
049: import java.util.Collections;
050: import java.util.Locale;
051: import java.util.Map;
052: import java.util.MissingResourceException;
053: import java.util.ResourceBundle;
054: import java.util.logging.Level;
055: import org.netbeans.api.sendopts.CommandException;
056: import org.netbeans.modules.sendopts.OptionImpl;
057: import org.openide.util.Lookup;
058:
059: /** Represents possible option that can appear on {@link org.netbeans.api.sendopts.CommandLine}
060: * and contains factory methods to create them.
061: * <p>
062: * An option can have letter short version, long name. It can
063: * accept arguments, one argument or an array of additional arguments.
064: *
065: * @author Jaroslav Tulach
066: */
067: public final class Option {
068: /** character of single command of (char)-1
069: */
070: private final int shortName;
071: /** long name or null */
072: private final String longName;
073: /** implementation of this option */
074: final OptionImpl impl;
075: /** bundle with message*/
076: private final String[] keys;
077: private final String[] bundles;
078:
079: /** Constant that represents no short name indicator.
080: */
081: public static final char NO_SHORT_NAME = (char) -1;
082:
083: private static String[] EMPTY = new String[2];
084:
085: /** Use factory method */
086: private Option(char shortName, String longName, int type) {
087: this .shortName = shortName == NO_SHORT_NAME ? -1
088: : (int) shortName;
089: this .longName = longName;
090: switch (type) {
091: case 0:
092: this .impl = OptionImpl.createNoArg(this );
093: break;
094: case 1:
095: this .impl = OptionImpl.createOneArg(this , false);
096: break;
097: case 2:
098: this .impl = OptionImpl.createOneArg(this , true);
099: break;
100: case 3:
101: this .impl = OptionImpl.createAdd(this , false);
102: break;
103: case 4:
104: this .impl = OptionImpl.createAdd(this , true);
105: break;
106: case 5:
107: this .impl = OptionImpl.createAlways(this );
108: break;
109: default:
110: throw new IllegalArgumentException("Type: " + type); // NOI18N
111: }
112: this .keys = EMPTY;
113: this .bundles = EMPTY;
114: }
115:
116: /** Complex option */
117: Option(int type, Option[] arr) {
118: this .shortName = -1;
119: this .longName = null;
120: this .impl = OptionImpl.create(this , type, Arrays.asList(arr));
121: this .keys = EMPTY;
122: this .bundles = EMPTY;
123: }
124:
125: /** clone with some description
126: */
127: private Option(Option old, int typeOfDescription, String bundle,
128: String description) {
129: this .shortName = old.shortName;
130: this .longName = old.longName;
131: this .impl = OptionImpl.cloneImpl(old.impl, this , null);
132: this .keys = (String[]) old.keys.clone();
133: this .bundles = (String[]) old.bundles.clone();
134:
135: this .keys[typeOfDescription] = description;
136: this .bundles[typeOfDescription] = bundle;
137:
138: }
139:
140: /** Programmatic textual representation of the option. Format is subject to change
141: * in future.
142: * @return textual description of the option
143: */
144: public String toString() {
145: StringBuffer sb = new StringBuffer();
146: sb.append(getClass().getName() + "@"
147: + Integer.toHexString(System.identityHashCode(this ))); // NOI18N
148: sb.append('[');
149: sb.append(shortName);
150: sb.append(',');
151: sb.append(longName);
152: sb.append(',');
153: impl.append(sb);
154: sb.append(']');
155: return sb.toString();
156: }
157:
158: /** Options with the same functionality, regardless of their descriptions
159: * {@link Option#shortDescription} and {@link Option#displayName} are always the same.
160: */
161: public boolean equals(Object o) {
162: if (o instanceof Option) {
163: Option option = (Option) o;
164: return impl.root == option.impl.root;
165: }
166: return false;
167: }
168:
169: public int hashCode() {
170: return System.identityHashCode(impl.root);
171: }
172:
173: /** Factory method that creates an option without any arguments.
174: * For example to create an option that handles <code>--help</code> or
175: * <code>-h</code> one can create it using:<pre>
176: * Option helpOption = Option.withoutArgument('h', "help");</pre>
177: * and inside of the {@link OptionProcessor} declaring this
178: * option use:<pre>
179: * protected void process(Env env, Map<Option,String[]> values) throws CommandException {
180: * if (values.containsKey(helpOption)) {
181: * printHelp(env.getErrorStream());
182: * }
183: * }</pre>
184: * The <code>values.get(helpOption)</code> is always <code>null</code> to signal
185: * that this options does not have any associated value.
186: *
187: * @param shortName character code or {@link Option#NO_SHORT_NAME}
188: * @param longName long name or null
189: * @return option representing the created definition
190: */
191: public static Option withoutArgument(char shortName, String longName) {
192: return new Option(shortName, longName, 0);
193: }
194:
195: /** Factory method for option that may, but does not need to have an argument.
196: * For example to have option that increments by one or by specified number
197: * one could write:<pre>
198: * Option incrementOption = Option.optionalArgument('i', "increment");</pre>
199: * and inside of the {@link OptionProcessor} declaring this
200: * option use:<pre>
201: * public void process(Env env, Map<Option,String[]> values) throws CommandException {
202: * if (values.containsKey(incrementOption)) {
203: * String[] inc = values.get(incrementOption);
204: * int increment = inc == null ? 1 : Integer.parseInt(inc[0]);
205: * // do what is necessary
206: * }
207: * }</pre>
208: * The <code>values</code> map always contains the <code>incrementOption</code>
209: * if it appeared on the command line. If it had associated value, then
210: * the <code>map.get(incrementOption)</code> returns array of length one,
211: * with item on position 0 being the value of the option. However if the
212: * option appeared without argument, then the value associated with the
213: * option is <code>null</code>.
214: * <p>
215: * If registered into to system using {@link OptionProcessor} then users could
216: * use command lines like <code>-i=5</code> or <code>--increment=5</code> to
217: * increase the value by five or just <code>-i</code> and <code>--increment</code>
218: * to increment by default - e.g. one.
219: *
220: * @param shortName the character to be used as a shortname or {@link Option#NO_SHORT_NAME}
221: * @param longName the long name or null
222: */
223: public static Option optionalArgument(char shortName,
224: String longName) {
225: return new Option(shortName, longName, 1);
226: }
227:
228: /** Factory method for option has to be followed by one argument.
229: * For example to have option that opens a file
230: * one could write:<pre>
231: * Option openOption = Option.optionalArgument('o', "open");</pre>
232: * and inside of the {@link OptionProcessor} declaring this
233: * option use:<pre>
234: * public void process(Env env, Map<Option,String[]> values) throws CommandException {
235: * if (values.containsKey(openOption)) {
236: * String fileName = values.get(openOption)[0];
237: * File file = new File({@link Env#getCurrentDirectory}, fileName);
238: * // do what is necessary
239: * }
240: * }</pre>
241: * The <code>values</code> map always contains the <code>openOption</code>
242: * if it appeared on the command line. Its value is then always string
243: * array of length one and its 0 element contains the argument for the
244: * option.
245: * <p>
246: * If registered into to system using {@link OptionProcessor} then users could
247: * use command lines like <code>-oX.java</code> or <code>--open Y.java</code> to
248: * invoke the open functionality.
249: *
250: * @param shortName the character to be used as a shortname or {@link Option#NO_SHORT_NAME}
251: * @param longName the long name or null
252: */
253: public static Option requiredArgument(char shortName,
254: String longName) {
255: return new Option(shortName, longName, 2);
256: }
257:
258: /** Creates an option that can accept <q>additional arguments</q>.
259: * For example to have option that opens few files
260: * one could write:<pre>
261: * Option openOption = Option.additionalArguments('o', "open");</pre>
262: * and inside of the {@link OptionProcessor} declaring this
263: * option use:<pre>
264: * public void process(Env env, Map<Option,String[]> values) throws CommandException {
265: * if (values.containsKey(openOption)) {
266: * for (String fileName : values.get(openOption)) {
267: * File file = new File({@link Env#getCurrentDirectory}, fileName);
268: * // do what is necessary
269: * }
270: * }
271: * }</pre>
272: * The <code>values</code> map always contains the <code>openOption</code>
273: * if it appeared on the command line. Its value is then always string
274: * array of arbitrary length containing all elements on the command line
275: * that were not recognised as options (or their arguments).
276: * For example line <pre>
277: * X.java -o Y.java Z.txt</pre>
278: * will invoke the {@link OptionProcessor} with
279: * <code>{ "X.java", "Y.java", "Z.txt" }</code>.
280: * <p>
281: * Obviously only one such {@link Option#additionalArguments} can be
282: * used at once on a command line. If there was not only the <q>open</q>
283: * but also <q>edit</q> option
284: * taking the additional arguments,
285: * then command line like: <pre>
286: * --edit X.java --open Y.java Z.txt</pre>
287: * would be rejected.
288: *
289: * @param shortName the character to be used as a shortname or {@link Option#NO_SHORT_NAME}
290: * @param longName the long name or null
291: */
292: public static Option additionalArguments(char shortName,
293: String longName) {
294: return new Option(shortName, longName, 3);
295: }
296:
297: /** Creates a default option that accepts <q>additional arguments</q>
298: * not claimed by any other option.
299: * For example to have option that opens few files
300: * one could write:<pre>
301: * Option openOption = Option.defaultArguments();</pre>
302: * and inside of the {@link OptionProcessor} declaring this
303: * option use:<pre>
304: * public void process(Env env, Map<Option,String[]> values) throws CommandException {
305: * if (values.containsKey(openOption)) {
306: * for (fileName : values.get(openOption)) {
307: * File file = new File({@link Env#getCurrentDirectory}, fileName);
308: * // do what is necessary
309: * }
310: * }
311: * }</pre>
312: * The <code>values</code> map always contains the <code>openOption</code>
313: * if there were some arguments on the command line that were not parsed
314: * by any other option. Its value is then always string
315: * array of arbitrary length containing all elements on the command line
316: * that were not recognised as options (or their arguments).
317: * For example line <pre>
318: * X.java Y.java Z.txt</pre>
319: * will invoke the {@link OptionProcessor} with
320: * <code>{ "X.java", "Y.java", "Z.txt" }</code>.
321: * <p>
322: * Obviously only one such {@link Option#defaultArguments} can defined.
323: * If there are two, then an error is reported when one tries to parse
324: * any command line with arguments not claimed by any other option.
325: * That is why it is always good idea to not define just {@link Option#defaultArguments}
326: * option, but also appropriate {@link Option#additionalArguments} one:<pre>
327: * Option openOption1 = Option.defaultArguments();
328: * Option openOption2 = Option.additionalArguments('o', "open");</pre>
329: * and handle both of them in the {@link OptionProcessor}. Then if the
330: * command line: <pre>
331: * X.java Y.java Z.txt</pre> is rejected due to ambiguities one can use <pre>
332: * X.java Y.java --open Z.txt</pre> to invoke the same functionality.
333: */
334: public static Option defaultArguments() {
335: return new Option(NO_SHORT_NAME, null, 4);
336: }
337:
338: /** Creates an option that is always present. This can be useful for
339: * processors that want to be notified everytime the command line
340: * is successfuly parsed.
341: *
342: * Option always = Option.always();</pre>
343: * and inside of the {@link OptionProcessor} declaring this
344: * option use:<pre>
345: * public void process(Env env, Map<Option,String[]> values) throws CommandException {
346: * assert values.contains(always);
347: * }</pre>
348: *
349: * @return the option that always matches correct command line
350: * @since 2.1
351: */
352: public static Option always() {
353: return new Option(NO_SHORT_NAME, null, 5);
354: }
355:
356: /** Associates a name with given option. By default
357: * the option display name is generated by the infrastructure from the
358: * short and long name plus generic description of options arguments, this
359: * method allows to completely replace the default behaviour.
360: *
361: * @param option the option to add description for
362: * @param bundleName name of a bundle to create
363: * @param key the bundle key to get the message from
364: * @return option with same behaviour as the old one plus with associated display name
365: */
366: public static Option displayName(Option option, String bundleName,
367: String key) {
368: return new Option(option, 0, bundleName, key);
369: }
370:
371: /** Associates a short textual description with given option. This message
372: * is going to be printed during {@link org.netbeans.api.sendopts.CommandLine#usage} next to the
373: * option name. Usually should be one liner comment.
374: *
375: * @param option the option to add description for
376: * @param bundleName name of a bundle to create
377: * @param key the bundle key to get the message from
378: * @return option with same behaviour as the old one plus with associated short description message
379: */
380: public static Option shortDescription(Option option,
381: String bundleName, String key) {
382: return new Option(option, 1, bundleName, key);
383: }
384:
385: static {
386: OptionImpl.Trampoline.DEFAULT = new OptionImpl.Trampoline() {
387: public OptionImpl impl(Option o) {
388: return o.impl;
389: }
390:
391: public Env create(InputStream is, OutputStream os,
392: OutputStream err, File currentDir) {
393: return new Env(is, os, err, currentDir);
394: }
395:
396: public void usage(PrintWriter w, Option o, int max) {
397: if (o.keys[1] != null) {
398: w.print(key(o.bundles[1], o.keys[1], Locale
399: .getDefault()));
400: }
401: }
402:
403: public Option[] getOptions(OptionProcessor p) {
404: return p.getOptions().toArray(new Option[0]);
405: }
406:
407: public void process(OptionProcessor provider, Env env,
408: Map<Option, String[]> options)
409: throws CommandException {
410: provider.process(env, Collections
411: .unmodifiableMap(options));
412: }
413:
414: public String getLongName(Option o) {
415: return o.longName;
416: }
417:
418: public int getShortName(Option o) {
419: return o.shortName;
420: }
421:
422: public String getDisplayName(Option o, Locale l) {
423: return key(o.bundles[0], o.keys[0], l);
424: }
425:
426: private String key(String bundle, String key, Locale l) {
427: if (key == null) {
428: return null;
429: }
430: ClassLoader loader = Lookup.getDefault().lookup(
431: ClassLoader.class);
432: if (loader == null) {
433: loader = Thread.currentThread()
434: .getContextClassLoader();
435: }
436: if (loader == null) {
437: loader = getClass().getClassLoader();
438: }
439: try {
440: ResourceBundle b = ResourceBundle.getBundle(bundle,
441: l, loader);
442: return b.getString(key);
443: } catch (MissingResourceException ex) {
444: OptionImpl.LOG.log(Level.WARNING, null, ex);
445: return key;
446: }
447:
448: }
449: };
450: }
451: }
|