From c1c00648d906ebc09e4f3d68ba511b4be3ed0b32 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 10 Jun 2017 10:59:53 -0700 Subject: [PATCH] MySQL and Flatfile databases now store and retrieve objects. --- .../us/tastybento/bskyblock/BSkyBlock.java | 13 +- .../flatfile/FlatFileDatabaseHandler.java | 202 +++++++++++++----- .../managers/AbstractDatabaseHandler.java | 7 +- .../database/mysql/MySQLDatabaseHandler.java | 45 ++-- .../us/tastybento/bskyblock/util/Util.java | 29 +++ 5 files changed, 203 insertions(+), 93 deletions(-) diff --git a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java index 947b25bfe..1385e40fb 100755 --- a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java +++ b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java @@ -2,6 +2,7 @@ package us.tastybento.bskyblock; import java.util.HashMap; import java.util.HashSet; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; @@ -14,7 +15,6 @@ import us.tastybento.bskyblock.config.BSBLocale; import us.tastybento.bskyblock.config.PluginConfig; import us.tastybento.bskyblock.config.Settings; import us.tastybento.bskyblock.database.BSBDatabase; -import us.tastybento.bskyblock.database.BSBDatabase.DatabaseType; import us.tastybento.bskyblock.database.managers.IslandsManager; import us.tastybento.bskyblock.database.managers.OfflineHistoryMessages; import us.tastybento.bskyblock.database.managers.PlayersManager; @@ -83,6 +83,7 @@ public class BSkyBlock extends JavaPlugin{ // Test: Create a random island and save it // TODO: ideally this should be in a test class! + /* UUID owner = UUID.fromString("ddf561c5-72b6-4ec6-a7ea-8b50a893beb2"); Island island = islandsManager.createIsland(new Location(getServer().getWorld("world"),0,0,0,0,0), owner); @@ -109,17 +110,21 @@ public class BSkyBlock extends JavaPlugin{ getLogger().info("DEBUG: ************ Finished saving, now loading *************"); - + */ playersManager.load(); islandsManager.load(); - + /* + *DEBUG CODE Island loadedIsland = islandsManager.getIsland(owner); getLogger().info("Island name = " + loadedIsland.getName()); getLogger().info("Island locked = " + loadedIsland.getLocked()); //getLogger().info("Random set = " + randomSet); getLogger().info("Island coops = " + loadedIsland.getCoops()); - + for (Entry flag: loadedIsland.getFlags().entrySet()) { + getLogger().info("Flag " + flag.getKey().name() + " = " + flag.getValue()); + } + */ // Save islands & players data asynchronously every X minutes Settings.databaseBackupPeriod = 10 * 60 * 20; plugin.getServer().getScheduler().runTaskTimer(plugin, new Runnable() { diff --git a/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java index 8b9ad796b..72dca091c 100644 --- a/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/flatfile/FlatFileDatabaseHandler.java @@ -7,18 +7,24 @@ import java.io.FilenameFilter; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.configuration.file.YamlConfiguration; import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.database.DatabaseConnecter; import us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler; +import us.tastybento.bskyblock.util.Util; /** * Class that creates a list of s filled with values from the corresponding @@ -53,9 +59,10 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InstantiationException + * @throws ClassNotFoundException */ @Override - public T selectObject(String key) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException { + public T selectObject(String key) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, ClassNotFoundException { YamlConfiguration config = databaseConnecter.loadYamlFile(type.getSimpleName(), key); return createObject(config); } @@ -68,9 +75,10 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { * @throws IllegalArgumentException * @throws InvocationTargetException * @throws IntrospectionException + * @throws ClassNotFoundException */ @Override - public List selectObjects() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException { + public List selectObjects() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, ClassNotFoundException { List list = new ArrayList(); FilenameFilter ymlFilter = new FilenameFilter() { @Override @@ -108,49 +116,73 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { * @throws IntrospectionException * @throws IllegalArgumentException * @throws InvocationTargetException + * @throws ClassNotFoundException */ - private T createObject(YamlConfiguration config) throws InstantiationException, IllegalAccessException, IntrospectionException, IllegalArgumentException, InvocationTargetException { + private T createObject(YamlConfiguration config) throws InstantiationException, IllegalAccessException, IntrospectionException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException { T instance = type.newInstance(); for (Field field : type.getDeclaredFields()) { - /* We assume the table-column-names exactly match the variable-names of T */ - // TODO: depending on the data type, it'll need deserializing - try { + PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); + Method method = propertyDescriptor.getWriteMethod(); + //plugin.getLogger().info("DEBUG: " + field.getName() + ": " + propertyDescriptor.getPropertyType().getTypeName()); + if (propertyDescriptor.getPropertyType().equals(HashMap.class)) { - PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); - Method method = propertyDescriptor.getWriteMethod(); - plugin.getLogger().info("DEBUG: " + field.getName() + ": " + propertyDescriptor.getPropertyType().getTypeName()); - if (propertyDescriptor.getPropertyType().equals(HashMap.class)) { - plugin.getLogger().info("DEBUG: is HashMap"); - // TODO: this may not work with all keys. Further serialization may be required. - HashMap value = new HashMap(); - for (String key : config.getConfigurationSection(field.getName()).getKeys(false)) { - value.put(key, config.get(field.getName() + "." + key)); - } - method.invoke(instance, value); - } else if (propertyDescriptor.getPropertyType().equals(Set.class)) { - plugin.getLogger().info("DEBUG: is Set " + propertyDescriptor.getReadMethod().getGenericReturnType().getTypeName()); - - // TODO: this may not work with all keys. Further serialization may be required. - Set value = new HashSet((List) config.getList(field.getName())); - - method.invoke(instance, value); - } else if (propertyDescriptor.getPropertyType().equals(UUID.class)) { - plugin.getLogger().info("DEBUG: is UUID"); - String uuid = (String)config.get(field.getName()); - if (uuid == null || uuid.equals("null")) { - method.invoke(instance, (Object)null); - } else { - Object value = UUID.fromString(uuid); - method.invoke(instance, value); - } - } else { - Object value = config.get(field.getName()); - method.invoke(instance, value); + // Note that we have no idea what type this is + List collectionTypes = Util.getCollectionParameterTypes(method); + // collectionTypes should be 2 long + Type keyType = collectionTypes.get(0); + Type valueType = collectionTypes.get(1); + //plugin.getLogger().info("DEBUG: is HashMap<" + keyType.getTypeName() + ", " + valueType.getTypeName() + ">"); + // TODO: this may not work with all keys. Further serialization may be required. + HashMap value = new HashMap(); + for (String key : config.getConfigurationSection(field.getName()).getKeys(false)) { + Object mapKey = deserialize(key,Class.forName(keyType.getTypeName())); + Object mapValue = deserialize(config.get(field.getName() + "." + key), Class.forName(valueType.getTypeName())); + value.put(mapKey, mapValue); } - } catch (Exception e) { - e.printStackTrace(); + method.invoke(instance, value); + } else if (propertyDescriptor.getPropertyType().equals(Set.class)) { + //plugin.getLogger().info("DEBUG: is Set " + propertyDescriptor.getReadMethod().getGenericReturnType().getTypeName()); + //plugin.getLogger().info("DEBUG: adding a set"); + // Loop through the collection resultset + // Note that we have no idea what type this is + List collectionTypes = Util.getCollectionParameterTypes(method); + // collectionTypes should be only 1 long + Type setType = collectionTypes.get(0); + //plugin.getLogger().info("DEBUG: is HashSet<" + setType.getTypeName() + ">"); + Set value = new HashSet(); + //plugin.getLogger().info("DEBUG: collection type argument = " + collectionTypes); + //plugin.getLogger().info("DEBUG: setType = " + setType.getTypeName()); + for (Object listValue: config.getList(field.getName())) { + //plugin.getLogger().info("DEBUG: collectionResultSet size = " + collectionResultSet.getFetchSize()); + ((Set) value).add(deserialize(listValue,Class.forName(setType.getTypeName()))); + } + // TODO: this may not work with all keys. Further serialization may be required. + //Set value = new HashSet((List) config.getList(field.getName())); + method.invoke(instance, value); + } else if (propertyDescriptor.getPropertyType().equals(ArrayList.class)) { + //plugin.getLogger().info("DEBUG: is Set " + propertyDescriptor.getReadMethod().getGenericReturnType().getTypeName()); + //plugin.getLogger().info("DEBUG: adding a set"); + // Loop through the collection resultset + // Note that we have no idea what type this is + List collectionTypes = Util.getCollectionParameterTypes(method); + // collectionTypes should be only 1 long + Type setType = collectionTypes.get(0); + List value = new ArrayList(); + //plugin.getLogger().info("DEBUG: collection type argument = " + collectionTypes); + //plugin.getLogger().info("DEBUG: setType = " + setType.getTypeName()); + for (Object listValue: config.getList(field.getName())) { + //plugin.getLogger().info("DEBUG: collectionResultSet size = " + collectionResultSet.getFetchSize()); + ((List) value).add(deserialize(listValue,Class.forName(setType.getTypeName()))); + } + // TODO: this may not work with all keys. Further serialization may be required. + //Set value = new HashSet((List) config.getList(field.getName())); + method.invoke(instance, value); + } else { + // Not a collection + Object value = config.get(field.getName()); + method.invoke(instance, deserialize(value,propertyDescriptor.getPropertyType())); } } @@ -182,8 +214,8 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { Method method = propertyDescriptor.getReadMethod(); // Invoke the read method to get the value. We have no idea what type of value it is. Object value = method.invoke(instance); - plugin.getLogger().info("DEBUG: writing " + field.getName()); - plugin.getLogger().info("DEBUG: property desc = " + propertyDescriptor.getPropertyType().getTypeName()); + //plugin.getLogger().info("DEBUG: writing " + field.getName()); + //plugin.getLogger().info("DEBUG: property desc = " + propertyDescriptor.getPropertyType().getTypeName()); // Depending on the vale type, it'll need serializing differenty // Check if this field is the mandatory UniqueId field. This is used to identify this instantiation of the class if (method.getName().equals("getUniqueId")) { @@ -199,36 +231,98 @@ public class FlatFileDatabaseHandler extends AbstractDatabaseHandler { filename = id; } // UUID's need special serialization - if (propertyDescriptor.getPropertyType().equals(UUID.class)) { - plugin.getLogger().info("DEBUG: writing UUID for " + field.getName()); - if (value != null) { - config.set(field.getName(), ((UUID)value).toString()); - } else { - // UUID's can be null, so they need to be saved as the string "null" - config.set(field.getName(), "null"); + if (propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(Map.class)) { + // Maps need to have keys serialized + //plugin.getLogger().info("DEBUG: Map for " + field.getName()); + Map result = new HashMap(); + for (Entry object : ((Map)value).entrySet()) { + // Serialize all key types + // TODO: also need to serialize values? + result.put(serialize(object.getKey()), object.getValue()); } + // Save the list in the config file + config.set(field.getName(), result); } else if (propertyDescriptor.getPropertyType().equals(Set.class)) { // Sets need to be serialized as string lists - plugin.getLogger().info("DEBUG: Set for " + field.getName()); - + //plugin.getLogger().info("DEBUG: Set for " + field.getName()); List list = new ArrayList(); for (Object object : (Set)value) { - if (object instanceof UUID) { - list.add(((UUID)object).toString()); - } + list.add(serialize(object)); } // Save the list in the config file config.set(field.getName(), list); } else { // For all other data that doesn't need special serialization - config.set(field.getName(), value); + config.set(field.getName(), serialize(value)); } } if (filename.isEmpty()) { - throw new IllegalArgumentException("No UUID in class"); + throw new IllegalArgumentException("No uniqueId in class"); } // Save the file in the right folder databaseConnecter.saveYamlFile(config, type.getSimpleName(), filename); } + + /** + * Serialize an object if required + * @param object + * @return + */ + private Object serialize(Object object) { + if (object == null) { + return "null"; + } + //plugin.getLogger().info("DEBUG: serializing " + object.getClass().getTypeName()); + if (object instanceof UUID) { + return ((UUID)object).toString(); + } + if (object instanceof World) { + return ((World)object).getName(); + } + if (object instanceof Location) { + return Util.getStringLocation((Location)object); + } + if (object instanceof Enum) { + //Custom enums are a child of the Enum class. Just get the names of each one. + return ((Enum)object).name(); + } + return object; + } + + private Object deserialize(Object value, Class clazz) { + //plugin.getLogger().info("DEBUG: deserialize - class is " + clazz.getCanonicalName()); + if (value instanceof String && value.equals("null")) { + // If the value is null as a string, return null + return null; + } + // Types that need to be deserialized + if (clazz.equals(UUID.class)) { + value = UUID.fromString((String)value); + } + // Bukkit Types + if (clazz.equals(Location.class)) { + // Get Location from String - may be null... + value = Util.getLocationString(((String)value)); + } + if (clazz.equals(World.class)) { + // Get world by name - may be null... + value = plugin.getServer().getWorld((String)value); + } + // Enums + if (clazz.getSuperclass() != null && clazz.getSuperclass().equals(Enum.class)) { + //Custom enums are a child of the Enum class. + // Find out the value + try { + Class enumClass = (Class)clazz; + value = Enum.valueOf(enumClass, (String)value); + } catch (Exception e) { + // Maybe this value does not exist? + // TODO return something? + e.printStackTrace(); + } + } + return value; + } + } diff --git a/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java index fc03f5a77..083b3361a 100644 --- a/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java @@ -108,8 +108,9 @@ public abstract class AbstractDatabaseHandler { * @throws IntrospectionException * @throws SecurityException * @throws SQLException + * @throws ClassNotFoundException */ - protected abstract List selectObjects() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, SecurityException; + protected abstract List selectObjects() throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, SecurityException, ClassNotFoundException; /** * Creates a filled with values from the corresponding @@ -122,8 +123,10 @@ public abstract class AbstractDatabaseHandler { * @throws IllegalAccessException * @throws InstantiationException * @throws SQLException + * @throws ClassNotFoundException + * @throws SecurityException */ - protected abstract T selectObject(String uniqueId) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException; + protected abstract T selectObject(String uniqueId) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, SecurityException, ClassNotFoundException; /** * Inserts T into the corresponding database-table diff --git a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java index b9d86b86f..062bfb5fc 100644 --- a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java @@ -249,29 +249,6 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { return columns; } - /** - * Get a list of parameter types for the collection argument in this method - * @param writeMethod - * @return - */ - private List getCollectionParameterTypes(Method writeMethod) { - List result = new ArrayList(); - // Get the return type - // This uses a trick to extract what the arguments are of the writeMethod of the field. - // In this way, we can deduce what type needs to be written at runtime. - Type[] genericParameterTypes = writeMethod.getGenericParameterTypes(); - // There could be more than one argument, so step through them - for (int i = 0; i < genericParameterTypes.length; i++) { - // If the argument is a parameter, then do something - this should always be true if the parameter is a collection - if( genericParameterTypes[i] instanceof ParameterizedType ) { - // Get the actual type arguments of the parameter - Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments(); - result.addAll(Arrays.asList(parameters)); - } - } - return result; - } - /* (non-Javadoc) * @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#createSelectQuery() @@ -504,12 +481,13 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { * @throws IllegalAccessException * @throws IntrospectionException * @throws InvocationTargetException + * @throws ClassNotFoundException */ @Override public List selectObjects() throws SQLException, SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, - IntrospectionException, InvocationTargetException { + IntrospectionException, InvocationTargetException, ClassNotFoundException { Connection connection = null; Statement statement = null; @@ -535,7 +513,7 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { @Override protected T selectObject(String uniqueId) throws InstantiationException, IllegalAccessException, IllegalArgumentException, - InvocationTargetException, IntrospectionException, SQLException { + InvocationTargetException, IntrospectionException, SQLException, SecurityException, ClassNotFoundException { Connection connection = null; Statement statement = null; ResultSet resultSet = null; @@ -578,13 +556,14 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { * @throws IllegalAccessException * @throws IntrospectionException * @throws InvocationTargetException + * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") private List createObjects(ResultSet resultSet) throws SecurityException, IllegalArgumentException, SQLException, InstantiationException, IllegalAccessException, IntrospectionException, - InvocationTargetException { + InvocationTargetException, ClassNotFoundException { List list = new ArrayList(); // The database can return multiple results in one go, e.g., all the islands in the database @@ -633,7 +612,7 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { //plugin.getLogger().info("DEBUG: adding a set"); // Loop through the collection resultset // Note that we have no idea what type this is - List collectionTypes = getCollectionParameterTypes(method); + List collectionTypes = Util.getCollectionParameterTypes(method); // collectionTypes should be only 1 long Type setType = collectionTypes.get(0); value = new HashSet(); @@ -641,13 +620,13 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { //plugin.getLogger().info("DEBUG: setType = " + setType.getTypeName()); while (collectionResultSet.next()) { //plugin.getLogger().info("DEBUG: collectionResultSet size = " + collectionResultSet.getFetchSize()); - ((Set) value).add(deserialize(collectionResultSet.getObject(1),setType.getClass())); + ((Set) value).add(deserialize(collectionResultSet.getObject(1),Class.forName(setType.getTypeName()))); } } else if (propertyDescriptor.getPropertyType().equals(ArrayList.class)) { //plugin.getLogger().info("DEBUG: Adding a list "); // Loop through the collection resultset // Note that we have no idea what type this is - List collectionTypes = getCollectionParameterTypes(method); + List collectionTypes = Util.getCollectionParameterTypes(method); // collectionTypes should be only 1 long Type setType = collectionTypes.get(0); value = new ArrayList(); @@ -655,14 +634,14 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { while (collectionResultSet.next()) { //plugin.getLogger().info("DEBUG: adding to the list"); //plugin.getLogger().info("DEBUG: collectionResultSet size = " + collectionResultSet.getFetchSize()); - ((List) value).add(deserialize(collectionResultSet.getObject(1),setType.getClass())); + ((List) value).add(deserialize(collectionResultSet.getObject(1),Class.forName(setType.getTypeName()))); } } else if (propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(HashMap.class)) { //plugin.getLogger().info("DEBUG: Adding a map "); // Loop through the collection resultset // Note that we have no idea what type this is - List collectionTypes = getCollectionParameterTypes(method); + List collectionTypes = Util.getCollectionParameterTypes(method); // collectionTypes should be 2 long Type keyType = collectionTypes.get(0); Type valueType = collectionTypes.get(1); @@ -673,9 +652,9 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { //plugin.getLogger().info("DEBUG: collectionResultSet size = " + collectionResultSet.getFetchSize()); // Work through the columns // Key - Object key = (deserialize(collectionResultSet.getObject(1),keyType.getClass())); + Object key = deserialize(collectionResultSet.getObject(1),Class.forName(keyType.getTypeName())); //plugin.getLogger().info("DEBUG: key = " + key); - Object mapValue = (deserialize(collectionResultSet.getObject(2),valueType.getClass())); + Object mapValue = deserialize(collectionResultSet.getObject(2),Class.forName(valueType.getTypeName())); //plugin.getLogger().info("DEBUG: value = " + mapValue); ((Map) value).put(key,mapValue); } diff --git a/src/main/java/us/tastybento/bskyblock/util/Util.java b/src/main/java/us/tastybento/bskyblock/util/Util.java index 180a9c48f..77a5b3c86 100755 --- a/src/main/java/us/tastybento/bskyblock/util/Util.java +++ b/src/main/java/us/tastybento/bskyblock/util/Util.java @@ -1,6 +1,12 @@ package us.tastybento.bskyblock.util; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -113,4 +119,27 @@ public class Util { } return location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ() + ":" + Float.floatToIntBits(location.getYaw()) + ":" + Float.floatToIntBits(location.getPitch()); } + + /** + * Get a list of parameter types for the collection argument in this method + * @param writeMethod + * @return + */ + public static List getCollectionParameterTypes(Method writeMethod) { + List result = new ArrayList(); + // Get the return type + // This uses a trick to extract what the arguments are of the writeMethod of the field. + // In this way, we can deduce what type needs to be written at runtime. + Type[] genericParameterTypes = writeMethod.getGenericParameterTypes(); + // There could be more than one argument, so step through them + for (int i = 0; i < genericParameterTypes.length; i++) { + // If the argument is a parameter, then do something - this should always be true if the parameter is a collection + if( genericParameterTypes[i] instanceof ParameterizedType ) { + // Get the actual type arguments of the parameter + Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments(); + result.addAll(Arrays.asList(parameters)); + } + } + return result; + } }