Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at RIA Unleashed (Nov. 2010) with: Carol Loffelmann

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

By Ben Nadel on
Tags: ColdFusion

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.




Reader 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 :)

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.