Improved Command API
Thanks to those who contribute to the Java source code, the most recent Command API is an improvement.
The Command interface now reflects the real intent of a command object: It has a life-cycle; it MUST NOT be reused.
Here is the life-cycle of a command object.
1. Create a command object.
2. Install the command line and command I/O.
3. Execute the command.
The execute() method requires no parameters and throws an exception.
public abstract void execute() throws Exception;
The Command I/O has been properly abstracted into a command I/O object. A command I/O object manages the standard streams.
When a command requires raw out, for example, it invokes getOutputStream(). When it requires character output, it invokes getPrintWriter().
For backward compatibility with the previous Command API, the default implementation of execute() invokes execute( command-line, input, output, error ).
- Login to post comments
Back to CommandContext
Now that we can see the benefit of a well-known command life-cycle, we can revisit the discussion of a command context.
A command line, command I/O, credentials and environment is part of a command context.
With Command, CommandContext and AbstractCommand, we are not required to rewrite commands when the Command API must be extended.
Sorry ... I've done it a bit differently.
Rather than creating a separate CommandContext interface, I'm taking the approach of putting the methods into the Command interface and implementing them in AbstractCommand.
Contrary to your implication, my approach does not necessarily require commands to be rewritten when the Command API changes. Obviously commands that need to use the new methods will need to be changed. But that would also be the case with your approach.
The key to avoiding "API ripple" (e.g. rewriting commands) is to understand Java's binary compatibility rules. Add new functionality as new methods and (when appropriate) add replacement methods. Avoid changing the signatures of existing methods if there is a reasonable alternative.
For example, take a look at how the "execute" methods are currently implemented in Command and AbstractCommand. I didn't change the old execute method; I added a new one with a default implementation that allowed existing commands to continue using the existing one. At some point, the old 'execute' method will be deprecated.
Backwards compatibility is important, but since JNode is still very much "alpha", we should not be afraid to make incompatible changes when it suits our purposes. For example, my recent changes to the console stream APIs will allow us to change consoles to support Unicode characters natively. (Without this, we'd have the bizarre situations that character data written to System.out would need to be converted to UTF-8 and back to UTF-16 twice!)
Hmm.
I'm sure that I did not mean to offend. Your approach is legitimate. Again, I am trying to understand the Command API and its implications.
Again, I say that I agree with you. You have been able to modify Command and AbstractCommand so that it is reasonably backward compatible. The Command API is, in my opinion, much more useable because of your recent changes.
I have also written a Command API and a few hundred production-quality commands. Your choices are different than mine and I want to understand why.
Can we see your code?
It is easier to understand your thinking if we can see the actual APIs and commands.
org.jos.program2a
I wrote the Program API on 3 August 1999. I have been using it in production-quality applications ever since.
Environment
In a modern operating system, an environment is a collection of attributes. These attributes are used to give a user a unique experience with an operating system. My current directory, for example, is not the same as yours. My classpath is not the same as yours. My home directory is not the same as yours.
In JNode, an environment object should represent the current environment. An environment should be set--just like the Command I/O--by invoking the initialize() method.
Current working directory
The current working directory is just another attribute of the environment object. Its key is "pwd".
The set command displays the environment. When given a key-value pair, the set command updates the environment.
It is not as simple as that ...
The problem is that the Java specs (e.g. the java.io.File#getAbsolutePath()) says that a Java application's current directory is determined by a system property. And the "current directory" behavior is implemented in parts of the code-base that we should avoid changing; e.g. the OpenJDK code. Finally, we have to be extremely careful in how we "fix" this if we want classic Java applications to work properly under JNode.
My most recent thinking on this was to extend the "proclet" mechanism, making the system Properties object a proxy that maps operations to a global or proclet-specific property set ... depending on the current proclet context. Details are still fuzzy, but there are old comments that talk about this; search for "proclet" if you are interested in understanding this.
Proclet?
Okay. A proclet is something like a process. And here we are again talking about the same thing--using different terms from different projects.
Let's say that a Proclet has a CommandLine, CommandIO, Credentials and Environment. A complete Proclet API would enable any Java object to access attributes of the Proclet environment, such as "pwd", without exposing the internal implementation details. A ProcletThreadGroup, for example, is an implementation detail.
The Proclet API should then be used by java.io.File, java.lang.System, and other classes. And then, multiple classic Java applications can run inside one JVM--as long as they are very well-behaved and work off the same system classpath.
Call it whatever. The current working directory is only one of many attributes that are part of the current thread-execution context.
If we keep on extending the Proclet mechanism, a JNode command is invoked within a Proclet. It uses the standard Java API. It might also use the Proclet API directly.
If we keep going, the CommandIO object is also part of the Proclet. System.in is the same object returned by CommandIO.getInputStream(). System.out is the same object returned by CommandIO.getOutputStream(). System.err is the same object returned by CommandIO.getErrorStream().
When the Proclet API is complete, is the Command API redundent? Or, is the Proclet API an inseparable part of the Command API?
Puzzling
Sometimes, it is difficult for me to talk about two things at the same time. On one hand, we want to start a classic Java application in JNode. There is no choice here. Assumptions of an off-the-shelf JRE must be preserved. By definition, a classic Java application must implement a static main() method, something like this:
On the other hand, we want to start a command in JNode, where a command does not implement a static main() method at all.
The current thinking suggests that Isolates is the most likely way to provide multiple applications within a JVM. Each classic application must have its own classpath, system class loader, static fields, system properties, and so much more.
What is a proclet? How does it resolve these issues? I'll continue my search for "proclet".
Proclets are ...
... either a lightweight alternative to isolates, or a short term workaround for the fact that Isolates weren't (and AFAIK still aren't) fully implemented in JNode. The mechanism is currently used to make System.{in,out,err} work "as expected" for commands running in multiple consoles and/or in pipelines. The mechanism could also be used for other kinds of light-weight isolation; e.g. properties and the "current" directory.
Tale of two commands
Let's say that commands A and B are running concurrently in separate threads.
Let's also say that their core functionality depends upon running in a specific directory.
When command A sets its current working directory, command B should not be affected. Likewise, when command B sets its current working directory, command A should not be affected.
When using Isolates API, commands A and B are running in separate isolates. Therefore, the Java system properties represent the environment for a command.
Without using Isolates API, command A and B are running in the same isolates. The Java system properties are shared by all commands.
Is it possible to run these commands without using isolates?
Yes ... with an extension to proclets
See my comments above.
ThreadLocal
You can get similar behaviour using java.lang.ThreadLocal. It will supply a seperate value for each Thread. Of course you'd still have a problem if command A or B have multiple Threads again, but I guess this could be solved too.
This is no suggestion or proposal, just a possible answer to your scenario/question.
Thread local doesn't directly solve this issue
... because the "current directory" behavior is implementing in java.io.File, etc. If you implement a new "current directory" mechanism in JNode-specific classes, classic Java apps that relied on the classic java.io.File behavior would see a different current directory to native JNode commands.
Associating command context with thread
Okay. Let's say that we use ThreadLocal (or InheritableThreadLocal). What does this provide? It provides a foundation for an API that can be used anywhere (by any class) and produce reasonable results.
This API must be able to define a custom ThreadLocal. In turn, our custom ThreadLocal must be created. It might be detected by the API, set automatically if it does not yet exist.
Any object can use this API to get/set the command context. In other words, the set command uses this API to get the current environment. The cd command uses this API to modify the "pwd" attribute of the current environment.
So, we are saying that a command context is not closely associated with a command object; but, it is closely associated with the command thread. (Think about the implications.)
We might adopt the following rule, "Every command must be invoked in a separate thread." If so, a ThreadLocal (or InheritableThreadLocal) is used to create and maintain a command context.
InheritableThreadLocal
Peter, if commands A and B have multiple threads, then you can use InheritableThreadLocal.
Fabien
my blog : en français, in english or both
Credentials
A modern operating system requires a mechanism for multiple users, or rather, accounts. A command must modify its behavior based upon the current user.
Let's introduce "credentials". The login command should establish credentials using a Credentials API. In turn, credentials should be passed to every command in a command chain.
In JNode, credentials should be set--just like command line and command I/O--by calling the initialize() method.