Monday, October 31, 2005
Don't worry. I'm not wasting any time manually polling your blog for updates. If you have something to say and you post it, great. My aggregator will let me know. If you go a couple weeks without posting because you're too busy or you just don't have anything interesting to say, that's OK, too. I'm not going anywhere. But please don't post an apology for not posting. That does take up my time, and it gets my hopes up for no reason. Do that too many times and I will go away. ;)
Sunday, October 30, 2005
Closure, But No Cigar
To my knowledge, closures aren't even on the drawing board yet let alone scheduled for a particular JDK release, so I thought I'd see what we can do in the mean time (apart from switching to Ruby ). Don't get your hopes up though. The results are interesting but far from ready for prime time.
First, let's start with a pre-closure example. Our program iterates over a series of numbers and invokes a
Runnable
to add them up (we could obviously implement this inline, but let's keep the example simple):static int sum; public static void main(String[] args) { for (int i = 0; i < 10; i++) { final int it = i; Runnable r = new Runnable() { public void run() { sum += it; } }; r.run(); } System.out.println(sum); }For starters, I'd like to get rid of the
run
method declaration. The Runnable
constitutes five lines of code of which one is important; that's 80% clutter.
My new class Closure
[ab]uses instance initializers, a lesser known language feature, as a substitute for the run
method. To execute the closure, I simply instantiate its class behind the scenes. This approach eliminates some boilerplate and even a couple lines of code:static int sum; static class Adder extends Closure<Integer> {{ sum += it; }} public static void main(String[] args) { for (int i = 0; i < 10; i++) { Closure.execute(Adder.class, i); } System.out.println(sum); }Unfortunately, you have to implement the closure using a static nested class which means you must explicity pass in all state via the closure argument
it
. If you create another nested class so you can pass in multiple arguments, you undo what little good this has done.
Ideally, we'd use an inner class instead. Inner classes can appear inline in methods and see final local variables. There's no reason we couldn't instantiate an inner class as we do the static nested class, however, short of bytecode manipulation, I know of no way to get the method's local variables and the outer instance to pass to the inner class's constructor.
Even if we could somehow scale this inner class wall, I don't feel the end justifies the means: one or more object creations, some thread local lookups, and unorthodox, most likely confusing syntax. That said, I give you Closure
:package org.crazybob.closure; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; public abstract class Closure<T> { protected T it; private static ThreadLocal<List> tlStack = new ThreadLocal<List>() { protected List initialValue() { return new ArrayList(); } }; protected Closure() { List stack = tlStack.get(); this.it = (T) stack.get(stack.size() - 1); } /** * Executes a closure with the given argument. */ public static <T> void execute( Class<? extends Closure<T>> closure, T it) { List stack = tlStack.get(); try { stack.add(it); execute(closure); } finally { stack.remove(stack.size() - 1); } } private static <T> void execute( Class<? extends Closure<T>> block) { try { Constructor c = block.getDeclaredConstructor(); c.setAccessible(true); c.newInstance(); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } } }
Generating Sequence Diagrams Using AspectJ
Over the past couple weeks a few people have inquired about a demo I wrote a few years ago named jAdvise SEQUENCE. It combined my old AOP framework with SEQUENCE, Alex Moffat's sequence diagram generator. In the demo, a simple method interceptor generated a SEQUENCE data file. The idea is you need runtime information to generate complete sequence diagrams. Rather than perpetuate retired code, I've implemented a more robust version using AspectJ.
Pardon me as I go out on a limb here and risk confusing you by using two AOP frameworks at once. As an example, we'll generate a sequence diagram for a Dynaop mixin method invocation. The following code mixes a Bar implementation into Foo and invokes a couple methods on the resulting object. We'll generate a sequence diagram of the last invocation.
If Alex adds support for notes or diagram annotations to SEQUENCE, this could put an interesting twist on log file analysis.
I used Dynaop 1.0 beta for this example. It will be interesting to compare the before and after diagrams when I release the upcoming performance enhancements and refactorings.
import dynaop.*; public class MixinExample { public abstract static class Foo implements Bar { public void foo() {} } public interface Bar { void bar(); void tee(); } public static class BarMixin implements Bar { public void bar() {} public void tee() {} } public static void main(String[] args) { // configure proxy factory. Aspects a = new Aspects(); a.mixin( Pointcuts.singleton(Foo.class), BarMixin.class, null ); ProxyFactory pf = ProxyFactory.getInstance(a); // create an instance of Foo that // delegates to BarMixin. Foo foo = (Foo) pf.extend(Foo.class); foo.foo(); foo.bar(); foo.tee(); Sequence.start(); foo.tee(); Sequence.stop(); } }The Sequence aspect hooks every method and dumps the SEQUENCE diagram data to standard out. Simply capture the output and open it in SEQUENCE.
import org.aspectj.lang.*; import dynaop.*; public aspect Sequence { static boolean running; pointcut traceMethods() : (execution(* *.*(..)) || execution(*.new(..))) && !execution(* *.*$*(..)) && !within(Sequence) && if(running) // omit problematic inner classes. && !within(Pointcuts.*) && !within(ProxyFactory.*) && !within(ClassProxyCreator.*); before() : traceMethods() { Signature s = thisJoinPointStaticPart.getSignature(); String methodName = s.getName(); // use "new" for constructors. if (methodName.equals("To include or exclude classes and methods from the diagram, tweak the pointcut accordingly. Our example generates this diagram:")) methodName = "new"; System.out.println( s.getDeclaringType().getSimpleName() + "." + methodName + " {"); } after() : traceMethods() { System.out.println("}"); } public static void start() { running = true; } public static void stop() { running = false; } }
Saturday, October 29, 2005
I Heart Getters and Setters
Keith Lea: Java has had the concept of beans and getters and setters for a long time. I thought we had all agreed that they work and clearly show intent. I felt annoyed when I saw the java.util.regex package appear in Java 1.4 with methods like matcher(CharSequence) and group(int), when they should have started respectively with create and get. I didn’t like so much the new Java 5 util.concurrent methods Executors.callable(Runnable), which should be prefixed with get or create.I know this sounds silly, but I can't convey how much dropping the get seriously upsets me. I understand the bean convention came second (look at String.length() for example), but
getXxx()
(as opposed to xxx()
) now constitutes the vast majority of cases with good reason. The lack of consistency throws everything off. Method names should be verb phrases. In a world with ubiquitous auto-completion, prefixing method names names with get helps users find methods; it doesn't result in extra typing. Dropping the get actually causes more work for me because I always look for a getter first only to realize the author subscribed to this atrocious convention. I also can't say I've ever seen a case where a get meant the difference between wrapping or not wrapping a line.
I've discovered many of the offending API designers spend most of their time in lower level Java libraries safely hidden from messy problems like automating UI design, handling HTTP requests, and mapping objects to relational databases and XML. The bean convention doesn't help them. They aren't aware that it enables frameworks which greatly simplify users' lives. Is it an ideal solution? No, but therein lies the nature of the domain.
Java's lack of syntactic support for properties does not justify these egregious hacks. We have a real problem, but we shouldn't try to contort method calls to look like property accesses; it doesn't work. Let's take a page out of the BeanShell book and solve the problem by overloading field operators. Which do you think looks more readable?
Foo f = bar.foo(); bar.foo(f);or
Foo f = bar.foo; bar.foo = f;For the latter case, in the absence of a visible field
bar.foo
, the compiler generates:
Foo f = bar.getFoo(); bar.setFoo(f);Who exposes fields anyway? Nobody. Simple, eh? Some may scream, "operator overloading," but truthfully special cases like this are perfectly safe, backward compatible and in the Java spirit.
Thursday, October 27, 2005
PowerBook Battery Pack
A replacement 58 WH (watt-hour) 17" PowerBook battery costs $129 or $2.22/WH. The Battery Geeks have a 122 WH external battery pack for $189.99 or $1.55/WH. That's more than 30% cheaper. It should last twice as long as the internal battery which means fewer interruptions. You can charge it and your internal battery at the same time whereas you can only charge the internal battery in your laptop (very annoying). I may have to order one before my next long trip. Does anyone have any experience with these?
Tuesday, October 25, 2005
Those were the days...
Wednesday, October 12, 2005
It's Finally Here!
10 months ago I originally predicted Apple would come out with a computer that would hook up to your entertainment center and enable you to download video. At the time Apple released the computer but left out the remote, software and video. Better late than never I guess. Say, "goodbye," to your DVR!
Thursday, October 06, 2005
The Servlet Spec. Continues to Amaze
I used a request dispatcher to include another servlet today and for the life of me couldn't figure out why it wouldn't dispatch to the correct place. Then I noticed that the request object in the included servlet returned the path for the original servlet, not the included servlet. What the heck? At first I thought the container implementor made a mistake, but then I checked the servlet spec. According to the spec., included servlets can get their path (servlet path, path info, etc.) from request attributes. For example, if you're included, not called directly, you must call HttpServletRequest.getAttribute("...") instead of HttpServletRequest.getServletPath(). Your servlet has to know whether or not it's included and look up the path accordingly. Whose bright ideas was that? This breaks servlets such as Struts and WebWork that dispatch to actions based on the path. getServletPath() should return the current servlet's path. If you need the path of the servlet that included you, the servlet API should provide a chain of requests.
I worked around the issue by wrapping the request object I pass to the dispatcher and overridding getServletPath(), getPathInfo(), etc., to return the values for the included servlet instead of the including servlet. Thankfully WebWork insulates me from this insanity most of the time.
Tuesday, October 04, 2005
Upgrade Makes Writeboard More Useless
An anonymous commenter pointed out that Writeboard has added locking. Now when you try to edit a document at the same time as someone else, Writeboard presents you with this:
I was personally hoping for a user friendly Ajax three-way merge.
Monday, October 03, 2005
Writeboard and Jotspot Live: Two Thumbs Down
Writeboard debuted today. I had high hopes after the buzz in the blogosphere, but unfortunately I'm sorely disappointed.
One selling point was that you don't actually have to sign up. You might as well have to; they ask for a writeboard name, a password, an email address, and an agreement to their terms.
Why can't I just go to writeboard.com and get an editor immediately? That's what I thought they were promising. It should keep track of my writeboards using a cookie (so I don't lose them when I accidentally close the window).
If I want to invite someone else or move to another browser or computer, I should be able to type email addresses into a field in the side bar. Right now I have to click a button, then type in the email address, then click another button. Invitees should get a URL which will take them directly to the writeboard. I don't think having them type a separate password is necessary so long as the URLs are difficult enough to guess. I'm probably going to email them the password anyway.
Second, why isn't this live? Isn't this the time of Ajax? Forget live editing. When two users make changes at the same time, it doesn't tell you! You see both changes in the sidebar, but the latter change replaces the first, no merging. How hard is diff?
How is Writeboard different from a wiki? At least a wiki makes it clear when there's a conflict, let's you link and format easily, etc.
Next, I tried Jotspot Live. It's basically the same thing as Writeboard except it supposedly enables live editing a la SubEthaEdit. The verdict: Jotspot Live is nothing like SubEthaEdit. It breaks down the changes into visible, paragraph-granular chunks. I want to collaborate on a single cohesive document.
Maybe Ajax can't cut it here. Thankfully we have Java applets and Flash. Could someone please use a technology because it's right for the job, not because it's the latest fad?
Sunday, October 02, 2005
PowerBook Performance
Matt's comparably equipped Dell trounced his PowerBook in a real world performance test:
- PowerBook: 58.3 seconds
- Latitude: 17.3 seconds
Private Feeds
Many aggregators don't handle password-protected feeds well: some don't support it at all, and some do support it (either fully or with the user ID and password in the URL) but aren't very secure. What if you used hard to guess feed URLs? For example:
http://myhost/feeds/[big cryptographically unique ID]It works with any reader. If it leaks out, others won't be able to access your account (they don't have your real password). On the down side, if you subscribed to this feed in something like Bloglines, wouldn't Bloglines index it so other users could search it? Of course Bloglines supports embedding the user ID and password in the URL. Does Bloglines index these feeds?
The Truth About OPML
Charles beat me to the punch: What is the difference between accepting OPML, and accepting arbitrary XML documents of unknown formats?Has Dave been playing a drawn out April Fools joke? "Hey, wouldn't it be funny if we repeated all of RSS's mistakes but left out the good parts this time?" I don't get. When I implemented a simple aggregator, RSS support gave me a headache, but OPML made me want to chew my leg off. The resulting code was a mess and I had no confidence in its compatibility. How could I? Is this what you were shooting for, Dave? RSS succeeded despite it's flaws and lack of specification, not because of it. I really wish Atom had been around a little sooner. Maybe there's still time.
Saturday, October 01, 2005
Stack Allocation Coming to Java
Brian Goetz: JVMs are surprisingly good at figuring out things that we used to assume only the developer could know. By letting the JVM choose between stack allocation and heap allocation on a case-by-case basis, we can get the performance benefits of stack allocation without making the programmer agonize over whether to allocate on the stack or on the heap.Maybe I'll hold off on jumping ship to C#. ;)
Tracking Referrers with Blogger
I just installed Refer from Textism. I hooked it into my blog by adding a PHP include to my Blogger template (I can do this because I configured Blogger to upload to my own server). My favorite feature: RSS feeds of your referrers. How cool is that?
Components: JSF & Tapestry vs. WebWork
Every time someone tries to tell me JSF and Tapestry are superior to WebWork, they claim JSF and Tapestry have one advantage: they're "component oriented." What does that mean anyway? Mike's presentation (PPT) from two years ago (when WebWork 2 was still in beta) cites "componentization" and "reuse" as two of WebWork's goals. Does a framework need superfluous XML and boilerplate code to qualify as "component oriented?" I would say emphatically, "no." Maybe the JSF camp counts writing more code to enable tools as a virtue? I'd just assume do without the tools and the extra code. From my experience, WebWork supports "components" quite elegantly with minimal coding. I used to think I was missing something. Now I'm starting to think the JSF and Tapestry proponents are the ones that don't get it. Which is it?
FeedBurning
FeedBurner rocks. I should have tried this sooner. FeedBurner works by decorating your XML feed: point FeedBurner at your feed and your subscribers at FeedBurner's feed. If you have existing subscribers, you can HTTP redirect your old feed URL to the new FeedBurner feed.
- The FeedBurner feed is web browser-friendly. Check out my feed to see what I'm talking about. It even has links to help users find a reader or subscribe with their existing readers.
- FeedBurner extrapolates your hits to get what appears to be a very accurate reader count. For example, Bloglines only hits your feed once, but FeedBurner is smart enough to ask Bloglines how many users have subscribed to your feed.
- FeedBurner has a feed for your feed's stats! You get updates on your feed in your own reader. Update: After further investigation, this may just be feed health notifications. We'll see.
- FeedBurner can redirect your links through their site and track clickthroughs.
What do you do if your host doesn't support Mod Rewrite?
Use a custom 404 error page! My host GoDaddy doesn't support Mod Rewrite, but they do support custom 404 pages, so when I wanted to redirect my site feed, I wrote the following PHP script to handle 404 page not found responses:
<? // get the requested URI. $uri = $_SERVER['REQUEST_URI']; // if the URI starts with my old feed path... if (strpos($uri, "/roller/rss/crazybob") === 0) { // redirect to the FeedBurner feed. header("Location: http://feeds.feedburner.com/crazybob/"); exit; } // redirect to the home page. header("Location: /"); ?>When the user requests
/roller/rss/crazybob
, this script redirects them to my FeedBurner feed. For any other missing path, the script redirects them to my home page. You could easily build up a robust rewriting facility around this.
Update: I fixed some escaping on the code example and rewrote this entry to make it clearer.
Blogger Comments
How did I miss this? Blogger now supports popup windows for comments and CAPTCHAs to prevent comment spam. The popup window look 100 times more consistent with my blog.
I really miss referral tracking and hit counting (both of which I had in Roller). I use GoDaddy's cheap PHP hosting. Is there anything I can do?