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.
Related
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 have implemented simple interpreted language with dynamic typing in Java. Unfortunately I ran into the following problem. When testing the following code:
def main() {
def ks = Map[[1, 2]].keySet();
return ks.size();
}
I stumbled upon the following exception:
java.lang.IllegalAccessException: class is not public: java.util.HashMap$KeySet.size()int/invokeSpecial
Of course This is true and caused by the fact that HashMap$KeySet class has "package" visibility. This means that when I call it's "size()" method, I call method from class that is not visible to my code. Java avoids this problem easily - method keySet() returns value of type Set, so method size() used is declared in public and abstract class "Set".
My question is: does anyone has an idea, how this should be handled in generic way? By "general" case I mean not only this simple case, where I can walk through whole inheritance chain and find "first declaration" of this method, but also pathological cases like the following:
interface I1 {
public void foo();
}
interface I2 {
public void foo();
}
interface I3 {
public void foo();
}
class C implements I1, I2, I3 {
public void foo() { .... }
}
My current impression is that I could ignore those pathological cases and select any matching method on the grounds that if such object exists, then it's creation was successful, so it's compilation was successful, so all these methods have identical signatures and since in Java there is no way to specify different implementations of these methods depending on how object is viewed (as I1, I2 or I3), then result will be always the same.
Any help will be appreciated.
Ok, so here is my solution. It isn't very nice, but hey, whatever works:
public static Method findMethod(Class<?> cls, String name, Class<?>[] fa) {
System.out.println("Checking class " + cls + " for method " + name);
// since it is called recursively, we want to stop some day, and when we are
// passed null (so most getSuperclass was called on Object.class or something similar)
if (cls == null) {
return null;
}
Method m = null;
if ((m = findMethod(cls.getSuperclass(), name, fa)) != null) {
return m;
}
// ok, if we're here, then m is null. so check if cls is public. it must be public, because
// otherwise we won't be able to call it - we are definitely in different package. if class
// isn't public, then check interfaces.
if (!Modifier.isPublic(cls.getModifiers())) {
System.out.println("Class is not public, and superclasses do not contain method " + name);
System.out.println("Checking all interfaces");
for (Class<?> iface: cls.getInterfaces()) {
if ((m = findMethod(iface, name, fa)) != null) {
return m;
}
}
}
return findMethodInClass(cls, name, fa);
}
private static Method findMethodInClass(Class<?> cls, String name, Class<?>[] fa) {
Method m = null;
// scan all methods and move plausible candidates to the start of an array
Method[] mm = cls.getMethods();
int n = 0;
for (int i = 0 ; i < mm.length ; ++i) {
if (checkMethod(mm[i], name, fa)) {
mm[n++] = mm[i];
}
}
if (n > 1) {
System.out.println("Caveat: we have to perform more specific test. n == " + n);
System.out.println("class: " + cls + "\nname: " + name);
for (int i = 0 ; i < n ; ++i) {
System.out.println(mm[i]);
}
}
if (n > 0) {
m = mm[0];
}
return m;
}
Method checkMethod() called in findMethodInClass simply checks if name is correct and if arguments with which method will be called more or less match formal argument list. It's implementation is left as a simple exercise for the reader. Any comments?
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.
I can't seem to find anything on google for this and I'm not sure it's possible. What I want to do, is pass a line of Java code as an argument to a method. Google only turns up results for passing cmd line arguments to methods, but I want to pass an actual line of code.
Basically I want to pass methodA to methodB except methodA isn't a method, but a line of code. Below is a full example of passing a method to a method using reflection.
public class Relation<T> {
protected Set<Pair<T,T>> pairs = null;
public Relation() {
this.pairs = new LinkedHashSet<Pair<T,T>>();
}
/* Next 2 methods are methods for sending methods to methods useing java.lang.reflect.Method */
public Method getMethod(String name) {
try { return Relation.class.getDeclaredMethod(name);
} catch (Exception e) {}
return null;
}
public boolean execute(Method method, Object... params) {
try { return (Boolean) method.invoke(this, params);
} catch (Exception e) {}
return false;
}
/* The method I reuse several times so I just put methods inside of it */
public boolean pairsTFIterator(Method method) {
for(Pair<T,T> x : pairs) {
boolean bool = false;
for(Pair<T,T> y : pairs) {
if(execute(method, x,y))
bool = true; break;
}
if(!bool) return false;
}
return true;
}
/* To be replaced by the line of code*/
public static <T> boolean isSymmetricPairs(Pair<T,T> a, Pair<T,T> b) {
return a.getFirst().equals(b.getSecond()) && a.getSecond().equals(b.getFirst()) ? true :false;
}
/* Method that calls others */
public boolean isSymmetric() {
return pairsTFIterator(getMethod("isSymmetricPairs"));
}
}
The above works fine and all, but I want to take it a step further and just forego methods like the "isSymmetricPairs" method by just putting that methods logic line directly in the "pairsTFIterator", like so:
public boolean isReflexive() {
return baseSetTFIterator(
a.getFirst().equals(b.getSecond()) && a.getSecond().equals(b.getFirst()) ? true :false
);
}
I'm pretty sure this is impossible, but if there is someway to do it, that would be great.
It sounds like what you are looking for are "first-class functions". Some languages treat functions just like a variable, in the sense that you can assign them to variables and pass them as arguments to other functions. Java 8 will be introducing the concept of lambda expressions which will support this type of functionality.
Also there are other JVM languages that provide this already, including Scala and Groovy to name two of the more popular ones.
Just to give you a flavor of what it looks like, in Groovy you can execute arbitrary functions on each element of a collection by calling the each() method and passing it a closure (a function essentially).
def list = [1, 2, 3, 4]
def printer = { x -> println x } // defines a closure that takes one arg and prints it
list.each(printer) // prints out the elements
def sum = 0
def summer = { x -> sum += x } // defines a closure that takes one arg and adds it to the sum variable
list.each(summer)
println sum // should be 1 + 2 + 3 + 4
Put you code in an anonymos inner class may satisfy your requirement:
interface PairFilter<T>{
boolean filter(Pair<T, T> a, Pair<T,T> b);
}
And in you iterator method:
public boolean pairsTFIterator(PairFilter filter) {
for(Pair<T,T> x : pairs) {
boolean bool = false;
for(Pair<T,T> y : pairs) {
if(filter.filter(x,y))
bool = true; break;
}
if(!bool) return false;
}
return true;
}
then call it:
pairsTFIterator(new PairFilter<T>(){
public boolean filter(Pair<T, T> a, Pair<T,T> b){
return a.getFirst().equals(b.getSecond()) && a.getSecond().equals(b.getFirst()) ? true :false;
}
});
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.