From 0915f15cfd1b55fdf402f3905e08a4a57107080e Mon Sep 17 00:00:00 2001 From: bernd Date: Wed, 14 Nov 2012 21:24:31 +0100 Subject: [PATCH 19/25] Custom query objects now fully support Map return values. Add unit tests for Map return values. --- .../eodsql/spi/util/AbstractResultSetWrapper.java | 9 +- .../net/lemnik/eodsql/spi/util/ArrayWrapper.java | 10 +- .../eodsql/spi/util/CollectionWrapperFactory.java | 4 +- .../eodsql/spi/util/DataIteratorWrapper.java | 2 +- .../lemnik/eodsql/spi/util/DataObjectBinding.java | 7 + .../net/lemnik/eodsql/spi/util/DataSetWrapper.java | 2 +- .../eodsql/spi/util/MapDataObjectBinding.java | 3 +- .../lemnik/eodsql/spi/util/ResultSetWrapper.java | 50 ++++- eodsql/test/net/lemnik/eodsql/DataSetQuery.java | 18 +- eodsql/test/net/lemnik/eodsql/MapTestObject.java | 241 +++++++++++++++++++++ 10 files changed, 329 insertions(+), 17 deletions(-) create mode 100644 eodsql/test/net/lemnik/eodsql/MapTestObject.java diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/AbstractResultSetWrapper.java b/eodsql/src/net/lemnik/eodsql/spi/util/AbstractResultSetWrapper.java index eac62b1..f9a0e83 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/AbstractResultSetWrapper.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/AbstractResultSetWrapper.java @@ -1,8 +1,8 @@ package net.lemnik.eodsql.spi.util; -import java.lang.reflect.Type; import java.lang.reflect.ParameterizedType; - +import java.lang.reflect.Type; +import java.util.Map; import java.util.Set; import net.lemnik.eodsql.InvalidQueryException; @@ -41,6 +41,9 @@ abstract class AbstractResultSetWrapper extends ResultSetWrapper { if(outerTypes.contains(paramType.getRawType())) { final Type[] args = paramType.getActualTypeArguments(); + if (args.length == 1 && isStringObjectMap(args[0])) { + return Map.class; + } if(args.length != 1 || !(args[0] instanceof Class)) { throw new InvalidQueryException( "Generic must have a single solid type-argument"); @@ -55,7 +58,7 @@ abstract class AbstractResultSetWrapper extends ResultSetWrapper { return null; } - + @Override public String toString() { return getClass().getName() + diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/ArrayWrapper.java b/eodsql/src/net/lemnik/eodsql/spi/util/ArrayWrapper.java index 63ad74a..1c410d2 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/ArrayWrapper.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/ArrayWrapper.java @@ -1,16 +1,13 @@ package net.lemnik.eodsql.spi.util; -import java.lang.reflect.Type; import java.lang.reflect.Array; - +import java.lang.reflect.Type; import java.sql.ResultSet; import java.sql.SQLException; - import java.util.Map; import net.lemnik.eodsql.EoDException; import net.lemnik.eodsql.InvalidDataTypeException; - import net.lemnik.eodsql.spi.util.DataObjectBinding.BindingType; /** @@ -210,6 +207,9 @@ class ArrayWrapper extends AbstractResultSetWrapper { final Class clazz = (Class)genericType; return clazz.isArray() && !clazz.getComponentType().isArray(); } + if (ResultSetWrapper.isStringObjectMapArray(genericType)) { + return true; + } return false; } @@ -224,7 +224,7 @@ class ArrayWrapper extends AbstractResultSetWrapper { ? (BindingType)parameters.get(PARAMETER_BINDING_TYPE) : BindingType.NORMAL_BINDING; - final Class arrayType = (Class)genericType; + final Class arrayType = toClass(genericType); if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { final DataObjectBinding diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java b/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java index abac6ec..d0acfc5 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/CollectionWrapperFactory.java @@ -144,6 +144,8 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { } return isCollectionConstructable(clazz); + } else if(ResultSetWrapper.isStringObjectMap(args[0])) { + return true; } else { throw new InvalidQueryException( "A Collection's generic argument must be " + @@ -173,7 +175,7 @@ class CollectionWrapperFactory implements ResultSetWrapper.Factory { ? (BindingType)parameters.get(PARAMETER_BINDING_TYPE) : BindingType.NORMAL_BINDING; - final Class dataObject = (Class)ptype.getActualTypeArguments()[0]; + final Class dataObject = DataSetWrapper.toClass(ptype.getActualTypeArguments()[0]); final DataObjectBinding binding; if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java b/eodsql/src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java index bdec678..7c1a31e 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/DataIteratorWrapper.java @@ -120,7 +120,7 @@ public class DataIteratorWrapper extends ResultSetWrapper> { final Map parameters) { final ParameterizedType parameterType = (ParameterizedType)genericType; - final Class clazz = (Class)parameterType.getActualTypeArguments()[0]; + final Class clazz = toClass(parameterType.getActualTypeArguments()[0]); final DataObjectBinding binding; if (parameters.containsKey(PARAMETER_CUSTOM_DATA_OBJECT_BINDING)) { diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/DataObjectBinding.java b/eodsql/src/net/lemnik/eodsql/spi/util/DataObjectBinding.java index cd3a20e..4452e96 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/DataObjectBinding.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/DataObjectBinding.java @@ -39,7 +39,14 @@ public abstract class DataObjectBinding { DEFAULT_BINDING_CONSTRUCTOR = null; static { + addStringObjectMapBinding(); + } + + @SuppressWarnings("unchecked") + private static void addStringObjectMapBinding() { setDataObjectBinding(MapDataObjectBinding.getStringObjectMapObjectType(), MapDataObjectBinding.class); + // We need this for Map[] as Java arrays don't have generic information. + setDataObjectBinding(Map.class, (Class) MapDataObjectBinding.class); } /** diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/DataSetWrapper.java b/eodsql/src/net/lemnik/eodsql/spi/util/DataSetWrapper.java index 48b6f3f..3bf4e83 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/DataSetWrapper.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/DataSetWrapper.java @@ -284,7 +284,7 @@ public class DataSetWrapper extends ResultSetWrapper { final ParameterizedType parameterType = (ParameterizedType)genericType; final Class dataType = - (Class)parameterType.getActualTypeArguments()[0]; + toClass(parameterType.getActualTypeArguments()[0]); return new DataSetWrapper(dataType, parameters); } diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/MapDataObjectBinding.java b/eodsql/src/net/lemnik/eodsql/spi/util/MapDataObjectBinding.java index 6883c84..2286517 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/MapDataObjectBinding.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/MapDataObjectBinding.java @@ -9,7 +9,6 @@ import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -37,7 +36,7 @@ public class MapDataObjectBinding extends DataObjectBinding> @SuppressWarnings("unchecked") public static Class> getStringObjectMapObjectType() { - return (Class>) Collections. emptyMap().getClass(); + return (Class) HashMap.class; } @Override diff --git a/eodsql/src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java b/eodsql/src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java index b4d736e..e15da63 100644 --- a/eodsql/src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java +++ b/eodsql/src/net/lemnik/eodsql/spi/util/ResultSetWrapper.java @@ -1,5 +1,7 @@ package net.lemnik.eodsql.spi.util; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.InvocationTargetException; @@ -374,10 +376,10 @@ public abstract class ResultSetWrapper { final Map parameters) throws InvalidDataTypeException, InvalidQueryException { - if(genericReturnType instanceof Class && - !((Class)genericReturnType).isArray()) { + if((genericReturnType instanceof Class && + !((Class)genericReturnType).isArray()) || isStringObjectMap(genericReturnType)) { - final Class type = (Class)genericReturnType; + final Class type = toClass(genericReturnType); return getResultSetWrapperForSimpleClass( type, @@ -396,6 +398,48 @@ public abstract class ResultSetWrapper { genericReturnType); } + static Class toClass(Type type) { + if (type instanceof Class) { + return (Class) type; + } + if (isStringObjectMap(type)) { + return MapDataObjectBinding.getStringObjectMapObjectType(); + } + if (isStringObjectMapArray(type)) { + return Map[].class; + } + throw new InvalidQueryException( + "Return type must be a class"); + } + + static boolean isStringObjectMap(Type type) { + if (type instanceof ParameterizedType) { + final Type raw = ((ParameterizedType) type).getRawType(); + if (raw != Map.class) { + return false; + } + final Type[] genArgs = ((ParameterizedType) type).getActualTypeArguments(); + if (genArgs.length == 2 && (genArgs[0] instanceof Class) && genArgs[1] instanceof Class) { + final Class genArg1 = (Class) genArgs[0]; + final Class genArg2 = (Class) genArgs[1]; + if (genArg1 == String.class && genArg2 == Object.class) { + return true; + } + } + } + return false; + } + + static boolean isStringObjectMapArray(Type type) { + if (type instanceof GenericArrayType) { + final GenericArrayType arrayType = (GenericArrayType) type; + if (isStringObjectMap(arrayType.getGenericComponentType())) { + return true; + } + } + return false; + } + /** *

* Each {@code ResultSetWrapper} implementation will need at least one {@code Factory} diff --git a/eodsql/test/net/lemnik/eodsql/DataSetQuery.java b/eodsql/test/net/lemnik/eodsql/DataSetQuery.java index ec60475..33492fb 100644 --- a/eodsql/test/net/lemnik/eodsql/DataSetQuery.java +++ b/eodsql/test/net/lemnik/eodsql/DataSetQuery.java @@ -1,6 +1,7 @@ package net.lemnik.eodsql; import java.util.Collection; +import java.util.Map; /** * @@ -11,7 +12,7 @@ public interface DataSetQuery extends BaseQuery { @Update("CREATE TABLE objects (" + "id CHAR(36), " + "data VARCHAR(100)," - + "index INTEGER)") + + "index INTEGER PRIMARY KEY)") public void createObjectsTable(); @Update("DROP TABLE objects") @@ -44,5 +45,20 @@ public interface DataSetQuery extends BaseQuery { public DataSet getConnected(); @Select(sql = "SELECT * FROM objects ORDER BY index", readOnly = false) + public DataSet> getAsDataSetMap(); + + @Select("SELECT * FROM objects ORDER BY index") + public DataIterator> getAsDataIteratorMap(); + + @Select("SELECT * FROM objects ORDER BY index") + public Collection> getAsCollectionMap(); + + @Select("SELECT * FROM objects ORDER BY index") + public Map[] getAsArrayMap(); + + @Select("SELECT * FROM objects ORDER BY index limit 1") + public Map getFirstAsMap(); + + @Select(sql = "SELECT * FROM objects ORDER BY index", readOnly = false) public DataSet getWritable(); } diff --git a/eodsql/test/net/lemnik/eodsql/MapTestObject.java b/eodsql/test/net/lemnik/eodsql/MapTestObject.java new file mode 100644 index 0000000..b701d22 --- /dev/null +++ b/eodsql/test/net/lemnik/eodsql/MapTestObject.java @@ -0,0 +1,241 @@ +package net.lemnik.eodsql; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * + * @author Bernd Rinn + */ +public class MapTestObject extends EoDTestCase { + + protected DataSetQuery query = null; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + query = QueryTool.getQuery( + getConnection(), + DataSetQuery.class); + + query.createObjectsTable(); + } + + @Override + protected void tearDown() throws Exception { + query.dropTable(); + query.close(); + super.tearDown(); + } + + protected SimpleObject newSimpleObject(final int index) { + final SimpleObject obj = new SimpleObject(); + obj.id = UUID.randomUUID(); + obj.data = Integer.toBinaryString(index); + obj.order = index; + + return obj; + } + + protected void insertData(final Collection> validation) { + for(int i = 0; i < 1000; i++) { + final SimpleObject obj = newSimpleObject(i); + + query.insert(obj); + final Map map = new HashMap(); + map.put("id", obj.id.toString()); + map.put("data", obj.data); + map.put("index", obj.order); + validation.add(map); + } + } + + private void assertDataSetEquals( + final List> validation, + final DataSet> objects) { + + assertEquals( + "DataSet size is not equal to the number of objects inserted", + validation.size(), + objects.size()); + + for(int i = 0; i < validation.size(); i++) { + assertEquals(validation.get(i), objects.get(i)); + } + } + + private void assertDataSetEquals( + final List> validation, + final Map[] objects) { + + assertEquals( + "DataSet size is not equal to the number of objects inserted", + validation.size(), + objects.length); + + for(int i = 0; i < validation.size(); i++) { + assertEquals(validation.get(i), objects[i]); + } + } + + private void assertDataSetEquals( + final List> validation, + final DataIterator> objects) { + + int idx = 0; + while (objects.hasNext()) { + assertEquals(validation.get(idx), objects.next()); + ++idx; + } + assertEquals( + "DataSet size is not equal to the number of objects inserted", + validation.size(), + idx); + } + + private void assertDataSetEquals( + final List> validation, + final Collection> objects) { + + int idx = 0; + Iterator> it = objects.iterator(); + while (it.hasNext()) { + assertEquals(validation.get(idx), it.next()); + ++idx; + } + assertEquals( + "DataSet size is not equal to the number of objects inserted", + validation.size(), + idx); + } + + public void testListIteratorDefault() throws Exception { + final Set> validation = new HashSet>(); + + insertData(validation); + + final DataSet> objects = query.getAsDataSetMap(); + final ListIterator> li = objects.listIterator(); + + assertFalse(li.hasPrevious()); + assertTrue(li.hasNext()); + assertEquals(0, li.nextIndex()); + assertEquals(-1, li.previousIndex()); + + final Map so = li.next(); + + assertNotNull(so); + + assertTrue(li.hasPrevious()); + assertTrue(li.hasNext()); + assertEquals(0, li.previousIndex()); + assertEquals(1, li.nextIndex()); + + objects.close(); + } + + public void testSubList() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + final DataSet> objects = query.getAsDataSetMap(); + + final List> sublist = objects.subList(100, 200); + final List> slvalidation = validation.subList(100, 200); + + assertEquals( + "Sublist size is wrong.", + slvalidation.size(), + sublist.size()); + + for(int i = 0; i < 100; i++) { + assertEquals(slvalidation.get(i), sublist.get(i)); + assertEquals(objects.get(i + 100), sublist.get(i)); + } + + assertEquals( + "List equality failed.", + slvalidation, + sublist); + } + + public void testDataSetContents() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + final DataSet> objects = query.getAsDataSetMap(); + assertDataSetEquals(validation, objects); + + objects.close(); + } + + public void testDataIteratorContents() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + final DataIterator> objects = query.getAsDataIteratorMap(); + assertDataSetEquals(validation, objects); + + objects.close(); + } + + public void testCollectionContents() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + final Collection> objects = query.getAsCollectionMap(); + assertDataSetEquals(validation, objects); + } + + public void testArrayContents() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + final Map[] objects = query.getAsArrayMap(); + assertDataSetEquals(validation, objects); + } + + public void testSingleRowContents() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + final Map object = query.getFirstAsMap(); + assertEquals(validation.get(0), object); + } + + public void testWriteToDataSet() throws Exception { + final List> validation = new ArrayList>(); + + insertData(validation); + + DataSet> objects = query.getAsDataSetMap(); + for (int i = 0; i < validation.size(); ++i) { + final UUID newUUID = UUID.randomUUID(); + validation.get(i).put("data", newUUID.toString()); + final Map map = objects.get(i); + map.put("data", newUUID.toString()); + objects.set(i, map); + } + objects.close(); + objects = query.getAsDataSetMap(); + assertDataSetEquals(validation, objects); + + objects.close(); + } + +} -- 1.8.0