Tuesday, September 27, 2005

XP cards in real life

Charles Miller has a great shot of XP-style cards in use, tacked up on the Wall of Death. As he describes:

When, as we do, you write your specs on a wiki, keep track of your development tasks in an issue tracker, and eventually ship products that only exist as bits downloaded from a server, it’s very easy to forget how much psychological impact physical objects can have.

Data exists in some ether that your mind never really has to deal with completely. Physical things have presence. They take up space. They can arrest your vision. When you move them they stay moved. You can fold, spindle and mutilate them as you wish.

Monday, September 19, 2005

Building JDK 5 projects with Maven 2

I had a difficult time working out from the Apache Maven 2 site documentation how to build my JDK 5 projects with m2. I found the spot in the documentation for configuring plugins and reasoned that the Java compilation goal must be connected to a plugin, but did not see exactly how to tie it all together.

After several futile Google attempts, I finally hit upon trying pom.xml source target 1.5 and found this JIRA issue covering an example and including an attached pom.xml. Using the example, a basic POM (what was previously project.xml with Maven 1) is:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Maven Quick Start Archetype</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

I got this by running the suggested new project generator:

m2 archetype:create -DgroupId=com.mycompany.app -DartifactId=my-app

And adding the needed <build/> section.

Serendipity

I've been looking a lot at continuations in recent posts. Along came this post on Jetty 6 continuations and the serendipity is enough to make me shiver. Are continuations finally making it into the mainstream?

I had the opportunity recently to talk with someone new to continuations about their clever usefulness. This Jetty 6 article is just the sort of example I wish I had had in my back pocket for that chat.

UPDATE: An interesting observation about Jetty's use of threads and continuations.

Thursday, September 15, 2005

Template legerdermain

Following up on yesterday's post, I worked out how to capture the return value for a continuation, that is, a future computation.

(Aside: This raised a question in my mind: what should be the behavior of asking for the result of a computation which has not yet taken place? In my prototype, I return a default constructed value (e.g., 0 for an int). Another option might be to raise an appropriate exception as it is likely a programming error. Yet another is to provide a sentinal value or a predicate to ask the state of the computation. That last choice is interesting: if the continuation changes to a different context before finishing, the computation could be half-completed or even discarded. Questions for another day.)

The new sample main is:

#include <cstdlib>
#include <iostream>

#include "continuations.h"

static int
increment (int id, int *n)
{
  int m = *n;
  std::cout << id << " thinks: " << *n << std::endl;
  ++*n;
  return m;
}

int
main (void)
{
  int initial_value = 0;

  continuation<void> main_uc;
  // TODO: Why not deduce from the return type of increment?
  continuation<int> inc0_uc (main_uc, increment, 1, &initial_value);
  continuation<int> inc1_uc (inc0_uc, increment, 2, &initial_value);

  std::cout << "1 said: " << static_cast<int> (inc0_uc) << std::endl;
  std::cout << "2 said: " << static_cast<int> (inc1_uc) << std::endl;

  main_uc.switch_to (inc1_uc);

  std::cout << "All done: " << initial_value << std::endl;
  std::cout << "1 says: " << static_cast<int> (inc0_uc) << std::endl;
  std::cout << "2 says: " << static_cast<int> (inc1_uc) << std::endl;

  return EXIT_SUCCESS;
}

The header is somewhat hairy, however:

#ifndef _CONTINUATION_H
#define _CONTINUATION_H

#include <stdexcept>

#include <errno.h>
#include <string.h>
#include <ucontext.h>

#define STACK_SIZE 4096

template <typename R> class continuation;

class continuation_base : public ucontext_t {
  char stack[STACK_SIZE];

protected:
  continuation_base () {
    initialize ();
  }

  template<typename R1>
  continuation_base (continuation<R1> &next) {
    initialize ();
    uc_link = &next;
  }

public:
  template<typename R1>
  int switch_to (continuation<R1> &next) {
    return swapcontext (this, &next);
  }

private:
  void initialize () {
    if (getcontext (this) < 0)
      throw std::runtime_error (strerror (errno));

    uc_stack.ss_sp = stack;
    uc_stack.ss_size = sizeof stack;
  }
};

template<typename R>
void apply (R (*lambda) (), R *r) {
  *r = lambda ();
}

template<typename R, typename T0>
void apply (R (*lambda) (T0), R *r, T0 t0) {
  *r = lambda (t0);
}

template<typename R, typename T0, typename T1>
void apply (R (*lambda) (T0, T1), R *r, T0 t0, T1 t1) {
  *r = lambda (t0, t1);
}

template<typename R>
class continuation : public continuation_base {
  R value;

public:
  continuation () { }

  template<typename R1>
  continuation (continuation<R1> &next, R (*lambda) ())
    : continuation_base (next) {
    void (*fp) (R (*) (), R *) = apply<R>;
    makecontext (this,
                 reinterpret_cast<void (*) (void)> (fp),
                 2, lambda, &value);
  }

  template<typename R1, typename T0>
  continuation (continuation<R1> &next, R (*lambda) (T0), T0 t0)
    : continuation_base (next) {
    void (*fp) (R (*) (T0), R *, T0) = apply<R, T0>;
    makecontext (this,
                 reinterpret_cast<void (*) (void)> (fp),
                 3, lambda, &value, t0);
  }

  template<typename R1, typename T0, typename T1>
  continuation (continuation<R1> &next, R (*lambda) (T0, T1),
                T0 t0, T1 t1)
    : continuation_base (next) {
    void (*fp) (R (*) (T0, T1), R *, T0, T1) = apply<R, T0, T1>;
    makecontext (this,
                 reinterpret_cast<void (*) (void)> (fp),
                 4, lambda, &value, t0, t1);
  }

  operator R () { return value; }
};

template<>
class continuation<void> : public continuation_base
{
public:
  continuation () { }

  template<typename R1>
  continuation (continuation<R1> &next) : continuation_base (next) { }
};

#endif /* _CONTINUATION_H */

Two techniques of note are the specialization for void return and moving non-return value-related code into a shared base class—a common trick when parameterizing return types—, and the use of apply handle the return value from lambda in the continuation.

makecontext takes a void (*) (void) function pointer for the program text of the ucontext_t, which requires a cast of apply. I found that simply:

makecontext (this,
             reinterpret_cast<void (*) (void)> (&apply<int, int, int *>),
             4, lambda, &value, t0, t1);

would not compile with GCC 3.3.5. This is an area of C++ where my fu is insufficient to know at sight if my code is legal—although I presume it is. So I resorted to local variable, fp, for the cast and suddenly GCC was satisfied (see above).

So the short of it is that now I can interrogate my continuation for its computed value.

Wednesday, September 14, 2005

Continuations in C++, a starting point

Starting from the example in the GNU C Library info pages on System V contexts, I cooked up a starting point for continuations in C++. Usage is simple:

#include <stdio.h>

#include "continuation.h"

static void
increment (int *n)
{
  printf ("Nothing interesting: %d\n", *n);
  ++*n;
}

int
main (void)
{
  int initial_value = 0;

  continuation main_uc;
  continuation inc0_uc (main_uc, increment, &initial_value);
  continuation inc1_uc (inc0_uc, increment, &initial_value);

  main_uc.switch_to (inc1_uc);

  printf ("I'm back: %d\n", initial_value);

  return 0;
}

Program output is as expected:

Nothing interesting: 0
Nothing interesting: 1
I'm back: 2

That is, the first frame runs increment; the second frame does the same; and the last frame runs main from where it left off after jumping to the first frame: a continuation.

The header:

#ifndef _CONTINUATION_H
#define _CONTINUATION_H

#include <stdexcept>

#include <errno.h>
#include <string.h>
#include <ucontext.h>

// Tricky to work out without cpu-os-specific information:
#define STACK_SIZE 4096

class continuation : public ucontext_t
{
  char stack[STACK_SIZE];

public:
  continuation () {
    initialize ();
  }

  continuation (continuation &next) {
    initialize ();
    uc_link = &next;
  }

  continuation (continuation &next, void (*lambda) (void)) {
    initialize ();
    uc_link = &next;
    set_lambda (lambda);
  }

  template<typename T0>
  continuation (continuation &next, void (*lambda) (T0), T0 t0) {
    initialize ();
    uc_link = &next;
    set_lambda (lambda, t0);
  }

  int switch_to (continuation &next) {
    return swapcontext (this, &next);
  }

private:
  void initialize () {
    if (getcontext (this) < 0)
      throw std::runtime_error (strerror (errno));

    uc_stack.ss_sp = stack;
    uc_stack.ss_size = sizeof stack;
  }

  void set_lambda (void (*lambda) ()) {
    makecontext (this, lambda, 0);
  }

  template<typename T0>
  void set_lambda (void (*lambda) (T0), T0 t0) {
    makecontext (this, (void (*) (void)) (lambda), 1, t0);
  }
};

#endif /* _CONTINUATION_H */

Tuesday, September 13, 2005

My kind of technical writer

Sam Ruby writes about continuations for old-time programmers like me. Anyone who starts of a technical article with:

This essay is for people who, in web years, are older than dirt.

and after the first example observes:

If you now look around, you will notice that all the tadpoles that never really programmed in C have jumped out of the pond.

is my kind of author. And I love that he drifts from language to language for his examples. Great article. Good ole' call/cc.

Sunday, September 11, 2005

Agile abuse

Alistair Cockburn posts an interesting link to the XP Yahoo! Group entitled Iterations harmful?. His synopsis:

I finally got around to writing up my recent experiences in seeing how people abuse iterations, user stories and velocity to hide the ways they are damaging their projects.

This promptly led to a rather length chain of posts wherein many good suggestions were offered and incorporated. Overall quite a nice article on agile practises and how to make practical use of them.

Pointing fingers

There is a series of defects in IntelliJ 5.0 on Linux that go like this:

I can not open 'Appearance' settings in IDE Settings (File -> Settings -> Appearance).
The main problem is that it is system-specific bug. On other PCs everything works fine.
No appearance settings window opens and exception listed below appears in log:

2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - Error during dispatching of java.awt.event.MouseEvent[MOUSE_RELEASED,(194,244),button=1,modifiers=Button1,clickCount=1] on dialog9
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - IntelliJ IDEA 5.0 Build #3436
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - JDK: 1.5.0_04
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - VM: Java HotSpot(TM) Client VM
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - Vendor: Sun Microsystems Inc.
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - OS: Windows XP
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - Last Action: ShowSettings
2005-08-03 13:39:45,250 [ 733235] ERROR - com.intellij.ide.IdeEventQueue - Error during dispatching of java.awt.event.MouseEvent[MOUSE_RELEASED,(194,244),button=1,modifiers=Button1,clickCount=1] on dialog9
java.lang.ArrayIndexOutOfBoundsException: -2147483648
at sun.font.CMap$CMapFormat4.getGlyph(CMap.java:540)

The defect status is a discouraging Won't Fix. The developer comment is:

This is identified Sun JRE issue: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6247425

Ok, what does that SUN bug say?

Bug ID: 6247425
Votes 0
Synopsis Unhandled OutOfBounds exception while parsing TTF files
Category java:classes_2d
Reported Against b28
Release Fixed mustang(b33)
State Closed, fixed
Related Bugs
Submit Date 29-MAR-2005
Description
For some TTF files with CMAP format 4 i am seeing following exception:

java.lang.ArrayIndexOutOfBoundsException: -2147483648
        at sun.font.CMap$CMapFormat4.getGlyph(CMap.java:547)
        at sun.font.TrueTypeGlyphMapper.charToGlyph(TrueTypeGlyphMapper.java:115)
        at sun.font.CharToGlyphMapper.canDisplay(CharToGlyphMapper.java:40)
        at sun.font.Font2D.canDisplay(Font2D.java:426)
        at java.awt.Font.canDisplay(Font.java:1780)

 xxxxx@xxxxx  2005-03-29 16:25:48 GMT
Work Around
N/A
Evaluation
Examining fonts with ttfdump it seems they are malformed - 
entrySelector=1024 and searchRange=8192 while segCount=22
(we read same numbers in CMap.java).

This is weird because according to spec these numbers are derived from 
segCount. Proper values for table like this are 4 and 32 - so it looks like byteorder is wrong.

We may either reject such fonts as malformed or simply ignore them and
derive appropriate values ourselves. (windows shows these fonts with 
font viewer)

In any case parsing CMAP we should catch all potential exceptions.

 xxxxx@xxxxx  2005-03-29 16:25:48 GMT

So, apparently, I have malformed fonts installed on my Linux box. But notice SUN's status: Closed, fixed, albeit in a future release.

IntelliJ's status of Won't fix is just stupid. Just surround the exception-throwing code with a handler and warn the user about broken fonts. This is infinitely better than crashing. And for bonus points, backport the JDK 6 fix to a replacement class so that IDEA can handle the broken fonts. What's so hard about that?

UPDATE: I just tried out Mustang b51 (JDK 1.6 beta) to see if the bug was indeed fixed. Again IDEA crashes. At least there is console output:

binkley@hermes:~/opt/idea/bin>java: ../../../src/share/native/sun/awt/font/t2k/t1.c:2178: tsi_T1GetGlyphIndexFromAdobeCode: Assertion `0' failed.

Time to go update the Closed, fixed SUN bug.

UPDATE: That was quick! Phil, the nice man from SUN, mailed me later after I posted the new bug:

6247425 was a bug in a TrueType font.
The message here is a bug in a Type1 font.
They are unrelated except that it sounds like you hit the first
bug before you could reach the second bug and now you can reach
the second bug.
I recall that SuSE 9.2 Pro has a *LOT* of fonts of variable quality.
We looked at those fonts - and there were a few - that caused a crash
in the same area as this - it was bug 6229389 ixed it in mustang b33.

I especially appreciate that he provided me means further in the mail to track down the bad font. Thank you, Phil!

Saturday, September 10, 2005

This is real programming

Real men port; wimps reinvent the wheel.

At work we have an extensive JNI layer for our Java applications to work with a series of cooperating Windows/Linux processes which implement our own file system and support services (remote printing, user/group management, etc). Sadly, we do not use Cygwin and are saddled with extensive #ifdef/#else/#endif code based on platform. (It also does not help that Windows was our first target platform.)

elliotth's outstanding Terminator program using Java/JNI/Cygwin would have been a good model for us in many respects had it been known earlier. It's a well thought out design and a beautiful application.

Friday, September 09, 2005

Off topic but not off key

Stephen Wolfram presents a new kind of programming, WolframTones. This would have been a cool way to cheat back when I was a composition major.

Thursday, September 08, 2005

Optimizing for scripting languages

Phlip on the The XP Yahoo! group writes a nice bit about Ruby v. Python (and along the way Perl). He references AlternateHardAndSoftLayers from the fabled C2 wiki in making a nice point about optimizing for scripting languages:

Dynamic languages and unit tests permit you to blaze through features and refactors. Without the requirement to maintain inheritance graphs just to substitute objects, and with deep support for containing objects and translating their types, these languages stay out of your way.

Command-and-control code does not require performance. Low-level inner loops do, so practitioners typically port to a C language as soon as parts slow down. This pattern (AlternateHardAndSoftLayers) allows Ruby and Python to ship with very powerful libraries, bundled with their default distributions.

This is excellent advice and another indication that the best programmers are also some of the most well-rounded. Being the world's greatest expert on FOO at the expense of knowing little else is not that useful outside of one-language/one-vendor closed shops.

Programmatically compiling Java in JDK 6

Build 47 (August 12th) of JDK 6 (Mustang) includes this improvement:

Category ID Synopsis
java:compiler 
  4164450 JSR 199: Standard interface for Java compilers

(Offhand, why is SUN's HTML for this page so bad? The source HTML is not even valid, let alone tasteful.)

The link is to this bug report; its heart:

This feature is for a standard interface that is available as a standard extension to Java. That way you have a richer way to access the compiler (if one is available) without forking a new process or relying on undocumented features. You also make it easy for the user to install a different compiler without breaking the tools that rely on it.

The Yahoo! group to discuss the feature makes it clear that this feature was originally intended for JDK 5 (Tiger) but pushed back to JDK 6 (Mustang), and that is as far as the group got. However, JDK 5 did expose com.sun.tools.javac.Main. However, the bug report says that JDK 6 addresses:

  • Programmatic API to invoke the compiler
  • API to access structured diagnostic information
  • API to override how the compiler reads and writes source and class files.

Progress!

The JDK 6 API docs do not show any obvious signs of this. So of to the sources (NB — this is the JRL download, not the SCSL). Comparing the two sources for com.sun.tools.javac.Main shows interesting changes. After building the javadocs for just this class, I can read some eye-catching improvements; the opening lines:

The programmatic interface for the Java Programming Language compiler, javac.

Except for the two methods compile(java.lang.String[]) compile(java.lang.String[],java.io.PrintWriter), nothing described in this source file is part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are subject to change or deletion without notice.

Some heavy caveats, but very promising! The documentation for main(java.lang.String[]) is amusing:

Unsupported command line interface.

The full method javadocs for this class are (after cleaning javadocs' peculiar HTML):

main

public static void main(java.lang.String[] args)
                 throws java.lang.Exception

Unsupported command line interface.

Parameters:
args - The command line parameters.
Throws:
java.lang.Exception

compile

public static int compile(java.lang.String[] args)

Programmatic interface to the Java Programming Language compiler, javac.

Parameters:
args - The command line arguments that would normally be passed to the javac program as described in the man page.
Returns:
an integer equivalent to the exit value from invoking javac, see the man page for details.

compile

public static int compile(java.lang.String[] args,
                          java.io.PrintWriter out)

Programmatic interface to the Java Programming Language compiler, javac.

Parameters:
args - The command line arguments that would normally be passed to the javac program as described in the man page.
out - PrintWriter to which the compiler's diagnostic output is directed.
Returns:
an integer equivalent to the exit value from invoking javac, see the man page for details.

Well, it's a start.

That cool FTP hack

As noted in several places, Avi Bryant thought up a very clever hack: having paths in an FTP server actually browse his Smalltalk image.

Cedric make the observation:

The article illustrates the challenge by creating a dynamic FTP server that exposes the internals of a Smalltalk application. Of course, it would be equally interesting to expose a Java application this way and browse the packages, classes and methods. You could also imagine creating a file system (/dev/java ?) that would let you create an object with touch and remove it with rm...

The next step is pretty clear. The FTP server should not just browse Java source, but should compile on upload with the upload failing if the compilation fails. Alternatively, the server would keep track of compiling and non-compiling code so that one could create and edit Java source files, but the system would distinguish between compiling and non-compiling sources. In either case downloads retrieve the original Java source (or are based on file extension).

Lastly, successful compilation of uploads would use class loader magic to affect the running system. One could then work on the live, running FTP server itself with GET, PUT and DELETE. Or, using any of the various FTP mount solutions, do the same work with cp, mv and rm as suggested by Cedric. Now that's a cool hack.

Wednesday, September 07, 2005

A common unit test error

Hani, in his usual unusual style (a warning to sensitive souls), points out a common unit test error:

So after a few days of pointlessly scrolling around, I find the culprit. It's at this point that I completely lose it and start laughing hysterically. I can't quite believe the test I'm looking at.

The test for http state works thusly: It gets at the class that holds the map. It uses reflection to access the (private) map, makes it accessible, and manually puts a value in it. Having done so, it then calls a method which basically just reads from the map, and then compares the two.

It's mind boggling to me that someone would write such a complex and awkward test to basically test that HashMap functions as a....map.

Simple solution: don't do this. There are several technical approaches superior to all the reflection described, but just don't bother to unit test the JDK.

Tuesday, September 06, 2005

JSF and AJAX

A delightful page on mixing JSF and AJAX. Particularly helpful is that the authors present three strategies—not just a simple HOWTO recipe—and a comparison among them. And the article is richly researched, a joy to read.

With the discussion of Sun open sourcing the JSF RI (reference implementation), this article deserves a wider audience.

Refactoring Thumbnails

What a wonderful resource! A set of simple descriptions and straight-forward UML diagrams for common refactorings. The link has been up on the refactoring web site for about a year now, but I somehow I missed it.

Take this example, Hide Implementation With Interface:

client code directly accesses features of an implementation class despite the presence of an interface   restrict the client code its use of features to those provided by the available interface

Monday, September 05, 2005

Drools

Drools, the Java rules engine, has been getting press lately. O'Reilly has a nice pair of articles, one with examples, the other a higher-level view.

A lot of Drools is based on work first done in CLIPS, the LISPy rules engine of the 80s and early 90s. Strangely, I already knew about CLIPS. They are all based around the Rete algorithm, a clever way to trade computation for memory with if-then-else rules systems.

I learned about CLIPS in the late 80s from Brian Donnell (now Dantes), a fine fellow. We were classmates together at in Baker College at Rice University, and in fact he was the very first person to ever explain Object Oriented Programming to me. (As he wrote COOL, the OOP system in CLIPS, this was a topic he enjoyed discussing.) He tried describing it in context of C with Classes and C++; at that time the only language I knew was C. I can thank him and Matt Cohen (who taught me C) for starting my life in programming.

So I am pleased to see systems like Drools and Jess getting some press. And still, LISP lives on.

Friday, September 02, 2005

Fun with Ant macros

Noticing that many Ant file operations were rather slow on large directory trees, I fell back to the tried and true: <exec/>. But I noticed that as I coded up cp, mv and rm, that the calls to <exec/> became rather tedious and repetitious. Looking around a bit, I found just the thing: <macrodef/>! First the starting code using rm as an example:

<echo message="rm -rf ${some.dir}"/>
<exec executable="rm">
    <arg value="-rf"/>
    <arg value="${some.dir}"/>
</exec>

Pretty straight-forward. Just note that <exec/> is silent so I add an <echo/>. This corresponds to the shell command:

$ rm -rf $some_dir

The next step is to turn this into a macro:

<macrodef name="rm">
    <attribute name="src"/>
    <sequential>
        <echo message="rm -rf @{src}"/>
        <exec executable="rm">
            <arg value="-rf"/>
            <arg value="@{src}"/>
        </exec>
    </sequential>
</macrodef>

Notice that the '$' became a '@' and the <exec/> is now wrapped in a <sequential/> tag. That is how Ant tells apart macro parameters from properties. With this change, I can now use this Ant script snippet:

<rm src="${some.dir}"/>

Much more readable!

Next I want to make this a bit more reusable. My example was super simple, but other cases might need to use some of the attributes for <exec/>. For my purposes, I added dir, spawn and failonerror. I found real uses of dir and failonerror in our codebase, and I wish to single out spawn in just a minute. That yields:

<macrodef name="rm">
    <attribute name="src"/>
    <attribute name="dir" default="."/>
    <attribute name="spawn" default="false"/>
    <attribute name="failonerror" default="false"/>
    <sequential>
        <echo message="rm -rf @{src}"/>
        <exec executable="rm" dir="@{dir}" spawn="@{spawn}"
                failonerror="@{failonerror}"
            <arg value="-rf"/>
            <arg value="@{src}"/>
        </exec>
    </sequential>
</macrodef>

Combined here are the techniques for macro attribute defaults and for passing down attributes for tasks wrapped in a macro. These serve well to preserve expected defaults and avoid surprises for macro users.

Aside: Why does failonerror default to false? This seems a perverse choice for a build system when fail-fast strategies save so much developer time in large projects.

Lastly, I want to make the macro generic to work with cp and mv, not just rm. So I did the obvious (to me) thing: I made a macro with the macro. Thus:

<macrodef name="file-operation">
    <attribute name="operation"/>
    <attribute name="message"/>
    <element name="attributes"/>
    <element name="args"/>
    <sequential>
        <macrodef name="@{operation}">
            <attributes/>
            <attribute name="dir" default="."/>
            <attribute name="spawn" default="false"/>
            <attribute name="failonerror" default="false"/>
            <sequential>
                <echo message="@{message}"/>
                <exec executable="@{operation}" dir="@{dir}"
                      spawn="@{spawn}" failonerror="@{failonerror}">
                    <args/>
                </exec>
            </sequential>
        </macrodef>
    </sequential>
</macrodef>

And my definition of the <rm/> macro becomes:

<file-operation operation="rm" message="rm -rf @{src}">
    <attributes>
        <attribute name="src"/>
    </attributes>
    <args>
        <arg value="-rf"/>
        <arg value="@{src}"/>
    </args>
</file-operation>

Usage stays the same:

<rm src="${some.dir}"/>

And likewise for cp and mv:

<file-operation operation="cp" message="cp -a @{src} @{dst}">
    <attributes>
        <attribute name="src"/>
        <attribute name="dst"/>
    </attributes>
    <args>
        <arg value="-a"/>
        <arg value="@{src}"/>
        <arg value="@{dst}"/>
    </args>
</file-operation>

<file-operation operation="mv" message="mv @{src} @{dst}">
    <attributes>
        <attribute name="src"/>
        <attribute name="dst"/>
    </attributes>
    <args>
        <arg value="@{src}"/>
        <arg value="@{dst}"/>
    </args>
</file-operation>

With corresponding Ant script calls:

<cp src="${some.dir}" dst="${dupliate.dir}"/>
<mv src="${some.dir}" dst="${renamed.dir}"/>

One last itch remains for me. The raison d’ĂȘtre I started down this road in the first place was to speed up <delete dir="${some.dir}"/>. An optimization I realized early on was not just to call to rm, but to run the operation in the background:

<tempfile property="tmp.dir" prefix=".tmp."/>
<mv src="${some.dir}" dst="${tmp.dir}"/>
<rm src="${tmp.dir}" spawn="true"/>

This pattern renames ${some.dir} to a random temporary directory and deletes the temporary directory in the background. The documentation of <exec/> even claims the operation continues after the Ant script exits. Perfect! Now to simplify usage of the pattern:

<macrodef name="rm-background">
    <attribute name="src"/>
    <attribute name="property"/>
    <attribute name="dir" default="."/>
    <attribute name="failonerror" default="false"/>
    <sequential>
        <tempfile property="@{property}" prefix=".@{property}."/>
        <mv src="@{src}" dst="${@{property}}" dir="@{dir}"
            failonerror="@{failonerror}"/>
        <rm src="${@{property}}" spawn="true" dir="@{dir}"
            failonerror="@{failonerror}"/>
    </sequential>
</macrodef>

Gives:

<rm-background src="${some.dir}" property="tmp.some.dir"/>

Notice that the temporary directory begins with a '.'. This is to make it hidden under UNIX/Cygwin so it doesn't clutter ls.

Time for a peanut butter sandwich.

UPDATE: I've saved everything in one place for easy examination.