Dynamic casting using List in Java - java

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.

Related

java.lang.InstantiationError when creating instance of static inner class with Objenesis

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;
}

Method.Invoke without knowing the paramaters

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.

Can we get the exact location where the condition fails, in an If case having multiple conditions?

I am new to Java,
Here is my code,
if( a.name == b.name
&& a.displayname == b.displayname
&& a.linkname == b.linkname
......... )
return true;
else
return false;
I will call this method and have to check that all properties of objects 'a' and 'b'.
Each object will have more than 20 properties. So, it is will be tidy if i use if case for each property.
An exception is throwed if the return is false and I have to report which property fails.
Is there any easy method to find where the condition fails within the if case.
Pls help. Ask if you are not clear about the question.
The question is, would you like to continue checking if one of the conditions fails?
You could do something like comparator where you have interface:
public interface IComparator {
boolean compare(YourObject o1, YourObject o2);
String getComparatorName();
}
Next you create set of implementations of that interface:
NameComparator implements IComparator {
private name="Name Comparator";
#Override
public boolean compare(YourObject o1, YourObjecto2) {
return o1.getName().equals(o2.getName());
}
#Override
public String getComparatorName() {
return name;
}
}
Next you store set of these comparators in arrayList and you iterate through them and record which one fails by adding them to some other collection.. Hope that helps!
For instance you create array:
IComparator[] comparators = new IComparator[]{ new NameComparator, new DisplayNameComparator};
List<IComparator> failedComparationOperations = new ArrayList<IComparator>();
for(IComparator currentComparator : comparators) {
if(!currentComparator.compare(o1, o2)) {
failedComparationOperations.add(currentComparator);
}
}
for(IComparator currentComparator: failedComparationOperations)
{
System.out.println("Failed Comparation at: "+currentComparator.getComparatorName());
}
You may use reflection: browse what fields are defined, and check each of them using method equals. Print error message if they're not equal, give summary at the end.
boolean equals = true;
Field[] fields = a.getClass().getDeclaredFields();
for (Field f: fields){
f.setAccessible(true);
try {
if (!f.get(a).equals(f.get(b))){
System.out.println(f.getName() + ": " + f.get(a) + "!="+ f.get(b));
equals = false;
};
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("equals?: " + equals);
If you need to know which of the conditions has failed you should check each of the conditions independently.
It might be a little overkill if you are dealing with this single requirement, but what about the Strategy Design Pattern?
http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism
It should be an interesting option if you have other business rules that you can combine with this check.
If a and b are instances of the same class, let's assume A, and the fields are visible, then you can use reflections:
for (Field f : A.class.getFields()) {
try {
if (!f.get(a).equals(f.get(b))) {
throw new RuntimeException("Field " + f.getName() + " is different.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
Without reflection you can't get maximum conciseness, but the followincg can help you to some extent. Make this kind of class:
class NamedEquals {
final String name;
final Object left, right;
NamedCondition(String name, Object left, Object right) { ...assign them... }
boolean areEqual() { return left.equals(right); }
}
Then make a List<NamedEquals>:
List<NamedEquals> conds = Arrays.asList(
new NamedEquals("name", left.name, right.name),
new NamedEquals("displayname", left. displayname, right.displayname),
...
);
And you can find if some of them fail:
for (NamedEquals eq : conds)
if (!eq.areEqual()) throw new ValidationException(eq.name);
Using a factory method can shorten the construction code:
static NamedEquals eq(String name, Object left, Object right) {
return new NamedEquals(name, left, right);
}
With that you can have
List<NamedEquals> conds = Arrays.asList(
eq("name", left.name, right.name),
eq("displayname", left. displayname, right.displayname),
...
);
How about?
// Adapted from your example:
if(!equalTo(a.name, b.name))
fail("name");
if(!equalTo(a.displayname, b.displayname))
fail("displayname");
... etc ...
...
// Allow for null values.
public boolean equalTo(Object a, Object b) {
return a != null ? a.equals(b) : b == null;
}
public void fail(String which) throws SomeException {
throw new SomeException("Failed on '"+which+"'!");
}
Another possible might be to turn each object into a Map<String,?>, perhaps by adding a Map<String,?> toMap() method to the value object, and implementing this by constructing a new map and dumping the value's fields into it. Then you can get the maps and do equals() on them.

java.reflection.Method.invoke(...) Not re-casting args

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.

Pass derived object into method wanting superclass using java reflection?

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.

Categories

Resources