Friday, January 12, 2007

Extend CF native Objects - Harnessing Java

Since Coldfusion native objects are java objects, you can harness the java APIs to extend the functionality of these objects. In this post we will take CF Array and see how we can use these APIs to get some cool functionalities from them.

ColdFusion array is actually an implementation of java list (java.util.List). So all the list methods are actually available for Array.
CF provides most of the list functionality using Array functions but there are few things possible with java list which you can not do directly with CF functions.

1. Merge : Lets say you create two arrays and you want to merge these two arrays to create one bigger array. There is no CF function to do this.
However You can call List.addAll() methods to do it.

Here is how it would look.

I am creating array this way just because it is easier and I don't have to write whole lot of code :)

<cfset y = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
<cfset z = ListToArray("dean,manju,jason,tim")>
<cfset y.addAll(z)>
<cfdump var="#y#">


2. Merge in middle : Lets say you want to add the second array somewhere in the middle of first array say after 4 elements. The code would look like

<cfset y = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
<cfset z = ListToArray("dean,manju,jason,tim")>
<cfset y.addAll(4, z)>
<cfdump var="#y#">


3. Search : I have heard people complaining that there is no find method in Array. Actually you had it all the time. Just that it was hidden :)
You can use List.Contains() or List.indexOf() methods to achieve that. Here is the code.

<cfset y = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
<cfoutput>Contains Hemant: #y.contains("hemant")#</cfoutput>
<cfoutput>Index of damon : #y.indexof("damon")#</cfoutput>


Please note that the java index starts at 0 where as CF index starts at 1. So the index here will be 2. You must also note that since java is case sensitive, this search will also be case sensitive. To build case insensitiveness, you will have to make the list as well as the search in same case - either uppercase it or lowercase it.

4. Search whole array : You can also search if all the elements of one array are present in another array using containsAll() method of java list.


<cfset y = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
<cfset z = ListToArray("ram,prank,rupesh")>

<cfoutput> y Contains z: #y.containsAll(z)#</cfoutput>


5. Equality check : You can find out if two arrays are same or not using list's equals method.

<cfset y = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
<cfset z = ListToArray("dean, manju,jason,tim")>
<cfset x = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
<cfoutput>x equals y : #x.equals(y)#</cfoutput>
<cfoutput>x equals z : #x.equals(z)#</cfoutput>


6. RemoveAll : You can remove all the elements of one array from the second array using removeAll()


<cfset y = ListToArray("rupesh,tom,damon,hemant,ashwin,ram,prank,sanjeev")>
before removal <cfdump var="#y#">

<cfset z = ListToArray("ram,prank,tom")>
<cfset y.removeAll(z)>
After removal <cfdump var="#y#">


Go ahead and play around with it!

26 comments:

Richard Davies said...

Wow! I had no idea it was this easy to use the underlying Java APIs on ColdFusion objects. This is a really neat thing to know.

How does one go about figuring out what type of Java object is used to implement the various ColdFusion objects? (i.e. in other words, how did you find out that arrays are really just an implementation of java.util.list?)

Rupesh Kumar said...

Thats an easy one. Ask me :) I am an engineer in the ColdFusion team !

Richard Davies said...

I would really hate to have to bug you every time I wanted to figure out the Java implementation of a CF object... and you probably don't want me (and everyone else reading this post) bugging you all of the time either, so...

Is there any way you can share that us "normal" developers can use to figure this out on our own?

If not, how about another blog post listing the CF objects and their corresponding Java objects?

Rupesh Kumar said...

i was just kiddin then.. Its gonna come.. a li'l patience :D

Rupesh Kumar said...

You can however write a java method yourself which can take any CF object and give you its class.
It can be as simple as

public String getClassName(Object cfdata)
{
return cfdata.getClass().getName()
}

PerpetualBurn said...

Or...you might find this page of assistance:

http://livedocs.macromedia.com/coldfusion/7/htmldocs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=ColdFusion_Documentation&file=00001568.htm

At least I did :-)

Mike.

Richard Davies said...

Thank you! Those are both excellent suggestions.

Fernando Trevisan said...

ohmy, this post is great! Thanks a lot for sharing this =)

Andy Matthews said...

Thank you Rupesh. I read this post on Friday and used the contains() method just today. This is a great find. Kudos to you!

Rupesh Kumar said...

Thanks everyone for these nice and wonderful comments and for being so encouraging!

Mark Mandel said...

The only thing I have found is that .contains() (and similar methods) won't work reliably with CFC instances.

At a Java level it seems that 2 CFC instance will always return 'true' when asked if they are equal()

So if I have:

arr = ArrayNew(1);
obj1 = createObject("component", "myCFC");
obj2 = createObject("component", "myCFC");

ArrayAppend(arr, obj1);

result = arr.contains(obj2);

=> result will be 'true', even though it actually isn't.

I believe this is because the .equals() on the CFC Java class simply returns true, regardless of if two CFC's are actually different instances.

Rupesh Kumar said...

Not really. cfc instance acts as a map which contains key value pairs. all the cfc fields and its value will be stored in this map along with its meta-data. If all the meta-data and all they key-value pairs are exactly same, they must be treated same.

Try your example with this cfc and your modified code.



<cfcomponent>
<cfset this.name="">
<cffunction name="setname">
<cfargument name="Name" type="any">
<cfset this.name=Name>
</cffunction>
</cfcomponent>

----------------- the cfm to check equality ---------------
<cfscript>
arr = ArrayNew(1);
obj1 = createObject("component", "C1");
obj2 = createObject("component", "C1");
obj1.setname("Mandel");
obj2.setname("Mark");
ArrayAppend(arr,obj1);
result = arr.contains(obj2);

WriteOutput(result);
</cfscript>

Also try adding objects of two different components and the “contains” check above will always return false.

Andy Matthews said...

Rupesh...

Since your Array post was such a huge hit, might I request that you also do some posts about extending some of the other CF data types, specifically lists.

Can't wait to read more from you.

Giancarlo Gomez said...

THank you so much for this post, you just made a 20 minute task into a 2 second task. I was about to write a udf to search an array and this made it oh so simple.

Thank You!

Dominic said...

Great post dude, opened the door for a whole load of goodiness :).

Just cos it's not been written down here properly, here's a little method I wrote to get at the class heirarchy of a CFObject:

function GetClassHeirarchy(obj)
{
var bFinished = false;
var thisClass = obj.GetClass();
var sReturn = thisClass.GetName();

do{
try{
thisClass = thisClass.GetSuperClass();
sReturn = sReturn & " EXTENDS: #thisClass.GetName()#";
}catch(any e){bFinished = true;}
}while(not bFinished);

return sReturn;
}

Dom

Rupesh Kumar said...

Thanks Dominic. Nice util function to get object hierarchy. I would suggest a small enhancement in it. Instead of depending on exception for breaking out of the while loop, you can loop until your super class name is "System.lang.Object". Thats because all the java objects extend from java.lang.Object.

Cheers,
Rupesh

Marco Antonio said...

Rupesh, thank you. Great! Only one challenge to you: how to compare 2 arrays and display the differences between? Looks like this:
arrayTest1
1 struct
NAME 01076354000000012006071101.BAA

arrayTest2
1 struct
NAME 0107635400000002200607110A.BAA

arrayTest1 haves 100 elements and arrayTest2 103 elements. I'll want to display all elements that exist in arrayTest1 and not exist in ArrayTest2 and vice versa. Impossible?

Marco Antonio said...

Rupesh, I'm using removeAll in arrays with more than 10 thousand elements. Java with big arrays works very unstable. For this example array1 haves 15000 elements and array2 15001 elements. Only 1 record in array2 doesn't match with array1. Please looks this simple code:
get Array size before removeAll
cfset arGetArquivosDir_Size = arGetArquivosDir.size()
cfset arArquivoIndice_Size = arArquivoIndice.size()
duplicate the final array - I'm using for the final removeAll
cfset arGetArquivosDir_Copia = Duplicate(arGetArquivosDir)

arGetArquivosDir_Copia: #arGetArquivosDir_Copia.size()# elements

remove elements in array1 matched with array2
cfset arGetArquivosDir.removeAll(arArquivoIndice)
vice versa
cfset arArquivoIndice.removeAll(arGetArquivosDir_Copia)

Using cfdump var="#arArquivoIndice#" should display only one record but after 2 tests look this dump:

TEST 1
arArquivoIndice - array
1 arArquivoIndice - struct
NAME 00000000000000122006070604.SGD

2 arArquivoIndice - struct
NAME 04025367000000162006070704.SGD

3 arArquivoIndice - struct
NAME 04025566000001132006070704.SGD

4 arArquivoIndice - struct
NAME 04026829000000742006071104.SGD

5 arArquivoIndice - struct
NAME 04027182000000112006071104.SGD

TEST 2
arArquivoIndice - array
1 arArquivoIndice - struct
NAME 00000000000000122006070604.SGD

2 arArquivoIndice - struct
NAME 04026827000000722006071104.SGD

3 arArquivoIndice - struct
NAME 04027172000000862006071104.SGD

4 arArquivoIndice - struct
NAME 04027180000000592006071104.SGD

5 arArquivoIndice - struct
NAME 04027415000000172006071204.SGD

Please look that 2 different results.

Rupesh Kumar said...

Marco,
You figured out right that you have to use removeAll() to do this. Just wanted to make sure that this is how you are doing.

<cfset arr1 = ListToArray("1,2,3,4,5,7")>
<cfset arr2 = ListToArray("2,3,4,5,6")>

<cfset dupArr1 = Duplicate(arr1)>
<cfset dupArr1.removeAll(arr2)>

<cfdump var="#dupArr1#">

Some how I am not convinced that java gives a inconsistent result. Can you send me your sample code which can show inconsistent result? If it is a big file, you can mail it to me at rukumar at adobe.com

Dom said...

Hey Rupesh, in late response to your response to my response (the util function to get object heirarchy) - I have modified the function and posted some results of it on my blog (has a link to this post):

http://fusion.dominicwatson.co.uk
(first ever post on it)

There is a little correction which I assume was a slip, it loops until the class is 'java.lang.object' and not 'System.lang.Object'.

Definately prettier anyway, thanks!

Anonymous said...

Here's an alternate, fairly speedy one liner that returns true if two shorter lists have anything in common (case sensitive listreplacenocase is an alternative too):
{cfif listreplace(myfirst_list,mysecond_list,"*,*,...") contains "*"}

One asterisk must exist in "*,*,..." for each mysecond_list element to be searched for.

CV said...

Rupesh, please look at the simple code below. (Change < and > to get code to work.) I can never get a value greater than zero even though I should. Any ideas what I can do?

<cfhttp url="http://ww2.collectorcartrader.com/details.php?adId=90177579"
method="get">

<cfset images = reMatchNoCase("<img([^>]*[^/]?)>",cfhttp.FileContent)>
<cfset temp = images.indexof("_1thumb.jpg") + 1>

<cfoutput>#temp#</cfoutput>

Rupesh Kumar said...

CV,
thats because "images" that you get from reMatchNoCase is an Array and it does not contain any element whose value is "_1thumb.jpg". Sure enough, there are elements in this array which contain this string, but indexof looks for the exact object.

If you need to get the count of elements that contain this string then, here is how you can modify the code

<cfhttp url="http://ww2.collectorcartrader.com/details.php?adId=90177579"
method="get">

<cfset images = reMatchNoCase("<img([^>]*[^/]?)>",cfhttp.FileContent)>

<cfset count = 0>
<cfloop from="1" to="#ArrayLen(images)#" index="i">
<cfset image = images[i]>
<cfif Find("_1thumb.jpg", image) gt 0>
<cfset count=count + 1>
</cfif>
</cfloop>

<cfoutput>#count#</cfoutput>

CV said...

Thanks Rupesh, I found that out yesterday. Using the code I originally posted, is there anyway to find the *first* index of a substring of an array using a "partial match" with these java utils?

That is what I am trying to accomplish. Using listtoarray() and listcontains() would be much slower.

Thanks again!

HellP said...

Note that if you create your array using the toArray() java function, you cannot use indexOf() java function anymore.

Ex.:

myArray = aQuery.aColumn.toArray(); // return you all rows corresponding to that column in an array.
findIt = myArray.indexOf('foo'); // Results in a crash -> method indexOf() not found.

Mike Causer said...

// this should help you discover hidden java methods.
// replace target with any type of object.
loc = {};
loc.target = arrayNew(1);
loc.methods = arrayNew(1);

loc.m = loc.target.getClass().getMethods();
for ( loc.i = 1; loc.i lte arrayLen( loc.m ); loc.i++ ) {
arrayAppend( loc.methods, loc.m[loc.i].toString() );
}
structDelete( loc, 'm' );