Author: hardy.ferentschik
Date: 2009-03-19 07:58:06 -0400 (Thu, 19 Mar 2009)
New Revision: 16184
Modified:
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/MetaConstraint.java
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/util/ReflectionHelper.java
validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/util/ReflectionHelperTest.java
Log:
HV-128 Added support for Iterable and resolved ambiguity with Maps
Modified:
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/MetaConstraint.java
===================================================================
---
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/MetaConstraint.java 2009-03-18
16:00:44 UTC (rev 16183)
+++
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/MetaConstraint.java 2009-03-19
11:58:06 UTC (rev 16184)
@@ -99,24 +99,6 @@
return constraintTree.getDescriptor().getGroups();
}
- /**
- * @return Returns <code>true</code> in case the constraint is defined on a
collection, <code>false</code>
- * otherwise.
- */
- public boolean isCollection() {
- Type t = typeOfAnnoatedElement();
- return ReflectionHelper.isCollection( t );
- }
-
- /**
- * @return Returns <code>true</code> in case the constraint is defined on an
array, <code>false</code>
- * otherwise.
- */
- public boolean isArray() {
- Type t = typeOfAnnoatedElement();
- return ReflectionHelper.isArray( t );
- }
-
public ConstraintDescriptor getDescriptor() {
return constraintTree.getDescriptor();
}
Modified:
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java
===================================================================
---
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java 2009-03-18
16:00:44 UTC (rev 16183)
+++
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java 2009-03-19
11:58:06 UTC (rev 16184)
@@ -36,6 +36,7 @@
import javax.validation.Validator;
import javax.validation.groups.Default;
+import com.googlecode.jtype.TypeUtils;
import org.slf4j.Logger;
import org.hibernate.validation.engine.groups.Group;
@@ -300,16 +301,16 @@
*/
private <T> Iterator<?>
createIteratorForCascadedValue(ExecutionContext<T> context, Type type, Object value)
{
Iterator<?> iter;
- if ( ReflectionHelper.isCollection( type ) ) {
- boolean isIterable = value instanceof Iterable;
- Map<?, ?> map = !isIterable ? ( Map<?, ?> ) value : null;
- Iterable<?> elements = isIterable ?
- ( Iterable<?> ) value :
- map.entrySet();
- iter = elements.iterator();
+ if ( ReflectionHelper.isIterable( type ) ) {
+ iter = ( ( Iterable<?> ) value ).iterator();
context.markCurrentPropertyAsIndexed();
}
- else if ( ReflectionHelper.isArray( type ) ) {
+ else if ( ReflectionHelper.isMap( type ) ) {
+ Map<?, ?> map = ( Map<?, ?> ) value;
+ iter = map.values().iterator();
+ context.markCurrentPropertyAsIndexed();
+ }
+ else if ( TypeUtils.isArray( type ) ) {
List<?> arrayList = Arrays.asList( value );
iter = arrayList.iterator();
context.markCurrentPropertyAsIndexed();
Modified:
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/util/ReflectionHelper.java
===================================================================
---
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/util/ReflectionHelper.java 2009-03-18
16:00:44 UTC (rev 16183)
+++
validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/util/ReflectionHelper.java 2009-03-19
11:58:06 UTC (rev 16184)
@@ -21,7 +21,6 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
-import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
@@ -29,13 +28,15 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.validation.ValidationException;
+import com.googlecode.jtype.TypeUtils;
+
/**
* Some reflection utility methods.
*
@@ -213,7 +214,7 @@
}
}
- public static Class<?> loadClass(String name, Class caller) throws
ClassNotFoundException {
+ public static Class<?> loadClass(String name, Class<?> caller) throws
ClassNotFoundException {
try {
//try context classloader, if fails try caller classloader
ClassLoader loader = Thread.currentThread().getContextClassLoader();
@@ -231,107 +232,70 @@
}
/**
- * Determines the type of elements of a generic collection or array.
+ * Determines the type of elements of an <code>Iterable</code>, array or the
value of a <code>Map</code>.
*
- * @param type the collection or array type.
+ * @param type the type to inspect
*
- * @return the element type of the generic collection/array or
<code>null</code> in case the specified type is not a collection/array or the
- * element type cannot be determined.
- *
- * @todo Verify algorithm. Does this hold up in most cases?
+ * @return Returns the type of elements of an <code>Iterable</code>, array
or the value of a <code>Map</code>. <code>
+ * null</code> is returned in case the type is not indexable (in the
context of JSR 303).
*/
public static Type getIndexedType(Type type) {
Type indexedType = null;
- if ( isCollection( type ) && type instanceof ParameterizedType ) {
+ if ( isIterable( type ) && type instanceof ParameterizedType ) {
ParameterizedType paramType = ( ParameterizedType ) type;
- Class collectionClass = getCollectionClass( type );
- if ( collectionClass == Collection.class || collectionClass == java.util.List.class ||
collectionClass == java.util.Set.class || collectionClass == java.util.SortedSet.class )
{
- indexedType = paramType.getActualTypeArguments()[0];
- }
- else if ( collectionClass == Map.class || collectionClass == java.util.SortedMap.class
) {
- indexedType = paramType.getActualTypeArguments()[1];
- }
+ indexedType = paramType.getActualTypeArguments()[0];
}
- else if ( ReflectionHelper.isArray( type ) && type instanceof GenericArrayType
) {
- GenericArrayType arrayTye = ( GenericArrayType ) type;
- indexedType = arrayTye.getGenericComponentType();
+ else if ( isMap( type ) && type instanceof ParameterizedType ) {
+ ParameterizedType paramType = ( ParameterizedType ) type;
+ indexedType = paramType.getActualTypeArguments()[1];
}
+ else if ( TypeUtils.isArray( type ) ) {
+ indexedType = TypeUtils.getComponentType( type );
+ }
return indexedType;
}
-
/**
* @param type the type to check.
*
- * @return Returns <code>true</code> if <code>type</code> is an
array type or <code>false</code> otherwise.
+ * @return Returns <code>true</code> if <code>type</code> is a
iterable type, <code>false</code> otherwise.
*/
- public static boolean isArray(Type type) {
- if ( type instanceof Class ) {
- return ( ( Class ) type ).isArray();
+ public static boolean isIterable(Type type) {
+ if ( type instanceof Class && isIterableClass( ( Class ) type ) ) {
+ return true;
}
- return type instanceof GenericArrayType;
+ if ( type instanceof ParameterizedType ) {
+ return isIterable( ( ( ParameterizedType ) type ).getRawType() );
+ }
+ if ( type instanceof WildcardType ) {
+ Type[] upperBounds = ( ( WildcardType ) type ).getUpperBounds();
+ if ( upperBounds.length == 0 ) {
+ return false;
+ }
+ return isIterable( upperBounds[0] );
+ }
+ return false;
}
-
/**
* @param type the type to check.
*
- * @return Returns <code>true</code> if <code>type</code> is a
collection type or <code>false</code> otherwise.
+ * @return Returns <code>true</code> if <code>type</code> is
implementing <code>Map</code>, <code>false</code> otherwise.
*/
- public static boolean isCollection(Type type) {
- return getCollectionClass( type ) != null;
- }
-
-
- /**
- * Returns the collection class for the specified type provided it is a collection.
- * <p>
- * This is a simplified version of commons annotations
</code>TypeUtils.getCollectionClass()</code>.
- * </p>
- *
- * @param type the <code>Type</code> to check.
- *
- * @return the collection class, or <code>null</code> if type is not a
collection class.
- */
- @SuppressWarnings("unchecked")
- public static Class<? extends Collection> getCollectionClass(Type type) {
- if ( type instanceof Class && isCollectionClass( ( Class ) type ) ) {
- return ( Class<? extends Collection> ) type;
+ public static boolean isMap(Type type) {
+ if ( type instanceof Class && isMapClass( ( Class ) type ) ) {
+ return true;
}
if ( type instanceof ParameterizedType ) {
- return getCollectionClass( ( ( ParameterizedType ) type ).getRawType() );
+ return isMap( ( ( ParameterizedType ) type ).getRawType() );
}
if ( type instanceof WildcardType ) {
Type[] upperBounds = ( ( WildcardType ) type ).getUpperBounds();
if ( upperBounds.length == 0 ) {
- return null;
+ return false;
}
- return getCollectionClass( upperBounds[0] );
+ return isMap( upperBounds[0] );
}
- return null;
- }
-
- /**
- * Checks whether the specified class parameter is an instance of a collection class.
- *
- * @param clazz <code>Class</code> to check.
- *
- * @return <code>true</code> is <code>clazz</code> is instance
of a collection class, <code>false</code> otherwise.
- */
- private static boolean isCollectionClass(Class<?> clazz) {
- Class[] interfaces = clazz.getInterfaces();
-
- for ( Class interfaceClass : interfaces ) {
- if ( interfaceClass == Collection.class
- || interfaceClass == java.util.List.class
- || interfaceClass == java.util.Set.class
- || interfaceClass == java.util.Map.class
- || interfaceClass == java.util.SortedSet.class // extension to the specs
- || interfaceClass == java.util.SortedMap.class ) { // extension to the specs)
- return true;
- }
- }
-
return false;
}
@@ -368,16 +332,14 @@
Iterator<?> iter = null;
Type type = value.getClass();
- if ( isCollection( type ) ) {
- boolean isIterable = value instanceof Iterable;
- Map<?, ?> map = !isIterable ? ( Map<?, ?> ) value : null;
- Iterable<?> elements = isIterable ?
- ( Iterable<?> ) value :
- map.values();
- iter = elements.iterator();
-
+ if ( isIterable( type ) ) {
+ iter = ( ( Iterable<?> ) value ).iterator();
}
- else if ( isArray( type ) ) {
+ else if ( isMap( type ) ) {
+ Map<?, ?> map = ( Map<?, ?> ) value;
+ iter = map.values().iterator();
+ }
+ else if ( TypeUtils.isArray( type ) ) {
List<?> arrayList = Arrays.asList( value );
iter = arrayList.iterator();
}
@@ -403,7 +365,7 @@
* @return Returns <code>true</code> if the cass contains a field or member
for the specified property, <code>
* false</code> otherwise.
*/
- public static boolean containsMember(Class clazz, String property) {
+ public static boolean containsMember(Class<?> clazz, String property) {
try {
clazz.getField( property );
return true;
@@ -435,5 +397,47 @@
return Class.forName( name, true, caller.getClassLoader() );
}
+ /**
+ * Get all superclasses and interfaces recursively.
+ *
+ * @param clazz The class to start the search with.
+ * @param classes List of classes to which to add all found super classes and
interfaces.
+ */
+ private static void computeClassHierarchy(Class<?> clazz,
List<Class<?>> classes) {
+ for ( Class current = clazz; current != null; current = current.getSuperclass() ) {
+ if ( classes.contains( current ) ) {
+ return;
+ }
+ classes.add( current );
+ for ( Class currentInterface : current.getInterfaces() ) {
+ computeClassHierarchy( currentInterface, classes );
+ }
+ }
+ }
+ /**
+ * Checks whether the specified class parameter is an instance of a collection class.
+ *
+ * @param clazz <code>Class</code> to check.
+ *
+ * @return <code>true</code> is <code>clazz</code> is instance
of a collection class, <code>false</code> otherwise.
+ */
+ private static boolean isIterableClass(Class<?> clazz) {
+ List<Class<?>> classes = new ArrayList<Class<?>>();
+ computeClassHierarchy( clazz, classes );
+ return classes.contains( Iterable.class );
+ }
+
+ /**
+ * Checks whether the specified class parameter is an instance of a collection class.
+ *
+ * @param clazz <code>Class</code> to check.
+ *
+ * @return <code>true</code> is <code>clazz</code> is instance
of a collection class, <code>false</code> otherwise.
+ */
+ private static boolean isMapClass(Class<?> clazz) {
+ List<Class<?>> classes = new ArrayList<Class<?>>();
+ computeClassHierarchy( clazz, classes );
+ return classes.contains( Map.class );
+ }
}
Modified:
validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/util/ReflectionHelperTest.java
===================================================================
---
validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/util/ReflectionHelperTest.java 2009-03-18
16:00:44 UTC (rev 16183)
+++
validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/util/ReflectionHelperTest.java 2009-03-19
11:58:06 UTC (rev 16184)
@@ -18,15 +18,21 @@
package org.hibernate.validation.util;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeSet;
import javax.validation.ValidationException;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -38,7 +44,48 @@
* @author Hardy Ferentschik
*/
public class ReflectionHelperTest {
+
@Test
+ public void testIsIterable() throws Exception {
+ Type type = TestTypes.class.getField( "stringList" ).getGenericType();
+ assertTrue( ReflectionHelper.isIterable( type ) );
+
+ assertTrue( ReflectionHelper.isIterable( new TreeSet<Object>().getClass() ) );
+
+ assertTrue( ReflectionHelper.isIterable( List.class ) );
+ assertTrue( ReflectionHelper.isIterable( HashSet.class ) );
+ assertTrue( ReflectionHelper.isIterable( Iterable.class ) );
+ assertTrue( ReflectionHelper.isIterable( Collection.class ) );
+
+ assertFalse( ReflectionHelper.isIterable( null ) );
+ assertFalse( ReflectionHelper.isIterable( Object.class ) );
+ }
+
+ @Test
+ public void testIsMap() throws Exception {
+ assertTrue( ReflectionHelper.isMap( Map.class ) );
+ assertTrue( ReflectionHelper.isMap( SortedMap.class ) );
+
+ Type type = TestTypes.class.getField( "objectMap" ).getGenericType();
+ assertTrue( ReflectionHelper.isMap( type ) );
+
+ assertFalse( ReflectionHelper.isMap( null ) );
+ assertFalse( ReflectionHelper.isMap( Object.class ) );
+ }
+
+ @Test
+ public void testGetIndexedType() throws Exception {
+ Type type = TestTypes.class.getField( "stringList" ).getGenericType();
+ assertEquals( String.class, ReflectionHelper.getIndexedType( type ) );
+
+ type = TestTypes.class.getField( "objectMap" ).getGenericType();
+ assertEquals( Object.class, ReflectionHelper.getIndexedType( type ) );
+
+ type = TestTypes.class.getField( "stringArray" ).getGenericType();
+ assertEquals( String.class, ReflectionHelper.getIndexedType( type ) );
+ }
+
+ @Test
public void testGetIndexedValueForMap() {
Map<String, Object> map = new HashMap<String, Object>();
Object testObject = new Object();
@@ -118,4 +165,10 @@
);
}
}
+
+ public class TestTypes {
+ public List<String> stringList;
+ public Map<String, Object> objectMap;
+ public String[] stringArray;
+ }
}