One of my Aussie chums hit me up the other day, asking me for some help with some quirky code he was seeing. We worked out what the issue was, but it stands repeating because it was non-obvious from a CFMLer's point of view.
The original code was along these lines:
// original.cfm
timezoneClass = createObject( "java", "java.util.TimeZone" );
allZones = timezoneClass.getAvailableIds();
writeDump(var=allZones, top=10);
arraySort(allZones, "text");
writeDump(var=allZones, top=10);
He was getting an array of all the time zones Java supports, and sorting them for use. However this is what was being output:
That's not particularly helpful, ColdFusion. How about saying what the frickin' error actually was!!
However I'm used to this sort of error when something goes wrong with some sort of Java operation. ColdFusion just doesn't report the error back very well.
So what's wrong here?
Well... I dunno the minutiae of the detail of the problem, but it's basically that
arraySort()
is designed to work on a CFML array, and what's coming back from that getAvailableIds()
call is a Java array. Those are two different things. And, for one thing, a CFML "array" actually isn't an array in the Java sense of the notion.When we looked at the data in the dump it looks like an array, so we didn't think twice about it. However let's look more closely:
cfmlArray = ["tahi", "rua", "toru", "wha"];
javaArray = cfmlArray.toList().split(",");
writeDump({
java = {
array=javaArray,
class=javaArray.getClass().getName()
},
cfml = {
array=cfmlArray,
class=cfmlArray.getClass().getName()
}
});
Here I do this:
- create a standard CFML array;
- convert that back into a string with
toList()
; - then
split()
that string (split()
is a java.lang.String method) which returns a Java array of Strings. This is what java.util.TimeZone'sgetAvailableIds()
returns. - Use Java to get the name of the class of each "array".
This outputs:
Note that the CFML array is a
coldfusion.runtime.Array
, and the Java array is literally a String array. So not the same type of data at all. Now if we use ClassViewer to use reflection to look at what methods each type provides for, we see that a coldfusion.runtime.Array has a bunch of arraySort()
methods mapping to the CFML arraySort()
function, whereas - obviously - normal Java array will not have those methods. So arraySort()
doesn't find the method, so it fails.
I'll not waste space here with the ClassViewer output, but they're here if you're interested:
So what's the solution then?
Well on a whim I figured Java arrays don't support sorting, but ArrayLists do, so I'd just convert the Array to an ArrayList. I didn't initially know how to do this, but Google did, and it's via java.util.Arrays, specifically the
asList()
method. So I arrived at this code:javaArrayList = createObject("java", "java.util.Arrays").asList(javaArray);
arraySort(javaArrayList, "text");
writeDump(javaArrayList);
And this works:
Note that an ArrayList also doesn't have all the same
arraySort()
methods that a CFML Array has, so I guess ColdFusion is just wise enough to look for a sort()
method if the collection passed to arraySort()
is an AbstractList (which an ArrayList is). A CFML array is also a subclass of AbstractList.Because the ArrayList does its own sorting, my mate ended up using that method directly, rather than using CFML's
arraySort()
.All of this shows up a bug in Lucee, btw. Whilst trying to work out what the initial error was, I ran my repro code (full code: sortJavaArrayInline.cfm) through Lucee. Here's the result:
Note the result of doing the
arraySort()
on the Java array: it didn't error, but it also didn't work: the array still has its original order. This isn't so cool. If it didn't work, it should throw an error like ColdFusion does.I'll raise a ticket for that, once I press send on this, so I have a URL to point back to this article to include in the ticket. Here it is: LDEV-320.
Righto. On with my Sunday.
--
Adam