Spent most of yesterday and a fairly decent amount of time today replacing the bulk of the hand-specialized jregions code with code generated from a template instead. I don't typically like templating as a rule: If I'm going to be producing text for a language (such as Java code, XML, etc) that's then going to be parsed and consumed by an external system (such as a compiler, an XHTML renderer, etc) then constructing the text step-by-step using an AST representation guarantees that the output will be well-formed. Templating systems don't provide any guarantees. In this case though, the output of the templating system is going to be immediately consumed by the Java compiler, which is then going to indicate any syntax and/or type errors before any other system has a chance to consume the code.
Of the templating systems I know about, only StringTemplate
seems to have been developed with any kind of discipline: The
primary concept is the strict separation of presentation and
logic. A StringTemplate
template does not contain any logic
and simply defines formal parameters that must be supplied with
values when the template is rendered. Note the must here: Failing
to provide a value for a parameter causes an error to be raised
at rendering time. Most template systems (notably Maven's resource filtering)
tend to silently fail or insert garbage in this situation.
I used Kevin Birch's StringTemplate Maven plugin
to generate sources as part of Maven's generate-sources
phase.
It appears to work well, but has some nasty failure modes when
individual templates can't be found. Basically, if you
tell the plugin to open a template file that doesn't exist,
or if the name of the template doesn't match the name of the
file within which it is defined, you'll get an unhelpful
error like this:
[ERROR] Failed to execute goal com.webguys:string-template-maven-plugin:1.1:render (generate-D) on project com.io7m.jregions.core: Unable to execute template. -> [Help 1]
Even with Maven's -X
switch (enables the display of exception stack traces and other debugging output)
there was no useful information available. I ended up using
the little-known mvnDebug
executable (bundled with every Maven
install but apparently undocumented) to step through the execution
of the plugin and work out what was going wrong. For those that
don't know, mvnDebug
loads a Java agent
into the JVM that causes Maven to wait until an external debugger
(in my case, Intellij IDEA)
connects before running the build. It turned out that the Maven
plugin wasn't exactly at fault: StringTemplate's
internal APIs
indicate failure by returning null
and maybe writing an error
message to a provided mutable sequence. In my case, there were
several mistakes:
I specified the name of a template file including the suffix: Template.st
.
Internally, StringTemplate
appends its own suffix, so the API was looking
for Template.st.st
, failing to find it, but not providing an error message.
I specified the name of a template file that didn't exist at all. Same failure mode as the above.
I specified the name of a template P
. StringTemplate
looked at the file P.st
,
found the file and parsed it, but it turned out that P.st
actually contained
a template called Q
. Again, this resulted in StringTemplate
being
unable to find the template I'd named, but refusing to tell me about it.
When those mistakes were found and corrected, the rest of the error reporting was decent. Failing to provide template parameters, making syntax errors within templates, etc, all provided good error messages with line and column numbers.
The result of this is that a lot of the jregions
source code (and
test suite) is now generated from a common template. No more manual
specialization. I also added float
and BigDecimal
specialized types
to exercise the generation system during development. I suspect
that I'm going to apply this same methodology to rewrite the
jtensors codebase when Java gets
value types.
Have started unifying jareas and jboxes into a new project: jregions.
The original projects were written about three years apart and
I'd not realized how much overlap there was between them until
it was too late. This sort of code is a prime target for
value types: There
are four sets of specialized classes for int
, long
, double
, and BigInteger
coordinates because Java's generics don't allow for abstraction over primitive
types without boxing. This is something that Brian Goetz
has complained about frequently. To paraphrase, "you sometimes
end up writing the same code eight times".
The jregions
project is also a first attempt at moving to
the OSGi conventions
I mentioned previously. Thought I might as well use them for
all new code and migrate the old code when JDK 9 appears.
Broke a pure-ftpd install this
morning by recklessly failing to read the change log before upgrading.
Missed this note for 1.0.44
:
The Perl and Python wrappers are gone. The daemon can now use a configuration file without requiring external dependencies.
This meant that the s6 run
script had to be updated:
#!/bin/sh exec /usr/local/sbin/pure-config.pl /ftpd/pure-ftpd.conf 2>&1
Became:
#!/bin/sh exec /usr/local/sbin/pure-ftpd /ftpd/pure-ftpd.conf 2>&1
The documentation was not updated. I had to work out how to get
the server to consume the configuration file by guessing, and had
to trace the executable with ktrace
to make sure that it actually
was reading the file.
I tend to forget that not all projects use semantic
versioning and what I expected to be a
simple bug-fix update from 1.0.43
to 1.0.45
turned out to be a
service-disrupting change.
If you maintain software and you're reading this, please make your version numbers mean something!