views:

427

answers:

3

I am using the following the following class org.apache.poi.hssf.usermodel.HSSFCell, with a list of the following methods:

void setCellValue(boolean value)

void setCellValue(java.util.Calendar value)

void setCellValue(java.util.Date value)

void setCellValue(double value)

void setCellValue(HSSFRichTextString value)

void setCellValue(java.util.Calendar value)

void setCellValue(HSSFRichTextString value)

Take note that, there are no methods with Object as method parameter.

Now, I have no way to determine my value class type during compiled time. I can only determine my value class type during runtime. Hence, how I can determine the correct method to be called, if I do not know the method signature during compiled time?

My code is below:

final int rowCount = tableModel.getRowCount();
for (int i = 0; i < rowCount; i++) {
    final HSSFRow row = sheet.createRow(i + 1);
    for (int j = 0; j < columnCount; j++) {
        final Object object = tableModel.getValueAt(i, j);
        final Class myClass = tableModel.getColumnClass(j);
        // How to perform casting during compiled time, and invoke
        // the setCellValue with correct signature?
        if (object != null) {
            row.createCell(j).setCellValue(??); // Does not accept Object!
        }
    }
}

Perhaps ugly if...else with instanceof would resolve my problem. However, if I do not want the ugly if...else with instanceof, is there any better way to do so?

A: 

I think the instanceof is the way to go. If you think it makes your code ugly extract that instanceof expressions into an auxiliary method:

public void setCellValue(HSSFCell cell, Object value) {
 if (null == cell)
  throw new IllegalArgumentException("cell");
 if (null == value)
  throw new IllegalArgumentException("value");
 if (value instanceof Double)
  cell.setCellValue((Double)value); // auto-boxing will handle this
 else if (value instanceof Boolean) {
  cell.setCellValue((Boolean)value); // auto-boxing will handle this
 } else if (value instanceof Calendar) {
  cell.setCellValue((Calendar)value);
 } else if ...
  .....
 } else {
  throw new UnsupportedTypeException("Object of class " + Value.class.getName() + " not supported.");
 }
}

Alternately you can use reflection. Even with reflection I think you still have to do some customization for the primitive types because the auto-boxing doesn't work for getMethod() ...

public void invokeSetCellValue(HSSFCell cell, Object obj) {
 try {
  Class<?> clazz = obj.getClass();
  if (obj instanceof Double) {
   clazz = double.class;
  } else if (obj instanceof Boolean) {
   clazz = boolean.class;
  }
  Method m = HSSFCell.class.getMethod("setCellValue", clazz);
  m.invoke(cell, obj);
 } catch (SecurityException e) {
 } catch (NoSuchMethodException e) {
 } catch (IllegalArgumentException e) {
 } catch (IllegalAccessException e) {
 } catch (InvocationTargetException e) {
 }

}
bruno conde
+3  A: 

One way of handling this is to load that list of methods at runtime into a Map and then for each call, use the Map. That is, something like this (where this code is simplified and omits error checking):

Map<? extends Object, Method> map;

Method[] methods = Setters.class.getMethods();
for (Method method : methods) {
  if (method.getName().equals("setCellValue")) {
    map.put(method.getParameterTypes()[0], method);
  }
}

then when you want to call this, look up the Method in the map by argument type and use that instance.

To show this off, again with simplified but this time full code. Note that to be fully general, the code gets a little more complicated, as is shown below. If you don't have to worry about primitives (which depends on your usage) or if you don't have to worry about interfaces or superclasses, then you can simplify the example below.

Also, if you can guarantee that there will be no overlap in interfaces or superclasses in the arguments that you have to worry about, you can move all of the complicated logic into initialization (which doesn't matter if it takes 1 ms longer). In this case, all of the logic in findMethodToInvoke() would be moved into the constructor, where you would loop over all interfaces and superclasses of each method you find and add them to your parameterTypeMap. If you do this optimization, then findMethodToInvoke() becomes a single line:

return parameterTypeMap.get(test.getClass());

but without this optimization and with full generality, here's my example of how to do this:

import java.lang.reflect.*;
import java.util.*;

public class Test {
  private final Map<Object, Method> parameterTypeMap = new HashMap<Object, Method>();

  private final Object[] tests = {Double.valueOf(3.1415),
                                  Boolean.TRUE,
                                  new Date(),
                                  new GregorianCalendar(),
                                  new HashMap<Object, Object>()};

  public Test() {
    Method[] methods = Setters.class.getMethods();
    for (Method method : methods) {
      if (method.getName().equals("setCellValue")) {
        Class<?>[] clazzes = method.getParameterTypes();
        if (clazzes.length != 1) {
          continue;
        }
        if (clazzes[0].isPrimitive()) {
          handlePrimitive(method, clazzes[0]);
        }
        parameterTypeMap.put(clazzes[0], method);
      }
    }
  }

  // See http://java.sun.com/javase/6/docs/api/java/lang/Class.html#isPrimitive()
  private void handlePrimitive(Method method, Class<?> clazz) {
    if (clazz == Boolean.TYPE) {
      parameterTypeMap.put(Boolean.class, method);
    } else if (clazz == Double.TYPE) {
      parameterTypeMap.put(Double.class, method);
    } // ... and so on for the other six primitive types (void doesn't matter)
  }

  public void doTests(Setters setter) {
    for (Object test : tests) {
      Method method = findMethodToInvoke(test);
      if (method == null) {
        System.out.println("Nothing found for " + test.getClass());
        continue;
      }

      try {
        method.invoke(setter, test);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  private Method findMethodToInvoke(Object test) {
    Method method = parameterTypeMap.get(test.getClass());
    if (method != null) {
      return method;
    }

    // Look for superclasses
    Class<?> x = test.getClass().getSuperclass();
    while (x != null && x != Object.class) {
      method = parameterTypeMap.get(x);
      if (method != null) {
        return method;
      }
      x = x.getSuperclass();
    }

    // Look for interfaces
    for (Class<?> i : test.getClass().getInterfaces()) {
      method = parameterTypeMap.get(i);
      if (method != null) {
        return method;
      }
    }
    return null;
  }

  public static void main(String[] args) {
    Test test = new Test();
    test.doTests(new Setters());
  }
}

class Setters {
  public void setCellValue(boolean value) {
    System.out.println("boolean " + value);
  }

  public void setCellValue(double value) {
    System.out.println("double " + value);
  }

  public void setCellValue(Calendar value) {
    System.out.println("Calendar " + value);
  }

  public void setCellValue(Date value) {
    System.out.println("Date " + value);
  }

  public void setCellValue(Map<?, ?> value) {
    System.out.println("Map " + value);
  }
}
Eddie
Pretty diabolical. For completeness, can you post a snippet which you actually look up the proper method and invoke it with the parameter? Also, maybe rename 'map' to something like typeToOverload to make it a bit clearer?
Outlaw Programmer
At first glance, I thought this solution is overcomplicated compared to TofuBeer's. After some detailed study, I realize that it covers 2 more additional case : (1) Primitive method parameter (2) Super class and interface. Hence, I decide to accept it as answer :)
Yan Cheng CHEOK
One more thing to be noted : if (clazzes[0].isPrimitive()) { handlePrimitive(method, clazzes[0]); }Shall be written as ? if (clazzes[0].isPrimitive()) { handlePrimitive(method, clazzes[0]); continue; }
Yan Cheng CHEOK
Actually, you probably want to put both the primitive and the wrapped version in there, which is why I didn't put in the "continue;" statement.
Eddie
This is because in findMethodToInvoke(Object test), test will ALWAYS be Object. It can never be primitive. Am my assumption correct?
Yan Cheng CHEOK
Well, you are calling in your code "final Object object = tableModel.getValueAt(i, j);" Thus you will never HAVE a primitive to pass to findMethodToInvoke. Primitives will become wrapped, by necessity.
Eddie
A: 

If you don't have subclasses (you can still do it if you do, but it will be harder, let me know if you do) you could use reflection:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main
{
    public static void main(final String[] argv)
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        final Object o;

        if(argv.length == 0)
        {
            o = "Hello";
        }
        else
        {
            o = Integer.valueOf(42);
        }

        callFoo(o);
    }

    private static void callFoo(final Object o) 
        throws NoSuchMethodException,
               IllegalAccessException,
               IllegalArgumentException,
               InvocationTargetException
    {
        Method method;

        method = Main.class.getDeclaredMethod("foo", o.getClass());
        method.invoke(null, o);
    }

    private static void foo(final String val)
    {
        System.out.println("foo(String) -> " + val);
    }

    private static void foo(final Integer val)
    {
        System.out.println("foo(Integer) -> " + val);
    }
}

The downside is that you don't have the compiler tell you if you try to call a method that does not exist.

The exception handling in the code above is utter crap, but I wanted to focus on the reflection part.

Using instance of is better from the point of view that it has compile time type safety. Reflection will not have to be updated if new methods are added.

TofuBeer