March 07, 2006

"remote" classpath revisited

i seem to have gotten myself into the habit of calling spike's cool "Loading java class files from a relative path" technique as the "remote classpath" technique--i guess i can blame christian cantrell for that. in any case, this technique works very well in most cases where you don't have access to a server's classpath (most shared hosts for example). where it tends not to work is, from my experience, with java classes that don't have "blind" constructors, ie where no arguments are required to initialize that class. classes like icu4j calendars, formatters, etc. usually work just fine but classes like icu4j's ULocale or MessageFormat don't as these require something to be passed to their constructors. for these classes (which are darned important to me) something like this fails:

<cfscript>
// remote init jarFile=jarLocation & "icu4j.jar";
URLObject = createObject('java','java.net.URL');
URLObject.init("file:" & jarFile);
URLArray = createObject("java","java.lang.reflect.Array").
newInstance(URLObject.getClass(),1);
arrayClass = createObject("java","java.lang.reflect.Array");
arrayClass.set(URLArray,0,URLObject);
loader = createObject("java","java.net.URLClassLoader");
loader.init(URLArray);
uLocale=loader.loadClass("com.ibm.icu.util.ULocale").newInstance();   
</cfscript>
<cfdump var="#uLocale#">


while i've managed to workaround this issue (ULocales are everywhere in icu4j, most classes that deal with locales have a getAvailableULocales() method) it's always kind of nagged at me. after a bit of poking and prodding i started looking into ways to get at the actual constructors for a given class:

// remote init
jarFile=jarLocation & "icu4j.jar";
URLObject = createObject('java','java.net.URL');
URLObject.init("file:" & jarFile);
URLArray = createObject("java","java.lang.reflect.Array").
newInstance(URLObject.getClass(),1);
arrayClass = createObject("java","java.lang.reflect.Array");
arrayClass.set(URLArray,0,URLObject);
loader = createObject("java","java.net.URLClassLoader");
loader.init(URLArray);
uLocale=loader.loadClass("com.ibm.icu.util.ULocale"); // don't init c=uLocale.getConstructors();
for (j=1; j LTE arrayLen(c); j=j+1) {
   params=c[j].getParameterTypes();
   for (i=1; i LTE arrayLen(params); i=i+1) {
      writeoutput("ULocale[#j#]: #i# #params[i].getName()#<br>");
   }
   writeoutput("<br>");
}   
</cfscript>


which in this case returned 3 constructors (just like the API says but not in the javadocs order):

ULocale[1]: 1 java.lang.String ULocale[1]: 2 java.lang.String ULocale[1]: 3 java.lang.String
ULocale[2]: 1 java.lang.String
ULocale[3]: 1 java.lang.String ULocale[3]: 2 java.lang.String
which i can easily match to the one i want (ULocale("th_TH")):

<cfscript>
// remote init jarFile=jarLocation & "icu4j.jar";
URLObject = createObject('java','java.net.URL');
URLObject.init("file:" & jarFile);
URLArray = createObject("java","java.lang.reflect.Array").
newInstance(URLObject.getClass(),1);
arrayClass = createObject("java","java.lang.reflect.Array");
arrayClass.set(URLArray,0,URLObject);
loader = createObject("java","java.net.URLClassLoader");
loader.init(URLArray);
uLocale=loader.loadClass("com.ibm.icu.util.ULocale");   
c=uLocale.getConstructors();
// the newInstance method wants an array
obj=listToArray("th_TH");
// we want the 2nd constructor
thaiLocale=c[2].newInstance(obj.toArray());
</cfscript>

<cfdump var="#thaiLocale#">


which indeed returns an object of com.ibm.icu.util.ULocale.

since in most cases, i only use one way to init a given class, this technique will work OK for us. my only question is will the order of constructors remain the same? can i always count on the 2nd constructor to be ULocale("th_TH")? or should i build metadata functionality to probe the constructors to see which one matches?

ps: i did indeed learn my lesson, notice how i passed the coldfusion array using toArray() ;-)

3 Comments:

At 3/08/2006 12:57 AM, Blogger Jim Collins said...

Please take a look at my JavaClassLoader at http://sourceforge.net/projects/cfsynergy
It does exactly what you're trying to accomplish, I believe. PLease let me know what you think

 
At 3/08/2006 2:02 AM, Blogger Paul Hastings said...

thanks but it seems to blow up when i try using classes that require some kind of argument (line 78).

using


jarDir="c:\JRun4\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\lib\";
cl=createObject("component","cfc.java.javaClassLoader").init();
ulocale=cl.load("icu4j.jar","com.ibm.icu.util.ULocale",jarDir);


got any examples?

 
At 3/10/2006 3:34 AM, Blogger Jim Collins said...

If you look at the useage notes you'll see an example. Email me at jimcollins at gmail.com and I'll help you out, or fix it :)

 

Post a Comment

<< Home