Work In Progress (WIP)

Added more to the MySQL database reading. Does not work yet for
collections.
This commit is contained in:
tastybento 2017-06-04 18:35:38 -07:00
parent 9f0a05773e
commit 6f842a9b29
4 changed files with 337 additions and 82 deletions

View File

@ -1,8 +1,11 @@
package us.tastybento.bskyblock; package us.tastybento.bskyblock;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin; 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.IslandsManager;
import us.tastybento.bskyblock.database.managers.OfflineHistoryMessages; import us.tastybento.bskyblock.database.managers.OfflineHistoryMessages;
import us.tastybento.bskyblock.database.managers.PlayersManager; 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; import us.tastybento.bskyblock.util.VaultHelper;
/** /**
@ -97,8 +102,12 @@ public class BSkyBlock extends JavaPlugin{
island.setName("new name"); island.setName("new name");
island.setPurgeProtected(true); island.setPurgeProtected(true);
islandsManager.save(false); islandsManager.save(false);
*/
getLogger().info("DEBUG: ************ Finished saving, now loading *************");
// TODO: Write loading code for MySQL // TODO: Write loading code for MySQL
*
*/
playersManager.load(); playersManager.load();
islandsManager.load(); islandsManager.load();

View File

@ -8,6 +8,7 @@ import java.util.List;
import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.database.DatabaseConnecter; 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 * An abstract class that handles insert/select-operations into/from a database
@ -120,8 +121,9 @@ public abstract class AbstractDatabaseHandler<T> {
* @throws IllegalArgumentException * @throws IllegalArgumentException
* @throws IllegalAccessException * @throws IllegalAccessException
* @throws InstantiationException * @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 * Inserts T into the corresponding database-table

View File

@ -28,7 +28,7 @@ public class MySQLDatabaseConnecter implements DatabaseConnecter {
e.printStackTrace(); e.printStackTrace();
} }
// jdbc:mysql://localhost:3306/Peoples?autoReconnect=true&useSSL=false // 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 @Override

View File

@ -16,9 +16,11 @@ import java.sql.Statement;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -44,7 +46,13 @@ import us.tastybento.bskyblock.util.Util;
*/ */
public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> { public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
/**
* Connection to the database
*/
private Connection connection = null; private Connection connection = null;
/**
* This hashmap maps Java types to MySQL SQL types because they are not the same
*/
private static HashMap<String, String> mySQLmapping; private static HashMap<String, String> mySQLmapping;
{ {
mySQLmapping = new HashMap<String, String>(); mySQLmapping = new HashMap<String, String>();
@ -71,7 +79,6 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
mySQLmapping.put(Location.class.getTypeName(), "VARCHAR(254)"); mySQLmapping.put(Location.class.getTypeName(), "VARCHAR(254)");
mySQLmapping.put(World.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 // Collections are stored as additional tables. The boolean indicates whether there
// is any data in it or not (maybe) // is any data in it or not (maybe)
mySQLmapping.put(Set.class.getTypeName(), "BOOL"); mySQLmapping.put(Set.class.getTypeName(), "BOOL");
@ -81,6 +88,13 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
} }
/**
* 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<T> type, DatabaseConnecter databaseConnecter) { public MySQLDatabaseHandler(BSkyBlock plugin, Class<T> type, DatabaseConnecter databaseConnecter) {
super(plugin, type, databaseConnecter); super(plugin, type, databaseConnecter);
try { try {
@ -110,45 +124,58 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
String sql = "CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "` ("; String sql = "CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "` (";
// Run through the fields of the class using introspection
for (Field field : type.getDeclaredFields()) { for (Field field : type.getDeclaredFields()) {
// Get the description of the field
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); 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 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(); Method writeMethod = propertyDescriptor.getWriteMethod();
// The SQL column name is the name of the field
String columnName = field.getName(); String columnName = field.getName();
// Get the mapping for this field from the hashmap
String mapping = mySQLmapping.get(propertyDescriptor.getPropertyType().getTypeName()); String mapping = mySQLmapping.get(propertyDescriptor.getPropertyType().getTypeName());
// If it exists, then create the SQL
if (mapping != null) { if (mapping != null) {
// Note that the column name must be enclosed in `'s because it may include reserved words.
sql += "`" + columnName + "` " + mapping + ","; 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) || if (propertyDescriptor.getPropertyType().equals(Set.class) ||
propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(Map.class) ||
propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(HashMap.class) ||
propertyDescriptor.getPropertyType().equals(ArrayList.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() + "` (" String setSql = "CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "." + field.getName() + "` ("
+ "uniqueId VARCHAR(36) NOT NULL, "; + "uniqueId VARCHAR(36) NOT NULL, ";
// Get columns separated by commas // Get columns separated by commas
setSql += getCollectionColumns(writeMethod,false,true); setSql += getCollectionColumns(writeMethod,false,true);
// Add primary key // Close the SQL string
setSql += ")"; setSql += ")";
plugin.getLogger().info(setSql); //plugin.getLogger().info(setSql);
// Execute the statement
PreparedStatement collections = connection.prepareStatement(setSql); PreparedStatement collections = connection.prepareStatement(setSql);
collections.executeUpdate(); collections.executeUpdate();
} }
} else { } 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),"; sql += field.getName() + " VARCHAR(254),";
plugin.getLogger().severe("Unknown type! Hoping it'll fit in a string!"); plugin.getLogger().severe("Unknown type! Hoping it'll fit in a string!");
} }
} }
//plugin.getLogger().info("DEBUG: SQL before trim string = " + sql); //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))"; 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 = connection.prepareStatement(sql.toString());
pstmt.executeUpdate(); pstmt.executeUpdate();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
// Close the database properly
MySQLDatabaseResourceCloser.close(pstmt); MySQLDatabaseResourceCloser.close(pstmt);
MySQLDatabaseResourceCloser.close(pstmt); MySQLDatabaseResourceCloser.close(pstmt);
} }
@ -157,6 +184,8 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
/** /**
* Returns a string of columns separated by commas that represent the parameter types of this method * 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<Location, Boolean> at runtime and find out Location
* and Boolean.
* @param writeMethod * @param writeMethod
* @param usePlaceHolders * @param usePlaceHolders
* true, if PreparedStatement-placeholders ('?') should be used * true, if PreparedStatement-placeholders ('?') should be used
@ -167,36 +196,52 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
private String getCollectionColumns(Method writeMethod, boolean usePlaceHolders, boolean createSchema) { private String getCollectionColumns(Method writeMethod, boolean usePlaceHolders, boolean createSchema) {
String columns = ""; String columns = "";
// Get the return type // 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(); Type[] genericParameterTypes = writeMethod.getGenericParameterTypes();
// There could be more than one argument, so step through them
for (int i = 0; i < genericParameterTypes.length; i++) { 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 ) { if( genericParameterTypes[i] instanceof ParameterizedType ) {
// Get the actual type arguments of the parameter
Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments(); Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments();
//parameters[0] contains java.lang.String for method like "method(List<String> value)" //parameters[0] contains java.lang.String for method like "method(List<String> value)"
// Run through them one by one and create a SQL string
int index = 0; int index = 0;
boolean first = true; boolean first = true;
for (Type type : parameters) { for (Type type : parameters) {
plugin.getLogger().info("DEBUG: set type = " + type.getTypeName()); plugin.getLogger().info("DEBUG: set type = " + type.getTypeName());
// first is used to add commas in the right place
if (first) if (first)
first = false; first = false;
else else
columns += ", "; columns += ", ";
// this is used if the string is going to be used to insert something so the value will replace the ?
if (usePlaceHolders) { if (usePlaceHolders) {
columns +="?"; columns +="?";
} else { } else {
// This is a request for column names.
String setMapping = mySQLmapping.get(type.getTypeName()); String setMapping = mySQLmapping.get(type.getTypeName());
// We know the mapping of this type
if (setMapping != null) { if (setMapping != null) {
// Write the name of this type, followed by an index number to make it unique
columns += "`" + type.getTypeName() + "_" + index + "`"; columns += "`" + type.getTypeName() + "_" + index + "`";
// If this is the schema, then we also need to add the mapping type
if (createSchema) { if (createSchema) {
columns += " " + setMapping; columns += " " + setMapping;
} }
} else { } 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 + "`"; columns += "`" + type.getTypeName() + "_" + index + "`";
if (createSchema) { if (createSchema) {
// If this is the schema, then guess the mapping type
columns += " VARCHAR(254)"; columns += " VARCHAR(254)";
plugin.getLogger().warning("Unknown type! Hoping it'll fit in a string!"); plugin.getLogger().warning("Unknown type! Hoping it'll fit in a string!");
} }
} }
} }
// Increment the index so each column has a unique name
index++; index++;
} }
} }
@ -204,6 +249,33 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
return columns; return columns;
} }
/**
* Get a list of parameter types for the collection argument in this method
* @param writeMethod
* @return
*/
private List<Type> getCollectionParameterTypes(Method writeMethod) {
List<Type> result = new ArrayList<Type>();
// 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 @Override
protected String createSelectQuery() { protected String createSelectQuery() {
@ -221,13 +293,17 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
return sb.toString(); return sb.toString();
} }
/* (non-Javadoc)
* @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#createInsertQuery()
*/
@Override @Override
protected String createInsertQuery() { protected String createInsertQuery() {
StringBuilder sb = new StringBuilder(); 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("REPLACE INTO ");
sb.append("`"); 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(type.getCanonicalName());
sb.append("`"); sb.append("`");
sb.append("("); sb.append("(");
@ -243,7 +319,7 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
/** /**
* Inserts a <T> into the corresponding database-table * Inserts a <T> into the corresponding database-table
* *
* @param instance <T> that should be inserted into the corresponding database-table * @param instance <T> that should be inserted into the corresponding database-table. Must extend DataObject.
* @throws SQLException * @throws SQLException
* @throws SecurityException * @throws SecurityException
* @throws IllegalArgumentException * @throws IllegalArgumentException
@ -253,6 +329,9 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
* @throws InvocationTargetException * @throws InvocationTargetException
* @throws NoSuchMethodException * @throws NoSuchMethodException
*/ */
/* (non-Javadoc)
* @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#insertObject(java.lang.Object)
*/
@Override @Override
public void insertObject(T instance) throws SQLException, public void insertObject(T instance) throws SQLException,
SecurityException, IllegalArgumentException, SecurityException, IllegalArgumentException,
@ -263,120 +342,145 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
PreparedStatement preparedStatement = null; PreparedStatement preparedStatement = null;
try { try {
// Try to connect to the database
connection = databaseConnecter.createConnection(); connection = databaseConnecter.createConnection();
// insertQuery is created in super from the createInsertQuery() method
preparedStatement = connection.prepareStatement(insertQuery); 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"); Method getUniqueId = type.getMethod("getUniqueId");
String uniqueId = (String) getUniqueId.invoke(instance); String uniqueId = (String) getUniqueId.invoke(instance);
plugin.getLogger().info("Unique Id = " + uniqueId); //plugin.getLogger().info("DEBUG: Unique Id = " + uniqueId);
if (uniqueId.isEmpty()) { if (uniqueId.isEmpty()) {
throw new SQLException("uniqueId is blank"); throw new SQLException("uniqueId is blank");
} }
// Create the insertion
int i = 0; int i = 0;
plugin.getLogger().info("DEBUG: insert Query " + insertQuery); plugin.getLogger().info("DEBUG: insert Query " + insertQuery);
// Run through the fields in the class using introspection
for (Field field : type.getDeclaredFields()) { for (Field field : type.getDeclaredFields()) {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor( // Get the field's property descriptor
field.getName(), type); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type);
// Get the read method for this field
Method method = propertyDescriptor.getReadMethod(); 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 + ","; //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); 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) || if (propertyDescriptor.getPropertyType().equals(Set.class) ||
propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(Map.class) ||
propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(HashMap.class) ||
propertyDescriptor.getPropertyType().equals(ArrayList.class)) { propertyDescriptor.getPropertyType().equals(ArrayList.class)) {
// Collection // Collection
// TODO Set the values in the subsidiary tables. // The table is cleared everytime the data is stored again
// TODO clear the table? String setSql = "DELETE FROM `" + type.getCanonicalName() + "." + field.getName() + "`;";
String setSql = "INSERT INTO `" + type.getCanonicalName() + "." + field.getName() + "` (uniqueId, "; // 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) + ") "; setSql += getCollectionColumns(propertyDescriptor.getWriteMethod(), false, false) + ") ";
// Get all the ?'s for the columns
setSql += "VALUES ('" + uniqueId + "'," + getCollectionColumns(propertyDescriptor.getWriteMethod(), true, false) + ")"; setSql += "VALUES ('" + uniqueId + "'," + getCollectionColumns(propertyDescriptor.getWriteMethod(), true, false) + ")";
// Prepare the statement
PreparedStatement collStatement = connection.prepareStatement(setSql); 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) // Do single dimension types (set and list)
if (propertyDescriptor.getPropertyType().equals(Set.class) || if (propertyDescriptor.getPropertyType().equals(Set.class) ||
propertyDescriptor.getPropertyType().equals(ArrayList.class)) { propertyDescriptor.getPropertyType().equals(ArrayList.class)) {
plugin.getLogger().info("DEBUG: set class for "); //plugin.getLogger().info("DEBUG: set class for ");
// Loop through the collection // Loop through the set or list
// Note that we have no idea what type this is
Collection<?> collection = (Collection<?>)value; Collection<?> collection = (Collection<?>)value;
Iterator<?> it = collection.iterator(); Iterator<?> it = collection.iterator();
while (it.hasNext()) { while (it.hasNext()) {
Object setValue = it.next(); Object setValue = it.next();
if (setValue instanceof UUID) { //if (setValue instanceof UUID) {
// Serialize everything // Serialize everything
setValue = serialize(setValue, setValue.getClass()); setValue = serialize(setValue, setValue.getClass());
} //}
// Set the value from ? to whatever it is
collStatement.setObject(1, setValue); collStatement.setObject(1, setValue);
plugin.getLogger().info("DEBUG: " + collStatement.toString()); plugin.getLogger().info("DEBUG: " + collStatement.toString());
// Execute the SQL in the database
collStatement.execute(); collStatement.execute();
} }
} else if (propertyDescriptor.getPropertyType().equals(Map.class) || } else if (propertyDescriptor.getPropertyType().equals(Map.class) ||
propertyDescriptor.getPropertyType().equals(HashMap.class)) { propertyDescriptor.getPropertyType().equals(HashMap.class)) {
// Loop through the collection // Loop through the map
Map<?,?> collection = (Map<?,?>)value; Map<?,?> collection = (Map<?,?>)value;
Iterator<?> it = collection.entrySet().iterator(); Iterator<?> it = collection.entrySet().iterator();
while (it.hasNext()) { while (it.hasNext()) {
Entry<?,?> en = (Entry<?, ?>) it.next(); Entry<?,?> en = (Entry<?, ?>) it.next();
// Get the key and serialize it
Object key = serialize(en.getKey(), en.getKey().getClass()); Object key = serialize(en.getKey(), en.getKey().getClass());
plugin.getLogger().info("DEBUG: key class = " + en.getKey().getClass().getTypeName()); //plugin.getLogger().info("DEBUG: key class = " + en.getKey().getClass().getTypeName());
Object mapValue = serialize(en.getValue(), en.getValue().getClass());; // 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(1, key);
collStatement.setObject(2, mapValue); collStatement.setObject(2, mapValue);
plugin.getLogger().info("DEBUG: " + collStatement.toString()); //plugin.getLogger().info("DEBUG: " + collStatement.toString());
// Write to database
collStatement.execute(); 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; value = true;
} else { } else {
// If the value is not a collection, it just needs to be serialized to go into the database.
value = serialize(value, propertyDescriptor.getPropertyType()); 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); preparedStatement.setObject(++i, value);
} }
// Add the statements to a batch
preparedStatement.addBatch(); preparedStatement.addBatch();
// Execute
preparedStatement.executeBatch(); preparedStatement.executeBatch();
} finally { } finally {
// Close properly
MySQLDatabaseResourceCloser.close(preparedStatement); MySQLDatabaseResourceCloser.close(preparedStatement);
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 value
* @param clazz - the known class of value * @param clazz - the known class of value
* @return * @return the object to write to the database
*/ */
private Object serialize(Object value, Class<? extends Object> clazz) { private Object serialize(Object value, Class<? extends Object> clazz) {
plugin.getLogger().info("DEBUG: serialize - class is " + clazz.getTypeName()); plugin.getLogger().info("DEBUG: serialize - class is " + clazz.getTypeName());
if (value == null) { if (value == null) {
// If the value is null to start, return null as a string
return "null"; return "null";
} }
// Types that need to be serialized // Types that need to be serialized
// TODO - add others, like Date, Timestamp, etc.
if (clazz.equals(UUID.class)) { if (clazz.equals(UUID.class)) {
value = ((UUID)value).toString(); value = ((UUID)value).toString();
} else }
// Bukkit Types else
if (clazz.equals(Location.class)) { // Bukkit Types
// Serialize if (clazz.equals(Location.class)) {
value = Util.getStringLocation(((Location)value)); // Serialize
} else value = Util.getStringLocation(((Location)value));
if (clazz.equals(World.class)) { } else
// Serialize - get the name if (clazz.equals(World.class)) {
value = ((World)value).getName(); // Serialize - get the name
} else value = ((World)value).getName();
if (clazz.getSuperclass() != null && clazz.getSuperclass().equals(Enum.class)) { } else
//Custom enums are a child of the Enum class. Just get the names of each one. if (clazz.getSuperclass() != null && clazz.getSuperclass().equals(Enum.class)) {
value = ((Enum<?>)value).name(); //Custom enums are a child of the Enum class. Just get the names of each one.
} value = ((Enum<?>)value).name();
}
if (value == null) { if (value == null) {
// The value could become null from the above checks
return "null"; return "null";
} }
return value; return value;
@ -422,15 +526,38 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
} }
} }
/* (non-Javadoc)
* @see us.tastybento.bskyblock.database.managers.AbstractDatabaseHandler#selectObject(java.lang.String)
*/
@Override @Override
protected T selectObject(String uniqueId) throws InstantiationException, protected T selectObject(String uniqueId) throws InstantiationException,
IllegalAccessException, IllegalArgumentException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, IntrospectionException { InvocationTargetException, IntrospectionException, SQLException {
// TODO Auto-generated method stub Connection connection = null;
return null; Statement statement = null;
ResultSet resultSet = null;
try {
connection = databaseConnecter.createConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(selectQuery);
List<T> 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 <T>s filled with values from the provided ResultSet * Creates a list of <T>s filled with values from the provided ResultSet
@ -456,49 +583,166 @@ public class MySQLDatabaseHandler<T> extends AbstractDatabaseHandler<T> {
InvocationTargetException { InvocationTargetException {
List<T> list = new ArrayList<T>(); List<T> list = new ArrayList<T>();
// 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()) { while (resultSet.next()) {
// Create a new instance of this type
T instance = type.newInstance(); 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()) { for (Field field : type.getDeclaredFields()) {
/* We assume the table-column-names exactly match the variable-names of T */ /* We assume the table-column-names exactly match the variable-names of T */
Object value = resultSet.getObject(field.getName()); Object value = resultSet.getObject(field.getName());
// Get the property descriptor of this type
PropertyDescriptor propertyDescriptor = new PropertyDescriptor( PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type);
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(); Method method = propertyDescriptor.getWriteMethod();
// If the type is a Collection, then we need to deal with set and map tables
// Create set and map tables.
if (propertyDescriptor.getPropertyType().equals(Set.class) || if (propertyDescriptor.getPropertyType().equals(Set.class) ||
propertyDescriptor.getPropertyType().equals(Map.class) || propertyDescriptor.getPropertyType().equals(Map.class) ||
propertyDescriptor.getPropertyType().equals(HashMap.class) || propertyDescriptor.getPropertyType().equals(HashMap.class) ||
propertyDescriptor.getPropertyType().equals(ArrayList.class)) { propertyDescriptor.getPropertyType().equals(ArrayList.class)) {
// Collection // Collection
// TODO Set the values in the subsidiary tables. plugin.getLogger().info("DEBUG: Collection");
value = null; // TODO Get the values from the subsidiary tables.
} // value is just of type boolean right now
// Types that need to be serialized String setSql = "SELECT (";
if (propertyDescriptor.getPropertyType().equals(UUID.class)) { // Get the columns, just the names of them, no ?'s or types
value = UUID.fromString((String)value); setSql += getCollectionColumns(method, false, false) + ") ";
} setSql += "FROM `" + type.getCanonicalName() + "." + field.getName() + "` ";
// Bukkit Types // We will need to fill in the ? later with the unique id of the class from the database
if (propertyDescriptor.getPropertyType().equals(Location.class)) { setSql += "WHERE uniqueID = ?";
// Serialize // Prepare the statement
value = Util.getLocationString(((String)value)); PreparedStatement collStatement = connection.prepareStatement(setSql);
} // Set the unique ID
if (propertyDescriptor.getPropertyType().equals(World.class)) { collStatement.setObject(1, uniqueId);
// Serialize - get the name plugin.getLogger().info("DEBUG: collStatement = " + collStatement.toString());
value = plugin.getServer().getWorld((String)value); 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<Type> collectionTypes = getCollectionParameterTypes(method);
// collectionTypes should be only 1 long
Type setType = collectionTypes.get(0);
Collection<Object> collection = new HashSet<Object>();
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<Type> collectionTypes = getCollectionParameterTypes(method);
// collectionTypes should be only 1 long
Type setType = collectionTypes.get(0);
Collection<Object> collection = new ArrayList<Object>();
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); method.invoke(instance, value);
} }
// Write the result into the list we are going to return
list.add(instance); list.add(instance);
} }
return list; 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<? extends Object> 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<Enum> enumClass = (Class<Enum>)clazz;
value = Enum.valueOf(enumClass, (String)value);
} catch (Exception e) {
// Maybe this value does not exist?
// TODO return something?
e.printStackTrace();
}
}
return value;
}
} }