views:

85

answers:

3

I'm writing a reflection-based RPC service that gets arguments passed in via a variety of mechanisms. Sometimes the arguments correctly match the parameter type, sometimes they're always strings, and sometimes they're wrapped up in dynamically typed "scripty" objects that need the appropriate value extracted out.

Before I can call method.invoke, I need to build the argument list, something like this:

Object a[] = new Object[method.parameterClasses.length];
for (int i = 0; i < a.length; ++i)
{
   a[i] = prepare(method.parameterClasses[i], rpc.arguments[i]);
}

The "prepare" method looks something like:

Object prepare(Class clazz, Object o)
{
   if (o == null) return null;
   if (clazz == o.getClass()) return o;
   if (clazz == String.class) return o.toString();

   // skip a bunch of stuff for converting strings to dates and whatnot

   // skip a bunch of stuff for converting dynamic types

   // final attempts:
   try
   {
       return clazz.cast(o);
   }
   catch (Exception e)
   {
       return o;     // I give up.  Try the invoke and hope for the best!
   }


}

During unit testing, I was recently rather surprised to discover that a method passed a boxed Integer that expected a primitive long was actually failing the cast and falling through the bottom, and then being properly converted by something during the invoke(). I'd assumed the call to "cast" would do the trick. Is there any way to explicitly perform and check the argument conversion done normally by invoke?

Lacking that, I thought about putting in explicit checks for numeric types, but the number of permutations seemed out of hand. When I add support for extracting numbers from the script dynamic types and converting strings, it gets even worse. I envision a bunch of conditional typechecks for each possible numeric target class, with Integer.decode, Long.decode etc for the String arguments, Short.decode, and Number.intValue, Number.longValue, etc. for Numbers.

Is there any better way to do this whole thing? It seemed like a good approach at first, but it's getting pretty yucky.

+1  A: 
erickson
Constrained sensible conversions are my goal, but I'd argue that numeric types are one area that it should be as flexible as possible, and just managing those is getting out of hand!
Jolly Roger
BTW, thanks for that idea... I'm looking at the JSPEL rules.. and sure enough, it's a long annoying list of conditional conversions. Sigh. Oh well, I probably could have written it in the time it took me to type up this question. :-)
Jolly Roger
A: 

There are at least a few shortcuts you can take to simplify your code:

  • If the parameter is an instance of java.lang.Number and the argument type is either byte, short, int, long, float or double (either as primitive, or as wrapper class), you can use the methods in Number like byteValue(), shortValue(), etc to convert the parameter.

  • If the parameter is an instance of java.lang.String and the argument type is one of the above mentioned, you can use the static valueOf(String) method in the respective class for conversion.

Converting further, and perhaps even proprietary types would of course need more work. If it's really necessary and your prepare method is getting to large, you should perhaps consider abstracting the conversion behind an interface and allowing pluggable conversion providers to be dynamically added to the application?

jarnbjo
+1  A: 

It is indeed surprising, but that is the current behavior. See bug 6456930.

In terms of a better way to approach the problem, at its core, no you will have to define all of those rules. There are better (more maintainable) patterns that a series of if's, though. For starters, I'd have some strategy objects that can be invoked for one side of those objects (probably the class side) so that you look up the appropriate object (say from a Map) based on the class, and then you can limit your conversions to one side (each object would be concerned about how to get things into that particular class). That way when there is a new kind of class conversion that you need to support, you can write an object for it, test it separately and just plug it into the map without changing further code.

Yishai