getMethod() works and works not


A

Alexander Burger

Hi all,

can anybody explain why the getMethod() call (in the line commented with
"???" below) throws a NoSuchMethodException?

Perhaps I'm doing something obvious wrong, but I couldn't find any hint.

################################################################
import javax.swing.*;
import java.lang.reflect.*;

public class F {
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Title");
JPanel panel = (JPanel)frame.getContentPane();
JTextArea area = new JTextArea(10, 40);
Method method;

frame.setSize(300, 200);
frame.setLocation(200, 200);

// This works ('add' JTextArea to JPanel):
panel.add(area);

// This, however, does not work (same 'add' JTextArea to JPanel):
// ??? method = panel.getClass().getMethod("add", area.getClass());

// This again works:
method = panel.getClass().getMethod("setName", "newName".getClass());

frame.setVisible(true);
}
}
################################################################

The direct call

panel.add(area);

works, but the corresponding getMethod()

method = panel.getClass().getMethod("add", area.getClass());

throws

NoSuchMethodException: javax.swing.JPanel.add(javax.swing.JTextArea)

getMethod() on another method like "setName" with a string type works.

Both methods - add() and setName() - should be inherited from the
superclasses of JPanel. They are 'public' in Container and/or Component.

What is the difference, or what am I doing wrong?

Cheers,
- Alex
 
Ad

Advertisements

A

Aeris

Alexander said:
What is the difference, or what am I doing wrong?

«add» prototype is
public Component add(Component comp)
So you have to do
method = panel.getClass().getMethod("add", Component.class);

your code searches for a
public Component add(*JTextArea* comp)
which doesn't exist
 
A

Alexander Burger

Aeris said:
«add» prototype is
public Component add(Component comp)
So you have to do
method = panel.getClass().getMethod("add", Component.class);

Thanks, Aeris.

However, this was just an example. The actual code is generic, so I
can't pass a specific class. I have to call getClass() on the object,
right?

And, according to the references, getMethod() searches the class and
superclasses for a matching method. This seems not to work here.

Cheers,
- Alex
 
A

Aeris

Alexander said:
However, this was just an example. The actual code is generic, so I
can't pass a specific class. I have to call getClass() on the object,
right?

No
Even your code is generic, this is always a Component, and nothing else
And, according to the references, getMethod() searches the class and
superclasses for a matching method. This seems not to work here.

Search for a method in hierarchy, not for a parameter.

class U

class V extends U

class A
void bar(U u)

class B extends A


B.getMethod("bar", U.class) returns the A.bar(U) by inheritance
But B.getMethod("bar", V.class) and A.getMethod("bar", V.class) fail,
because there is no B.bar(V) or A.bar(V)

But this is not a problem because this code works as expected:
B.getMethod("bar", U.class).invoke(new V())
and call the A.bar(U) with a V parameter, because V is an U by inheritance
 
M

markspace

Thanks, Aeris.

However, this was just an example. The actual code is generic, so I
can't pass a specific class. I have to call getClass() on the object,
right?

And, according to the references, getMethod() searches the class and
superclasses for a matching method. This seems not to work here.


This could still be made to work, even for the generic case. Can you
tell us more about the context? There's a short cut if you are using
the Proxy class, otherwise you'll have to go about it the longer way.
 
M

Mike Schilling

markspace said:
This could still be made to work, even for the generic case. Can you tell
us more about the context? There's a short cut if you are using the Proxy
class, otherwise you'll have to go about it the longer way.

To begin with, why is the OP using reflection at all? It's of no value in
the code actually posted, which could simply call method the normal way.
It's fairly uncommon for reflection to be needed, and it may be that the
best solution to the problem (whatever that turns out to be) won't require
it.
 
Ad

Advertisements

A

Alexander Burger

Mike Schilling said:
To begin with, why is the OP using reflection at all? It's of no value in
the code actually posted, which could simply call method the normal way.

Sure, but this is just a prepared, reproducible example.

The actual code is the Java version of PicoLisp. The plain runtime
system ist at "http://software-lab.de/ersatz.tgz", the full system
including sources at "http://software-lab.de/picoLisp.tgz".

There is a generic 'java' function which allows to call arbitrary
constructors and methods, on arbitrary objects. And, this indeed works
most of the cases, but sometimes (like here for 'JPanel' and 'add') not.

The lisp-level function 'java' accepts four syntax patterns:

(java 'obj ['cnt]) -> any
Converts a Java object back to Lisp data

(java 'obj 'msg 'any ..) -> obj
Invokes a method 'msg' on an object 'obj' with arguments 'any'

(java 'cls 'msg 'any ..) -> obj
Invokes a static method 'msg' in class 'cls' with arguments 'any'

(java 'cls 'T 'any ..) -> obj
Returns a new object of class 'cls' by calling the constructor of
that class with arguments 'any'.

For example, this works (analog to the posted plain Java example):

(setq
frame (java "javax.swing.JFrame" T "Title")
panel (java frame "getContentPane")
area (java "javax.swing.JTextArea" T 10 40) )

(java frame "setSize" 300 200)
(java frame "setLocation" 100 100)

# ??? (java panel "add" JTextArea)

(java frame "setVisible" T)

Just the line with "???" does not, and this is the case which also has
"???" in the original post.

Cheers,
- Alex
 
A

Alexander Burger

Hi markspace,
This could still be made to work, even for the generic case. Can you
tell us more about the context? There's a short cut if you are using
the Proxy class, otherwise you'll have to go about it the longer way.

How would that longer way look like?

About the context I just wrote in another reply, so I don't repeat it
here.

There must be a way, because the combination of Java compiler and
runtime system does exactly the same.

Cheers,
- Alex
 
A

Alexander Burger

Hi Aeris,

class U

class V extends U

class A
void bar(U u)

class B extends A


B.getMethod("bar", U.class) returns the A.bar(U) by inheritance
But B.getMethod("bar", V.class) and A.getMethod("bar", V.class) fail,
because there is no B.bar(V) or A.bar(V)

I understand. But in my case I have no class at all. Just an object and
a method name.

From the reference I expect that getMethod() is able by searching up the
class hierarchy to locate a method matching the signature (i.e. the
array of classes passed in the second argument to getMethod()). Otherwise
it would be pretty useless.

Cheers,
- Alex
 
A

Alexander Burger

Alexander Burger said:
For example, this works (analog to the posted plain Java example):

(setq
frame (java "javax.swing.JFrame" T "Title")
panel (java frame "getContentPane")
area (java "javax.swing.JTextArea" T 10 40) )

(java frame "setSize" 300 200)

This for example is an interesting case.

As I said, it works, and in effect it calls

getMethod("setSize", java.lang.JFrame)

But the class 'JFrame' doesn't have 'setSize' defined, it inherits it
from java.awt.Window. Why does it work here, but not in the following
case?
# ??? (java panel "add" JTextArea)

Cheers,
- Alex
 
A

Alexander Burger

Alexander Burger said:
This for example is an interesting case.

As I said, it works, and in effect it calls

getMethod("setSize", java.lang.JFrame)

Oops, it is

frame.getClass().getMethod("setSize", Integer.TYPE, Integer.TYPE)

to be exact.
 
Ad

Advertisements

M

Mike Schilling

Alexander Burger said:
Hi Aeris,



I understand. But in my case I have no class at all. Just an object and
a method name.

From the reference I expect that getMethod() is able by searching up the
class hierarchy to locate a method matching the signature (i.e. the
array of classes passed in the second argument to getMethod()). Otherwise
it would be pretty useless.

The precise search used by the compiler to find a matching method is
documentedn the JLS at
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.
As you'll see it's*very* complicated, and getMethod() doesn't even try to
replicate it. Rather, getMethod() assumes you've given it the genuine
parameter types (not subclasses thereof), and looks up the hierarchy for an
exact match.

You could probably get what you want with a small subset of the logic in
15.12, e.g.:

0. Given the object O you want to call the method on, the method name M,
and a list of arguments A:
1. Call O.getClass().getMethods() to find all the public methods callable on
O.
2. Reject the ones not named M
3. Reject the ones with the wrong number of arguments. (Be careful here if
you're going to support variable-argument methods)
4. If more than one remains, check which have compatible argument types
5. If exactly one remains, call it. Otherwise throw an exception.
 
M

markspace

Sure, but this is just a prepared, reproducible example.

The actual code is the Java version of PicoLisp. The plain runtime
system ist at "http://software-lab.de/ersatz.tgz", the full system
including sources at "http://software-lab.de/picoLisp.tgz".


I tend to agree with Mike, nevertheless. This kind of reflection
binding is something only an author could love. Static types and strong
typing are too valuable to give up.

The only way to do this is to search the method signatures. Probably
something you could have done, but I promised help, so here it is. Note
this is a first fit algorithm, not a best fit. If there are multiple
matches for signatures (i.e., the method name is overloaded) we take the
first one that works.


package test;

import javax.swing.*;
import java.lang.reflect.*;

public class F
{

public static void main( String[] args )
throws Exception
{
JFrame frame = new JFrame( "Title" );
JPanel panel = (JPanel) frame.getContentPane();
JTextArea area = new JTextArea( 10, 40 );

frame.setSize( 300, 200 );
frame.setLocation( 200, 200 );

// This works ('add' JTextArea to JPanel):
panel.add( area );

// This, however, does not work (same 'add' JTextArea to JPanel):
//??? method = panel.getClass().getMethod("add",area.getClass());
exeMethod( panel, "add", area );

frame.setVisible( true );
}

public static Object exeMethod( Object o, String method,
Object... params )
throws NoSuchMethodException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
Object retVal = null;
Method[] methodList;
methodList = o.getClass().getMethods();
looking: // looking for methods
for( Method m : methodList ) {
if( m.getName().equals( method ) ) {
Class<?>[] paramTypes = m.getParameterTypes();
if( paramTypes.length == params.length ) {
for( int i = 0; i < paramTypes.length; i++ ) {
Class<?> class1 = paramTypes;
if( !(class1.isInstance( params )) ) {
continue looking;
}
}
// all parameters check out here:
System.err.println( "Calling: " + m );
retVal = m.invoke( o, params );
}
}
}
return retVal;
}
}
 
M

markspace

5. If exactly one remains, call it. Otherwise throw an exception.


This is a good idea and something I might want to think about
implementing in my example. Hmm....
 
M

markspace

Done.

package test;

import javax.swing.*;
import java.lang.reflect.*;

public class F
{

public static void main( String[] args )
throws Exception
{
JFrame frame = new JFrame( "Title" );
JPanel panel = (JPanel) frame.getContentPane();
JTextArea area = new JTextArea( 10, 40 );

frame.setSize( 300, 200 );
frame.setLocation( 200, 200 );

// This works ('add' JTextArea to JPanel):
panel.add( area );

// This, however, does not work (same 'add' JTextArea to JPanel):
// ??? method = panel.getClass().getMethod("add",area.getClass());
exeMethod( panel, "add", area );

frame.setVisible( true );
}

public static Object exeMethod( Object o, String method, Object...
params )
throws NoSuchMethodException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
Method[] methodList;
methodList = o.getClass().getMethods();
Method invokeMethod = null;
looking: // looking for methods
for( Method m : methodList ) {
if( m.getName().equals( method ) ) {
Class<?>[] paramTypes = m.getParameterTypes();
if( paramTypes.length == params.length ) {
for( int i = 0; i < paramTypes.length; i++ ) {
Class<?> class1 = paramTypes;
if( !(class1.isInstance( params )) ) {
continue looking;
}
}
// all parameters check out:
System.err.println( "Canidate: " + m );
if( invokeMethod == null ) {
invokeMethod = m;
} else {
// more than one canidate, bail
throw new IllegalArgumentException( method+
" has multiple canidates, cannot resolve.");
}
}
}
}
return invokeMethod.invoke( o, params );
}
}
 
T

Tom Anderson

Hi markspace,


How would that longer way look like?

About the context I just wrote in another reply, so I don't repeat it
here.

There must be a way, because the combination of Java compiler and
runtime system does exactly the same.

One option would be to sidestep the problem of reproducing what the
compiler does, and just use the compiler. Could you generate little source
fragments at runtime with the relevant inputs, then compile them to
bytecode on the fly? If you had an interface like:

public interface Invoker {
public Object invoke(Object receiver, Object... params);
}

Then when you're in the situation that the receiver is a
javax.swing.JPanel (i'm guessing here), the method is called "add", and
there is one parameter, a javax.swing.JTextArea, you would generate code
like this (from a very simple template - note that all the types are based
on the runtime class of the parameters you happen to have at the time,
which is easily determined):

class javax$swing$JPanel_add_javax$swing$JTextArea implements Invoker {
public Object invoke(Object receiver, Object... params) {
javax.swing.JPanel castReceiver = (javax.swing.JPanel)receiver;
if (params.length != 1) throw new IllegalArgumentException(); // or something more appropriate
javax.swing.JTextArea param0 = (javax.swing.JTextArea)params[0];
// you also need to do something about exceptions
return castReceiver.add(param0);
}
}

Compile it, put the generated class file on your classpath, then load it
and use it. You would keep a global cache of generated invokers, and reuse
them rather than generating them afresh where you could.

tom
 
Ad

Advertisements

M

Mike Schilling

Tom Anderson said:
Hi markspace,


How would that longer way look like?

About the context I just wrote in another reply, so I don't repeat it
here.

There must be a way, because the combination of Java compiler and runtime
system does exactly the same.

One option would be to sidestep the problem of reproducing what the
compiler does, and just use the compiler. Could you generate little source
fragments at runtime with the relevant inputs, then compile them to
bytecode on the fly? If you had an interface like:

public interface Invoker {
public Object invoke(Object receiver, Object... params);
}

Then when you're in the situation that the receiver is a
javax.swing.JPanel (i'm guessing here), the method is called "add", and
there is one parameter, a javax.swing.JTextArea, you would generate code
like this (from a very simple template - note that all the types are based
on the runtime class of the parameters you happen to have at the time,
which is easily determined):

class javax$swing$JPanel_add_javax$swing$JTextArea implements Invoker {
public Object invoke(Object receiver, Object... params) {
javax.swing.JPanel castReceiver = (javax.swing.JPanel)receiver;
if (params.length != 1) throw new IllegalArgumentException(); // or
something more appropriate
javax.swing.JTextArea param0 = (javax.swing.JTextArea)params[0];
// you also need to do something about exceptions
return castReceiver.add(param0);
}
}

Compile it, put the generated class file on your classpath,

Or use a custom classloader that knows about all the invokers you've
generated. It could probably also be the cache you describe below.
then load it and use it. You would keep a global cache of generated
invokers, and reuse them rather than generating them afresh where you
could.

Clever. I think Invoker.invoke() throws InvocationException, by the way.
 
A

Alexander Burger

Thanks Mike!

Mike Schilling said:
http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.
...
0. Given the object O you want to call the method on, the method name M,
and a list of arguments A:
1. Call O.getClass().getMethods() to find all the public methods callable on
O.
2. Reject the ones not named M
3. Reject the ones with the wrong number of arguments. (Be careful here if
you're going to support variable-argument methods)

I see. My assumption that getMethod() handles the lookup more
dynamically was wrong.
 
A

Alexander Burger

Thanks markspace!

markspace said:
The only way to do this is to search the method signatures. Probably
something you could have done, but I promised help, so here it is. Note

That's very nice! You saved me a lot of time digging and experimenting.
this is a first fit algorithm, not a best fit. If there are multiple
matches for signatures (i.e., the method name is overloaded) we take the
first one that works.

Thanks also for the other post checking for multiple candidates! For now
I went with the first-fit way. Also, I use isAssignableFrom() instead of
isInstance(), because primitive type arguments must also be handled, and
an array of classes is already prepared by the interpreter. Constructors
are handled in a similar way.
 
Ad

Advertisements

A

Alexander Burger

Tom Anderson said:
One option would be to sidestep the problem of reproducing what the
compiler does, and just use the compiler. Could you generate little source
fragments at runtime with the relevant inputs, then compile them to
bytecode on the fly?

Yes, good idea. PicoLisp does similar things in the C and asm versions
calling the C compiler dynamically. I'll keep that option in mind.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top