See: Description
Package | Description |
---|---|
org.netbeans.api.sendopts |
Start here if you are in a standalone application and you want to
parse a command line using the
sendopts infrastructure. |
org.netbeans.spi.sendopts |
Package for those that want to write a
command line handler
that can participate on handling parts of a
command line send to the application.
|
SendOptsAPI allows anyone to parse an array of strings - e.g. a command line. The infrastracture of the module then locates all providers registered using the SendOptsSPI and distributes the parts of the command line to their handlers. It is expected that the handlers do not know about each other and are in fact provided by different modules. The goal of the sendopts framework is to get the description of the handlers, apply the gained knowledge to the actual content of the command line and distribute the parts of the command line arguments to the designated handlers. Beyond this optimal state the error handling and help composition is also supported by this infrastructure.
Create command line with instances of OptionProcessor and ArgsProcessor.
Env.usage
method now takes an optional OutputStream
parameter.
CommandLine.html.create(Class...) can now be called with classes that extend OptionProcessor.
--help
request
Env.usage
can be called when one wants to process own --help
option.
@Arg annotation and associated classes allow to register options declaratively. There is a new factory method to create multiple instances of differently configured command lines.
CommandLine.getDefault().process(args)
.
public final class MyOption implements Runnable { @Arg(longName="hello") public String name; public void run() { System.out.println("Hello " + name + "!"); } public static void main(String... args) { CommandLine line = CommandLine.create(MyOption.class); line.process(args); } }
If the above main class is called with parameters --hello World
it
will print out Hello World!
.
getopts
supports short form of options - e.g. a dash
followed with one letter - or long form using two dashes followed with a word.
Moreover the long form is optimized for abbrevations. If there are no
conflicts between multiple options, then one can only use double dash followed
with a prefix of a long option.
When using the declarative annotation style
one can always specify
@Arg(longName="text", shortName='t')
.
The longName
attribute is required, but if there is supposed to be
no long version of the argument, it can be set to empty string.
One can create an Option
by calling any of its factory methods
(like
withoutArgument)
and provider char
for the one letter option and/or string for
the long getopts option.
boolean
to create
an option without an argument.
--. If these characters do appear on the command line, the rest of it is treated as extra arguments and not processed. The sendopts infrastructure supports this as well.
Q: How shall one write an
OptionProcessor
that recognizes set of basic options, however contains one open slot
?
The processor wants other modules to provide recognizers for that slot
and wants to communicate with them. For example, by default the processor
recognizes option --channel <name_of_the_channel>
which describes a source of data, and stores such data into a sink
.
There can be multiple sinks - discard the output, save it to file, show
it on stdout, stream it to network. The processor itself can handle the
copying of data, but does not itself know all the possible sink
types.
To implement
OptionProcessor
like this one shall define an additional interface to communicate with
the sink
providers:
package my.module; public interface SinkProvider { /** gets the option (even composite) that this sink needs on command line */ public Option getOption(); /** processes the options and creates a "sink" */ public OutputStream createSink(Env env, Map<Option,String[]> values) throws CommandException; }
Other modules would then registered implementations of this
interface in the
META-INF/services/my.module.SinkProvider
files.
The
OptionProcessor
itself would just look all the implementations up, queried for
the sinks
, and then did the copying:
class CopyingProvider extends OptionProvider {
public Option getOption() {
List<Option> l = ...;
for (SinkProvider sp : Lookup.getDefault().lookupAll(SinkProvider.class)) {
l.add(sp.getOption());
}
// we need only one provider to be present
Option oneOfSinks = OptionGroups.oneOf(l.toArray(new Option[0]));
// our channel option
Option channel = ...;
// the channel option needs to be present as well as a sink
return OptionGroups.allOf(channel, oneOfSinks);
}
public void process(Env env, Map<Option,String[]> values) throws CommandException {
OutputStream os = null;
for (SinkProvider sp : Lookup.getDefault().lookupAll(SinkProvider.class)) {
if (values.containsKey(sp.getOption())) {
os = sp.createSink(env, values);
break;
}
}
if (os == null) {
throw CommandException.exitCode(2);
}
// process the channel option and
// handle the copying to the sink os
}
}
Another possible approach how to allow sharing of one option between multiple modules is to expose the option definition and its handling code as an interface to other modules, and then let the modules to write their own OptionProcessors. Necessary condition is that each of the processor is uniquely identified by some additional option, so when the shared option appears the infrastructure knows which processor to delegate to. This is demonstrated in the SharedOptionTest which basically does the following:
/** the shared option, part of an interface of some module */ public static final Option SHARED = ...; /** finds value(s) associated with the SHARED option and * creates a JPanel based on them */ public static JPanel getSharedPanel(Map<Option,String[]> args) { ... }
Then each module who wishes to reuse the SHARED option and the factory method that knows how to process their values for their own processing can just:
public static final class ShowDialog extends OptionProcessor { private static final Option DIALOG = Option.withoutArgument('d', "dialog"); protected Set<Option> getOptions() { // the following says that this processor should be invoked // everytime --dialog appears on command line, if the SHARED // option is there, then this processor wants to consume it // as well... return Collections.singleton(Option.allOf(DIALOG, Option.anyOf(SHARED))); } protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException { JPanel p = getSharedPanel(optionvalues); if (p == null) { // show empty dialog } else { // show some dialog containing the panel p } } }
The other modules are then free to write other processors refering to
SHARED
, for example one can write ShowFrame
that does the same, just shows the panel in a frame, etc. The infrastructure
guarantees that the exactly one provider which matches the command
line options is called.
--open X.java Y.java Z.txt X.java Y.java --open Z.txtif the option
openhandles
extra arguments. The sendopts infrastructure must distinquish between them and pass the non-option ones to the only one handler (active because it processed an option) that knowns how to parse them. It is an error if more than one or no handler expresses an interest in extra arguments and those are given. One can register such option by using the
Option.additionalArgument
factory method.
When using the
declarative annotation style
one may annotate a field of type String[]
which then means
this field should be filled with all additional arguments.
CommandLine.getDefault().parse
methods taking additional arguments like input and output streams.
This gets transfered to providers as an
Env
argument of their methods.
Option.defaultArguments
factory method. With the
declarative annotation style
one can annotate a field of type String[]
and specify that
it is supposed to be implicit.
class PP extends OptionProcessor { private static Option tune = Option.requiredArgument(Option.NO_SHORT_NAME, "tune"); private static Option stream = Option.requiredArgument(Option.NO_SHORT_NAME, "stream"); public Set<Option> getOptions() { return Collections.singleton( OptionGroups.allOf(tune, stream) ); } public void process(Env env, Map>Option,String[]> values) throws CommandException { String freq = values.get(tune)[0]; String output = values.get(stream)[0]; // XXX handle what is needed here } }When the two options are registered and command line like
--tune 91.9 --stream radio1.mp3is being processed, the
PP
's process
method is going to get
called with values 91.9and
radio1.mp3. This kind of grouping is not currently supported with the declarative annotation style registration.
Option freq = Option.requiredArgument(Option.NO_SHORT_NAME, "tune"); Option station = Option.requiredArgument(Option.NO_SHORT_NAME, "station"); Option tune = OptionGroups.oneOf(freq, station);The option
tune
then signals that just one of the station or
freq options can appear and that they both are replaceable.
|
|
The sources for the module are in the NetBeans Mercurial repositories.
Nothing.
Read more about the implementation in the answers to architecture questions.