diff --git a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java index adcaba3c5..6ae060b79 100755 --- a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java +++ b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java @@ -1,8 +1,11 @@ package us.tastybento.bskyblock; import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; +import org.bukkit.Location; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -15,6 +18,8 @@ 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; +import us.tastybento.bskyblock.database.objects.Island; +import us.tastybento.bskyblock.database.objects.Island.SettingsFlag; import us.tastybento.bskyblock.util.VaultHelper; /** @@ -97,8 +102,12 @@ public class BSkyBlock extends JavaPlugin{ island.setName("new name"); island.setPurgeProtected(true); islandsManager.save(false); - */ + + + getLogger().info("DEBUG: ************ Finished saving, now loading *************"); // TODO: Write loading code for MySQL + * + */ playersManager.load(); islandsManager.load(); 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 4ce304e1f..fc03f5a77 100644 --- a/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/managers/AbstractDatabaseHandler.java @@ -8,6 +8,7 @@ import java.util.List; import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.database.DatabaseConnecter; +import us.tastybento.bskyblock.database.objects.DataObject; /** * An abstract class that handles insert/select-operations into/from a database @@ -120,8 +121,9 @@ public abstract class AbstractDatabaseHandler { * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InstantiationException + * @throws SQLException */ - protected abstract T selectObject(String uniqueId) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException; + protected abstract T selectObject(String uniqueId) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException; /** * Inserts T into the corresponding database-table diff --git a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseConnecter.java b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseConnecter.java index dfd695de4..d473bf1b5 100644 --- a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseConnecter.java +++ b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseConnecter.java @@ -28,7 +28,7 @@ public class MySQLDatabaseConnecter implements DatabaseConnecter { e.printStackTrace(); } // jdbc:mysql://localhost:3306/Peoples?autoReconnect=true&useSSL=false - connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + "/" + dbSettings.getDatabaseName() + "?autoReconnect=true&useSSL=false"; + connectionUrl = "jdbc:mysql://" + dbSettings.getHost() + "/" + dbSettings.getDatabaseName() + "?autoReconnect=true&useSSL=false&allowMultiQueries=true"; } @Override 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 41034d740..43cad12c2 100644 --- a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java @@ -16,9 +16,11 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -44,7 +46,13 @@ import us.tastybento.bskyblock.util.Util; */ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { + /** + * Connection to the database + */ private Connection connection = null; + /** + * This hashmap maps Java types to MySQL SQL types because they are not the same + */ private static HashMap mySQLmapping; { mySQLmapping = new HashMap(); @@ -71,7 +79,6 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { mySQLmapping.put(Location.class.getTypeName(), "VARCHAR(254)"); mySQLmapping.put(World.class.getTypeName(), "VARCHAR(254)"); - // TODO: Collections - these need to create another table and link to it // Collections are stored as additional tables. The boolean indicates whether there // is any data in it or not (maybe) mySQLmapping.put(Set.class.getTypeName(), "BOOL"); @@ -81,6 +88,13 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { } + /** + * Handles the connection to the database and creation of the initial database schema (tables) for + * the class that will be stored. + * @param plugin + * @param type - the type of class to be stored in the database. Must inherit DataObject + * @param databaseConnecter - authentication details for the database + */ public MySQLDatabaseHandler(BSkyBlock plugin, Class type, DatabaseConnecter databaseConnecter) { super(plugin, type, databaseConnecter); try { @@ -110,45 +124,58 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { PreparedStatement pstmt = null; try { String sql = "CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "` ("; + // Run through the fields of the class using introspection for (Field field : type.getDeclaredFields()) { + // Get the description of the field PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); - plugin.getLogger().info("DEBUG: Field = " + field.getName() + "(" + propertyDescriptor.getPropertyType().getTypeName() + ")"); + //plugin.getLogger().info("DEBUG: Field = " + field.getName() + "(" + propertyDescriptor.getPropertyType().getTypeName() + ")"); // Get default SQL mappings + // Get the write method for this field. This method will take an argument of the type of this field. Method writeMethod = propertyDescriptor.getWriteMethod(); + // The SQL column name is the name of the field String columnName = field.getName(); + // Get the mapping for this field from the hashmap String mapping = mySQLmapping.get(propertyDescriptor.getPropertyType().getTypeName()); + // If it exists, then create the SQL if (mapping != null) { + // Note that the column name must be enclosed in `'s because it may include reserved words. sql += "`" + columnName + "` " + mapping + ","; - // Create set and map tables. + // Create set and map tables if the type is a collection if (propertyDescriptor.getPropertyType().equals(Set.class) || propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(ArrayList.class)) { - // The ID in this table relates to the parent table + // The ID in this table relates to the parent table and is unique String setSql = "CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "." + field.getName() + "` (" + "uniqueId VARCHAR(36) NOT NULL, "; // Get columns separated by commas setSql += getCollectionColumns(writeMethod,false,true); - // Add primary key + // Close the SQL string setSql += ")"; - plugin.getLogger().info(setSql); + //plugin.getLogger().info(setSql); + // Execute the statement PreparedStatement collections = connection.prepareStatement(setSql); collections.executeUpdate(); } } else { + // The Java type is not in the hashmap, so we'll jus guess that it can be stored in a string + // This should NOT be used in general because every type should be in the hashmap sql += field.getName() + " VARCHAR(254),"; plugin.getLogger().severe("Unknown type! Hoping it'll fit in a string!"); } } //plugin.getLogger().info("DEBUG: SQL before trim string = " + sql); + // For the main table for the class, the unique ID is the primary key sql += " PRIMARY KEY (uniqueId))"; - plugin.getLogger().info("DEBUG: SQL string = " + sql); + //plugin.getLogger().info("DEBUG: SQL string = " + sql); + // Prepare and execute the database statements pstmt = connection.prepareStatement(sql.toString()); pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { + // Close the database properly MySQLDatabaseResourceCloser.close(pstmt); MySQLDatabaseResourceCloser.close(pstmt); } @@ -157,6 +184,8 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { /** * Returns a string of columns separated by commas that represent the parameter types of this method + * this is used to get into parameters like HashMap at runtime and find out Location + * and Boolean. * @param writeMethod * @param usePlaceHolders * true, if PreparedStatement-placeholders ('?') should be used @@ -167,36 +196,52 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { private String getCollectionColumns(Method writeMethod, boolean usePlaceHolders, boolean createSchema) { String columns = ""; // 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(); //parameters[0] contains java.lang.String for method like "method(List value)" + // Run through them one by one and create a SQL string int index = 0; boolean first = true; for (Type type : parameters) { plugin.getLogger().info("DEBUG: set type = " + type.getTypeName()); + // first is used to add commas in the right place if (first) first = false; else columns += ", "; + // this is used if the string is going to be used to insert something so the value will replace the ? if (usePlaceHolders) { columns +="?"; } else { + // This is a request for column names. String setMapping = mySQLmapping.get(type.getTypeName()); + // We know the mapping of this type if (setMapping != null) { + // Write the name of this type, followed by an index number to make it unique columns += "`" + type.getTypeName() + "_" + index + "`"; + // If this is the schema, then we also need to add the mapping type if (createSchema) { columns += " " + setMapping; } } else { + // We do not know the type, oops + // Write the name of this type, followed by an index number to make it unique columns += "`" + type.getTypeName() + "_" + index + "`"; if (createSchema) { + // If this is the schema, then guess the mapping type columns += " VARCHAR(254)"; plugin.getLogger().warning("Unknown type! Hoping it'll fit in a string!"); } } } + // Increment the index so each column has a unique name index++; } } @@ -204,6 +249,33 @@ 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() + */ @Override protected String createSelectQuery() { @@ -221,13 +293,17 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { return sb.toString(); } + /* (non-Javadoc) + * @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#createInsertQuery() + */ @Override protected String createInsertQuery() { StringBuilder sb = new StringBuilder(); - + // Replace into is used so that any data in the table will be replaced with updated data sb.append("REPLACE INTO "); sb.append("`"); + // The table name is the canonical name, so that add-ons can be sure of a unique table in the database sb.append(type.getCanonicalName()); sb.append("`"); sb.append("("); @@ -243,7 +319,7 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { /** * Inserts a into the corresponding database-table * - * @param instance that should be inserted into the corresponding database-table + * @param instance that should be inserted into the corresponding database-table. Must extend DataObject. * @throws SQLException * @throws SecurityException * @throws IllegalArgumentException @@ -253,6 +329,9 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { * @throws InvocationTargetException * @throws NoSuchMethodException */ + /* (non-Javadoc) + * @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#insertObject(java.lang.Object) + */ @Override public void insertObject(T instance) throws SQLException, SecurityException, IllegalArgumentException, @@ -263,120 +342,145 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { PreparedStatement preparedStatement = null; try { + // Try to connect to the database connection = databaseConnecter.createConnection(); + // insertQuery is created in super from the createInsertQuery() method preparedStatement = connection.prepareStatement(insertQuery); - // Get the uniqueId + // Get the uniqueId. As each class extends DataObject, it must have this method in it. Method getUniqueId = type.getMethod("getUniqueId"); String uniqueId = (String) getUniqueId.invoke(instance); - plugin.getLogger().info("Unique Id = " + uniqueId); + //plugin.getLogger().info("DEBUG: Unique Id = " + uniqueId); if (uniqueId.isEmpty()) { throw new SQLException("uniqueId is blank"); } + // Create the insertion int i = 0; plugin.getLogger().info("DEBUG: insert Query " + insertQuery); + // Run through the fields in the class using introspection for (Field field : type.getDeclaredFields()) { - PropertyDescriptor propertyDescriptor = new PropertyDescriptor( - field.getName(), type); - + // Get the field's property descriptor + PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); + // Get the read method for this field Method method = propertyDescriptor.getReadMethod(); - plugin.getLogger().info("DEBUG: Field = " + field.getName() + "(" + propertyDescriptor.getPropertyType().getTypeName() + ")"); + //plugin.getLogger().info("DEBUG: Field = " + field.getName() + "(" + propertyDescriptor.getPropertyType().getTypeName() + ")"); //sql += "`" + field.getName() + "` " + mapping + ","; - + // Invoke the read method to obtain the value from the class - this is the value we need to store in the database Object value = method.invoke(instance); - // Create set and map tables. + // Create set and map table inserts if this is a Collection if (propertyDescriptor.getPropertyType().equals(Set.class) || propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(ArrayList.class)) { // Collection - // TODO Set the values in the subsidiary tables. - // TODO clear the table? - String setSql = "INSERT INTO `" + type.getCanonicalName() + "." + field.getName() + "` (uniqueId, "; + // The table is cleared everytime the data is stored again + String setSql = "DELETE FROM `" + type.getCanonicalName() + "." + field.getName() + "`;"; + // Insert into the table + setSql += "INSERT INTO `" + type.getCanonicalName() + "." + field.getName() + "` (uniqueId, "; + // Get the columns we are going to insert, just the names of them setSql += getCollectionColumns(propertyDescriptor.getWriteMethod(), false, false) + ") "; + // Get all the ?'s for the columns setSql += "VALUES ('" + uniqueId + "'," + getCollectionColumns(propertyDescriptor.getWriteMethod(), true, false) + ")"; + // Prepare the statement PreparedStatement collStatement = connection.prepareStatement(setSql); - plugin.getLogger().info("DEBUG: collection insert =" + setSql); + //plugin.getLogger().info("DEBUG: collection insert =" + setSql); // Do single dimension types (set and list) if (propertyDescriptor.getPropertyType().equals(Set.class) || propertyDescriptor.getPropertyType().equals(ArrayList.class)) { - plugin.getLogger().info("DEBUG: set class for "); - // Loop through the collection + //plugin.getLogger().info("DEBUG: set class for "); + // Loop through the set or list + // Note that we have no idea what type this is Collection collection = (Collection)value; Iterator it = collection.iterator(); while (it.hasNext()) { Object setValue = it.next(); - if (setValue instanceof UUID) { - // Serialize everything - setValue = serialize(setValue, setValue.getClass()); - } + //if (setValue instanceof UUID) { + // Serialize everything + setValue = serialize(setValue, setValue.getClass()); + //} + // Set the value from ? to whatever it is collStatement.setObject(1, setValue); plugin.getLogger().info("DEBUG: " + collStatement.toString()); + // Execute the SQL in the database collStatement.execute(); } } else if (propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(HashMap.class)) { - // Loop through the collection + // Loop through the map Map collection = (Map)value; Iterator it = collection.entrySet().iterator(); while (it.hasNext()) { Entry en = (Entry) it.next(); + // Get the key and serialize it Object key = serialize(en.getKey(), en.getKey().getClass()); - plugin.getLogger().info("DEBUG: key class = " + en.getKey().getClass().getTypeName()); - Object mapValue = serialize(en.getValue(), en.getValue().getClass());; + //plugin.getLogger().info("DEBUG: key class = " + en.getKey().getClass().getTypeName()); + // Get the value and serialize it + Object mapValue = serialize(en.getValue(), en.getValue().getClass()); + // Write the objects into prepared statement collStatement.setObject(1, key); collStatement.setObject(2, mapValue); - plugin.getLogger().info("DEBUG: " + collStatement.toString()); + //plugin.getLogger().info("DEBUG: " + collStatement.toString()); + // Write to database collStatement.execute(); } } - // Set value for the main insert + // Set value for the main insert. For collections, this is just a dummy value because the real values are in the + // additional table. value = true; } else { + // If the value is not a collection, it just needs to be serialized to go into the database. value = serialize(value, propertyDescriptor.getPropertyType()); } + // Set the value in the main prepared statement and increment the location + // Note that with prepared statements, they count from 1, not 0, so the ++ goes on the front of i. preparedStatement.setObject(++i, value); } - + // Add the statements to a batch preparedStatement.addBatch(); - + // Execute preparedStatement.executeBatch(); } finally { + // Close properly MySQLDatabaseResourceCloser.close(preparedStatement); MySQLDatabaseResourceCloser.close(preparedStatement); } } /** - * Serializes value + * Serializes values if required to go into a database. + * TODO: This method will need expanding to include additional Java types * @param value * @param clazz - the known class of value - * @return + * @return the object to write to the database */ private Object serialize(Object value, Class clazz) { plugin.getLogger().info("DEBUG: serialize - class is " + clazz.getTypeName()); if (value == null) { + // If the value is null to start, return null as a string return "null"; } // Types that need to be serialized + // TODO - add others, like Date, Timestamp, etc. if (clazz.equals(UUID.class)) { value = ((UUID)value).toString(); - } else - // Bukkit Types - if (clazz.equals(Location.class)) { - // Serialize - value = Util.getStringLocation(((Location)value)); - } else - if (clazz.equals(World.class)) { - // Serialize - get the name - value = ((World)value).getName(); - } else - if (clazz.getSuperclass() != null && clazz.getSuperclass().equals(Enum.class)) { - //Custom enums are a child of the Enum class. Just get the names of each one. - value = ((Enum)value).name(); - } + } + else + // Bukkit Types + if (clazz.equals(Location.class)) { + // Serialize + value = Util.getStringLocation(((Location)value)); + } else + if (clazz.equals(World.class)) { + // Serialize - get the name + value = ((World)value).getName(); + } else + if (clazz.getSuperclass() != null && clazz.getSuperclass().equals(Enum.class)) { + //Custom enums are a child of the Enum class. Just get the names of each one. + value = ((Enum)value).name(); + } if (value == null) { + // The value could become null from the above checks return "null"; } return value; @@ -422,15 +526,38 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { } } + /* (non-Javadoc) + * @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#selectObject(java.lang.String) + */ @Override protected T selectObject(String uniqueId) throws InstantiationException, IllegalAccessException, IllegalArgumentException, - InvocationTargetException, IntrospectionException { - // TODO Auto-generated method stub - return null; + InvocationTargetException, IntrospectionException, SQLException { + Connection connection = null; + Statement statement = null; + ResultSet resultSet = null; + + try { + connection = databaseConnecter.createConnection(); + statement = connection.createStatement(); + resultSet = statement.executeQuery(selectQuery); + + List result = createObjects(resultSet); + if (!result.isEmpty()) { + return result.get(0); + } + return null; + + } finally { + MySQLDatabaseResourceCloser.close(resultSet); + MySQLDatabaseResourceCloser.close(statement); + MySQLDatabaseResourceCloser.close(connection); + } } + + /** * * Creates a list of s filled with values from the provided ResultSet @@ -456,49 +583,166 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { InvocationTargetException { List list = new ArrayList(); - + // The database can return multiple results in one go, e.g., all the islands in the database + // Run through them one by one while (resultSet.next()) { - + // Create a new instance of this type T instance = type.newInstance(); - + // Get the unique ID from the results + String uniqueId = resultSet.getString("uniqueId"); + if (uniqueId == null) { + throw new SQLException("No unique ID in the results!"); + } + // Use introspection to run through all the fields in this type class for (Field field : type.getDeclaredFields()) { - /* We assume the table-column-names exactly match the variable-names of T */ Object value = resultSet.getObject(field.getName()); - - PropertyDescriptor propertyDescriptor = new PropertyDescriptor( - field.getName(), type); - + // Get the property descriptor of this type + PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); + // Get the write method for this field, because we are going to use it to write the value + // once we get the value from the database Method method = propertyDescriptor.getWriteMethod(); - - // Create set and map tables. + // If the type is a Collection, then we need to deal with set and map tables if (propertyDescriptor.getPropertyType().equals(Set.class) || propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(ArrayList.class)) { // Collection - // TODO Set the values in the subsidiary tables. - value = null; - } - // Types that need to be serialized - if (propertyDescriptor.getPropertyType().equals(UUID.class)) { - value = UUID.fromString((String)value); - } - // Bukkit Types - if (propertyDescriptor.getPropertyType().equals(Location.class)) { - // Serialize - value = Util.getLocationString(((String)value)); - } - if (propertyDescriptor.getPropertyType().equals(World.class)) { - // Serialize - get the name - value = plugin.getServer().getWorld((String)value); + plugin.getLogger().info("DEBUG: Collection"); + // TODO Get the values from the subsidiary tables. + // value is just of type boolean right now + String setSql = "SELECT ("; + // Get the columns, just the names of them, no ?'s or types + setSql += getCollectionColumns(method, false, false) + ") "; + setSql += "FROM `" + type.getCanonicalName() + "." + field.getName() + "` "; + // We will need to fill in the ? later with the unique id of the class from the database + setSql += "WHERE uniqueID = ?"; + // Prepare the statement + PreparedStatement collStatement = connection.prepareStatement(setSql); + // Set the unique ID + collStatement.setObject(1, uniqueId); + plugin.getLogger().info("DEBUG: collStatement = " + collStatement.toString()); + ResultSet collectionResultSet = collStatement.executeQuery(); + plugin.getLogger().info("DEBUG: collectionResultSet = " + collectionResultSet); + // Do single dimension types (set and list) + if (propertyDescriptor.getPropertyType().equals(Set.class)) { + 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); + // collectionTypes should be only 1 long + Type setType = collectionTypes.get(0); + Collection collection = new HashSet(); + plugin.getLogger().info("DEBUG: collection type argument = " + collectionTypes); + int i = 0; + while (collectionResultSet.next()) { + plugin.getLogger().info("DEBUG: adding to the collection"); + collection.add(deserialize(collectionResultSet.getObject(i++),setType.getClass())); + } + value.getClass().cast(HashSet.class); + value = collection; + } 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); + // collectionTypes should be only 1 long + Type setType = collectionTypes.get(0); + Collection collection = new ArrayList(); + plugin.getLogger().info("DEBUG: collection type argument = " + collectionTypes); + int i = 0; + while (collectionResultSet.next()) { + plugin.getLogger().info("DEBUG: adding to the collection"); + collection.add(deserialize(collectionResultSet.getObject(i++),setType.getClass())); + } + value = collection; + } else if (propertyDescriptor.getPropertyType().equals(Map.class) || + propertyDescriptor.getPropertyType().equals(HashMap.class)) { + // Loop through the map + /* + Map collection = (Map)value; + Iterator it = collection.entrySet().iterator(); + while (it.hasNext()) { + Entry en = (Entry) it.next(); + // Get the key and serialize it + Object key = serialize(en.getKey(), en.getKey().getClass()); + //plugin.getLogger().info("DEBUG: key class = " + en.getKey().getClass().getTypeName()); + // Get the value and serialize it + Object mapValue = serialize(en.getValue(), en.getValue().getClass()); + // Write the objects into prepared statement + collStatement.setObject(1, key); + collStatement.setObject(2, mapValue); + //plugin.getLogger().info("DEBUG: " + collStatement.toString()); + // Write to database + collStatement.execute(); + + }*/ + } + // Set value for the main insert. For collections, this is just a dummy value because the real values are in the + // additional table. + value = true; + + + + + + + } else { + plugin.getLogger().info("DEBUG: regular type"); + value = deserialize(value, propertyDescriptor.getPropertyType()); } + plugin.getLogger().info("DEBUG: invoking method " + method.getName()); + plugin.getLogger().info("DEBUG: value class = " + value.getClass().getName()); + // Write the value to the class method.invoke(instance, value); } - + // Write the result into the list we are going to return list.add(instance); } return list; } + /** + * Deserialize any values according to their class + * TODO: expand to include additional types + * @param value + * @param clazz + * @return the deserialized value + */ + @SuppressWarnings("unchecked") + private Object deserialize(Object value, Class clazz) { + plugin.getLogger().info("DEBUG: deserialize - class is " + clazz.getTypeName()); + 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; + } + }