Jelly: Scripting for the Soulless
The more I learn about Java and Java-related technologies, the more I hate them. I am convinced that these things are developed by demons to torment righteous programmers.
My latest gripe is with Jelly, which advertises itself as “executable XML”. It flaunts this, in fact, as if it were actually a desirable feature. It’s like saying, “hey, look, we’ve got a burrito that you can actually drive!” Jelly, in my opinion, is one of the more insidious aspects of the Java world. It is finding its way into more and more widely-spread applications, most notably Maven.
If it weren’t for Maven, I’d never have to worry about Jelly. However, Maven uses Jelly to do all of its scripting. All of its plugins are built using Jelly, and if a developer wants to do any kind of custom task using Maven, it must be scripted using Jelly. I know this, because I was trying all morning to add a simple task to my Maven-based build system at work.
All I wanted was this: collect all of the variables in the Maven context, build them into a delimited string, and pass that string to an external program.
That’s all! And after a morning of wrestling with the Beast, I managed to turn out 15 lines of Jelly script that did just that. Doesn’t sound like much, does it? At least, not until you understand that the exact same functionality can be done in a mere 2 lines of Ruby code! And in writing those 15 lines of code, I came up with not one, not two, nor even three reasons why Jelly scripting ought to be made a punishable offense. I came up with eight reasons. That’s more than one reason per two lines of code.
So, in an effort to persuade the unpersuadable and bring those straying programmers back to the light, here are Eight Reasons Why Executable XML Is an Evil-Sick-Wrong-Misbegotten Idea.
1. XML Entities
You want to do a test on acouple of conditions to see if they are both true. In Java, the syntax for the and
conjunction is two ampersands, &&
. However, just you try and use that in a Jelly expression! Won’t work, because Jelly is XML, remember? And in XML, the ampersand is a reserved character. You have to escape the ampersands using XML entities, like so:
<jelly:if test="${something.isTrue() && somethingElse.isTrue()}"> ... </jelly:if>
2. No if/else
Logic
Programming languages are all, generally-speaking, very much alike. Certain constructs are shared between many of them, and one of the most common of all is the if/elsif/else
construct. That’s a good thing because it makes it easier for a programmer who knows one language to learn another one, since much of the terminology carries right over.
Welcome to Hell Jelly. There is, it is true, an if
construct for Jelly. However, this construct has no else
clause. Thus, if you want to do an if/else
type of construct, you have to resort to one of two alternate approaches.
The first is to use two if
tags, the first testing for truth, and the second testing for falsehood, like this:
<jelly:if test="${something.isTrue()}"> ... </jelly:if> <jelly:if test="${!something.isTrue()}"> ... </jelly:if>
Ugly.
The second approach is to use the choose/when/otherwise
construct. (Great name, by the way. Took me a long time of just reading the docs to discover it, because it is sooo intuitive. Not.)
<jelly:choose> <jelly:when test="${something.isTrue()}"> ... </jelly:when> <jelly:when test="${somethingElse.isTrue()}"> ... </jelly:when> <jelly:otherwise> ... </jelly:otherwise> </jelly:choose>
3. XML (and therefore Jelly) is VERBOSE.
Ohmygosh. Can we please try saying EVERYTHING EVERYTHING TWICE TWICE? This is one of my biggest gripes with the XML format in general, and why I much prefer the YAML format for things like configuration files. It is also why I think XML is a lousy choice for a programming language syntax.
<jelly:while test="..."> <jelly:if test="..."> <jelly:choose> <jelly:when test="${...}"> ... </jelly:when> <jelly:when test="${...}"> ... </jelly:when> <jelly:otherwise> ... </jelly:otherwise> </jelly:choose> </jelly:if> </jelly:while>
4. Error Reporting (and the Absence Thereof)
Error reporting in Jelly (and therefore Maven) is awful. Terrible. Miserable. Hateful. Evil.
Either, (a) you get no errors and your problem statements just fail silently, or (b) your problem statements fail spectacularly and you wind up with reams of beautiful stack traces that tell you nothing at all.
For example:
<jelly:set var="variable" value="hello" /> <jelly:if test="${varable.equals('hello')}"> ... </jelly:if>
This will fail silently, leaving you wondering why the if statement isn’t being entered. Can you see the problem? Look carefully: the word “variable” is misspelled. And Jelly won’t tell you that; instead, it will just shrug it’s shoulders, mark the test as false, and move on. Thanks for that, Jelly. You somehow read my mind and discovered exactly what I didn’t want you to do.
Here’s an example of one that fails spectacularly:
<! -- returns an interator --> <jelly:set var="names" value="context.getVariableNames()" /> <jelly:while test="${names.hasNext()}"> <! -- throws ConcurrentModification exception! --> <jelly:set var="name" value="${names.next()}" /> </jelly:while>
There, where it calls names.next()
. Can you see what’s wrong? Can you figure out why an exception is thrown, which barfs all over my screen and gives me EVERYTHING AND HIS SORRY EXCUSE FOR A DOG back? Everything, that is, except what I really need to know, like why the script is failing in the first place.
Well, I’ll save you some grief and explain: it’s failing because on line 2, we grab an iterator that iterates over all of the variables in the current execution context. However, on line 5, we are creating a new variable in that context. This causes the iterator to complain about a concurrent modification. The solution? Declare “name” before you open the iterator. Thanks again, Jelly. You’re just lovely.
5. No Control Characters in Strings
In C, C++, Ruby, Python, Perl, and yes, even Java, you embed control characters in strings using escaped character sequences. For example, a newline is a backslash followed by an n: \n
.
Is it this way in Jelly, our friendly executable XML syntax? Of course not. You have to use XML syntax for control characters. What is XML syntax for this? Why, XML entities, of course:
<! -- WRONG --> <jelly:set var="variable" value="first\nsecond" /> <! -- RIGHT --> <jelly:set var="variable" value="first second" />
To me, it looks like someone threw up in the middle of the string.
6. XML Comments
Forgive me, all you XML fans out there, but the XML comment is one of the ugliest structures on the planet:
<! -- here is the comment -->
Come on! At least make the comment delimiters brief! But no—it needs 4 characters to introduce the comment, and 3 characters to end it. And there is no such thing as a simple one-line comment in XML. In Java and C++ you can have all text to the end of the line ignored by typing two forward slashes: //
. Ruby, Perl and Python do even better and only require you to type a pound character: #
. XML requires you to type seven characters for every comment, no matter how small.
7. XML Namespaces
Before you can use certain Jelly tags, you have to declare and name the corresponding namespace. Only maven and ant tags are “globally” accessible, everything else you have to prefix with a namespace. This only compounds the problem of excessive verbosity and redundancy in the scripts. And “good programming style” expects you to include the namespaces of the maven and ant tags, even though they are automatically accessible.
<project xmlns:ant="jelly:ant" xmlns:jelly="jelly:core" xmlns:maven="jelly:maven" xmlns:util="jelly:util"> ... <jelly:if test="..."> </jelly:if> ... </project>
8. Unpredicable Jelly Expressions
Jelly expressions are supposed to solve all problems, making life easier for developers. In practice, it is very frustrating to use them, since if you mistype something, you get no feedback indicating that (see the “error” issue above), and not all Java expressions are valid. It’s hard to know what expressions are acceptible and which aren’t, without actually trying it, and even then, you never know if you may have accidentally mistyped something and it would have worked if you’d typed it right.
For instance:
<! -- valid --> <jelly:set var="something" value="${context.getVariable('line.separator')}" /> <! -- invalid --> <jelly:set var="something" value="${System.getProperty('line.separator')}" />
Well, there you have it. I’m convinced. “Choose you this day whom ye will serve…but as for me and my house, we will serve the LORD.” Jelly is obviously a tool of Satan and I will therefore refuse to use it.
I’ll use Ruby, thank-you-very-much. It’s like a cool balm to a tortured programmer. If you have found yourself straying down the path of executable XML, it’s not too late to come back!