Tuesday 7 August 2012

ClassViewer.java

G'day
I've had a busy weekend and a crazy time at work and have been tied-up in the evenings recently, hence the radio silence. But anyway I'm gonna squeeze a quick one out now (oh... please don't dwell too much on the potential double entendres there. You probably wouldn't've until I mentioned that, I guess... Oops).

Today I'm just publicising some code that I've found very handy in working out what ColdFusion is getting up to behind the scenes, as well as understanding how the "internal" methods of CF's class implementations of its various data types work.



This is not my own work, but it was publicised as open source when I found it so I figure it's OK to repost it here (if it's your code and you'd rather not have it here: let me know!), and it's becoming quite difficult to find online. So I'm just posting it here so it's easier to find. This is not quite the original code as it had a bug in how it reported the inheritance hierarchy, which I have fixed.

I guess I might try to knock out a CFML version of this too if I find time today.

Here's the code:

All one needs to do is compile that, and stick it in your CF install's WEB-INF/classes directory (if I can work out how to attach a file to these blog posts, I'll attach the compiled version too...).  And here's how it's called:

<cfset q = queryNew("")>
<cfset cv = createObject("java", "ClassViewer")>

<pre>
<cfoutput>#cv.viewObject(q)#</cfoutput>
</pre>

And here's some sample output (which I've truncated for the sake of brevity):

public class coldfusion.sql.QueryTable
extends coldfusion.sql.Table
extends coldfusion.sql.imq.imqTable
extends java.lang.Object
extends  implements javax.sql.RowSet, coldfusion.wddx.RecordSet
{
/*** CONSTRUCTORS ***/
public coldfusion.sql.QueryTable()

public coldfusion.sql.QueryTable(com.allaire.cfx.Query)

public coldfusion.sql.QueryTable(java.sql.ResultSet)

// snip


/*** METHODS ***/
public boolean equals(java.lang.Object)

public java.lang.Object getObject(int, java.util.Map)

public java.lang.Object getObject(java.lang.String)

public java.lang.Object getObject(java.lang.String, java.util.Map)

public java.lang.Object getObject(int)

// snip


/*** FIELDS ***/
public static final int FETCH_FORWARD
public static final int FETCH_REVERSE
public static final int FETCH_UNKNOWN

// snip

}

With Java objects, one gets an abbreviated version of this if one just uses <cfdump>:

object of java.lang.String
Class Namejava.lang.String
Methods
MethodReturn Type
charAt(int)char
codePointAt(int)int
codePointBefore(int)int
codePointCount(int, int)int
compareTo(java.lang.String)int
compareToIgnoreCase(java.lang.String)int
// SNIP
Fields
FieldValue
java.util.Comparator CASE_INSENSITIVE_ORDERjava.util.Comparator


But I think ClassViewer's output is clearer, plus includes all the constructor and inheritance stuff too. With CF objects (like queries or dates), <cfdump> just gives the value, not the "reflection" so it's dead handy in these cases.

One word of warning: having explored the internal gubbinses of a ColdFusion data type, one might be tempted to leverage this functionality in production code. Whilst I'd fall short of saying "don't do it!", make sure you understand that this stuff - despite being exposed as public methods - does not fall under the remit of CF's holy grail: Adobe make no promises about maintaining backwards compatibility with what's exposed here.

My personal opinion here is that as long as one bears that in mind, and has 100% test coverage of the elements one decided to use, then it's fine. And the reality is that very little has changed with this lot since I first started keeping an eye on them. But there had been some changes occasionally.

It's also relevant to remember that a CF string is just a Java string, and this is very unlikely to change, so I reckon using the underlying Java methods is OK. And CF's array implementation is an implementation of a Vector, and structs are an implementation of the Map interface (not directly it seems, but it mirrors it fairly closely). I don't think these will change either.

But use with caution.

One again, I'm keen to know people's opinion of what I say here. I don't always reply, but I do read and digest all comments. If I 100% agree or a comment stands on its own merit, I leave it as is.  If someone asks me a question though, I'll answer.  But whether I respond or not, it's always good to have other people's opinions here, rather than just my own.

Right... I'm gonna crack on writing a CFML version of that Java code...

--
Adam