JSR 292 Goodness: named parameters
Today, I want to show you a way to implement method invocation with named parameters using JSR 292.
But before using JSR 292 API, we need a way to reflect the parameter names of any existing methods.
The problem is that java.lang.reflect doesn't provide any way to get those parameter names,
so I had to first write a small class that does reflection of parameter names using my second favorite API, ASM.
public static List<methodinfo><MethodInfo> getMethods(final Class<?> clazz) throws IOException {<br /> String className = clazz.getName().replace('.', '/') + ".class";<br /> final ClassLoader classLoader = clazz.getClassLoader();<br /> <br /> final ArrayList<methodinfo> methods = new ArrayList<>(); // thanks Joe<br /> try(InputStream input = (classLoader != null)? // thanks Joe<br /> classLoader.getResourceAsStream(className):<br /> ClassLoader.getSystemResourceAsStream(className)) {<br /> <br /> ClassReader reader = new ClassReader(input);<br /> reader.accept(new EmptyVisitor(){<br /> @Override<br /> public MethodVisitor visitMethod(final int access, final String name, String desc, String signature, String[] exceptions) {<br /> final MethodType methodType = MethodType.fromMethodDescriptorString(desc, classLoader);<br /> int parameterCount = methodType.parameterCount();<br /> final String[] parameterNames = new String[parameterCount];<br /> <br /> if (parameterCount == 0) { // shortcut for method with no parameter<br /> methods.add(new MethodInfo(access, clazz, name, methodType, parameterNames));<br /> return null;<br /> }<br /> <br /> return new EmptyVisitor() {<br /> @Override<br /> public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {<br /> if (index>= parameterNames.length || parameterNames[index] != null) {<br /> return;<br /> }<br /> parameterNames[index] = name;<br /> }<br /> <br /> @Override<br /> public void visitEnd() {<br /> for(int i=0; i<parameterNames.length; i++) {<br /> if (parameterNames[i] == null) {<br /> parameterNames[i] = "args" + i;<br /> }<br /> }<br /> <br /> methods.add(new MethodInfo(access, clazz, name, methodType, parameterNames));<br /> }<br /> };<br /> }<br /> }, ClassReader.SKIP_FRAMES);<br /> <br /> return methods;<br /> }<br /></methodinfo></methodinfo>
This code is not very efficient, it will be more efficient to lookup and decode the code attribute LocalVariableTable, anyway it does the job.
Otherwise, I think my co-worker had serious doubt about my mental sanity because I thank Joe loudly (the sound has to cross the Atlantic ocean) every 20 lines of codes,
his project Coin really simplifies my day to day job. We should start a Joe Thanker Club !
Named parameters
Now, let suppose I want to resolve MyClass.foo(s: "eleven", i:7) knowing that foo is declared like that:
public static String foo(int i, String s) {
return i + s;
}
One can notice that the names of the parameters are constant. The permutation can't be processed at compile time because we want that the declaration can be modified and recompiled independently of its usage.
Also instead of doing the permutation at each call, we can process the corresponding permutation of parameters once at linked time. Let's implement that with invokedynamic.
Calling foo is equivalent to this invokedynamic call
invokedynamic [#bsm, MyClass.class, "s", "i"] foo("eleven", 7)
but because Java the language has no syntax for invokedynamic, I will use the class DynamicIndy that I had previously introduced. I've just updated it to use a newer beta of ASM4.
MethodHandle mh = new DynamicIndy().invokeDynamic("foo",
MethodType.methodType(String.class, String.class, int.class),
Main.class,
"bsm",
MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Object[].class),
Main.class, "s", "i");
System.out.println((String)mh.invokeExact("eleven", 7));
Writting the boostrap method is straightforward knowing that Methodhandles has a method permuteArguments. To avoid to do the reflection more than once by class. The result of a reflection is cached.
Instead of using a homemade concurrent WeakHashMap and to try to workaround its defects (4429536, 6389107),
I use a new class introduced by JSR 292, <tt>ClassValue</tt> that safetly associates to a class a value computed on demand and cached.
private final static ClassValue<HashMap<String, MethodInfo>> methodsClassValue =
new ClassValue<>() { // thanks Joe
@Override
protected HashMap<String, MethodInfo> computeValue(Class<?> type) {
List<methodinfo> methods;<br /> try {<br /> methods = MethodInfo.getMethods(type);<br /> } catch(IOException e) {<br /> throw (LinkageError)new LinkageError().initCause(e);<br /> }<br /> HashMap<String, MethodInfo> methodMap = new HashMap<>(); // thanks Joe<br /> for(MethodInfo method: methods) {<br /> methodMap.put(method.getName(), method);<br /> }<br /> return methodMap;<br /> }<br /> };<br /> <br /> public static CallSite bsm(Lookup lookup, String name, MethodType methodType, Object... bsmArgs) throws Throwable {<br /> HashMap<String, MethodInfo> methodMap = methodsClassValue.get((Class<?>)bsmArgs[0]);<br /> MethodInfo method = methodMap.get(name);<br /> if (method == null) {<br /> throw new InvokeDynamicBootstrapError("no method "+name+" found");<br /> }<br /> <br /> int parameterCount = method.getParameterCount();<br /> if (parameterCount != methodType.parameterCount()) {<br /> throw new InvokeDynamicBootstrapError("wrong number of parameters "+methodType+ " for method "+method);<br /> }<br /> <br /> HashMap<String,Integer> parametersMap = new HashMap<>(); // thanks Joe<br /> for(int i=0; i < parameterCount; i++) {<br /> parametersMap.put(method.getParameterName(i), i);<br /> }<br /> <br /> int[] permutation = new int[parameterCount];<br /> for(int i=0; i < parameterCount; i++) {<br /> Object parameterName = bsmArgs[i + 1];<br /> Integer slot = parametersMap.get(parameterName);<br /> if (slot == null) {<br /> throw new InvokeDynamicBootstrapError("unknown parameter name "+parameterName);<br /> }<br /> permutation[i] = slot;<br /> }<br /> <br /> MethodHandle mh = MethodHandles.permuteArguments(<br /> method.asMethodHandle(lookup), methodType, permutation);<br /> return new ConstantCallSite(mh);<br /> }<br /></methodinfo>
The code is freely available, see the attachment.
That's all folks, at least for today,
Rémi
- Login or register to post comments
- Printer-friendly version
- forax's blog
- 1359 reads






Comments
JSR 292 Goodness: named
by olefevre - 2011-01-23 09:31
Are you aware that both bugs are now marked as "not available"? Are they security-related?
JSR 292 Goodness: named
by forax - 2011-01-24 07:37
Weird, these bugs aren't, as far as I know, related to some security stuff.
There is a ghost in the shell or a bug in the bug database.
Rémi
JSR 292 Goodness: named
by olefevre - 2011-01-24 19:56
Or maybe oracle really is evil? Unlike many people I gave it the benefit of doubt so far but if it starts futzing with the immensely useful bugs database, esp. in such a sneaky way, I'm going to have to reconsider. You are thick with the core Java team at Oracle; maybe you can ask them what's going on?
JSR 292 Goodness: named
by forax - 2011-01-25 08:17
I can access to bug 4429536 now.
It's more a db failure than a new policy in my opinion but I will ask.
Rémi
JSR 292 Goodness: named
by olefevre - 2011-01-29 12:14
Indeed that works once again. False alarm (this time, at any rate).
// thanks Rémi
by tranehead - 2011-01-21 14:06
// thanks Rémi
// thanks Rémi
by forax - 2011-01-22 07:08
:))