diff --git a/pom.xml b/pom.xml index 73b4ee541..1073d9434 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ org.bukkit bukkit - 1.12-pre2-SNAPSHOT + 1.12-pre5-SNAPSHOT provided diff --git a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java index 8643fb52e..bba446562 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.IslandsManager; import us.tastybento.bskyblock.database.OfflineHistoryMessages; import us.tastybento.bskyblock.database.PlayersManager; +import us.tastybento.bskyblock.database.objects.Island; +import us.tastybento.bskyblock.database.objects.Island.SettingsFlag; import us.tastybento.bskyblock.util.VaultHelper; /** @@ -38,7 +43,7 @@ public class BSkyBlock extends JavaPlugin{ @Override public void onEnable(){ plugin = this; - + // Load configuration and locales. If there are no errors, load the plugin. if(PluginConfig.loadPluginConfig(this)){ // TEMP DEBUG DATABASE @@ -48,12 +53,16 @@ public class BSkyBlock extends JavaPlugin{ Settings.dbName = "ASkyBlock"; Settings.dbUsername = "username"; Settings.dbPassword = "password"; - + playersManager = new PlayersManager(this); islandsManager = new IslandsManager(this); + // Only load metrics if set to true in config + if(Settings.metrics) metrics = new Metrics(plugin); - playersManager.load(); - islandsManager.load(); + // If metrics are loaded, register the custom data charts + if(metrics != null){ + registerCustomCharts(); + } offlineHistoryMessages = new OfflineHistoryMessages(this); offlineHistoryMessages.load(); @@ -63,24 +72,57 @@ public class BSkyBlock extends JavaPlugin{ Settings.useEconomy = false; } - // Only load metrics if set to true in config - if(Settings.metrics) metrics = new Metrics(this); - - // If metrics are loaded, register the custom data charts - if(metrics != null){ - registerCustomCharts(); - } - - // Save islands & players data asynchronously every X minutes - plugin.getServer().getScheduler().runTaskTimer(this, new Runnable() { + // These items have to be loaded when the server has done 1 tick. + // Note Worlds are not loaded this early, so any Locations or World reference will be null + // at this point. Therefore, the 1 tick scheduler is required. + getServer().getScheduler().runTask(this, new Runnable() { @Override public void run() { - playersManager.save(true); - islandsManager.save(true); - offlineHistoryMessages.save(true); + + // Test: Create a random island and save it + // TODO: ideally this should be in a test class! + /* + Island island = islandsManager.createIsland(new Location(getServer().getWorld("world"),0,0,0,0,0), UUID.randomUUID()); + // Add members + Set randomSet = new HashSet(); + for (int i = 0; i < 10; i++) { + randomSet.add(UUID.randomUUID()); + island.addMember(UUID.randomUUID()); + island.addToBanList(UUID.randomUUID()); + } + island.setBanned(randomSet); + island.setCoops(randomSet); + island.setTrustees(randomSet); + island.setMembers(randomSet); + for (SettingsFlag flag: SettingsFlag.values()) { + island.setFlag(flag, true); + } + island.setLocked(true); + island.setName("new name"); + island.setPurgeProtected(true); + islandsManager.save(false); + */ + // TODO: Write loading code for MySQL + playersManager.load(); + islandsManager.load(); + + + // Save islands & players data asynchronously every X minutes + Settings.databaseBackupPeriod = 10 * 60 * 20; + plugin.getServer().getScheduler().runTaskTimer(plugin, new Runnable() { + + @Override + public void run() { + playersManager.save(true); + islandsManager.save(true); + offlineHistoryMessages.save(true); + } + }, Settings.databaseBackupPeriod, Settings.databaseBackupPeriod); } - }, Settings.databaseBackupPeriod, Settings.databaseBackupPeriod); + // TODO Auto-generated method stub + + }); } } diff --git a/src/main/java/us/tastybento/bskyblock/database/AbstractDatabaseHandler.java b/src/main/java/us/tastybento/bskyblock/database/AbstractDatabaseHandler.java index cb19f34c3..358cbc5bb 100644 --- a/src/main/java/us/tastybento/bskyblock/database/AbstractDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/AbstractDatabaseHandler.java @@ -134,7 +134,8 @@ public abstract class AbstractDatabaseHandler { * @throws InstantiationException * @throws SecurityException * @throws SQLException + * @throws NoSuchMethodException */ - protected abstract void insertObject(T instance) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, SecurityException, InstantiationException; + protected abstract void insertObject(T instance) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, SQLException, SecurityException, InstantiationException, NoSuchMethodException; } 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 8298cf8f3..d4d1a1ea1 100644 --- a/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java +++ b/src/main/java/us/tastybento/bskyblock/database/mysql/MySQLDatabaseHandler.java @@ -7,7 +7,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.math.BigDecimal; import java.sql.Connection; import java.sql.PreparedStatement; @@ -17,10 +16,13 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; @@ -63,7 +65,7 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { mySQLmapping.put(Date.class.getTypeName(), "DATE"); mySQLmapping.put(Time.class.getTypeName(), "TIME"); mySQLmapping.put(Timestamp.class.getTypeName(), "TIMESTAMP"); - mySQLmapping.put(UUID.class.getTypeName(), "VARCHAR(32)"); // TODO: How long is a UUID to string? + mySQLmapping.put(UUID.class.getTypeName(), "VARCHAR(36)"); // Bukkit Mappings mySQLmapping.put(Location.class.getTypeName(), "VARCHAR(254)"); @@ -107,21 +109,29 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { private void createSchema() throws IntrospectionException, SQLException { PreparedStatement pstmt = null; try { - String sql = "CREATE TABLE IF NOT EXISTS " + type.getSimpleName() + "("; + String sql = "CREATE TABLE IF NOT EXISTS `" + type.getCanonicalName() + "` ("; for (Field field : type.getDeclaredFields()) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), type); plugin.getLogger().info("DEBUG: Field = " + field.getName() + "(" + propertyDescriptor.getPropertyType().getTypeName() + ")"); + // Get default SQL mappings + Method writeMethod = propertyDescriptor.getWriteMethod(); + String columnName = field.getName(); String mapping = mySQLmapping.get(propertyDescriptor.getPropertyType().getTypeName()); if (mapping != null) { - sql += "`" + field.getName() + "` " + mapping + ","; + sql += "`" + columnName + "` " + mapping + ","; // Create 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)) { - String setSql = "CREATE TABLE IF NOT EXISTS " + type.getSimpleName() + "_" + field.getName() + " ("; - // Get the type - setSql += getMethodParameterTypes(propertyDescriptor.getWriteMethod()); + // The ID in this table relates to the parent table + 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 + setSql += ")"; + plugin.getLogger().info(setSql); PreparedStatement collections = connection.prepareStatement(setSql); collections.executeUpdate(); @@ -132,7 +142,7 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { } } //plugin.getLogger().info("DEBUG: SQL before trim string = " + sql); - sql = sql.substring(0,(sql.length()-1)) + ")"; + sql += " PRIMARY KEY (uniqueId))"; plugin.getLogger().info("DEBUG: SQL string = " + sql); pstmt = connection.prepareStatement(sql.toString()); pstmt.executeUpdate(); @@ -144,42 +154,54 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { } } + /** - * Gets the types for parameters in a method + * Returns a string of columns separated by commas that represent the parameter types of this method * @param writeMethod - * @return List of strings with the SQL for parameter and type set + * @param usePlaceHolders + * true, if PreparedStatement-placeholders ('?') should be used + * instead of the names of the variables + * @param createSchema if true contains the columns types + * @return Returns a string of columns separated by commas. */ - private String getMethodParameterTypes(Method method) { - String result = ""; + private String getCollectionColumns(Method writeMethod, boolean usePlaceHolders, boolean createSchema) { + String columns = ""; // Get the return type - Type[] genericParameterTypes = method.getGenericParameterTypes(); + Type[] genericParameterTypes = writeMethod.getGenericParameterTypes(); for (int i = 0; i < genericParameterTypes.length; i++) { if( genericParameterTypes[i] instanceof ParameterizedType ) { Type[] parameters = ((ParameterizedType)genericParameterTypes[i]).getActualTypeArguments(); //parameters[0] contains java.lang.String for method like "method(List value)" int index = 0; - String firstColumn = ""; + boolean first = true; for (Type type : parameters) { plugin.getLogger().info("DEBUG: set type = " + type.getTypeName()); - String setMapping = mySQLmapping.get(type.getTypeName()); - String notNull = ""; - if (index == 0) { - firstColumn = "`" + type.getTypeName() + "_" + index + "`"; - notNull = " NOT NULL"; - } - if (setMapping != null) { - result += "`" + type.getTypeName() + "_" + index + "` " + setMapping + notNull + ","; + if (first) + first = false; + else + columns += ", "; + if (usePlaceHolders) { + columns +="?"; } else { - result += "`" + type.getTypeName() + "_" + index + "` VARCHAR(254)" + notNull + ","; - plugin.getLogger().severe("Unknown type! Hoping it'll fit in a string!"); + String setMapping = mySQLmapping.get(type.getTypeName()); + if (setMapping != null) { + columns += "`" + type.getTypeName() + "_" + index + "`"; + if (createSchema) { + columns += " " + setMapping; + } + } else { + columns += "`" + type.getTypeName() + "_" + index + "`"; + if (createSchema) { + columns += " VARCHAR(254)"; + plugin.getLogger().warning("Unknown type! Hoping it'll fit in a string!"); + } + } } index++; } - // Add primary key - result += " PRIMARY KEY (" + firstColumn + "))"; } } - return result; + return columns; } @Override @@ -191,8 +213,10 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { sb.append(super.getColumns(false)); sb.append(" FROM "); - /* We assume the table-name exactly matches the simpleName of T */ - sb.append(type.getSimpleName()); + /* We assume the table-name exactly matches the canonical Name of T */ + sb.append("`"); + sb.append(type.getCanonicalName()); + sb.append("`"); return sb.toString(); } @@ -202,8 +226,10 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { StringBuilder sb = new StringBuilder(); - sb.append("INSERT INTO "); - sb.append(type.getSimpleName()); + sb.append("REPLACE INTO "); + sb.append("`"); + sb.append(type.getCanonicalName()); + sb.append("`"); sb.append("("); sb.append(super.getColumns(false)); sb.append(")"); @@ -225,33 +251,39 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { * @throws IllegalAccessException * @throws IntrospectionException * @throws InvocationTargetException + * @throws NoSuchMethodException */ @Override public void insertObject(T instance) throws SQLException, SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, - IntrospectionException, InvocationTargetException { + IntrospectionException, InvocationTargetException, NoSuchMethodException { Connection connection = null; PreparedStatement preparedStatement = null; try { connection = databaseConnecter.createConnection(); - preparedStatement = connection.prepareStatement(selectQuery); - + preparedStatement = connection.prepareStatement(insertQuery); + // Get the uniqueId + Method getUniqueId = type.getMethod("getUniqueId"); + String uniqueId = (String) getUniqueId.invoke(instance); + plugin.getLogger().info("Unique Id = " + uniqueId); + if (uniqueId.isEmpty()) { + throw new SQLException("uniqueId is blank"); + } int i = 0; - + plugin.getLogger().info("DEBUG: insert Query " + insertQuery); for (Field field : type.getDeclaredFields()) { PropertyDescriptor propertyDescriptor = new PropertyDescriptor( field.getName(), type); - Method method = propertyDescriptor - .getReadMethod(); + Method method = propertyDescriptor.getReadMethod(); plugin.getLogger().info("DEBUG: Field = " + field.getName() + "(" + propertyDescriptor.getPropertyType().getTypeName() + ")"); //sql += "`" + field.getName() + "` " + mapping + ","; - + Object value = method.invoke(instance); - + // Create set and map tables. if (propertyDescriptor.getPropertyType().equals(Set.class) || propertyDescriptor.getPropertyType().equals(Map.class) || @@ -259,23 +291,50 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { 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, "; + setSql += getCollectionColumns(propertyDescriptor.getWriteMethod(), false, false) + ") "; + setSql += "VALUES ('" + uniqueId + "'," + getCollectionColumns(propertyDescriptor.getWriteMethod(), true, false) + ")"; + PreparedStatement collStatement = connection.prepareStatement(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 + 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()); + } + collStatement.setObject(1, setValue); + plugin.getLogger().info("DEBUG: " + collStatement.toString()); + collStatement.execute(); + } + } else if (propertyDescriptor.getPropertyType().equals(Map.class) || + propertyDescriptor.getPropertyType().equals(HashMap.class)) { + // Loop through the collection + Map collection = (Map)value; + Iterator it = collection.entrySet().iterator(); + while (it.hasNext()) { + Entry en = (Entry) it.next(); + 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());; + collStatement.setObject(1, key); + collStatement.setObject(2, mapValue); + plugin.getLogger().info("DEBUG: " + collStatement.toString()); + collStatement.execute(); + } + } + // Set value for the main insert value = true; + } else { + value = serialize(value, propertyDescriptor.getPropertyType()); } - // Types that need to be serialized - if (propertyDescriptor.getPropertyType().equals(UUID.class)) { - value = ((UUID)value).toString(); - } - // Bukkit Types - if (propertyDescriptor.getPropertyType().equals(Location.class)) { - // Serialize - value = Util.getStringLocation(((Location)value)); - } - if (propertyDescriptor.getPropertyType().equals(World.class)) { - // Serialize - get the name - value = ((World)value).getName(); - } - - preparedStatement.setObject(++i, value); } @@ -289,6 +348,41 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { } } + /** + * Serializes value + * @param value + * @param clazz - the known class of value + * @return + */ + private Object serialize(Object value, Class clazz) { + plugin.getLogger().info("DEBUG: serialize - class is " + clazz.getTypeName()); + if (value == null) { + return "null"; + } + // Types that need to be serialized + 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(); + } + if (value == null) { + return "null"; + } + return value; + + } + /** * Creates a list of s filled with values from the corresponding * database-table @@ -376,7 +470,7 @@ public class MySQLDatabaseHandler extends AbstractDatabaseHandler { field.getName(), type); Method method = propertyDescriptor.getWriteMethod(); - + // Create set and map tables. if (propertyDescriptor.getPropertyType().equals(Set.class) || propertyDescriptor.getPropertyType().equals(Map.class) || diff --git a/src/main/java/us/tastybento/bskyblock/database/objects/Island.java b/src/main/java/us/tastybento/bskyblock/database/objects/Island.java index 84d09df4c..a50165314 100755 --- a/src/main/java/us/tastybento/bskyblock/database/objects/Island.java +++ b/src/main/java/us/tastybento/bskyblock/database/objects/Island.java @@ -22,7 +22,24 @@ import us.tastybento.bskyblock.config.Settings; * @author Poslovitch */ public class Island extends DataObject { + + private String uniqueId = ""; + + @Override + public String getUniqueId() { + // Island's have UUID's that are randomly assigned if they do not exist + if (uniqueId.isEmpty()) { + uniqueId = UUID.randomUUID().toString(); + } + return uniqueId; + } + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + + } + /** * Island Guard Settings flags * Covers island, spawn and system settings @@ -332,8 +349,6 @@ public class Island extends DataObject { //// Protection //// private HashMap flags = new HashMap(); - private String uniqueId = ""; - public Island() {}; public Island(Location location, UUID owner, int protectionRange) { @@ -427,6 +442,10 @@ public class Island extends DataObject { * @return the members of the island (owner included) */ public Set getMembers(){ + if (members == null) { + Bukkit.getLogger().info("DEBUG: members = null"); + members = new HashSet(); + } return members; } /** @@ -675,6 +694,7 @@ public class Island extends DataObject { * @param members - the members to set */ public void setMembers(Set members){ + Bukkit.getLogger().info("DEBUG: setting members = " + members); this.members = members; } @@ -791,15 +811,4 @@ public class Island extends DataObject { } } - @Override - public String getUniqueId() { - return uniqueId; - } - - @Override - public void setUniqueId(String uniqueId) { - this.uniqueId = uniqueId; - - } - } \ No newline at end of file