Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Liz Ojukwu
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Liz Ojukwu ( @e_irene_ )

ToJava() - A ColdFusion User Defined Function For Complex Java Casting

By on
Tags:

ColdFusion is a typeless language; and, when we reside in the ColdFusion world, this is a beautiful feature that none of us really have to care about. But, from time to time, we have to dip down into the strongly-typed world of Java that lies just below the surface. At that point, going from lose ColdFusion data types to strict Java data types can be a bit of a headache. ColdFusion comes with a function - javaCast() - that is supposed to make this transition easier; and, most of the time it does. However, I find that javaCast() starts fall short in more complex conversions. As such, I wanted to try and make a more comprehensive Java-casting function - toJava() - that would perform a bit more magic behind the scenes.

Before we look at the toJava() function code, let's take a look at a situation in which the core javaCast() function fails to help us. If you've ever played around with Java's URL Class Loader, you've probably run into a situation where you have to build a typed-array of "java.net.URL" instances. While javaCast()'s documentation states that you can use Java classes to create arrays, I have found that this mostly fails:

<!---
	Create an array of URLs to JAR directories that we want to use
	in a URL class loader.
--->
<cfset jarPaths = [
	"file://#getDirectoryFromPath( getCurrentTemplatePath() )#"
	] />

<!---
	Try to cast this JAR path array to Java array using the native
	JavaCast(). When we create a java.net.URLClassLoader instance,
	we have to pass in a URL[]-typed array of URL objects.
--->
<cfset javaURLArray = javaCast( "java.net.URL[]", jarPaths ) />

<!--- Output our cast Java URL array. --->
<cfdump
	var="#javaURLArray#"
	label="Java URL Array"
	/>

As you can see here, we are creating a native ColdFusion array of JAR file paths. Then, we are using ColdFusion's native javaCast() function to try and convert that to a Java array of "java.net.URL" instances:

javaCast( "java.net.URL[]", jarPaths )

ColdFusion executes this command without raising any exceptions; however, when we run the above code, we get the following output:

ColdFusion's JavaCast() Function Cannot Handle Complex Data Type Conversions.

As you can see, our Java array now contains one NULL (undefined) value rather than an instance of the "java.net.URL" class.

Now, let's run the same code; except, rather than using the native javaCast() function, we'll use my ColdFusion user defined function, toJava().

<!---
	Create an array of URLs to JAR directories that we want to use
	in a URL class loader.
--->
<cfset jarPaths = [
	"file://#getDirectoryFromPath( getCurrentTemplatePath() )#"
	] />

<!---
	Again, we are going to cast this JAR path array to Java array,
	however, this time, we are going to use a custom toJava()
	function that handles type-casting a bit more manually.
--->
<cfset javaURLArray = toJava( "java.net.URL[]", jarPaths ) />

<!--- Output our cast Java URL array. --->
<cfdump
	var="#javaURLArray#"
	label="Java URL Array"
	/>

As you can see, the only difference in the two code blocks is the function that we are using to cast our lose ColdFusion data types to strict Java data types (NOTE: we are doing more than simply casting at this point - I discuss this further in my conclusion). This time, when we run the code, we get the following page output:

ToJava() ColdFusion User Defined Function Can Handle Complex Data Type Conversions Between ColdFusion And Java.

As you can see, our strongly typed Java array now contains an instance of the "java.net.URL" class. And, just to make sure that the "conversion" (ie. instantiation) worked properly, we can try to convert the URL instance back to a string:

toString: #javaURLArray[ 1 ].toString()#

This gives us the following output:

toString: file:/Sites/bennadel.com/testing/tojava/

Clearly, the "java.net.URL" instance was instantiated with the appropriate constructor arguments; and, as such, was able to be converted back to a string in a meaningful way.

OK, so what is the toJava() function doing? At its most core layer, it is using the native javaCast() function that ColdFusion provides. JavaCast() works great in many situations; so, if we can use it, we do. If, however, we are presented with a situation in which javaCast() is not a great choice, then we either use createObject() for single Java objects; or, we use the "java.lang.reflect.Array" class when we need to create an array of Java objects.

To keep the code as clean as possible, all but the most basic casting situations rely on being able to call the toJava() function recursively. While this adds some processing overhead, it removes a good deal of conditional logic and, I believe, makes the code easier to follow:

<cffunction
	name="toJava"
	access="public"
	returntype="any"
	output="false"
	hint="I convert the given ColdFusion data type to Java using a more robust conversion set than the native javaCast() function.">

	<!--- Define arguments. --->
	<cfargument
		name="type"
		type="string"
		required="true"
		hint="I am the Java data type being cast. I can be a core data type, a Java class. [] can be appended to the type for array conversions."
		/>

	<cfargument
		name="data"
		type="any"
		required="true"
		hint="I am the ColdFusion data type being cast to Java."
		/>

	<cfargument
		name="initHint"
		type="string"
		required="false"
		default=""
		hint="When creating Java class instances, we will be using your ColdFusion values to initialize the Java instances. By default, we won't use any explicit casting. However, you can provide additional casting hints if you like (for use with JavaCast())."
		/>

	<!--- Define the local scope. --->
	<cfset var local = {} />

	<!---
		Check to see if a type was provided. If not, then simply
		return the given value.

		NOTE: This feature is NOT intended to be used by the outside
		world; this is an efficiency used in conjunction with the
		javaCast() initHint argument when calling the toJava() method
		recursively.
	--->
	<cfif !len( arguments.type )>

		<!--- Return given value, no casting at all. --->
		<cfreturn arguments.data />

	</cfif>


	<!---
		Check to see if we are working with the core data types -
		the ones that would normally be handled by javaCast(). If so,
		we can just pass those off to the core method.

		NOTE: Line break / concatenation is being used here strickly
		for presentation purposes to avoid line-wrapping.
	--->
	<cfif reFindNoCase(
		("^(bigdecimal|boolean|byte|char|int|long|" & "float|double|short|string|null)(\[\])?"),
		arguments.type
		)>

		<!---
			Pass the processing off to the core function. This will
			be a quicker approach - as Elliott Sprehn says - you have
			to trust the language for its speed.
		--->
		<cfreturn javaCast( arguments.type, arguments.data ) />

	</cfif>


	<!---
		Check to see if we have a complex Java type that is not an
		Array. Array will take special processing.
	--->
	<cfif !reFind( "\[\]$", arguments.type )>

		<!---
			This is just a standard Java class - let's see if we
			can invoke the default constructor (fingers crossed!!).

			NOTE: We are calling toJava() recursively in order to
			levarage the constructor hinting as a data type for
			native Java casting.
		--->
		<cfreturn createObject( "java", arguments.type )
			.init(
				toJava( arguments.initHint, arguments.data )
				)
			/>

	</cfif>


	<!---
		If we have made it this far, we are going to be building an
		array of Java clases. This is going to be tricky since we
		will need to perform this action using Reflection.
	--->

	<!---
		Since we know we are working with an array, we want to remove
		the array notation from the data type at this point. This
		will give us the ability to use it more effectively belowy.
	--->
	<cfset arguments.type = listFirst( arguments.type, "[]" ) />

	<!---
		Let's double check to make sure the given data is in array
		format. If not, we can implicitly create an array.
	--->
	<cfif !isArray( arguments.data )>

		<!---
			Convert the data to an array. Due to ColdFusion
			implicit array bugs, we have to do this via an
			intermediary variable.
		--->
		<cfset local.tempArray = [ arguments.data ] />
		<cfset arguments.data = local.tempArray />

	</cfif>

	<!---
		Let's get a refrence to Java class we need to work with
		within our reflected array.
	--->
	<cfset local.javaClass = createObject( "java", arguments.type ) />

	<!---
		Let's create an instance of the Reflect Array that will
		allows us to create typed arrays and set array values.
	--->
	<cfset local.reflectArray = createObject(
		"java",
		"java.lang.reflect.Array"
		) />

	<!---
		Now, we can use the reflect array to create a static-length
		Java array of the given Java type.
	--->
	<cfset local.javaArray = local.reflectArray.newInstance(
		local.javaClass.getClass(),
		arrayLen( arguments.data )
		) />

	<!---
		Now, we can loop over the ColdFusion array and reflectively
		set the data type into each position.
	--->
	<cfloop
		index="local.index"
		from="1"
		to="#arrayLen( arguments.data )#"
		step="1">

		<!---
			Set ColdFusion data value into Java array. Notice that
			this step is calling the toJava() method recursively.
			I could have done the type-casting here, but I felt that
			this was a cleaner (albeit slower) solution.
		--->
		<cfset local.reflectArray.set(
			local.javaArray,
			javaCast( "int", (local.index - 1) ),
			toJava(
				arguments.type,
				arguments.data[ local.index ],
				arguments.initHint
				)
			) />

	</cfloop>

	<!--- Return the Java array. --->
	<cfreturn local.javaArray />
</cffunction>

The first two arguments of the toJava() method - "type" and "data" - are required; the third argument - "initHint" - is not. This last argument is used when you want to provide casting information in the context of the Java class constructor. For example, in the above demo, I am creating instances of the Java class, "java.net.URL." When I do this, the toJava() function is actually invoking the constructor on the "java.net.URL" class. This constructor expects a strongly-typed string value. If you get into a situation where ColdFusion can't figure out how to translate your value into the required string, you can use this third argument to provide constructor-specific hinting:

toJava( "java.net.URL[]", jarPaths, "string" )

Now, you might be looking at this toJava() function and think that it is totally off base; after all, instantiating and initializing a Java class instance is a completely different concept than that of casting one data type to another. And Yes, I agree entirely with that point of view; however, when you go from a typeless world to a typed world, I am not sure if that mindset necessarily needs to carry over. In any case, whether it is a good choice or bad choice to combine these two potentially divergent gestures, I believe that this function will prove to be very useful for those ColdFusion developers who need to reach into the Java layer from time to time.

Want to use code from this post? Check out the license.

Reader Comments

15,640 Comments

@Bill,

Thanks my man - glad you like it. Going from the loosely typed ColdFusion layer to the strongly typed Java layer can be irksome. Making the data conversions easier should help :)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel