Proposal: Changing the command API without rewriting commands.
Here is a basic proposal for the Command API to enable future improvements to the API without rewriting commands.
The execute() method is something like this:
public void execute( CommandLine cl, InputStream stdin, OutputStream stdout, OutputStream stderr );
This works--for as long as we do not pass a new parameter to execute method.
Let's talk about an equivalent method. Let's say that a command has-a command context. And let's say that a command context has-a command line, a standard input stream, a standard output stream, a standard error stream. The execute() method becomes something like
public void execute( CommandContext ctx );
This works, too--but it is more future-oriented.
Next week, let's say that we want to pass credentials to a command, so that permissions are based upon credentials. We rework the command context interface. We add a credentials property. The execute method becomes something like
public void execute( CommandContext ctx );
But wait! That is the same signature we already have. Commands that do not interact with credentials are unchanged.
The week after next, let's say that we want to pass a collection of attributes to a command. We rework the command context interface, again. We add an environment property. The execute method becomes something like
public void execute( CommandContext ctx );
Yet again, the signature remains the same.
- Login to post comments
OutputStream vs. PrintStream vs. Writer vs. Tty
For this discussion, let's say that a CommandContext interface is passed instead of stdin, stdout, stderr in the execute() method.
Yes, it supports a raw binary output stream (tar, gz, md5sum).
And yes, it supports an optional PrintStream (connected to the underlying output stream).
And yes, it supports an optional Writer (connect to the underlying output stream).
And yes, it supports an optional tty-style console (connected to the underlying output stream.
If there is no tty, the getTty() method has nothing to return.
And we go on supporting whatever anyone wants connected to their standard output. Why? Because custom implementations of CommandContext are possible.
When we close() a command context, we close all of its resources.
There are strong arguments for keeping the same execute method.
And yet, it is not possible to implement certain command with this interface. Some commands require OutputStream stdout. Some commands require PrintWriter stdout. Some commands require Writer stdout. Some commands require Tty stdout.
This suggests a new StandardOutput interface (or a CommandContext).
wget
The wget command is popular. It downloads a file, for example, from an HTTP service.
Let's say that we get a zip file.
Normally, it opens a file (java.io.FileOutputStream).
Is it possible to redirect the stdout and stderr of wget to the same file?
It is possible with the Bjorne interpreter
Or at least it should be. However the RedirectingInterpreter that the shell uses by default soes not support this.
I guess I need to get back to finishing off the Bjorne interpreter.
ls|grep
Let's say that we really want the ls command to behave like ls -1 when its output stream is redirected.
Like output, like input
And if we are going to have a StandardOutput, we might try a StandardInput.
Worthy to keep in mind
This idea might make sense on the next occasion when due to exact demand we will have to change the signature of execute().
The time may be soon
I think the time to make other changes to the execute method is not far away. For example, we need to address the issue of making the console subsystem UTF-8 (etc) capable. Among other things, that means changing the Command API to allow the input/output/error streams to implement Reader/Writer rather than InputStream and (yuk!) PrintStream.
Yes, changing this API will be a bit of a pain, but the code of the various JNode shell commands is much more consistent now, so the pain shouldn't be to great. Trust me, I've seen it all
Reader/Writer
A command and command context is intended for octets, regardless of their interpretation. It does not presume characters.
Let's say that we have an md5sum command and it reads from standard input. It reads its standard input as a binary stream of octets. It does not use reader/writer, does it?
Let's say that we have a gzip command for compression and it reads from standard input, writes to standard output. It too reads/writes as a binary stream.
becomes
(Please forgive me if it isn't the exact Linux syntax.)
Therefore, an abstract command class should be designed for character commands. It should connect a reader to its standard input and writer to its standard output.
Instead of AbstractCommand, we might provide AbstractUtf8Command and AbstractBinaryCommand.
Command interface
Alternatively ...
Alternatively, make the methods in your CommandContext interface part of the Command interface and implement them in AbstractCommand.
Explanation for possible implementations
Please let me explain why a command has-a command context, instead of command is-a command context.
First, this preserves an important feature of the current execute() method. The entire command context is passed to a command as parameters. We continue to use the default constructor to instantiate a command. We continue to not invoke any other method(s); a command object has a very simple life cycle. We continue to invoke the execute() method; we are passing the context as a single parameter instead of separate parameters.
Second, there is an issue of command object reuse. Should a command object be reusable, constructed once and invoked many times? (Should a command become something like a servlet?) There are two options. (1) A command interface provides these methods, requiring a new command object to be instantiated every time the command is invoked. (2) A command context interface provides these methods, enabling a command object to be optionally reused.
When a command context is passed to a command as parameters, a new command object is created once and only once. The execute method is invoked multiple times.
Should a shell create a new command object every time? Or, should it cache and reuse command objects? This is a shell implementation issue, not an API issue.
When we are writing a command class, is it safe to assume that a new instance of the class is created by the shell every time? If so, the API should expose this assumption and insist on an interface something like this:
But this blocks a shell from implementing a cache of reusable command objects.
Thanks,
Some facts about the current Command APIs
Things have changed a lot in the past 6 months.
I think these two points somewhat invalidate your reasons for using a separate CommandContext object. This is not to say that the idea won't work. But I think it reduces to a minor style debate, and that the "implementors choice" principal applies.
Puzzled with Command API.
Please help me to understand this. I am putting a lot of effort into comprehending the Command API for 0.2.7 so that I might re-implement existing classes as JNode command classes.
1. Are you saying that, starting with 0.2.7, a command class MUST extend AbstractCommand? that it is not enough to simply implement the Command interface?
2. You have explained that a command instance CANNOT be reused. I'm sorry that I did not understand this important feature from the JavaDoc documentation.
Clearly, I do not understand. When a command class CANNOT be reused, it has a very specific life-cycle.
1. A shell MUST create an instance of a command class.
2. A shell MUST set the command line arguments.
3. A shell MUST open standard input, output and error streams.
4. A shell MUST invoke execute(...).
5. A shell MUST close standard input, output and error streams.
6. A command is abandoned to the garbage collector.
Is this a more accurate description of the most recent Command API?
The lifecycle of a command.
There is more info on some aspects of the shell / command / syntax mechanisms here:
http://www.jnode.org/node/2546
In general though, your best bet is to read the code. (This is true for most of the JNode codebase.) A decent IDE (such as Eclipse) is a lot of help in figuring things out.
Anyway ,,, here is a rough sketch of the lifecycle of a command, assuming that we are using the ThreadInvoker or ProcletInvoker,
So, the real reason that AbstractCommand instances are not reusable is that they contain state about the command arguments (in the ArgumentBundle), and potentially other state created during command execution. If we tried to clear the state and recycle command objects, we'd have the problem of command classes that behave unpredictably because they don't reset their state properly. Not to mention garbage retention and potential security issues.
It is generally a bad idea to try and recyle objects in Java. Assuming you have a good GC it is (usually) more efficient to drop old objects and create new ones as required. And if you have a poor GC, it is better to fix the GC than to "optimize" applications for the poor GC. (Not least because optimizing for the poor GC will de-optimize for a good one!!)
CommandContext interface
Are you sure the commands
Are you sure the commands shoud be able to access the setters?
Why setters?
A command context is used by a command and a shell.
A shell must be able to set the properties of a command context. Ideally, it should use the command context interface--rather than the constructor of an implementation of a command context interface. In other words, a shell is a command and it too has-a command context. It creates a command context from its own context.
A command can replace its own output stream. Are there any additional security concerns? Already, a command must have permission to open a stream. A command can be invoked. It can also invoke another command. The called command gets a clone of the calling context.
For example, let's say that I really want to use my own custom shell. At the command prompt, I type 'myshell'. The next thing I see is the prompt from myshell. The command context of myshell is a copy of the command context from the default shell.
And at the myshell prompt, I might type 'shell' to start a default shell.
Now, let's say I type 'myshell < myscript'. An input stream in the command context is no longer attached to a keyboard. The myshell command does not need to know and does not need to care that bits are coming from a file called myscript.
An API provides all the possible implementations. If just one command needs a setter, it can be part of the interface. Where else would it go?
Thanks,
Probably they mostly shouldn't ...
... but there may be cases where they do. So it is not a bad idea to provide the APIs and (of course) protect them using Java security / permissions. (OTOH, it would be simpler to delay the provision of the setters until the actual need arises ....)