views:

70

answers:

6

I have a method with the signature public void setFoo(int newFoo) in a model I'm writing. I'm using the following code to call it from within my controller:

protected void setModelProperty(String propertyName, Object newValue) {

    for (AbstractModel model: registeredModels) {
        try {
            Method method = model.getClass().
                getMethod("set"+propertyName, new Class[] {
                                                  newValue.getClass()
                                              }
                         );
            method.invoke(model, newValue);

        } catch (Exception ex) {
            //  Handle exception.
            System.err.println(ex.toString());
        }
    }
}

Calling this method like controller.setModelProperty("Foo",5); results in an exception being thrown: java.lang.NoSuchMethodException: foo.bar.models.FooModel.setFoo(java.lang.Integer) -- it looks like the int is being boxed as an Integer, which doesn't match the signature of setFoo.

Is there any way to convince this reflection code to pass 5 (or whatever int I pass in) as an Integer, without the boxing? Or do I have to create public void setFoo(Integer newFoo) in my model and unbox explicitly, then call the original setFoo?

A: 

Did you try

Integer.valueOf(5) to pass 5 along as an Integer instead of an int?

Trefex
+1  A: 

You could specialise your setModelProperty for any primitives you expect to be used with:

protected void setModelProperty(String propertyName, int newValue) { /* ... */ }

Alternatively, you could use instanceof on newValue to check for boxed primitives:

Class[] classes;
if (newValue instanceof Integer) {
  classes = new Class[] { int.class };
} else if (newValue instanceof Double) {
  /* etc...*/
} else {
  classes = new Class[] {newValue.getClass() };
}

Or finally, if you have the source for setFoo, you could change it to take a boxed Integer instead of an int - the overhead is usually negligible.

MHarris
+1  A: 

newValue is being boxed as an Integer when setModelProperty() is called. This is the only way it can be called; 'int' is not an instanceof Object. newValue.getClass() returns "Integer" and thus the call to getMethod() fails.

If you want to work with primitives here you'll need a special version of setModelProperty. Alternatively you could write a method setFoo(Integer).

Or more generally you could write:

if (newValue.getClass()==Integer.class) {
  // code to look for a method with an int argument
}
DJClayworth
+2  A: 

Is there any way to convince this reflection code to pass 5 (or whatever int I pass in) as an Integer, without the boxing?

Not while your method signature says Object newValue, because an int can never be an Object. A way to keep the method generic would be to have callers pass in the type explicitly, i.e.:

protected void setModelProperty(String propertyName, Object newValue, Class type) {

Alternatively, you could test the type of newValue to see if it's a primitive wrapper and in that case look for both the primitive and the wrapped version of the method. However, that won't work when the user passes in a null. Actually, the method won't work at all in that case...

Michael Borgwardt
This way you can overload the method to receive 2 parameters like you are doing if it's a normal object, and that calls `setModelProperty(propertyName, newValue, newValue.getClass())`. But if you know the value must be a primitive type you can create a method with params `String, int` which calls `setModelProperty(propertyName, Object newValue, Integer.TYPE)`. You will need one for every primitive you want to use, though.
Andrei Fierbinteanu
A: 

Problem is not on invoking but on getting the method, as you are using Integer.class which is different from int.class.

You can fix it by doing a second call to get method if the first one fails:

protected void setModelProperty(String propertyName, Object newValue) {

    for (AbstractModel model: registeredModels) {
        Method method = null;
        try {
            method = model.getClass().
                 getMethod("set"+propertyName, new Class[] {
                                                  newValue.getClass()
                                               }
                         );
        } catch (Exception ex) {
            try {
                if (canBoxPrimitive(newValue.getClass()) {
                    method = model.getClass().
                           getMethod("set"+propertyName, new Class[] {
                                                  primitiveClass(newValue.getClass())
                                               }
                }
            }
            catch (Exception ex) {
                // handle it
            }
        }

        method.invoke(model, newValue);
   }
}

For options to code the functions: boolean canBoxPrimitive(Class) and Class primitiveClass(Class) you can take a look here: http://stackoverflow.com/questions/180097/dynamically-find-the-class-that-represents-a-primitive-java-type

bashflyng
A: 

Outoboxing will be done, but you are looking for the wrong method. The parameter type array is not correct. For int the type would be Integer.TYPE instead of Integer.class.

Try some like the following to get it working regardless of the parameer type (outoboxing will do it):

protected void setModelProperty(String propertyName, Object newValue) {

    for (AbstractModel model: registeredModels) {
        try {
            Method method = findSetter(model.getClass(), propertyName);
            method.invoke(model, newValue);

        } catch (Exception ex) {
            //  Handle exception.
            System.err.println(ex.toString());
        }
    }
}

private Method findSetter(Class<?> type, String propertyName) {
    for (Method methos : type.getMethods()) {
        if (method.getName().equalsIgnoreCase("set" + propertyName) && method.getParameterTypes().length == 1) {
            return method;
        }
    }
    throw new NoSuchMethodError("No setter for " + propertyName);
}
Arne Burmeister