|
|
||
Evan Summers's BlogMay 2006 ArchivesSwing and Roundabouts 1: Event DTsPosted by evanx on May 30, 2006 at 07:30 AM | Permalink | Comments (14)
In his blog "A Simple Framework for Desktop Applications" John O'Conner summarised a JavaOne presentation by Hans Muller and Scott Violet vis-a-vis JSR 296. John reports that this framework would give some guidance to Swing developers, to avoid common bad practices. Such as Swing apps that only support English, and Swing apps that are "a tangle of actionPerformed methods that block the event dispatch thread (EDT)." I commented there that the issue of EDT-blocking event handlers is a big one for me. In my last project, most event handlers kicked off a string of "long tasks" (namely, communicating to a server over a GSM network), and I got into such a tangle with SwingWorkers upon SwingWorkers, that the code became increasingly difficult to follow. This blog presents how I eventually untangled that application.
Imagine a client-server application, where the client application communicates to the server application via the network. Actually, this system is deployed on a remote planet managed by aliens. Those same class of aliens as we met in the Aliens movies, if you can believe it. The server room is installed in a secure complex on one of the moons. (I can't be more specific because of the NDA the Company makes everyone sign, so...) Let's consider the login dialog. The alien enters its username and password, and then hits the "Login" button. This invokes our event handler to communicate to the server as follows.
Gui gui = new Gui(); // our GUI helper
Messenger messenger = new Messenger(); // our server comms helper
public void actionPerformed(ActionEvent event) {
if (event.getSource() == loginButton) {
loginActionPerformed();
}
}
protected void loginActionPerformed() {
try {
LoginResponse loginResponse = messenger.sendLogin(getLoginBean());
if (loginResponse.getErrorMessage() != null) {
gui.showMessageDialog(loginResponse.getErrorMessage());
if (loginResponse.isInvalidPassword()) passwordField.requestFocusInWindow();
else usernameField.requestFocusInWindow();
return;
}
if (loginResponse.isPasswordExpiring()) {
String password = gui.showPasswordInputDialog("Enter new password");
if (password != null) {
ChangePasswordResponse response = messenger.sendChangePassword(loginResponse, password);
if (response.getErrorMessage() != null) {
gui.showMessageDialog(response.getErrorMessage());
} else {
gui.showMessageDialog("Password changed");
}
}
}
login(loginResponse);
gui.showMessageDialog("Welcome, " + loginResponse.getAlienName());
} catch (CommsException ce) {
gui.showMessageDialog(ce, "The frelling connection is down again.");
} catch (Exception e) {
gui.showMessageDialog(e, "System failure. Try a hard reboot.");
}
}
As you can see, we communicate the login information to the server (ie. username and password), which might return with an error such as "Unknown Alien", "Access Denied" or "Invalid password." Or we might get a comms exception, eg. caused by the high gamma ray activity on this particular alien planet.
Now the problem is that we are blocking the EDT in this method! In particular when we communicate to the server. Because it might take 10 or 20 seconds to get a response from the server on the moon.
To be 100% safe, we should not "manipulate" Swing components outside the EDT. Because, as the Java Tutorial says, "Swing event-handling and painting code executes in a single thread, called the event-dispatching thread. This ensures that each event handler finishes executing before the next one executes and that painting isn't interrupted by events. To avoid the possibility of deadlock, you must take extreme care that Swing components and models are created, modified, and queried only from the event-dispatching thread." That's fine because we typically manipulate our components (causing visual changes on the screen) in our EDT event handlers. For example, when a button is pressed, an actionPerformed() method is invoked by, and within, the EDT. And that's where our application actually does stuff, ie. in response an alien pressing or clicking something. Because GUI applications are event-driven.
So the solution is to use a SwingWorker thread, so that our long task can run as a separate thread outside the EDT. Then we can start a SwingWorker thread, and let the EDT exit the actionPerformed() method immediately, so that it can service other events if necessary (eg. in the response to the user clicking on something). We might restructure our earlier loginActionPerformed() method to use a SwingWorker as follows.
public void loginActionPerformed() {
SwingWorker loginWorker = new SwingWorker() {
LoginResponse loginResponse = null;
Exception exception = null;
public Object construct() {
// this is outside of the EDT, so no GUI stuff, just for our long task
try {
loginResponse = messenger.sendLogin(getLoginBean());
} catch (Exception e) {
exception = e;
}
return null;
}
public void finished() { // this is inside the EDT, for GUI stuff
if (exception != null) {
String message = null;
if (loginResponse != null) message = loginResponse.getErrorMessage();
if (message == null) message = "A frak up has occurred";
gui.showMessageDialog(exception, message);
} else if (loginResponse == null) {
gui.showMessageDialog(exception, "No response from server");
} else if (loginResponse.getErrorMessage() != null) {
gui.showMessageDialog(exception, loginResponse.getErrorMessage());
} else {
if (loginResponse.isPasswordExpiring()) {
String password = gui.showPasswordInputDialog("Enter new password");
if (password != null) {
ChangePasswordResponse response = messenger.sendChangePassword(loginResponse, password);
... // oh frell, a long task, and we are in the EDT here...
... // so this is so not gonna work
}
}
gui.showMessageDialog("Welcome, " + loginResponse.getAlienName());
}
}
};
loginWorker.start(); // start the worker thread and free the EDT
}
As you can see, we had to cajole our event handler quite significantly to make it jump through a SwingWorker hoop. "Uh oh, this is crashing my code-assist neural implants... I don't like it."
So we have to switch maybe a few times in and out of the EDT. SwingWorker is only good for one switch out of the EDT (and then back again). "Frell it! You just can't win with these alien requirements!" Nested SwingWorkers? Let's not even go there!
Really, we wanna leave the code as it was before ie. as in the original loginActionPerformed() implemention further above, without having to worry about blocking the EDT and SwingWorkers and all that. But we know for sure that in order to make our event handler non-blocking, we will need to run it via a SwingWorker thread. That's a given. OK, so let's try the following.
public void actionPerformed(ActionEvent event) {
if (event.getSource() == loginButton) {
gui.startWorker(new Runnable() {
public void run() {
loginActionPerformed();
}
});
}
}
We have introduced a startWorker(runnable) helper method, which we implement as follows.
public void startWorker(final Runnable runnable) {
SwingWorker swingWorker = new SwingWorker() {
public Object construct() {
try {
setWaitCursor(true);
runnable.run();
} catch (Exception e) {
showExceptionDialog(e);
} finally {
setWaitCursor(false);
}
return null;
}
};
swingWorker.start();
}
Note that we have no finished() method for GUI code, and our runnable event handler is run from the construct() method ie. outside the EDT, so it must be "EDT-safe." Let's put that aside for a second... The above method takes care of showing the hour glass, as follows.
public void setWaitCursor(boolean waitCursor) {
if (!waitCursor) applicationFrame.setCursor(null);
else applicationFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
glassPane.setVisible(waitCursor);
}
Notice that we might also activate a glass pane to disable a particular panel while our task is in progress, eg. to force the user to wait for the task to complete.
Firstly, let's implement an EDT-agnostic invokeAndWait() method as follows.
public void invokeAndWait(Runnable runnable) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeAndWait(runnable);
} else {
runnable.run();
}
}
The above implementation of invokeAndWait() can be invoked safely inside the EDT, or outside it, eg. in a SwingWorker's construct() method. As such, it is "EDT-safe." Now we can "EDT-harden" methods invoked on Swing components in the loginActionPerformed() further above, such as showConfirmDialog() in our Gui helper class, as follows.
int dialogOptionValue;
public int showConfirmDialog(final Object message, final String title, final int option) {
invokeAndWait(new Runnable() {
public void run() {
dialogOptionValue = JOptionPane.showConfirmDialog((Component) applicationFrame, message, title, option);
}
});
return dialogOptionValue;
}
Similarly, we EDT-harden other methods in our GUI helper class, where these are commonly in our event handlers, eg. requestFocusInWindows() as below.
public void requestFocusInWindow(final Component component) {
invokeAndWait(new Runnable() {
public void run() {
component.requestFocusInWindow();
}
});
}
While this seems verbose and tedious, it is relatively quick to EDT-harden a method in our GUI helper class, by cutting and pasting the invokeAndWait(Runnable) boiler-plate code from another EDT-hardened method.
Similarly when we encounter a need for a method on a Swing component, we should delegate that to our helper class, where we can make it EDT-safe.
So we should use a SwingWorker thread, for our long tasks. However, the SwingWorker is only good for one switch out of the EDT, and then back again. This is a problem if our "long task" is actually a chain of long tasks, requiring GUI invocations in between, eg. popping up a confirmation dialog before proceeding to the next long task. If our event handler performs a long task, then we put it into a SwingWorker thread. But the trick is that we take care that any GUI manipulation is performed via an EDT-agnostic helper class. In our helper class, we use an invokeAndWait() method which will work whether we are in the Swing EDT or not, by checking SwingUtilities.isEventDispatchThread(). Our helper class may seem verbose and tedious, owing to the invokeAndWait(Runnable) boiler-plate code everywhere. However, our application code is simpler and clearer. So it's a very good bargain. We isolate the tedium in a single helper class, to free up the whole application for developers. It's a win-win, for us and the aliens. Which means the aliens won't be blowing any developers out of the hatch, and visa versa, for now.
See the articles "Bean Curd"
and "Swing trashes Ajax" on my blog for further Swing reading :)
Refactoring TranslationsPosted by evanx on May 26, 2006 at 05:20 AM | Permalink | Comments (3)
That means moving strings, like exception messages, into a resource bundle. I had some fun with a phased approach, which I present here.
The first phase was moving the string literals in the code into a "message class" as below.
public class IMessage {
public static String systemErrorLogin = "~ logging in";
public static String systemBusyCommunicatingWithServer = "Communicating with the server...";
public static String systemErrorOccurred = "An error has occurred";
public static String systemErrorOccuredFormatTilde = "An error has occurred while %s";
public static String systemSendOpLogoffReq = "~ sending logoff message";
public static String systemUpdateError = "~ updating application";
public static char systemLoginMnemonic = 'L';
public static String[] periodOptions = {"Today", "Yesterday", ...};
...
}
Note that we allow different types in the message class, ie. char for mnemonics, and string arrays for combo boxes. (Incidently, in the above example, we use a notation where a tilde at the beginning of exception messages is substituted with "An error has occurred while..." It's lazy, and that's what I really dig about it, man! The application code becomes "stringless" as follows.
public void run() {
try {
login();
} catch (Exception e) {
e.printStackTrace();
gui.showExceptionDialog(e, IMessage.systemErrorLogin);
}
}
By the way, this is in keeping with my "Bean Curd" blog, where I argue that "string references" (eg. resource bundle keys in this case) hinder refactoring, and so we should aim for applications with "no string references attached."
The second phase is to generate the resource bundle from this message class. We use the field name as the key, and use reflection to generate the resource bundle as follows.
public void generateResourceBundleContent() throws Exception {
IMessage messages = new IMessage();
for (Field field : IMessage.class.getFields()) {
// iterate through all the fields in the message class
String key = field.getName();
Object value = field.get(messages);
if (field.getType() == String.class) {
// output a regular string message
logger.println(key + " = " + value);
} else if (field.getType() == String[].class) {
// output a string array, eg. combo box items
String[] array = (String[]) value;
int index = 0;
for (String string : array) {
logger.println(key + index + " = " + array[index]);
index++;
}
} else if (field.getType() == char.class) {
// output a char, eg. a mnemonic
logger.println(key + " = " + value);
}
}
}
In the above method, we generate the content to be cut and pasted into our resource bundle file eg. myapp_en.properties. Note that we handle string arrays by appending an index digit to the key.
public static final Map<String, String> resourceMap = new HashMap();
public void loadMessages() throws Exception {
for (Enumeration<String> it = resourceBundle.getKeys(); it.hasMoreElements();) {
String key = it.nextElement();
String value = resourceBundle.getString(key);
resourceMap.put(key, value);
}
logger.exiting(resourceMap.size());
}
So now we gonna load the resource bundle messages into our messages class (which is otherwise still initialised to the original English strings). We use reflection, as follows.
public void configureMessages() throws Exception {
IMessage messages = new IMessage();
for (Field field : IMessage.class.getFields()) {
// iterate through all the fields in the message class
String key = field.getName();
Object defaultValue = field.get(messages);
Object resourceValue = null;
if (field.getType() == String.class) {
// we are looking for a regular string message
resourceValue = resourceMap.get(key);
if (resourceValue == null) {
throw new IRuntimeException(field);
}
field.set(messages, resourceValue);
} else if (field.getType() == String[].class) {
// we are looking for an array of strings in this case
String[] defaultArray = (String[]) defaultValue;
List<String> stringList = new ArrayList();
for (int index = 0;; index++) {
String string = resourceMap.get(key + index);
if (string == null) break;
stringList.add(string);
}
if (stringList.size() != defaultArray.length) {
throw new IRuntimeException(field);
}
resourceValue = stringList.toArray(new String[stringList.size()]);
field.set(messages, resourceValue);
} else if (field.getType() == char.class) {
// we are looking for a char resource
String resourceString = resourceMap.get(key);
if (resourceString == null || resourceString.length() != 1) {
throw new IRuntimeException(field);
}
resourceValue = resourceString.charAt(0);
field.set(messages, resourceValue);
}
}
Note that in the above code, if an entry is found in the resource bundle that is inconsistent with the messages class, eg. an unrecognised key, or different length string array, then an exception is thrown. This should be performed as a unit test. Anyway we will know as soon as we run the application if our resource bundle is not as it should be (via an exception).
An advantage of the message class approach, is that it enables unit testing of our resource bundles. For example, we can easily test that every one of our messages (as declared in the message class) is translated in our resource bundles, as follows.
public void test() throws Exception {
IMessage messages = new IMessage();
for (Field field : IMessage.class.getFields()) {
String key = field.getName();
if (resourceMap.get(key) == null) {
throw new IRuntimeException(key);
}
}
}
The above code sample is over-simplified, but hopefully illustrates the point.
public void emitMessagesCode() throws Exception {
for (String key : resourceMap.keySet()) {
String value = resourceMap.get(key);
logger.println("public static String " + key + " = \"" + value + "\";");
}
}
public void installGerman() {
IMessage.systemError = "Eine Störung trat auf";
...
}
This first phase enables us to ensure neat and consistent naming of the keys we use to reference messages. For example, we can readily rename the message keys using IDE refactorings, to correct spelling mistakes and inconsistent naming conventions. The next phase is to generate the master resource bundle from the message class. We use reflection on the message class to generate the key/value pairs, which we cut and paste into the master resource bundle file. After this stage, the resource bundle can be translated into multiple languages. At startup, the application loads the resource bundle for the current locale, and uses reflection to configure the message class from the resource bundle. This offers a mechanism to ensure that the resource bundle is consistent, ie. there are no strings that remain untranslated. In general, I argue that source code should contain no string literals whatsoever! The reason for this is that string literals are typically fragile references, which are not refactorable. This applies to strings that refer to field or method names as discussed in my earlier blog "Explicit Reflection", and string references to properties, as discussed in "Bean Curd (Chapter 1)". (Strings used in OR queries will be discussed in an up-coming blog, "Bean Curd 2: Native Query Beans.") Clearly strings that are text messages are also undesirable, because they should be externalised for translation (in resource bundles). And finally string references to externalised messages in resource bundles, are fragile and unable to be unit tested, and consequently dangerous, eg. getString("loginError"). So I think that covers all the evil strings that we might find lurking in our code? Let's root them out and banish them forever!
My Postscript Punt for today is... "Bin Bash Java (Chapter 1)." And not "Swing trounces Ajax" as usual ;)
Bean Curd 1: Swing TurntablesPosted by evanx on May 25, 2006 at 05:07 AM | Permalink | Comments (0)
Scott Violet's blog "Ease of Swing Development - Beans Binding" introduces JSR 295, which aims to provide a standardised API for binding GUI components to beans, yay! In an earlier blog, I wished for "Explicit Reflection" of fields and methods, for "stringless referencing." I should amend that to include properties, in addition to fields and methods, for the purposes of bean binding. We glean from Graham Hamilton's JavaOne 2006 slides, that "lightweight references" of methods (for GUI event handlers), and hopefully also of properties (for "bean binding"), are in the works for Dolphin, which is expected out in 2008.
I'll present a Swing JTable example here. In a follow-up blog, I'll show the same approach used for ORM with "native queries" - now that is cool, so look out for that in a week or two. I've been wanting to write about that six months, and now the time has come, I'm so excited!
Consider that we are building a CRUD application for a database of users. You know, CURD, as in... create, um, update, delete, you know, CUD. What is the R for? Google knows. So anyway, these users are cows that need to login to our new MMORP, "World of Animal Farm Revisited: Cult of the Raging Bulls." So we gonna have a CowBean class, with properties realName, username, password, et cetera.
public class CowBean {
protected String realName;
protected String username;
protected String password;
protected int earId;
public void setRealName(String realName) {
this.realName = realName;
}
... // other getters and setters
}
Say we wanna build a GUI form for editing a cow. For when customers phone our call center saying, "Update my real name on your system, cow!" "Who you calling a cow? I aint nobody's cow!"
public void actionPerformed(ActionEvent event) {
if (event.getSource() == realNameField) {
cowBean.setRealName(realNameField.getText());
}
}
In order to do this binding in a framework, we would refer to the realName property using a string ie. "realName", maybe as follows.
GBeanBinding cowBeanBinding = new GBeanBinding(CowBean.class);
cowBeanBinding.bind(realNameField, "text", "realName");
Now we get to the rub...
There are a couple of problems with using string literals as references to properties (and fields and methods). The most serious problem is that they make refactoring fragile. For example, consider that we wish to rename the realName property to just name. The problem is that our IDE doesn't know to change the string reference "realName" as well.
Hey, I'm lazy. So i don't like string references, because they do not take advantage of the IDE's prompting, auto-completion, and error highlighting. In this sense, they are not readily "toolable." Like when we rename the realName property to name, "realName" string references are not underlined with a swigly red line. "Where are you, you silly error!? Don't you make me come down there to find you!" And worse, we can't type "use" and press Control-Space to have that completed to "username" for us by the IDE. We have to actually type the whole thing out. "Thaaat's mentil! I'm a developer, not a typewriter!" So the two related problems with string references are that they are "fragile" and not readily "toolable."
A possible future solution to this problem is to introduce a convention into the language for the explicit referencing of properties. For example, imagine a notation where CowBean:realName is a reference to the "realName" PropertyDescriptor. This might be compiled into bytecode as Introspector.getBeanInfo(CowBean.class).getPropertyDescriptor("realName") for all I care. The important thing is that it is recognised by the IDE for refactoring, prompting, completion and error-detection. But back to the real world of today... In the absence of such a language feature, we could define references to our properties in explicit bean info classes as follows (where our GBeanInfo superclass implements BeanInfo).
public class CowBeanInfo extends GBeanInfo<CowBean> {
public final GPropertyDescriptor realName = createPropertyDescriptor();
public final GPropertyDescriptor username = createPropertyDescriptor();
public final GPropertyDescriptor password = createPropertyDescriptor();
... // references to other properties
}
In the GBeanInfo superclass, we would configure and verify each GPropertyDescriptor property descriptor, ie. find its corresponding java.beans.PropertyDescriptor using its field name as the property name. Hey, I guess one could easily write an Ant task to generate our explicit bean info classes. So maybe I'll try that one of these days, and write a follow-up article on it. But anyway if CowBean is used by only one component, we find it convenient to absorb the above into that component, as we will now demonstrate. "Please stand back, cows and bulls!"
Consider that we wish to build a JTable, which will display CowBean instances. So let's build the table model for starters.
So our CowTableModel might be coded as follows.
As you can see, we use an annotation to configure some default GUI properties in the code. Just to be different.
Incidently, notice that the above table model implementation exposes the column components explicitly, in keeping with the "no string references attached" rule, for later when we want to play nicely with them in our IDE. That is, we can manipulate columns by referencing them explicitly, eg. passwordColumn.setRenderer().
"Carry on, soldier!"
Now there are a few things we need to accomplish. Firstly we must bind realNameColumn to the realName property of CowBean.
As you might guess, that configure() method lurking in the constructor comes into play...
So let's look at the implementation of the framework's GTableModel superclass, in particular this configure() method.
So the configure() method uses reflection to invoke column.configure(field) on the columns declared in the table model.
So let's check out the GTableColumn implementation, in particular its configure(field) method.
So the configure(field) method sets the default "binding property name" for binding this component to a property in the bean, eg. CowBean.realName. It uses the component's field name as a clue, eg. from "realNameColumn" we cunningly deduce that the default binding property name might be "realName". "Dr Watson, do you concur?"
At this stage, we would also want to configure our column via resource bundles and user preferences. For example, our default column label might be translated in a resource bundle.
We might want to set the width of the column to the user's preference from last time the user ran the application. That is, when they use the mouse to change the column width, we might wanna remember that for next time using the Preferences API.
So we configure the column from externalised properties and preferences, using the field name as the key. In the case of resource bundles, this is fragile, since if you rename the column field, then you need to remember to rename the key in the resource bundles as well. Darn, we just can't win!
This brings us to unit tests we should implement. "I'll give you two to start with, and if you need more, then lemme know. Globalisation, Streamlining..." (From the "The Office" heh heh)
First we check that our binding is valid. For example, if we have a realNameColumn component bound to CowBean.class with a binding property name of "realName" then, upon reflection, we should find a CowBean.realName property.
The thing is that when we rename a property in the bean, we have to remember to rename components that will be bound to that property eg. realNameColumn. And we will forget, so we need unit tests to remind us, because the IDE and the compiler aint gonna.
For example, since we have a realNameColumn component, our resource bundles should contain a key like "CowTableModel.realNameColumn.label".
This is fragile because as soon as we rename components, all our resource bundles break. "Dammit to hell, Leeroy, why'd you rush in there like that and rename things?!"
We present an approach for binding GUI components to data beans, without using string references.
However, when we rename components, or properties, we need to take care to rename them in multiple places, including in the resource bundles.
That is the price we pay, if we wish not to use string literal references, as a rule. Those are similarly fragile, and not readily toolable.
We hope that Dolphin will make things better via property references. In the meantime, if you can think of a better way, or some tweaks, please comment.
I look forward to continuing this series, in particular, applying a similar approach to object-relational mapping, to enable native queries. So look out for that article which is coming up.
Postscript. I miss writing tongue-in-cheek articles. You know, trying to get all riled up, emotions flying off the handle, and old gripes bursting out of my chest like a scifi movie. Goddamn, this technical article is so dry and boring! Sorry about that. Hey I'll spice it up before I publish it. And the next one too. Hey that'll be easy, it'll be about persistence so I can slag off Hibernate and slaughter some other sacred cows while I'm at it, so check it out, cow! :)
In the meantime if you are looking for some heavy reading, then "Swing trashes Ajax" was made for you!
In the preceding blogs "Java is all you'll ever need" and "A Fool's Errand" I alluded to using Java for "small tasks" eg. file/system tasks, rather than shell scripts. I promised to present some examples along these lines.
This is Chapter 1 of many, and presents a basic design. We'll thrash it out in subsequent chapters.
My motivation for trying to move away from shell scripting (or using some other scripting language like python or groovy), in favour of writing "tasklets" in Java, are as follows.
A typical task is backing up our computer, so let's consider this one of our first goals. OK, this could take a while. Hopefully we'll achieve this goal in subsequent chapters. Doing a good design here would be a great start.
Of course our "framework" should be reusable for other tasks, besides the backup one. And very convenient to use. So that some day soon, we can write all our file and system tasks in Java, and never look back, woohoo!
Let's create some helper classes, eg. TFileHelper for handling files, and TProcessHelper for handling processes. And we will create a TTaskContext which exposes our "library" to our tasklets, and/or can be used as a superclass by our tasklet classes.
Note that I've choosen the letter T to prefix our "tasking" project classes. This avoids any potential namespace conflicts, and makes it clear which classes are our new ones.
To give us some flexibility, let's create our own classes to represent files and processes, namely TFile and TProcess. We will extend those eg. TDir and TZipFile from TFile.
First let's design our file helper class. (Note that I'm using abstract declarations to highlight the interface at this stage, rather than the implementation.)
That's just for starters. We can expect this class to grow a lot!
Next let's design our process helper class.
The file reader and writer processes above would be used for redirection, eg. the file reader as the first argument in the pipe(), and/or the file writer as the last, as follows.
As you can see, we are loving Java5 varargs :)
For convenience, let's introduce a superclass for our tasklets.
Let's slide this baby into gear and start putting rubber to road.
So let's imagine what our first backup tasklet implementation might look like, eg. for zipping up our home directory on Linux.
Ok, so if we wanna write tasklets like the above, we still gotta bit of work to do. It's gonna rock tho, innit.
So you spotted those annotations. The idea is that our framework could prompt for those eg. if not specified on the command-line...
So we gonna use annotations to tell the framework what it needs to know to provide a "tasklet container" with a management console in future maybe. Now we're talking!
I'm getting way ahead of myself here, but let's dream on...
This "management console" could have a Swing interface, and/or a web interface.
On the other hand, a web interface would be quick and easy for administering remote boxen, eg. backing up our media center in the lounge using our laptop. Even on a local machine, having a web-based console for tasks would be handy.
You know what, I just decided we gonna embed a web server into our tasklet framework. So that our tasklets are always remoteable, and deployable via a single self-contained jar...
OK, I better stop, I'm getting too excited here!
Like when you are reversing your car towards a wall, you find out very quickly when you've gone too far. Rather that than driving in the wrong direction for hours on end, and not knowing it.
Let's hope for accidents of the former "loud and clear" type, rather than misdirections of the latter, later type.
In the next chapter, we might refine this design before we kick off, so please post comments to that effect.
Then we can start rolling up our sleeves to implement some of the file IO functionality. Uh oh, sounds like work. So we'll google and cut and paste. We are developers after all, with a limited amount of time, so the less code we actually write the better, innit ;)
Update. Check out NailGun which aims to obviate the start time of the JVM, eg. for Java command-line tasks. They say, "Java has an extensive and robust core API and huge number of available open source libraries. It's a great big hammer, making almost any programming project look like a nail." :)
Postscript. Here's punting my blog "Swing thumps Ajax" again. Check it out, dog.
A reader commented to my blog "Java is all you'll ever need" that "anyone thinking he needs only a single tool to do any job is a fool." That would be me. So lemme introduce you to this fool's errand...
I have my favourite Java Leatherman with me, and so I gonna leave my Python Army Knife at home. Bash is written in C. Clearly it could be rewritten in Java. But let's not do that, let's write a better bash. And rather than a scripting language, let's just write a framework and library. Even if not as concise as a purpose-built scripting language, our approach will have the advantage of being able to leverage tools like Netbeans and Eclipse right off the bat to write our "scripts." And that is no small thing in my book. My main argument against bash, python, perl, C et al, is very simple and personal. Just that they are not my favourite language, programmable using my favourite IDE. That is, they are not my personal favourite programming tool. When I was at school in the 1980s, my first big pet project was writing a 4GL in Basic. A scripting language of sorts. I spent years on that thing. I was emulating my father because he wrote 4GL language tools. When i discovered C, everything changed. Now rather than write a new scripting language, you would just write a new framework and library, because C let you do that. And Java has made that truer than it ever was with C.
But Java (like any language) let's us write a library to support writing tasks, so that we can approach the brevity of shell scripts, without the downsides of shell programming. And with a big upside if Java/Netbeans is your favourite hammer and so you want everything to start looking like a nail. So watch out for my blog in the next few days titled "Bin Bash Java (Chapter 1)." As you can surmise, it's the first in a series. It's the beginning of a journey, to create a library so that I can say goodbye to my bash scripts and do some serious "scripting" on Windows too. I hope to meet you along the way!
Update. This blog continues over at "Bin Bash Java (Chapter 1)". Also Check out NailGun which aims to obviate the start time of the JVM, eg. for Java command-line tasks. They say, "Java has an extensive and robust core API and huge number of available open source libraries. It's a great big hammer, making almost any programming project look like a nail."
CDDL'ing up with SunPosted by evanx on May 18, 2006 at 07:52 AM | Permalink | Comments (6)Now that Sun's big boys are trying to decide "how" to opensource Java (with the "why" worked out by Jonathan and Rich a long time ago I'm sure), it's time for the little boys like me to get excited about opensource licenses and what they mean. By the way, I suggested in an earlier blog "Swing trounces Ajax" that Sun will opensource Java to counter competitors (Microsoft, IBM and Red Hat) and their competitive offerings (C#, Harmony and GCJ) because "Sun wants developers to be field of Sunflowers following the Sun." I liked that quote, so I'm repeating it, and I liked that blog so that's why I keep punting it. What I do change with each punt is the title, eg. Swing trumps, thumps, trounces... and who knows what'll be next? ;) In my previous blog "Duke in a Tux" I mused about Java being opensourced as GPL, as opposed to Apache. This was a silly suggestion because then all Java programs would have to be GPL'ed too! (I suppose the JVM could be GPL'ed and the class libraries LGPL'ed but lemme put that bone aside.) Jim Driscoll commented that OpenSolaris and GlassFish are both under CDDL. Since I didn't know what CDDL is (besides being an approved opensource license) I decided to do some reading... (Isn't Google great?)
So the story is that Sun liked the Mozilla Public License (MPL), but it needed some bugfixes, and so that's what the CDDL is - a refreshed MPL after a day at the health spa. CDDL aims to reduce the proliferation of opensource licenses by fixing up the MPL license for everyone. I get the impression that this was an honest and noble effort on Sun's part. Hey, it now has the full support of this little fish in the sea of developers - hold the presses! OpenSolaris prompted the creation of the CDDL. Sun has subsequently reused the CDDL for GlassFish. Maybe Mustang will be next? So what is this CDDL, and how does it differ from the GPL and ASL (the Apache Software License)? Claire explains that the GPL and the BSD-family of licenses (which includes Apache's ASL) where considered for OpenSolaris. GPL was too viral, in that derived works also have to be GPL'ed. So it's not company-friendly in that respect. We know that the GPL is developer-friendly license. Hey I need one of those RMS collars, that processes your speech, and everytime you say "Linux" instead of "GNU/Linux" it gives you an electric shock ;) The BSD-family of licenses (of which the ASL is the state-of-the-art, Simon Phipps says) is great for companies, since it says "Use this software as you wish, baby, just don't sue me." Not in those exact words of course. I guess they got some lawyers in, and they worked it out. After the GPL and BSD-family, the third distinct class of license is the MPL (of which the CDDL is the state-of-the-art, Simon says). The MPL neatly defines "modifications" and "derived works" in terms of files, eg. C source files. Changes to existing files are modifications, and new files that you add to the source tree, are "derived works." I like this because it's clear what's what, innit.
In this case, (re)licensing your software means you are placing it into a "public commons" where everyone can play nicely with it. According to the license of that particular commons of course. And here's the difference... In the BSD commons, you don't have to contribute your modifications or your derived works back into the commons. In the MPL commons, you have to contribute only your modifications back into the commons. In the GPL commons, you have to contribute both your modifications and your derived works back into the commons. So it's clear that the MPL is some happy medium between the ASL and GPL licenses. I can see why it's important to have a state-of-the-art bug-free MPL. Enter the CDDL. "I'll take one of those happy meals with the free Mozilla CDDLy toy - hold the BSD fries and GNU cola please!" Here is Simon Phipps' definition of opensource (April 2005). An open source software project is a software source-code commons maintained by a creative community, which uses the content of the commons to create richness and innovation, with and around the commons. In the process of that creativity, the community enriches the commons for the benefit of all and may be compensated by the recipients of the creative act. He goes on to say of the CDDL that the commons is continually enriched... That's GPL-style (copyleft) for your work on the commons and BSD-style for your own creations So the CDDL is copy-left for modifications (like the GPL), and "free use" for derived works (like the BSD/ASL). GPL is the most restrictive license, and BSD the least, with the CDDL sitting inbetween. The CDDL is less restrictive than GPL (it allows "free use" of derived works) but is more restrictive than BSD/ASL licenses (it is copyleft with respect to modifications).
As Jim points out, many groups require this joint-copyrighting, including Apache. And including the FSF, just joint-copylefting in their case of course ;) Apache have their Contributor Agreements, after which Sun modelled their SCA (Sun Contributor Agreement). SCO taught the world the need for these, innit. The GPL is great for developers that don't want some other company building a closed commercial product using their work. But what if later you wanna relicense your GPL project, eg. under ASL or CDDL, and/or build a commercial system using your project? Maybe for your new best client that is gonna help you pay off your house, by letting you work on your project all week instead of all weekend. The problem is that if you've accepted contributions, you can't. Because you don't hold copyright over all those little patches - here, there and everywhere - submitted by a whole bunch of handy helpers.
This does make the point that the license a project is released under, is not the whole part of the story. The terms and conditions of acceptance of contributions is another chapter. I guess in the extreme case, no contributions are accepted, ie. such a project would have to be forked in order to make any "contribution" to the project. Another case is the requirement that contributions are multiply-licensed, ie. contributed under a different license in addition to the original license. In my example, a project is released under the GPL (the most restrictive license), but contributions to the master source tree are only accepted if made under the ASL (the least restrictive license). Finally, a common case seems to be the requirement of a Contributor's Agreement which grants joint-copyright. This is an attractive solution for the project originators, since they retain the freedom to relicense their software into another commons, and/or use the software (which is predominantly their own) in a closed product. That is, keep their options open for the future. Also, as the FSF says, it it makes it easier to defend against infringements in court, if you can argue as the single copyright holder (and not one of hundreds). Traditionally one would assume that contributions would be accepted under the same license as the source is released under, without any further conditions. The point is that there is no guarantee of this, or that contributions would be accepted at all - clearly patches can be, and are, rejected, eg. in the case of the Linux kernel. Opensource promotes common development, where the idea is to engender a community of contributors. But maybe in some cases it's more about distribution. And in others, it's a marketing gimmick. And in most cases, it's some combination of all of the above! Duke in a TuxPosted by evanx on May 17, 2006 at 03:07 AM | Permalink | Comments (13)As we know, Sun is relicensing JDK for inclusion in Linux distros. This is a fantastic development for Java. In this blog, I muse about GPL'ing Java as "GlassFishBowl" and rigorously protect the Java trademark, so that Java still means "Java" as in the JCP, TCK, JEE, et cetera. I posted a comment to this effect on Calvin's article "Java on Linux - the real story". By the way, I rattled on a bit about opensourcing Java in my earlier blog "Swing thumps Ajax" together with too many other issues.
This makes it a real option for opensource developers to consider Java for their desktop apps, knowing that they can get shipped in Linux distros, alongside Sun's Mustang - which is such a great JRE for Java and Swing on Linux. I would like to see Netbeans shipping in Linux distros too! Since Netbeans provides a great out-the-box experience, for both GUI and web apps (and mobile apps), this would encourage young developers to give Java/Swing/Netbeans a twirl, on their freshly squeezed Ubuntu boxen. As Calvin quoted on his blog, Sun's lawyers and Debian Ubuntu developers spent months thrashing out a new "binary license for the Java platform." The distribution of the Java platform, in particular the JRE, is clearly very important for Sun to get right, and I'm sure they have done so. Probably this is just a first step, on the Duke's opensource march. As Rich Green said at JavaOne about opensourcing Java, "It's not a question of whether, it's a question of how." So now I'm gonna muse about a possible next step...
Hey Linux and OpenSolaris are GPL. KDE and GNOME are GPL. Because GPL is the most developer-friendly license. And in the case of Java, the developer is Sun. Apache is BSD-style, and is the most company-friendly, and hence all the support they get from the "biggest" company of them all, IBM. Sun don't want to be too friendly to their competition, like IBM and Red Hat. So they don't want an Apache license. So I think the GPL would be best. Or a simple variant of it, eg. the GPL plus a clause or two agreeing to protect the "Java" trademark with our lives. Let's try to imagine how this would unfold. The competitors that Sun worries about the most are IBM/Apache, and Red Hat. How would IBM and Red Hat react to the above scenario? Would Apache fork GlassFishBowl into TomcatFishingBowl? Well all Apache products are Apache-licensed, so forking a GPL project into another GPL project doesn't make sense for Apache. Let's say IBM or Red Hat wanted to take Java off into their own direction. (That would be crazy, but let's pretend they got crazy.) They could fork it, and call it SeaShark. But it wouldn't be Java. It would have to compete with C# and Java. Hey, it might introduce some great features that the JCP community includes into a JSR for Java, and that might make it into Java and the GlassFishBowl reference implementation. So Java wins there. Maybe researchers and students would want to fork GlassFishBowl into PlexiFishBowl to try out some wierd and wonderful tangential things. But surely Red Hat et al, would rather choose to distribute GlassFishBowl, so that they can license the official Java branding (with its promise of compatibility) for their distribution? And the licensing royalties for "Java" would still come flowing in for Sun, wouldn't they? Update. If Java was gonna be GPL'ed, I guess the class libraries would have to be LGPL'ed, to allow non-GPL'ed Java programs to link to them? Anyway, see my follow-on blog "CDDLing up with Sun" which considers the CDDL as a better bet for Java than the GPL/LGPL.
Java is all you'll ever needPosted by evanx on May 15, 2006 at 02:16 AM | Permalink | Comments (9)In his blog, Damien Katz wrote on "Signs you are a crappy programmer, and don't know it." At the top of the list is "Java is all you'll ever need." Since i'm guilty of that, here is my response - why i choose not to add other languages to my toolbox. So you have some system task to do. This is where you jump into vi and hack together a bash, python or perl script to accomplish this task in quick (and dirty) way, right? This first problem for me is that this means leaving my Netbeans environment. These days I'd rather go to the dentist for root canal than do that. Like most people, I guess I don't like going to the dentist. It does have one advantage though, which is if you aren't enjoying your job, you can schedule the dentist in the late afternoon and leave work early. Which i did regularly one year, when i wasn't enjoying my job, and had to go to the dentist a few times for a nagging pain. For my perly whites. Yes, I was using Perl at the time, and vi. So I'd be in that dentist's chair, with the drill screeching in my ears, and thinking, "Ah, what a pleasure not to be at work right now!"
So I choose Netbeans, to build up a library of support classes for various archetypes of tasks, eg. system tasks. This sharpens my skills in Java in different elements of the standard libraries, eg. System and Runtime. And it keeps me "thinking in Java." I'm obsessive compulsive about code. Happily Netbeans lets me rename things, to keep my code "clean." Writing in python and C, I get an immediate dentist reaction to lazy and legacy programming conventions, like abbreviations and underscores. I've had that sore tooth removed so that pain would never come back. Incidently, C# is shaping up well for system tasks, with the C# .Net command-line in Vista. Hopefully the Java community will get a similar thing via Groovy at some point. In a large company, every engineer and programmer might choose their own favourite language, to automate various tasks. Very soon you end up with an unmanaged and unmanageable environment which is held together with prestuck and sticky-taped scripts, written by ex-employees, in every language you've heard of, and some you haven't.
And a great way to encourage employees to write overview documentation (in addition to javadocs) is to have a technical company newsletter or blog - and incentivise and reward submissions, with gift vouchers, public praise, and "get out of the dentist's chair free" cards - you know, the important things in life! But LAMP hackers are real men and use vi innit. They don't want or need an IDE, or strict conventions. The problem is that in large teams, some programmers are veteran gurus of the system, while others are one-month newbies (which might be future veteran gurus, or otherwise working for your competition). Now those newbies want IDEs, to navigate the (javadoc'ed) code with ease, to be prompted with method names, etcetera. And you, the CIO, want to help them help you - show them the Netbeans! Having those fantastic hackers that Paul Graham waxes lyrically about, is great, until they leave the company to go and work for Google. And leave fantastically terse, undocumented code, behind. To make their newbie replacement's life a spaghetti hell for a few months. Time for the dentist! Another thing Damien mentions, is cutting and pasting. I think that API's, and superclasses and such, should be blindingly simple. Because it's difficult to predict how you might want to (re)use them in future. So if you build in functionality you need for your current problem, that might render that superclass unsuitable later, for a different unforeseen case. And in trying to change that superclass, or API, you tend to break older code that has been working fine. So cutting and pasting can serve to isolate code, to make it safe from the rug being pulled out from under it later. An (over-engineered) superclass or API, that is great today, might be a sore tooth tomorrow...
Update. Check my blog "A Fool's Errand" which follows on from this, and introduces an upcoming series "Bin Bash Java" inspired by this blog entry.
My Desktop OS: Windows XPPosted by evanx on May 11, 2006 at 10:06 AM | Permalink | Comments (4)Linux.com has a great series "My Desktop OS..." which is usually a Linux desktop. I thought i should do a tongue-in-cheek one "My Desktop OS: Windows XP" - to highlight some of my frustrations with the Linux desktop of late - multimedia to be specific. Which boils down to me being too lazy to bother about my own freedom. I'll follow this up with another article, "My Desktop OS: FreeDOS" ;) So after getting my hands dirty with Linux desktops over the past 8 years, I started using XP again. ("'Shock horror! Linux advocate caught using XP!' it says here in the paper.")
Actually I had lots of "exotic" computers to play with because my father's company had to port their 4GL software to all these different "micro-computers" and mini-computers, mostly from ICL (which was British for "IBM" in those days). This was in the days before the IBM PC came along and changed everything. Before the PC arrived, my favourite "micro" computer was an CP/M multi-terminal machine. It was powered by a Zilog CPU, just like my friends' Sinclair Spectrums. I didn't have a Spectrum - I had a great museum playground of ICL computers of the twentieth century! Naturally I spent every hour I could out of school at that computer playground. So this favourite CP/M box of mine was the size of the original PC, but it had four terminals hanging off it - fantastic! Especially when you're boss' kid playing at the office after school. There was always a terminal free, so I never cycled all the way from school to the office, only to have to wait for hours for someone to finish running their job. Then an "IBM" arrived. An "IBM PC." Now understand, we were predominantly an "ICL" house. But we had heard of this American "startup" computer company called "IBM." Just that I personally hadn't seen a computer made by IBM (or DEC) before. South Africa was sanctioned by the US, and I think that IBM PC had to be sneaked in via the UK. (Maggie Thatcher, you did the wrong thing for the wrong reasons - you turned me into a computer geek!)
I would have preferred this PC to run my favourite multi-user CP/M, but that didn't curb my enthusiasm one iota - I was on a path of discovery! (Pity it was computers and not girls, but still it was something.) My IBM PC desktop then was the GW-Basic fullscreen editor - fantastic! You could move the cursor around the whole screen - and that really was something special in those days, I promise you! In the mid-eighties, I progressed to Norton's Editor, and TurboC - woohoo! Little did i know that for the next decade and a half, except for the addition of a mail application (pine), and a browser application (Netscape), my desktop metaphor would remain essentially the same. Instead of programming in Norton's Editor, using the DOS command-line to compile and run, I would be programming in vi, and using the bash command-line to compile and run. I made the not-so-huge leap from run.bat to run.sh. "A small step for man, a giant leap for mankind." RMS will back me up on that.
You were right Bill, you really did only need 640k in those days, and those PCs flew! I don't remember any hour-glasses, or waiting for web pages to load... Oh, because there was no web, i forgot about that. But Linux, with its X Windows and Netscape, rocked so hard! The best thing about Linux for me, was that it opened up the wonderful world of networking, and the Internet, and the World Wide Web, oh wow, i was blown away! Once you got your XF86 config file working, you were on Al's superhighway. Actually I never got my XF86 config working - so a friend got it working for me, yeah baby! I xterminated X windows with bash, pine, vi, gcc and later java. To be honest, I only used two X applications: Netscape and xterm. Then a few years later I got a Java development contract through that very same friend that had converted me to Linux six years earlier (and got my XF86 config file working for me). He upgraded to a new laptop and gave me his old laptop (exactly what i would have done). I was all fully set up with IntelliJ, JDO, MySQL, on... XP! He explained that our clients all use Windows XP, so we are deploying our application to XP, and so we develop on XP. Actually for the month before that, I had been using my sister's XP machine to check my webmail and surf occassionally, because I was between contracts and minus a computer to call my own. So I was au fait with Windows XP, or should I say with Firefox running on Windows XP. At first I found XP very frustrating. Mostly Windows Explorer. Point and click and wait. Repeat. I was used to bash, which with its file name completion, was quick and easy to navigate the file system, and run applications - even better than the 1980's with CP/M and PC-DOS - because bash has got file name completion - it's the bomb, baby!
So I got used to Windows XP. It is slick, and runs all my favourite opensource apps, so it's pretty much like a Linux desktop. I spend 99% of my time in my IDE (which was IntelliJ), Firefox and Thunderbird, just like on Linux. I was a happy puppy, I won't lie to you. For one thing, IntelliJ was definitely a step up from that vi rock i had been living under for so many years. My next contract, the same thing happened. "Here is your XP machine. I am your network admin. Let me know if your Outlook doesn't work, or you can't print something." I somehow knew I couldn't ask him if he had a set of Fedora CDs laying around for me for reinstall my machine. My job was a Java developer, paid by the hour expressly not to be self-installing company property with unsupported operating systems and software! So I was a good user. Except for occassionally like when the printer wasn't working. I emailed the network admin that I've pressed Print about twenty times and still nothing is coming out of the network printer, but i'll keep trying! Best part is he never knew if I was joking or not. After all I was just a lame Windows user and not a l33t Linux hax0r.
When that contract was over, I tried to go back to Linux on my new home PC. Because really, I couldn't be using this "Windows Phone Home." I am a real programmer and IT professional, goddamnit! And a longtime Linux advocate to boot. Heck, I've co-founded at least one Linux assocation that i can remember, and a LUG too. My frikkin car license plate is even "LINUX." It's a known Linux bug in Cape Town. Yes, a VW Beetle. So i booted into Knoppix for starters. This is when the strange thing happened. I hated it! I wanted my XP back! The fonts looked terrible! The icons looked unfamiliar! The resolution was terrible! No accelerated video drivers. The mouse went flying across the screen at the million miles an hour. My bluetooth USB key didn't work. How am I gonna get pictures from my phone to email my family!? No iTunes for my iPod! No MP3, no Flash, no Quicktime, no podcasts, no YouTube. Darn, my life as I knew it was taken away from me in the blink of a lilo! I phoned a friend, a Linux friend, in a panic, and said, I'm doomed. I can't play that new Doom3 demo, and that's just the start of it! He said, no problem, you just gotta set up your system properly. Check this "Tips and Tricks" site, download those RPMs, these dependencies, insert those suppositories, and slap-get this other thing. I said, "Listen, buddy, I'm on frikkin holiday. It's my frikkin day off, so I'm not going to spend the whole weekend being a computer geek. I've been a computer geek my whole life, and it's enough! I just want my computer to work. Yes I'm concerned about freedom - my freedom to go outside and have a beer in the sun, rather than spending the day slapt-getting my frikkin hard drive! I've been working for the whole week on a frikkin computer, and my evenings and weekends must be free as in beer! Configuring drivers and plugins sounds very much like work to me. So get your slapt arse over here, linux boy, and configure my frikkin computer for me, bitch!"
Everything just works. My sound and video works like a dream. My weekends are my own, or should I say, MPlayer's. My bluetooth camera phone works, so I can send pictures to my Mom of me blowing up nitrous ballons at parties again. (That's my story and I'm sticking to it.) My iPod works. I got my tunes. Can life be better? (I heard that, RMS. Just let me enjoy the moment please!) Ah. I can hear the birds chirping outside... Oh it's my GoogleTalk! Swing trumps Ajax and Web 2.0Posted by evanx on May 10, 2006 at 05:03 AM | Permalink | Comments (25)
Joshua Marinacci asked what's stopping people from shipping Swing applications: was it the large JRE to download, or difficulty in Swing programming, or what? That was a really great question that enjoyed a lot of comments. Including this one, which goes right off topic. Most contracts/companies i have been involved with are all deploying Swing apps, woohoo! Stands to reason since i'm a Swing developer, innit. Developers complain that Swing components are buggy "on the edges." Now i'm talking about bright colleagues (not me) that tried to make Swing components deliver some corner-case functionality. These colleagues also complained bitterly about old bugs not getting fixed, even after years. Presumably because of Sun's focus on web/JEE in the dot-com/server-side era which is understandable, or maybe because fixing them could break some customers' applications, also understandable.
So if i appear to "criticise" Sun at any point, that's just a joke. And as the Russians say (loosely translated) "Every joke is partly a joke". Another russian joke is: "You know why you can't have sex in public? Because you'll get too many suggestions!" - as Sun well knows.
Since Mono gives GNOME's "unixversal" platforms a foot-up to run Windows .NET applications, i assume Mono's choice of C# over Java is, at its root, a Linux-advocacy decision (to see flat the desktop mono-poly), rather than a pure technology decision. (Excuse me when i mistakingly say "Linux" when i actually mean "GNU, GNOME, KDE/Qt, GNU/Linux, *BSD, Darwin/MacOSX and OpenSolaris.") The Mono guys will argue that C# is a better language and has easier native library integration, which is clearly very important for GNOME. Still i wish the opensource community would unite behind Java. When Sun opensources Java, we'll have a complete opensource Java "stack" including tools (eg. Netbeans, Eclipse) and enterprise platform (eg. Glassfish, JBoss). The problem with Mono, Python et al, is that they do not have awesome opensource IDEs like Netbeans and Eclipse. In my opinion, this makes them less suitable than Java for many project archetypes. (By the way, having this "opensource" Java development stack doesn't help the Mono guys because their philosophy requires "free software," and so "opensource" just won't do.)
At some point, MacOSX might want to move away from Objective-C towards a "modern C." I predict it will be C#/Mono, because that would enable MacOSX to win over Windows developers, applications and users. So "desktop Java" is sidelined in a possible future where most Windows, Linux, and MacOSX developers might favour .Net/Mono for new desktop applications. And desktop tinkerers grow up to be enterprise programmers, so... I look forward to a practical Java desktop emerging from Looking Glass, one that encourages young opensource developers to tinker with Java/Swing, rather than C#. Otherwise... "Houston, we got a problem." Java is perfectly poised right now, with Netbeans 5, JDNC, Glassfish, and millions of students being taught Java... Hopefully the Java Community will capitalise on this, and achieve world domination, woohoo! I want a Jubuntu desktop with a Looking Glass desktop manager, and "NetFinder" - a super-browser built on Netbeans RCP, with modules like Gecko renderer, ODF viewers, GMailer, GDriver, GoogleData editor, WebStart... Oh man! Back to the main thread, which is Joshua's question why people might not be shipping Swing applications? Let me say right out: Swing looks beautiful and is plenty fast enough - just look at applications like IntelliJ and Netbeans. And Swing just gets better, in every department, with every release. I'm sure the Netbeans team, and the Java Desktop community, have been giving great feedback to Sun's Swing developers, and it shows.
Incidently, a by-product of Swing's lightweight approach, is that it makes it feasible for projects such as GNU Classpath to deliver an opensource Java implementation, which together with IBM donating their JVM to Harmony, might spur Sun to opensource their Java implementation. Some people argue that blazing performance is all that users want. I argue that better usability plus new features, is what users want sooner. (With near-blazing performance if possible please!) So it's gotta be blindingly quick and easy for teams of developers to deliver features that users want. That's why we gotta use modern languages like Java, with its safe syntax, memory-management and extensive libraries, and great tools like Netbeans and Eclipse. Thankfully, Moore's Law doubles our application's performance every year, for free, and JDK engineers boost performance with every release. And we can use the Netbeans profiler to join the party. But back to Joshy's question...
I suspect we lack this simplicity (through a standard framework and components as promised by JDNC/SwingX) after 10 years because AWT/Swing was envisaged as a web/consumer/desktop application platform rather than an enterprise application presentation platform. And because Java's focus quickly shifted to the web and server-side, and Swing lost some attention in all the dot-com excitement. It appears that much work has been done on JEE/EJB, and relatively little on Swing as an presentation platform for JEE applications, eg. JDNC. "That's mentil!" (That's from the BBC Series "The Office", when everyone tells David, the manager, that they preferred the other boss Neil's jokes. David says "Thaaat's mentil!" and storms out. Heh heh.) So this cuban friend of mine used to start every sentence with "Attention!" pointing his finger, and ended every sentence with "Unnerstan!?". As in one of his memorable quotes: "Attention! In Cuba you do what the gover'ment want you to do, not what you wanna do... Unnerstan!?"
So back to Joshy's question: who is not developing and shipping Swing? This brings me to the main thrust of this blog and if you believe that you'll believe anything. There is one company i worked for recently that ships a JBoss application when it really really should have been Swing. It's a big intranet application run in hundreds of sites. They went web-based rather than Swing/WebStart, and their developers and clients and themselves are paying a very big price for that decision, because web applications are so convoluted and kludgy! Maintaining the application and adding functionality is very slow and difficult. Granted this company's architecture has other problems that exacerbate their situation, but lemme press on before i forget what i was going to say... Darn, i have actually forgotten! Oh yes... So why did this company go the web route? Because everyone's infatuated with the web as a platform. Lets rather leverage the web as a Swing application delivery platform, not an application platform in itself. But we need JDNC, with lots of high-level components, blueprints and tutorials, to illustrate that Swing apps are easier to develop and maintain, compared to the mix-up of Java/JSP/Javascript/HTML/CSS that is web applications. That's a hodge-podge of languages and technologies mixed and mashed up something terrible! ;) Besides being easier to develop, Swing apps offer a better user experience to boot. They make Ajax apps look relatively pedesterian, innit. Ajax might make Web 2.0 applications a better experience for the user, but Ajax doesn't make developers' jobs fundamentally easier. In the crystal ball of my dreams, Web 3.0 is powered by Swing, JDNC, and Webstart, and not Ajax. And not .Net with Avalon or WinForms or whatever.
I argue that Google Calendar and Google Mail would be considerably easier to develop in Swing rather than Ajax. Soon some Web 3.0 startup is bound to build a better Google Calendar, GMail et al, in Swing, using Webstart, and they will rubbish all Web 2.0 Ajax applications faster than you can google. Swing webapps will have more richness it their little inbox than GMail and Zimbra put together, and their simplicity and agility will ensure that enhancements come fast and furious. I also wonder, compared to developing GMail (which is a technological feat), how relatively difficult would it be for Google to zhouzh-up Thunderbird to support their GMail back-end transparently, including persisting contacts, settings, et al, to your GDrive - ie. make Thunderbird a stateless rich client internet application. I can't accept that people are not willing to download a 5Mb Google "GoZilla" client, which loads its configuration from your GDrive, rather than your local filesystem. I wish that GDrive-powered versions of Thunderbird and OpenOffice are coming to a Google Pack near me soon, woohoo!
As the designer of the NASA Apollo computer pointed out, the most reliable components are those you leave out. We not gonna leave Java out, so lets try to leave out everything else that we possibly can, like all that convoluted web presentation stuff. Keep it simple, safe, easily deliverable. To wrap up, my dream for the future of computing, is stateless rich client applications using web data services, where we cache in on rich applications via the web (using some kind of mashup of Google Toolbar, Google Pack and WebStart), and where your documents and settings follow you around on your GDrive. So if your hard-drive crashes (or you walk into an internet cafe, or sit down at a colleague's computer), just pop in a live/install USB key (or not), login in to your Google/Yahoo/Live account, and you're good to go - your documents, settings, and program shortcuts, are all at your finger tips, exactly where they were last time you logged in. So I love what Red Hat is doing with "Stateless Linux." And I hope Google and Sun are cooking up a "OpenOffice with GDrive stateless dressing" free lunch in your Google Pack. I suspect that OpenOffice is gonna light the way to Web 3.0, with purposeful integration to your searchable GDrive repository. Ok it's not in your browser, it's from your browser, so what?. Have a cup of coffee while OpenOffice2Go and Thunderbird2Go downloads/updates, and thereafter forever have a richer application to use everytime and anywhere - nicer for the users, and easier on us developers. Similarly with enterprise applications. I hope they are going to use Swing and not Ajax. Then everyone's a winner - end users get a richer application, and features coming sooner, and developers enjoy rapid painless development and happy users.
Disclaimer: I'm a Swing developer, writing a Swing framework, namely aptframework.dev.java.net - so take everything i say as self-serving conjecture, which is exactly what it is, with the main purpose of having this opportunity to shamelessly plug myself and my project, so...
Netbeans Day (South Africa) Part 3Posted by evanx on May 07, 2006 at 11:08 AM | Permalink | Comments (3)This is Part 3 of my notes and impressions from Netbeans Day (South Africa), held in Johannesburg this week, and covers Sang Shin's talk "What makes Netbeans the best IDE for Java EE Development?" (See Part 1 about Netbeans IDE 5.0 features, and Part 2 on Netbeans RCP). Health warning: the italic text is just me rambling and so should probably be skipped.
Sang Shin is a Java evangelist from Sun. See his site, javapassion.com. This has a very cool master tutorial index - check it out. Sang Shin (and Geertjan) emphasised that Netbeans offers an unrivalled "out the box" experience, eg. it comes bundled with servers, namely Tomcat and the Sun App Server. Just one download, and you're good to go. A Netbeans 5.5 preview is available with bundled Glassfish (JEE 1.5 with EJB 3.0), and of course you can develop/deploy to JBoss and WebLogic. Support for Websphere is coming soon. (One of the new features in 5.5, is the generation of entity classes and CRUD JSF pages from a database table, as Geertjan writes.) I read that Derby (which i think Sun is calling "Java DB") is gonna be bundled in future Netbeans releases. I must say, i love that Netbeans works out-the-box - for one thing, this makes it easy to upgrade to a newer release without hassle - whether you are a desktop, mobile or web developer. (By the way, for this reason, i think it's worthwhile switching from HSQL to Derby for Netbeans-ready demos and such - and to Glassfish too.) Excuse me for gushing, but the thought of Netbeans 6.0 with integrated Derby and Glassfish gets me so excited, i gotta jump up from my computer and walk around the garden to calm down! It is so 100% "good to go" - just add coffee! What a pleasure for the next generation of developers - we never had it so good before. The golden age is dawning, with fantastic languages, libraries, tools, databases, servers, frameworks, components - all freely available, and cross-platform - with tutorials to rule them all - and with Netbeans, it's all in one litt | ||