I am trying out Java Reflection API. I am just fetching the Method objects of any given class into a JComboBox, and on it's itemSelected, creating an interface for the parameters (and of course, a calling object.)
This works fine, no issued.
But on the invokeButton's action, I am trying to invke the selected method with given params.
Initially it said that the param count differed. I was guided by one of my friend saying that the paramVals array has references to actual values, which might be causing problem, may be due to scope. I then started creating new objects of class Object and then assigning them the values. This worked for param count. But now the problem is that the parameters are not type cast properly. Even a String typecast to Object (as it has to be an array of Objects) is not being cast back to String.
The doc says that the invoke method will cast them on it's own and if cast fails, will throw an IllegalArgumentException.
I am not getting what is causing the call of invoke method fails...
Here is the code for the frame:
package nttraining.abhay.reflectiondemo;
//imports go here
public class ReflectionFrame
extends JFrame
implements ActionListener, ItemListener{
JComboBox methods;
JButton invokeButton;
public ReflectionFrame(String title) throws HeadlessException {
super(title);
//Layout components
//adding methods of class String to a combo
Class<String> c = String.class;
Method ml[] = c.getMethods();
for(Method m : ml){
methods.addItem(m);
}
invokeButton.addActionListener(this);
methods.addItemListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(invokeButton)){
Method selected = (Method) methods.getSelectedItem();
Class paramtypes[] = selected.getParameterTypes();
Object paramVals[] = new Object[paramtypes.length];
System.out.println("Method : " + selected.toString());
for(int i=0; i<paramtypes.length; i++){
Object obj = new Object();
obj = paramtypes[i].cast(params[i].getText());
paramVals[i] = obj;
System.out.println("Added " + paramtypes[i].cast(params[i].getText()).toString() + " to params");
}
try {
result.setText(selected.invoke(object.getText(), params).toString());
} catch (Exception ex) {
System.out.println(ex.getClass().getName() + ": " + ex.getMessage());
}
}
}
#Override
public void itemStateChanged(ItemEvent e) {
Method selected = (Method) methods.getSelectedItem();
if(selected==null)
return;
Class paramtypes[] = selected.getParameterTypes();
int paramCount = paramtypes.length;
object = new JTextField();
paramNames = new JLabel[paramCount];
params = new JTextField[paramCount];
panel.removeAll();
panel.setLayout(new GridLayout(paramCount+1, 2));
panel.add(new JLabel("Calling object"));
panel.add(object);
for(int i=0; i<paramCount; i++){
paramNames[i] = (JLabel) panel.add(new JLabel(paramtypes[i].getName()));
params[i] = (JTextField) panel.add(new JTextField());
}
invalidate();
validate();
}
}
A problem I found is in this line:
obj = paramtypes[i].cast(params[i].getText());
cast does not convert objects, it only verfies that the given object is of a certain class. Since you always provide a String.class as parameter (via .getText()), this will fail for anything other then a String type parameter. Even Integer.class to primitive int will fail.
Below a piece of code that demonstrates the cast problem.
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class Q21642768 {
public static void main(String[] args) {
try {
// Calls String.indexOf(str, fromIndex) via reflection.
callStringMethod("Hello reflection world", "reflection", 1);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void callStringMethod(String s, String subString, int startIndex) throws Exception {
Class<String> c = String.class;
Method ma[] = c.getMethods();
Method indexOfSub = null;
Class<?>[] indexOfSubPTypes = null;
List<Method> stringMethods = new ArrayList<Method>();
List<Type[]> stringMethodsPTypes = new ArrayList<Type[]>();
for (Method m: ma) {
stringMethods.add(m);
System.out.print(m.getName() + ": ");
Class<?>[] mptypes = m.getParameterTypes();
stringMethodsPTypes.add(mptypes);
boolean first = true;
for (Type t : mptypes) {
if (first) {
first = false;
} else {
System.out.print(", ");
}
System.out.print(t.toString());
}
if ("indexOf".equals(m.getName())
&& mptypes.length == 2
&& mptypes[0].equals(String.class)
&& mptypes[1].equals(int.class)) {
indexOfSub = m;
indexOfSubPTypes = mptypes;
System.out.println(" <-- ");
} else {
System.out.println();
}
}
if (indexOfSub == null) {
System.out.println("target method not found");
return;
}
Object[] pValues = new Object[2];
pValues[0] = indexOfSubPTypes[0].cast(subString);
// Fails:
// pValues[1] = indexOfSubPTypes[1].cast(startIndex);
// pValues[1] = indexOfSubPTypes[1].cast(startIndex + "");
pValues[1] = startIndex;
Object result = indexOfSub.invoke(s, pValues);
System.out.println("Result: " + result);
}
}
The problem is that all your parameter values are String objects, since you get them with JTextField.getText(). String is the runtime type of these values, whereas the type of the method parameters will generally be different, and this is what matters.
To successfully invoke the method, you will first need to convert each value to the proper type specified in the paramTypes array. Neither cast() nor invoke() are going to do that for you. This means you must find a way to do the conversion from a String, basically deserializing from a String value into an object of the proper class, and that may not always be possible or too complex to do. At this point, I think you can start to imagine the complexity of what you are trying to do. This is very far from trivial. Remember that each parameter value will generally not be a simple value, but rather a full object graph, that's where the complexity is.
For example, if the type of a method parameter is an interface, how will you know which concrete implementation to instantiate? If you do find a concrete class implementing it - and that may not always be possible - how will you create instances of that class? Here you're entering a domain covered by serialization frameworks in Java. There are quite a few of these frameworks that are open source and you might want to take a look at some of them. You will find a comprenhesive list of such fraleworks here.
A few years ago, I worked on a related project, where I had to provide a Swing GUI to enable end-users to create objects of arbitrary types, used as input for a rule engine. What I came up with was a JTree with multiple roots, associated with a property sheet (i.e. a JTable with 2 columns), where the tree leaves were either simple types (primitives, primitive wrappers, Date, etc.) or object references. Each reference would point to a specific tree root representing the actual object to be later instantiated. I don't remember exactly how long it took, just that it took several months to get it tested and working.
So, I don't want to crush your hopes, but you should be aware that it's going to take a huge amount of work to do this.
Related
I am trying to create a utility method that should be able to deep-clone any object.
(Object.clone() only works on Object implementing Cloneable and I heard it's flawed anyways.)
I am using Objenesis to create new instances of objects without the use of constructors.
However, when trying to clone a JFrame I get the following Exception:
(using this class because I think it should be a good and complex test)
java.lang.InstantiationError: [Ljava.util.concurrent.ConcurrentHashMap$Node;
at sun.reflect.GeneratedSerializationConstructorAccessor12.newInstance(Unknown Source)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
at org.objenesis.ObjenesisBase.newInstance(ObjenesisBase.java:73)
I am open to any solution, not necessarily limited to Objenesis.
My Code:
private static ObjenesisStd OBJENESIS = new ObjenesisStd();
#SuppressWarnings("unchecked")
public static <T> T clone(T object, boolean deep){
if(object == null){
return null;
}else{
try {
T clone = (T) OBJENESIS.newInstance(object.getClass());
List<Field> fields = ReflectionUtil.getAllFieldsInHierarchy(object.getClass());
for(Field field : fields){
boolean isAccessible = field.isAccessible();
boolean isFinal = ReflectionUtil.isFinal(field);
field.setAccessible(true);
ReflectionUtil.setFinal(field, false);
Class<?> type = field.getType();
if(!deep || type.isPrimitive() || type == String.class){
field.set(clone, field.get(object));
}else{
field.set(clone, clone(field.get(object), true));
}
field.setAccessible(isAccessible);
ReflectionUtil.setFinal(field, isFinal);
}
return clone;
} catch (Throwable e) {
e.printStackTrace();
//throw new RuntimeException("Failed to clone object of type " + object.getClass(), e);
return null;
}
}
}
public static void main(String[] args) {
GetterSetterAccess access = new GetterSetterAccess(JFrame.class);
JFrame frame = new JFrame("Test Frame");
for(String attr : access.getAttributes()){
System.out.println(attr + " " + access.getValue(frame, attr));
}
System.out.println("----------------------------------------------");
frame = clone(frame, true);
for(String attr : access.getAttributes()){
System.out.println(attr + " " + access.getValue(frame, attr));
}
}
EDIT: Got it to work with the accepted answer and a few more fixes:
Avoided cloning Wrappers of Primitive Types (Integer.class etc.)
Avoided cloning Classes (Objects of the class Class.class)
Stored the cloned objects in a Map and reused them, so if Object A has a reference to Object B and Object B one to Object A it doesn't get stuck in an infinite loop. I also used a Map that checks for exact equality (==) instead of using equals().
Created a custom exception class which would just be passed on instead of throwing a new exception on every level (causing a huge caused-by-depth).
I finally figured it out. Your code doesn't handle arrays. So it fails with instantiating "[Ljava.util.concurrent.ConcurrentHashMap$Node;" which is an array of Nodes.
However, I will advocate that indeed, you should not do that. You will end up with fairly complicated code. Depending on what you want to do, you could use Jackson or XStream to do a marshall / unmarshall to perform the copy.
If you really want to continue that path, you will need something like this after the null check of your clone method.
if(object.getClass().isArray()) {
int length = Array.getLength(object);
Object array = Array.newInstance(object.getClass().getComponentType(), length);
for (int i = 0; i < length; i++) {
Array.set(array, i, clone(Array.get(object, i), true));
}
return (T) array;
}
Hello everyone I'm writing a Java program where I need to copy the values of an old object into a new one (the two must be separated, if I chance one, the other must not be affected);
Once the copy is done the data should be displayed on Jtable, however the 2 object created seems to be linked(if I try to change one the other get modified too)
I suspect the problem is this method :
public void CopiatoreDiArea(Area nuova, Area daCopiare){
nuova.setNome(daCopiare.getNome());
nuova.setInter(daCopiare.getInter());
nuova.setRischioInerente(daCopiare.getRischioInerente());
nuova.setRischioResiduo(daCopiare.getRischioResiduo());
nuova.setControlli(daCopiare.getChecklists());
nuova.setStrategicita(daCopiare.getStrategicita());
nuova.setRischiosita(daCopiare.getRischiosita());
nuova.setMediaHpReato(daCopiare.getMediaHpReato());
nuova.setProbabilitaInerente(daCopiare.getProbabilitaInerente());
nuova.setEsposta(daCopiare.isEsposta());
nuova.setStrumentale(daCopiare.isStrumentale());
nuova.setCommento(daCopiare.getCommento());
nuova.setCondivisa(daCopiare.isCondivisa());
if (daCopiare.getNomeCompleto() != null){
nuova.setNomeCompleto(daCopiare.getNomeCompleto());
}
else{
nuova.setNomeCompleto(daCopiare.getNome());
}
if (daCopiare.getInterCompany() != null){
nuova.setInterCompany(daCopiare.getInterCompany());
}
if (daCopiare.getArticoli() != null || daCopiare.getArticoli().size() != 0){
nuova.setArticoli(daCopiare.getArticoli());
}
}
If this is the wrong way, how can I accomplish that?
How do you create the new instance of nuova object?
You have to make a new instance of it, if you created the new object with the reference of the oldest they will be linked and every change in the one will be reflected in other.
If you call the method as following:
CopiatoreDiArea(new Area(), oldArea)
Your code should work as the new 'Area' has all the attributes the other object has without any references being made to the new Area.
Try this:
public Area CopiatoreDiArea(Area daCopiare){
Area nuova = new Area();
nuova.setNome(daCopiare.getNome());
nuova.setInter(daCopiare.getInter());
nuova.setRischioInerente(daCopiare.getRischioInerente());
nuova.setRischioResiduo(daCopiare.getRischioResiduo());
nuova.setControlli(daCopiare.getChecklists());
nuova.setStrategicita(daCopiare.getStrategicita());
nuova.setRischiosita(daCopiare.getRischiosita());
nuova.setMediaHpReato(daCopiare.getMediaHpReato());
nuova.setProbabilitaInerente(daCopiare.getProbabilitaInerente());
nuova.setEsposta(daCopiare.isEsposta());
nuova.setStrumentale(daCopiare.isStrumentale());
nuova.setCommento(daCopiare.getCommento());
nuova.setCondivisa(daCopiare.isCondivisa());
if (daCopiare.getNomeCompleto() != null){
nuova.setNomeCompleto(daCopiare.getNomeCompleto());
}
else{
nuova.setNomeCompleto(daCopiare.getNome());
}
if (daCopiare.getInterCompany() != null){
nuova.setInterCompany(daCopiare.getInterCompany());
}
if (daCopiare.getArticoli() != null || daCopiare.getArticoli().size() != 0){
nuova.setArticoli(daCopiare.getArticoli());
}
}
And then in your call:
Area nuova = CopiatoreDiArea(daCopiare);
If you copy primitives (or the special primitive wrapper classes) in Java, the runtime will always do a deep copy of the values. To illustrate this, I show a few examples.
See http://jdoodle.com/a/3TL for an online runnable version of the code below:
public class MyClass {
public static void main(String[] args) {
int a = 10;
int b = a;
b = 20;
System.out.println(a + " != " + b);
// We did not change a when changing b.
Integer c = 10;
Integer d = c;
d = 20;
System.out.println(c + " != " + d);
// We did not change c when we changed d, even though they are class instances.
// This shows that primitive wrappers are handled differently due to
// autoboxing.
// See https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
MyInteger e = new MyInteger(10);
MyInteger f = e;
f.value = 20;
System.out.println(e + " == " + f);
// Changing the data contained within f also affected e. The two variables now point to the same instance.
MyInteger g = new MyInteger(10);
MyInteger h = new MyInteger(g);
h.value = 20;
System.out.println(g + " != " + h);
// We prove that we have two instances, initially containing
// the same value but changing their internal values does not
// affect each other.
}
private static class MyInteger {
public int value;
public MyInteger(int value) {
this.value = value;
}
public MyInteger(MyInteger other) {
this.value = other.value;
}
public String toString() {
return Integer.toString(this.value);
}
}
}
From your example, it seems you might be trying to copy complex classes, even lists maybe, and these will always copy a reference only when you use simple assignment.
There are a few options that you could take, for instance the copy constructor that I use above, or alternatively rely on some form of reflection or serialization that does the hard work for you.
Apache Commons Lang has a SerializationUtils class that will run through all internal data in your classes, serialize them into byte format and then try to re-instantiate new versions of the same classes with the serialized data.
The Cloner library makes use of reflection and some special performance cases for known types such as collections and calendars, with some great debugging options.
Declare a copy constructor for deep cloning your object
public class Area {
public Area() { // constructor }
public Area(Area objectToCopy) {
setNome(objectToCopy.getNome());
setInter(objectToCopy.getInter());
setRischioInerente(objectToCopy.getRischioInerente());
setRischioResiduo(objectToCopy.getRischioResiduo());
setControlli(objectToCopy.getChecklists());
setStrategicita(objectToCopy.getStrategicita());
setRischiosita(objectToCopy.getRischiosita());
setMediaHpReato(objectToCopy.getMediaHpReato());
setProbabilitaInerente(objectToCopy.getProbabilitaInerente());
setEsposta(objectToCopy.isEsposta());
setStrumentale(objectToCopy.isStrumentale());
setCommento(objectToCopy.getCommento());
setCondivisa(objectToCopy.isCondivisa());
if (objectToCopy.getNomeCompleto() != null)
setNomeCompleto(objectToCopy.getNomeCompleto());
else
setNomeCompleto(objectToCopy.getNome());
if (objectToCopy.getInterCompany() != null)
setInterCompany(objectToCopy.getInterCompany());
if (objectToCopy.getArticoli() != null || objectToCopy.getArticoli().size() != 0)
setArticoli(objectToCopy.getArticoli());
}
}
and then do it calling
Area newArea = new Area(oldArea);
One of a method in my API, doesn't take Java.lang.Object type as an argument but it takes all the sub types of it as an argument (java.lang.Integer, java.lang.String etc).
Now, I want to store the DataType in a list by doing:
List<Object> listObjects = new ArrayList<Object>();
if(listObjects.get(0) instanceof Integer){
//then do
listDataTypesForCast.add(Integer);
}
so that, I can cast like this:
myMethod((listDataTypesForCast.get(0)"returning Java.lang.Object datatype"))
But, I don't know how to declare my List: listDataTypesForCast, so that I can use it for Casting. Please let me know if you know the answer?
PS: I'm using Apache POI library and setCellValue() method there can't have java.lang.Object as a DataType in arguments, and I can't check the DataType at the time of inserting the value in cell because it is in a loop and it will add too much of boiler plate code.
If you want to call one of many overloads of a single-argument method, but don't know the argument type until run-time, you can do it using reflection.
Here is a helper method for doing that:
private static void call(Object obj, String methodName, Object arg) {
Class<?> argClass = arg.getClass();
// Try simple approach
Method methodToCall;
try {
methodToCall = obj.getClass().getMethod(methodName, argClass);
} catch (#SuppressWarnings("unused") NoSuchMethodException unused) {
methodToCall = null;
}
// Search for method, if simple approach didn't work
if (methodToCall == null) {
List<Method> candidates = new ArrayList<>();
for (Method method : obj.getClass().getMethods()) { // Note: Public methods only
if (method.getParameterCount() == 1 && method.getName().equals(methodName)) {
Parameter parameter = method.getParameters()[0];
if (parameter.getType().isAssignableFrom(argClass))
candidates.add(method);
}
}
if (candidates.isEmpty()) {
throw new NoSuchMethodError(obj.getClass().getName() + '.' +
methodName + '(' + argClass.getName() + ')');
}
if (candidates.size() > 1) {
// Implement extended overload resolution logic, if needed
throw new NoSuchMethodError("Multiple candidates found for parameter type " +
argClass.getName() + ": " + candidates);
}
methodToCall = candidates.get(0);
}
// Call method
try {
methodToCall.invoke(obj, arg);
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (InvocationTargetException e) {
throw new RuntimeException("Checked exception: " + e.getCause(), e);
}
}
Test
public static void main(String[] args) {
Test obj = new Test();
for (Object arg : Arrays.asList("Foo", 42, 42L, 42f, 42d))
call(obj, "myMethod", arg);
}
public void myMethod(String s) {
System.out.println("String: " + s);
}
public void myMethod(Number s) {
System.out.println("Number: " + s);
}
public void myMethod(Long s) {
System.out.println("Long: " + s);
}
Output
String: Foo
Number: 42
Long: 42
Number: 42.0
Number: 42.0
However, my problem can be solved using Reflection (as suggested by Andreas) but, in this particular case I decided to not use reflection extensively because I've a lot of data to write to excel cells and therefore, I used the following approach:
Find out the data type of each data (of type Object) using instanceof, for the first row cells and then saving the result to Enum. Because rest of the rows share the same datatype across the columns.
Now, looping through the Enum and setting the cell values and data type.
I believe instaceof is also using reflection internally but, I just have to use it once.
I am using Method.Invoke in java to dynamically call methods in another class. The only issue is that if the methods have paramaters i need to start that in the class.getDeclaredMethod("method", something.class) or else it wont see those methods. The issue with this that i don't know when calling the methods what the parameters will be. How do I get around this?
Also I have done this in C# and its easy and does not require me to state the parameters but this is in Java.
Here is the code that does the Invoke:
public void DoCommand(String msg){
System.out.println(msg);
String[] temp = msg.split(" ");
String command = temp[0];
Class c = commander.getClass();
try {
Object obj = c.newInstance();
try {
System.out.println("'" + command + "'");
Method method = c.getDeclaredMethod(command);
Object[] pars = new Object[temp.length];
for(int i = 0; i < pars.length; i++){
pars[i] = temp[i + 1];
}
if((String)pars[pars.length - 1] == null){
pars[pars.length - 1] = socket;
}
Parameter[] paramaters = method.getParameters();
Object[] endParameters = AltimitConverter.ConvertParameters(pars, paramaters);
try {
method.invoke(obj, endParameters);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {}
}catch (NoSuchMethodException e){
System.out.println(e.toString());
}
}catch (Exception e){
System.out.println(e.toString());
}
}
So how do I call different methods with different parameters without stating the parameters when getting the method.
This is the C# version that does work:
private static void DoCommand(string msg, Socket soc){
string[] temp = msg.Split (' ');
string command = temp [0];
Type type = commandObject.GetType ();
MethodBase commandFunction = type.GetMethod (command);
if (commandFunction != null) {
object[] pars = new object[temp.Length - 1];
for (int i = 0; i < pars.Length; i++) {
pars [i] = temp [i + 1];
}
if ((string)pars [pars.Length - 1] == "") {
pars [pars.Length - 1] = soc;
}
ParameterInfo[] paramaters = commandFunction.GetParameters ();
object[] endParamaters = AltimitConverter.ConvertParams (pars, paramaters);
if (commandFunction != null) {
try {
commandFunction.Invoke (commandObject, endParamaters);
} catch (Exception e) {
Debug.Log (e);
}
} else {
Debug.Log ("commandFunction is null");
}
}
}
Instances of java.lang.reflect.Method reflect specific methods. Each one is associated with a specific class, has a specific parameter list, and has a specific return type. When method overloading is in play, each of the overloaded methods will be reflected by a distinct Method object; these are not interchangeable.
If you need to account for selecting among overloaded methods, then you can do so only with reference to the number and types of the intended arguments. If you have to rely on the arguments themselves to determine matching parameter types, then you're looking at duplicating Java's method-resolution logic, which is complex.
In the event that you need only worry about looking up a non-overloaded method declared by the subject class itself (i.e. not inherited) then you can invoke getDeclaredMethods() on the Class object and scan the resulting array of Method objects for one with the correct name.
You can go a little way into overloaded methods while preserving your sanity if different overloads are distinguished by different numbers of parameters, or maybe if there are specific limits on the parameter type patterns you need to account for, but at that point you really should be asking yourself whether there's a better way. This kind of design absolutely begs for trouble.
I figured out a solution. I created a function to take the intended parameters which are strings and convert them to a data type depending on if it looks like a float, Integer, or string. then i send that into another method that gets all methods in a class and get the ones with the method name i am trying to call and then gets the one with the data types i had in the object[] returned from the first method. and then i use the method i got and the converted data types to call the method.
EDIT: I wasn't clear. I have to use reflection because I am interpreting from a command line. I am doing the reflection equivalent of the code examples I have provided.
hope this isn't a duplicate since it seems like an everyday thing to want to do.
I have a class A, and a class B that extends A. If I have a method in class C like public void doSomething(A a), how can I use reflection to pass a B object into this function? I want to do the (reflection) equivalent of:
B b = new B(); //B inherits from A
C c = new C();
c.doSomething(b); // method signature is doSomething(A a);
What I have done (using reflection) is:
get the Objects which are the arguments to the function.
get the Classes of the arguments
look up the method based upon the classes of the arguments.
invoke the method, passing in the argument Objects.
This works great if I were going to pass an A object into C.doSomething(...). However, if I am trying to pass a B object into C.doSomething(...) it fails on step 3, with this error:
java.lang.NoSuchMethodException: C.doSomething(B)
What is the appropriate way to get C.doSomething to recognize that B is an A? (when looking up a method using getDeclaredMethod(String name, Class... parameterTypes) and passing B.class in as the parameter type)
EDIT:
I'll post my own solution in case somebody wants to see one quickly hacked way of doing what Roland Illig suggested. In this example I reference these pre-made variables:
String methodToken; //the name of the method
Object obj; //the object whose method we are trying to call
Object[] args; //the user given arguments for the method
Class[] argTypes; //the types of the args gotten by args[i].getClass();
so...
//*** try to get the specified method from the object
Method m = null;
// if we are looking for a no-arg version of the method:
if(null == args)
{
try
{
m = obj.getClass().getMethod(methodToken, argTypes);
}
catch ( /*errors*/ )
{
// do stuff
}
}
else // if we are looking for a version of the method that takes arguments
{
// we have to do this type of lookup because our user arguments could be
// subclasses of the arguments required by the method. getMethod will not
// find a match in that case.
try
{
boolean matchFound = false;
Class c = obj.getClass();
do
{ // for each level in the inheritance hierarchy:
// get all the methods with the right name
//(matching the name that the user supplied for the method)
Method[] methodList = c.getMethods();
ArrayList<Method> matchingMethods = new ArrayList<Method>();
for( Method meth : methodList)
{
if(meth.getName().equals(methodToken))
{
matchingMethods.add(meth);
}
}
// check for a matching method signature
for( Method meth : matchingMethods)
{
// get the types of the arguments the method under
// investigation requires.
Class[] paramList = meth.getParameterTypes();
// make sure the signature has the required number of
// elements. If not, this is not the correct method.
if(paramList.length != args.length)
{
continue;
}
// Now check if each method argument is assignable from the
// type given by the user's provided arguments. This means
// that we are checking to see if each of the user's
// arguments is the same as, or is a superclass or
// superinterface of the type found in the method signature
//(i.e. it is legal to pass the user arguments to this
// method.) If one does not match, then this is not the
// correct method and we continue to the next one.
boolean signatureMatch = false;
for ( int i = 0; i < paramList.length; ++i)
{
if(paramList[i].isAssignableFrom( argTypes[i] ) )
{
signatureMatch = true;
}
else
{
continue;
}
}
// if we matched the signature on a matchingly named
// method, then we set the method m, and indicate
// that we have found a match so that we can stop
// marching up the inheritance hierarchy. (i.e. the
// containing loop will terminate.
if(true == signatureMatch)
{
m = meth;
matchFound = true;
break;
}
}
// move up one level in class hierarchy.
c = c.getSuperclass();
}
while(null != c && false == matchFound);
}
catch( /*errors*/)
{
// do stuff
}
}
// check that m got assigned
if(null == m)
{
System.out.println("From DO: unable to match method");
return false;
}
// try to invoke the method !!!!
try
{
m.invoke(obj, args);
}
catch ( /* errors */ )
{
// do stuff
}
Hope it will help someone sometime!
You need to follow the same process as outlined in the Java Language Specification, section 15.12 "Method Invocation Expressions", for finding the same method that would be found at compile time. In short, it's more complicated than you think.
A simple variant would be to check all the methods with the correct name (and don't forget the methods of all superclasses). For each of these methods, check whether all of your arguments are assignment-compatible to the corresponding method parameter. That might not be perfect, but works in most cases.
[Update:] The "simple variant" fails when there are multiple overloaded methods in a class. Here is some example code that you can play with:
package so7691729;
import static org.junit.Assert.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class MethodCaller {
private boolean isCompatible(Method m, Object... args) {
Class<?>[] parameterTypes = m.getParameterTypes();
if (parameterTypes.length == args.length) {
for (int i = 0; i < args.length; i++) {
if (args[i] != null) {
if (!parameterTypes[i].isAssignableFrom(args[i].getClass())) {
// TODO: make primitive types equivalent to their boxed types.
return false;
}
}
}
} else {
// TODO: maybe handle varargs methods here
return false;
}
return true;
}
public Object call1(String fullyQualifiedMethodName, Object obj, Object... args) throws ClassNotFoundException, IllegalAccessException,
InvocationTargetException {
int lastDot = fullyQualifiedMethodName.lastIndexOf(".");
String className = fullyQualifiedMethodName.substring(0, lastDot);
String methodName = fullyQualifiedMethodName.substring(lastDot + 1);
Class<?> clazz = Class.forName(className);
for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
Set<String> sameNameMethods = Sets.newTreeSet();
Map<String, Method> compatibleMethods = Maps.newTreeMap();
for (Method method : c.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
sameNameMethods.add(method.toString());
if (isCompatible(method, args)) {
compatibleMethods.put(method.toString(), method);
}
}
}
if (compatibleMethods.size() > 1) {
throw new IllegalArgumentException("Multiple candidates: " + compatibleMethods.keySet());
}
if (compatibleMethods.size() == 1) {
return compatibleMethods.values().iterator().next().invoke(obj, args);
}
if (!sameNameMethods.isEmpty()) {
throw new IllegalArgumentException("Incompatible types for " + sameNameMethods);
}
}
throw new IllegalArgumentException("No method found.");
}
public Object call(String fullyQualifiedMethodName, Object obj, Object... args) {
try {
return call1(fullyQualifiedMethodName, obj, args);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(e);
}
}
public String str(Object obj) {
return "object " + obj;
}
public String str(String str) {
return "string " + str;
}
public int add(int a, int b) {
return a + b;
}
#SuppressWarnings("boxing")
public int addObj(Integer a, Integer b) {
return a + b;
}
private void assertCallingError(String msg, String methodName, Object obj, Object... args) {
try {
call(methodName, obj, args);
fail();
} catch (IllegalArgumentException e) {
assertEquals(msg, e.getMessage());
}
}
#SuppressWarnings("boxing")
#Test
public void test() {
MethodCaller dummy = new MethodCaller();
assertEquals("object 1", call("so7691729.MethodCaller.str", dummy, 1));
assertCallingError("Multiple candidates: " + //
"[public java.lang.String so7691729.MethodCaller.str(java.lang.Object), " + //
"public java.lang.String so7691729.MethodCaller.str(java.lang.String)]", //
"so7691729.MethodCaller.str", dummy, "str");
assertCallingError("Incompatible types for [public int so7691729.MethodCaller.add(int,int)]", "so7691729.MethodCaller.add", dummy, 3, 4);
assertEquals(7, call("so7691729.MethodCaller.addObj", dummy, 3, 4));
assertCallingError("Incompatible types for [public int so7691729.MethodCaller.addObj(java.lang.Integer,java.lang.Integer)]", "so7691729.MethodCaller.addObj", dummy, "hello", "world");
}
}
And maybe the Java Beans specification or implementation has something for you. They may have had the same problem to solve. Or look at Rhino, a JavaScript implementation in pure Java. It lets you call Java methods directly from JavaScript code, so that is very similar to your problem.
3) look up the method based upon the classes of the arguments
You are asking the class: "Do you have any method with exactly this signature?" The class says "No!" You are not asking "Class, do you have something I can call with these parameters?" As already mentioned, this is not easy to answer as soon as inheritance and overloaded methods are involved and so the complete Reflection API does not address this issue.
However: You are not the first who wants a usable answer to the second question. Perhaps the MethodUtils.invokeMethod or any sibling from the Apache Commons Beanutils project is suitable for you.