Author: alex.guizar(a)jboss.com
Date: 2010-05-22 00:22:48 -0400 (Sat, 22 May 2010)
New Revision: 6372
Modified:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/util/ReflectUtil.java
jbpm4/trunk/modules/pvm/src/test/java/org/jbpm/pvm/internal/wire/ObjectWireTest.java
Log:
JBPM-2717: allow unboxing and widening primitive conversions in property injection
Modified:
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/util/ReflectUtil.java
===================================================================
---
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/util/ReflectUtil.java 2010-05-21
11:46:12 UTC (rev 6371)
+++
jbpm4/trunk/modules/pvm/src/main/java/org/jbpm/pvm/internal/util/ReflectUtil.java 2010-05-22
04:22:48 UTC (rev 6372)
@@ -5,7 +5,9 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.jbpm.api.JbpmException;
import org.jbpm.internal.log.Log;
@@ -18,10 +20,32 @@
import org.jbpm.pvm.internal.wire.WireContext;
import org.jbpm.pvm.internal.wire.descriptor.ArgDescriptor;
-public abstract class ReflectUtil {
+public class ReflectUtil {
+
+ private ReflectUtil() {
+ // hide default constructor to prevent instantiation
+ }
- private static Log log = Log.getLog(ReflectUtil.class.getName());
-
+ private static final Log log = Log.getLog(ReflectUtil.class.getName());
+
+ /**
+ * Maps wrapper <code>Class</code>es to their corresponding primitive
types.
+ */
+ private static final Map<Class<?>, Class<?>> wrapperPrimitiveMap =
createWrapperPrimitiveMap();
+
+ private static Map<Class<?>, Class<?>> createWrapperPrimitiveMap() {
+ Map<Class<?>, Class<?>> map = new HashMap<Class<?>,
Class<?>>();
+ map.put(Boolean.class, boolean.class);
+ map.put(Byte.class, byte.class);
+ map.put(Character.class, char.class);
+ map.put(Short.class, short.class);
+ map.put(Integer.class, int.class);
+ map.put(Long.class, long.class);
+ map.put(Double.class, double.class);
+ map.put(Float.class, float.class);
+ return map;
+ }
+
/** searches for the field in the given class and in its super classes */
public static Field findField(Class<?> clazz, String fieldName) {
return findField(clazz, fieldName, clazz);
@@ -200,38 +224,136 @@
}
public static boolean isArgumentMatch(Class<?>[] parameterTypes,
List<ArgDescriptor> argDescriptors, Object[] args) {
- int nbrOfArgs = 0;
- if (args!=null) nbrOfArgs = args.length;
+ int nbrOfArgs = args!=null ? args.length : 0;
+ int nbrOfParameterTypes = parameterTypes!=null ? parameterTypes.length : 0;
- int nbrOfParameterTypes = 0;
- if (parameterTypes!=null) nbrOfParameterTypes = parameterTypes.length;
+ if (nbrOfArgs!=nbrOfParameterTypes) {
+ return false;
+ }
- if ( (nbrOfArgs==0)
- && (nbrOfParameterTypes==0)
- ) {
+ if (nbrOfArgs==0) {
return true;
}
- if (nbrOfArgs!=nbrOfParameterTypes) {
- return false;
- }
-
- for (int i=0; (i<parameterTypes.length); i++) {
+ for (int i=0; i<parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
- String argTypeName = (argDescriptors!=null ? argDescriptors.get(i).getTypeName() :
null);
- if (argTypeName!=null) {
- if (! argTypeName.equals(parameterType.getName())) {
- return false;
- }
- } else if ( (args[i]!=null)
- && (! parameterType.isAssignableFrom(args[i].getClass()))
- ) {
+ String argTypeName;
+ if (argDescriptors == null || (argTypeName = argDescriptors.get(i).getTypeName())
== null) {
+ Object arg = args[i];
+ if (!isAssignable(parameterType, arg)) {
+ return false;
+ }
+ }
+ else if (!parameterType.getName().equals(argTypeName)) {
return false;
}
}
return true;
}
+ /**
+ * <p>
+ * Checks if the given <code>value</code> can be assigned to a variable of
the specified
+ * <code>type</code>.
+ * </p>
+ * <p>
+ * Unlike the {@link Class#isAssignableFrom(Class)} method, this method takes into
+ * account widenings of primitive types and <code>null</code>s.
+ * </p>
+ * <p>
+ * Primitive widenings allow an int to be assigned to a long, float or double. This
method
+ * returns the correct result for these cases.
+ * </p>
+ * <p>
+ * <code>null</code> may be assigned to any reference type. This method
will return
+ * <code>true</code> if <code>null</code> is passed in and the
specified <code>type</code> is
+ * a reference type.
+ * </p>
+ * <p>
+ * Specifically, this method tests whether the class of the given
<code>value</code> parameter
+ * can be converted to the type represented by the specified
<code>Class</code> via an
+ * identity, widening primitive or widening reference conversion. See the
+ * <a
href="http://java.sun.com/docs/books/jls/">Java Language
Specification</a>,
+ * sections 5.1.1, 5.1.2 and 5.1.4 for details.
+ * </p>
+ * @param type the Class to try to assign into
+ * @param value the object to check, may be <code>null</code>
+ * @return <code>true</code> if assignment is possible
+ * @see <a
+ *
href="http://commons.apache.org/lang/api-release/org/apache/commons/...
+ * >ClassUtils.isAssignable()</a>
+ */
+ private static boolean isAssignable(Class<?> type, Object value) {
+ // check for null value
+ if (value == null) {
+ // null is assignable to reference types
+ return !type.isPrimitive();
+ }
+
+ if (type.isPrimitive()) {
+ // unboxing
+ Class<?> valueType = wrapperToPrimitive(value.getClass());
+ if (null == valueType) {
+ return false;
+ }
+ if (type == valueType) {
+ return true;
+ }
+ // widening primitive conversion
+ if (int.class == valueType) {
+ return long.class == type || float.class == type || double.class == type;
+ }
+ if (long.class == valueType) {
+ return float.class == type || double.class == type;
+ }
+ if (boolean.class == valueType) {
+ return false;
+ }
+ if (double.class == valueType) {
+ return false;
+ }
+ if (float.class == valueType) {
+ return double.class == type;
+ }
+ if (char.class == valueType) {
+ return int.class == type || long.class == type || float.class == type
+ || double.class == type;
+ }
+ if (short.class == valueType) {
+ return int.class == type || long.class == type || float.class == type
+ || double.class == type;
+ }
+ if (byte.class == valueType) {
+ return short.class == type || int.class == type || long.class == type
+ || float.class == type || double.class == type;
+ }
+ // should never get here
+ return false;
+ }
+
+ return type.isInstance(value);
+ }
+
+ /**
+ * <p>
+ * Converts the specified wrapper class to its corresponding primitive class.
+ * </p>
+ * <p>
+ * If the passed in class is a wrapper class for a primitive type, this primitive type
will be
+ * returned (e.g. <code>Integer.TYPE</code> for
<code>Integer.class</code>). For other
+ * classes, or if the parameter is <code>null</code>, the return value is
<code>null</code>.
+ * </p>
+ * @param cls the class to convert, may be <code>null</code>
+ * @return the corresponding primitive type if <code>cls</code> is a
wrapper class,
+ * <code>null</code> otherwise
+ * @see <a
+ *
href="http://commons.apache.org/lang/api-release/org/apache/commons/...
+ * >ClassUtils.wrapperToPrimitive</a>
+ */
+ private static Class<?> wrapperToPrimitive(Class<?> cls) {
+ return wrapperPrimitiveMap.get(cls);
+ }
+
public static String getSignature(String methodName, List<ArgDescriptor>
argDescriptors, Object[] args) {
String signature = methodName+"(";
if (args!=null) {
Modified:
jbpm4/trunk/modules/pvm/src/test/java/org/jbpm/pvm/internal/wire/ObjectWireTest.java
===================================================================
---
jbpm4/trunk/modules/pvm/src/test/java/org/jbpm/pvm/internal/wire/ObjectWireTest.java 2010-05-21
11:46:12 UTC (rev 6371)
+++
jbpm4/trunk/modules/pvm/src/test/java/org/jbpm/pvm/internal/wire/ObjectWireTest.java 2010-05-22
04:22:48 UTC (rev 6372)
@@ -534,7 +534,7 @@
}
public static class OverriddenFieldInjectionClass extends FieldInjectionClass {
- String txtOne = null;
+ String txtOne;
}
public void testOverriddenFieldInjection() {
@@ -623,66 +623,143 @@
}
public static class PropertyInjectionClass {
- String p = null;
- String q = null;
- String propertyP = null;
- String propertyQ = null;
- public void setP(String p) {
- propertyP = p;
+ String s;
+ String propertyS;
+ boolean z;
+ boolean propertyZ;
+ char c;
+ char propertyC;
+ int i;
+ int propertyI;
+ long l;
+ long propertyL;
+ float f;
+ float propertyF;
+ double d;
+ double propertyD;
+
+ public void setS(String s) {
+ propertyS = s;
}
- public void setQ(String q) {
- propertyQ = q;
+ public void setZ(boolean z) {
+ propertyZ = z;
}
+ public void setC(char c) {
+ propertyC = c;
+ }
+ public void setI(int i) {
+ propertyI = i;
+ }
+ public void setL(long l) {
+ propertyL = l;
+ }
+ public void setF(float f) {
+ propertyF = f;
+ }
+ public void setD(double d) {
+ propertyD = d;
+ }
}
public void testPropertyInjection() {
WireContext wireContext = createWireContext(
"<objects>" +
" <object name='o'
class='"+PropertyInjectionClass.class.getName()+"'>" +
- " <property name='p'>" +
+ " <property name='s'>" +
" <string value='hello' />" +
" </property>" +
- " <property name='q'>" +
- " <string value='world' />" +
+ " <property name='z'>" +
+ " <true/>" +
" </property>" +
+ " <property name='c'>" +
+ " <char value='x'/>" +
+ " </property>" +
+ " <property name='i'>" +
+ " <int value='32768'/>" +
+ " </property>" +
+ " <property name='l'>" +
+ " <long value='2147483648'/>" +
+ " </property>" +
+ " <property name='f'>" +
+ " <float value='3e9'/>" +
+ " </property>" +
+ " <property name='d'>" +
+ " <double value='1e39'/>" +
+ " </property>" +
" </object>" +
"</objects>"
);
- Object o = wireContext.get("o");
+ PropertyInjectionClass pic = (PropertyInjectionClass)
wireContext.get("o");
- assertNotNull(o);
- assertEquals(PropertyInjectionClass.class, o.getClass());
- assertNull(((PropertyInjectionClass)o).p);
- assertNull(((PropertyInjectionClass)o).q);
- assertEquals("hello", ((PropertyInjectionClass)o).propertyP);
- assertEquals("world", ((PropertyInjectionClass)o).propertyQ);
+ assertNull(pic.s);
+ assertEquals("hello", pic.propertyS);
+ assertFalse(pic.z);
+ assertTrue(pic.propertyZ);
+ assertEquals('\0', pic.c);
+ assertEquals('x', pic.propertyC);
+ assertEquals(0, pic.i);
+ assertEquals(1 << 15, pic.propertyI);
+ assertEquals(0, pic.l);
+ assertEquals(1l << 31, pic.propertyL);
+ assertEquals(0, pic.f, 0);
+ assertEquals(3e9f, pic.propertyF, 0);
+ assertEquals(0, pic.d, 0);
+ assertEquals(1e39, pic.propertyD, 0);
}
- public void testPropertyInjectionWithSetter() {
+ public void testWideningPropertyInjection() {
WireContext wireContext = createWireContext(
"<objects>" +
" <object name='o'
class='"+PropertyInjectionClass.class.getName()+"'>" +
- " <property setter='setP'>" +
+ " <property name='s'>" +
" <string value='hello' />" +
" </property>" +
- " <property setter='setQ'>" +
- " <string value='world' />" +
+ " <property name='i'>" +
+ " <char value=' '/>" +
" </property>" +
+ " <property name='l'>" +
+ " <int value='2147483647'/>" +
+ " </property>" +
+ " <property name='f'>" +
+ " <int value='16777216'/>" +
+ " </property>" +
+ " <property name='d'>" +
+ " <long value='9007199254740992'/>" +
+ " </property>" +
" </object>" +
"</objects>"
);
- Object o = wireContext.get("o");
+ PropertyInjectionClass pic = (PropertyInjectionClass)
wireContext.get("o");
- assertNotNull(o);
- assertEquals(PropertyInjectionClass.class, o.getClass());
- assertNull(((PropertyInjectionClass)o).p);
- assertNull(((PropertyInjectionClass)o).q);
- assertEquals("hello", ((PropertyInjectionClass)o).propertyP);
- assertEquals("world", ((PropertyInjectionClass)o).propertyQ);
+ assertEquals(0, pic.i);
+ assertEquals(' ', pic.propertyI);
+ assertEquals(0, pic.l);
+ assertEquals(Integer.MAX_VALUE, pic.propertyL);
+ assertEquals(0, pic.f, 0);
+ assertEquals(1 << 24, pic.propertyF, 0);
+ assertEquals(0, pic.d, 0);
+ assertEquals(1l << 53, pic.propertyD, 0);
}
+ public void testPropertyInjectionWithSetter() {
+ WireContext wireContext = createWireContext(
+ "<objects>" +
+ " <object name='o'
class='"+PropertyInjectionClass.class.getName()+"'>" +
+ " <property setter='setS'>" +
+ " <string value='hello' />" +
+ " </property>" +
+ " </object>" +
+ "</objects>"
+ );
+
+ PropertyInjectionClass pic = (PropertyInjectionClass)
wireContext.get("o");
+
+ assertNull(pic.s);
+ assertEquals("hello", pic.propertyS);
+ }
+
public void testBadPropertyDescriptor() {
List<Problem> problems = parseProblems(
"<objects>" +
@@ -727,30 +804,24 @@
WireContext wireContext = createWireContext(
"<objects>" +
" <object name='o'
class='"+InheritedPropertyInjectionClass.class.getName()+"'>" +
- " <property name='p'>" +
+ " <property name='s'>" +
" <string value='hello' />" +
" </property>" +
- " <property name='q'>" +
- " <string value='world' />" +
- " </property>" +
" </object>" +
"</objects>"
);
- Object o = wireContext.get("o");
+ InheritedPropertyInjectionClass ipic = (InheritedPropertyInjectionClass)
wireContext.get("o");
- assertNotNull(o);
- assertEquals(InheritedPropertyInjectionClass.class, o.getClass());
- assertNull(((InheritedPropertyInjectionClass)o).p);
- assertNull(((InheritedPropertyInjectionClass)o).q);
- assertEquals("hello", ((InheritedPropertyInjectionClass)o).propertyP);
- assertEquals("world", ((InheritedPropertyInjectionClass)o).propertyQ);
+ assertNull(ipic.s);
+ assertEquals("hello", ipic.propertyS);
}
public static class OverwrittenPropertyInjectionClass extends PropertyInjectionClass {
- String overwrittenPropertyQ = null;
- public void setQ(String q) {
- overwrittenPropertyQ = q;
+ String overwrittenPropertyS;
+ @Override
+ public void setS(String s) {
+ overwrittenPropertyS = s;
}
}
@@ -758,25 +829,18 @@
WireContext wireContext = createWireContext(
"<objects>" +
" <object name='o'
class='"+OverwrittenPropertyInjectionClass.class.getName()+"'>"
+
- " <property name='p'>" +
+ " <property name='s'>" +
" <string value='hello' />" +
" </property>" +
- " <property name='q'>" +
- " <string value='world' />" +
- " </property>" +
" </object>" +
"</objects>"
);
- Object o = wireContext.get("o");
+ OverwrittenPropertyInjectionClass opic = (OverwrittenPropertyInjectionClass)
wireContext.get("o");
- assertNotNull(o);
- assertEquals(OverwrittenPropertyInjectionClass.class, o.getClass());
- assertNull(((OverwrittenPropertyInjectionClass)o).p);
- assertNull(((OverwrittenPropertyInjectionClass)o).q);
- assertEquals("hello", ((OverwrittenPropertyInjectionClass)o).propertyP);
- assertNull(((OverwrittenPropertyInjectionClass)o).propertyQ);
- assertEquals("world",
((OverwrittenPropertyInjectionClass)o).overwrittenPropertyQ);
+ assertNull(opic.s);
+ assertNull(opic.propertyS);
+ assertEquals("hello", opic.overwrittenPropertyS);
}
public static class InvokeClass {