|
|
||
Tom Ball's BlogCommunity: JavaDesktop ArchivesDivide and ConquerPosted by tball on April 01, 2008 at 09:07 AM | Permalink | Comments (1)As anyone following the JavaFX Script compiler project knows, we have a schedule gun to our heads to finish a useful tool quickly, so the interpreter-based prototype shown at last year's JavaOne can be retired. It's a big job since the language doesn't have a specification yet, but our team of four engineers plus some really good volunteers are set to deliver what looks to be a great product. Of course I'm biased in this appraisal since I'm one of its contributors, but feel free to browse the project's source or subscribe to its dev and/or commits email aliases and make your own assessment. Unfortunately, it has become more obvious as the project progresses that when our clients say "compiler" they really mean a full SDK, similar to what is delivered by the JDK. In particular, our management just assumed that a version of the javadoc tool which understands JavaFX Script would magically appear without any stated requirements or staffing. So we had a sub-project with a short deadline, no engineer available, and frustrated customers. To make matters worse, javadoc engineers are hard to find since the job requires two unique skill sets: compiler engineering and web designing. Why does the JDK's javadoc output look dated? Because compiler engineers have owned it for the past few years. Why was it lagging behind the language a few years ago? You guessed it, our doc-writers were supporting it. Now, our team understands compilers, obviously, but none of us have any design skills worth noting. However, we have some engineers with great design skills on other teams. The "correct" solution would be to pull one engineer from each team and have them work together, but there wasn't time nor anyone available. Sound familiar? What we did was follow the Unix rules for tool development: break the task apart, define a tool for each part, and use ASCII text to communicate between them. I wrote a simple doclet that dumped all information available from the Doclet API to an XML file (other people have done this over the years), then copied the JDK's javadoc source and enhanced it to provide JavaFX Script-specific information for that doclet (the doclet also works with Java sources when used with the JDK's javadoc). Joshua Marinacci then wrote an XSLT translator to generate XHTML pages similar to what javadoc's standard doclet creates. We use this tool to generate the project's UI Runtime documentation, and soon, all JavaFX Script-based API. The full tool source is here. This divide-and-conquer strategy really pays off. Neither Josh nor I put much time into this project (as shown by how short both the doclet and XSLT script are), so our main jobs weren't impacted much. This approach also makes it much easier for our community to contribute: don't like the (currently primitive) output? An XSLT file is much less intimidating than javadoc's source, so jump in and make it better. But why limit ourselves to one output format? Just use the tool's -xsltfile option to use your own translator. And why limit ourselves to documentation generation? That XML file defines a model of any JavaFX Script and/or Java-based program, which can be used by other tools. As the Unix tool masters learned early on, breaking a big tool up into smaller, specialized tools opens the door to all kinds of unanticipated uses. Elephants and the JavaFX Script CompilerPosted by tball on November 10, 2007 at 04:42 PM | Permalink | Comments (4)I don't know what it is about elephants, but they sure work well in analogies. Whether we are ignoring them, seeing pink ones or trying to sell white ones, elephant analogies seem to communicate ideas well. An elephant analogy came to me when I was recently grilled about exactly when the JavaFX Script compiler team will deliver our first milestone release. "I can't give you an accurate date," I said. "It's like pushing an elephant through a door*; until a critical mass makes it past the threshold you just don't know when you'll be finished. Once you pass that threshold, though, the rest happens quickly and in a manner that can be more accurately predicted." And then the tension in the room broke, as I think everyone has been in that situation sometime in their respective pasts. In a way, this compiler project has turned out to be very similar to some of the GUI toolkit ports I've worked on in the past. We're making lots of measurable progress, which anyone can monitor in our Another similarity this project has to a GUI port is that most graphical toolkits have a few core components that depend upon most of the rest of the toolkit; call those classes Widget, Component, JComponent, Window, (graphic) Node, it doesn't matter -- there are so many benefits to sharing code between GUI components that these elephant-sized classes are just part of the GUI toolkit territory. So porting a GUI toolkit means that often there will an excruciating period where there is little visible progress as you work through all the dependencies, and then your first window appears, and then most of the remaining toolkit seems to just fall into place quickly. That's what happened many years ago when I was part of a team porting Presentation Manager from OS/2 to CTOS (which was about as unlike OS/2 as could be possible in those days). For several weeks, we slogged along without any visible progress (as voiced daily by our frustrated management), then we finally got the login panel showing and, in less than a week, all of the core applications were up-and-running. That login application was the critical part of our elephant, since the closure of its toolkit requirements encompassed most of the system. It wasn't really the login panel itself, of course, because whatever application is chosen first will be the hardest. This is all a long-winded way of saying that we are really close toward delivering our first milestone release, but when the exact date will be remains unknown. If anyone is interested in monitoring our progress, check out our JIRA issue tracking system (courtesy of Atlassian), and/or subscribe to our developers alias (comments from experienced elephant pushers are welcome). Once we hit this first milestone, we'll be able to provide a much better road map for the remainder of this release. *No elephants were hurt during the writing of this blog entry.
The Birth of the JavaFX Script CompilerPosted by tball on July 20, 2007 at 06:49 AM | Permalink | Comments (3)Today a new JavaFX Script compiler incubator project, openjfx-compiler went live. Yes, it has source code, issues, mailing lists and a wiki, but the compiler itself actually does very little right now (although Chris Oliver did offer a nice tease). So why open it now? The answer is that our team wants to open not just its source, but the design and development of the compiler itself. We are also asking for feedback on the JavaFX Script language to make it fully specified and compilable. If you have an interest in watching or participating in a compiler for JavaFX Script, please join our project and help shape its future. If the past couple of months is any indication, it's going to be fast, furious, and fun! Are Closures Just Delegates?Posted by tball on August 25, 2006 at 02:29 PM | Permalink | Comments (20)The recent blogs on closures have left me with a real sense of déjà vu. A few months after the JDK 1.0 released (over ten years ago), Microsoft proposed that the Java language be extended with type-safe method references, which they called delegates. The AWT team I was a part of liked this proposal and pushed for implementing it, as it fit well with the new GUI event model we were designing to support Java Beans. The Java language team shot the proposal down, however, responding publicly with the About Microsoft's "Delegates" white paper, which described how everything you might want from a delegate can be better handled with inner classes. Microsoft responded with The Truth about Delegates, and the two companies eventually went their separate ways, language-wise, with C# supporting delegates, and adding anonymous delegates (which look a lot like what's described in the Java closures proposal) in C# 2.0. When the Java language team shot down the delegates proposal, the AWT team grumbled quite a bit since delegate-like equivalents are frequently used for event-driven application development (which is undoubtedly why Microsoft's GUI team drove the proposal from their side). But then an interesting thing happened: due to this constraint (no delegates), our event design improved significantly. As the old saying goes, sometimes unanswered prayers are blessings. If we had delegates back then, the AWT might be (even) slower and (even) harder to use than it is today. I don't mean to suggest closures are evil (I live close enough to the old Xerox Parc site that some Smalltalk fanatics might pass messages to me wrapped around a rock!), but language features do impact API and performance work. In this case, the AWT has a design constraint that was better addressed without using delegates: lots of different event methods that need to be dispatched to interested components very quickly. This may be hard to believe, but in Mustang the AWT and Swing define over ninety component events, while toolkit performance must keep improving even as the complexity of desktop applications increases. In 1.0, the AWT routed events up component hierarchy until a component "consumed" the event, which doesn't scale well to even small applications due to the number of events fired during normal usage. What made this worse was that frequently fired events such as mouse movement or scrolling events weren't interesting to most applications, so their dispatch took the longest since no component consumed them. Think I'm exaggerating? During the early Swing days, Hans Muller's favorite performance test was to open a scrolling window with a lot of text or a big graphic, click-and-hold on the vertical scrollbar, and wiggle it up and down quickly to check for lags. Not the most elegant test, but it quickly and reliably uncovered performance issues because thousands of events get fired during that wiggling, and if they aren't dispatched and handled quickly, then the whole GUI bogs down. But what does this have to do with closures/delegates/better design? I'm getting to that. What's the fastest performance Big O number? O(log N)? O(1)? Try zero. The fastest code is the code that isn't executed. That's why in 1.1 the AWT moved to a publish-and-subscribe event model (the "new" event model), so that events the application didn't care about weren't dispatched. This is great for performance, but it makes the API more complex because you need a separate method for each event, thus making it harder to use. It also encourages code duplication in similar event handlers, making the application potentially less robust (fixing a bug in multiple places is less reliable than just one). We defined groups of GUI events to address the API complexity issue, by declaring a shared data instance (Event class) and event interface (Listener) for each group. The advantage of listeners is that related event methods are in one class, each of which share a common Event class for event data. This reduced the complexity and data sharing issues to a certain extent. The problem then became that developers didn't want to write event handlers for events they weren't interested in, so Adapter classes were created so uninteresting events get no-op'd. This combination of listener interfaces with Event objects and Adapter classes balances the optimization needs of the toolkit with developer ease-of-use. Tom, WHAT DOES THIS HAVE TO DO WITH CLOSURES/DELEGATES/BETTER DESIGN?!? Simple: multiple event Listener interfaces and Adapter classes aren't possible with delegates or closures, as each event requires a separate delegate. If delegates had been added to 1.1, the AWT team would have used them instead of the current design (since delegates are well-suited for event dispatch systems), and AWT development would now be (even) harder while performance would be (even) slower. I'm therefore on the fence regarding adding closures to Java. While I like functional programming and agree that certain problems can be coded more succiently using closures, I'm worried about how overuse of them can hurt performance and API design. Read the whitepapers linked above, and then tell me what points I missed. Finally, a Good Use for FinalizersPosted by tball on August 17, 2005 at 03:46 AM | Permalink | Comments (12)For years Java developers have been warned about the dangers of using finalize methods to release system resources. Josh Block describes the issues thoroughly in his book, Effective Java (Item 6: Avoid finalizers), but just Google for "avoid finalizers" or "finalizers considered harmful" to find hundreds of similar discussions not just about Java, but most other languages that offer the facility. Yet I just found out that the problem cropped up again in a recent internal Mustang build. What is it that is so alluring about this bad programming practice that it can blind even very experienced Java developers with its false charms? Finalizers bit me hard back in the JDK pre-1.0 days, because they were originally used to free the Windows resources from AWT graphic objects. This technique gave Tumbling Duke the power to cause the Blue Screen of Death, but such power is cannot be entrusted to a mere applet, or for any class for that matter. Any class that allocates a critical resource needs an explicit "close", "release" or similarly named method in its API. Its clients must then always use that method. The problem is that a library provider cannot always regulate its use, or misuse, by developers who use that library. The java.io documentation, for example, can warn, argue, even plead that developers close file streams after using them, but it cannot enforce that requirement. So what a lot of library engineers do is add a fallback routine in a finalize method to check whether a resource has been released, and if not, release it during finalization. It's an ugly hack, but often necessary to avoid granting Tumbling Duke-like powers to their client programs. But cleaning up after one's messy clients doesn't encourage good behavior on their part. I found this to be true while on vacation this summer with our children, who didn't understand why food wrappers cannot just be thrown on our hotel floor since a maid will pick them up later. "Because you have to" may work for making children clean up after themselves (it didn't for me, but maybe for other parents), but such an approach is guaranteed not to work for developers. As long as the maid (or library) silently cleans up, messes will be left to clean. But what if the maid left a nasty note whenever such a mess was left? Something like (using java.util.Logger):
protected void finalize() throws Throwable {
if (handle != null) {
Logger.global.log(Level.WARNING, "my handle not released"); // new
release();
}
}
Now when a client tests his poorly written code which seemed to work because the library was silently cleaning up after it, several nasty-grams start appearing. Even better, add a stack trace which will point to where in the client code is the problem:
private Throwable trace;
...
// during handle allocation
callerTrace = new Throwable("handle allocation");
...
protected void finalize() throws Throwable {
if (handle != null) {
Logger.global.log(Level.WARNING, "my handle not released", trace); // new
release();
}
}
This takes advantage of how scary stack traces are to clients, plus it forwards all the information a library engineer has to the offending developer. Finally, we have a way finalizers can improve quality instead of detracting from it, by throwing up warning flags when library resources are being leaked.
Easy Version Support with ClassLoadingPosted by tball on July 13, 2005 at 03:10 PM | Permalink | Comments (0)Versioning, which I'm defining for this entry as how a Java application manages its external library dependencies, has been a tough issue ever since Java first released. Back when Java was born, the vision was that each machine would have a single Java runtime and standard libraries which would always be fully backwards-compatible. The reality has been that for most apps, the only reasonable alternative to testing a full matrix of released JREs and libraries is to instead package everything the app needs, install the whole hairball on each customer's system and use a custom classpath to access it. The problem with a custom classpath is that it is easy for your customers to break in subtle (and not so subtle) ways, which makes them cranky and can drive your tech support engineers crazy. Some work has been done in the JDK via its Package Versioning Specification and API, but there are still times when your app really needs to keep specific libraries under tight control. NetBeans has this problem with its javac bridge, which allows its editor and refactoring modules access to javac's error checking and parsing support. The problem is that javac doesn't have a public API, so while a tool can leverage a specific version of javac, it cannot rely on whatever is on the customer's machine since its internal API may be radically different. We have a recent version of javac that works with our bridge, but just adding it to the NetBeans classpath won't work for two reasons:
As you can see, we rely on URLClassLoader to do all the heavy lifting. Our version isolation support is in loadClass(), where a test is made of the requested class name to see if it is in one of the packages to be isolated (here, we test whether the class is a javac or bridge class). If it is an isolated class, URLClassLoader.findLoadedClass() and findClass() look it up in the jar file we supplied in the constructor; otherwise we let URLClassLoader.loadClass() delegate to the parent classloader.
Now, we need to interact with classes loaded by this classloader. What works best for us is to define a simple interface which the versioned classes and their client code shares, and a factory class that uses reflection to load the class which implements that interface (in 1.0, you needed a default constructor and used Class.newInstance()). Here's a simplified example (from the same Factory class):
In the above, we fetch the ASTErrorChecker constructor via reflection, then use it whenever the client requests a new ErrorChecker implementation. Because the interface doesn't directly or indirectly reference any class types in our private javac copy (CompilerException is also shared), objects created using its classes can interact with the client without conflict.
There is one thing to watch for (there always is), however: sometimes you can find yourself pondering the impossible, like I did yesterday: ![]() What caught me off-guard is that the debugger shows the type of "ex" is EmptyScriptException, but if it were that type then it should have been caught by previous catch block. Worse, a "(ex instanceof EmptyScriptException)" watchpoint returns "false", when it "obviously" should be true. The issue is that a class isn't just defined by its bytecode (the classfile's contents), but by the combination of bytecode and classloader. Here, there were two copies of EmptyScriptException loaded: once by Jackpot's private classloader, and once by the NetBeans one. Instances of one class copy will fail instanceof and catch tests with the other. I frequently forget this subtlety until reminded by a few head bangs against my monitor. The fix is to add the class to your list of classes which your classloader ignores and therefore shares with its parent classloader. Over time, I have learned the value of this behavior (the classes not mixing, not the head banging). Since I'm pretty lazy, the extra work required to share classes between classloaders means that my designs do as little class sharing as possible. It is easier to maintain a really strict isolation with only a few, simple interfaces, than it is to maintain a big list of shared classes and deal with the headaches of managing their dependencies. A nice bonus is that this sort of isolation lends itself to distributed and parallel designs, where the more lightly coupled remote objects are to each other, the better they work together. Besides, it's hard to convince your manager you need the latest fire-breathing multi-processor workstation if your design is hopelessly interlocked. This blog entry is way too long. I hope however that it dispells the idea that writing a classloader is rocket-science or limited to a few obscure uses. Managing application versioning is a problem many application teams face, and some judicious classloading can make it much easier. No Regrets with UndoPosted by tball on December 17, 2004 at 11:47 AM | Permalink | Comments (8)One of the traps to avoid when aging is regret; the more one accomplishes in life, the harder it can be not to regret some of your decisions along the way. Some days it seems life would be perfect if only we had the ability to "hit undo" on those mistakes. Life doesn't have such an undo facility, but happily we can easily code them into our applications. "Easily?", most developers might ask, cringing at the thought of an "undo" product requirement especially if it has to be added to an existing, working project. Traditionally it's been considered very difficult to determine the correct undo points and to test that the facility works correctly. But with a good design, a robust undo facility can indeed be easy to implement. Lately I've been focusing on isolating model and controller code in my application to simplify its design and make it more testable. One key to successful unit testing is having tests that run quickly, so you aren't inclined to skip running them in a crunch (when you need them the most). Independent data models can be tested very quickly since very few secondary classes need to be loaded for the JVM to execute each test. Now, Swing has a powerful, fully-featured undo facility, but it is difficult to use in a way that doesn't cause lots of other Swing classes to be loaded (here are the Swing classes from the UndoManager's transitive closure, generated with this tool). While working on Jackpot, we used a much simpler undo facility that James Gosling wrote, which has been improved and incorporated into Huckster's Undo class. This class defines a single Undo class, to which custom Entry instances are added which implement the undo and redo methods. Since an undo command from the user's perspective can consist of several associated state changes to the model, you define the start of each action by calling Undo's newCommand method. The application's undo and redo commands invoke Undo.undo() and redo(), not specific entries. That's all there is to his framework.
To use this facility in your application, find each place where your model is changed. Ideally, you have already refactored your application so that all of these mutations are in small, separate methods (ideally in the model class itself); if not, do this refactoring first as it will help your design regardless of whether it supports undo or not. Here is a simple example:
public void setLocation(int x, int y) {
currentX = x;
currentY = y;
}
Next, define a singleton Undo instance for your application (we'll call it "undo"). Then for each mutating method, add a new Undo.Entry type to undo, moving the method's current code into the Entry's redo method:
public void setLocation(final int x, final int y) {
undo.add(new Undo.Entry() {
public void undo() {
}
public void redo() {
currentX = x;
currentY = y;
}
});
}
The undo command still doesn't work, but all existing unit tests should pass, since adding an Undo.Entry to the Undo instance invokes the entry's redo method. Next, figure out the state you are changing and define a n instance variable for each, initializing it to the current value before the change:
public void setLocation(final int x, final int y) {
private final int oldX = currentX;
private final int oldY = currentY;
undo.add(new Undo.Entry() {
public void undo() {
}
public void redo() {
currentX = x;
currentY = y;
}
});
}
Finally, determine how to undo the redo method's code. If you have refactored to a fine granularity (important for maintainability and testability), it should be fairly obvious:
public void setLocation(final int x, final int y) {
private final int oldX = currentX;
private final int oldY = currentY;
undo.add(new Undo.Entry() {
public void undo() {
currentX = oldX;
currentY = oldY;
}
public void redo() {
currentX = x;
currentY = y;
}
});
}
Your code now supports fine-grained undo. To define user-level undo command actions, find where the start of each command is and call undo.startCommand() before the first modification. You can move these startCommand calls around, add or delete them depending on the UI spec and/or what "feels right" during user testing. If you want to flush the undo list (say, when saving a document), just replace the application's Undo instance with a new one.
Testing an undoable model is pretty straightforward: you need deep-copy and equality methods. Some classes already have these, such as the collections classes, but for custom data structures you'll probably have to write them yourself. The good news is that these methods increase the value of your model as a separate data type, rather than just being a testing chore. I added duplicate and equals (with hashCode, of course!) to an abstract syntax tree model for test purposes, and later found them useful for productive code enhancements.
Now if anyone has a way I can undo my own past, please let me know. There are several old JDK warts I would love to erase! 'Tis a Gift to be SimplePosted by tball on November 20, 2004 at 09:49 PM | Permalink | Comments (7)'Tis a gift to be simple, 'tis a gift to be free,
| ||
|
|