From 7a011687817fc77c4a4b05eef3391c9b8110031d Mon Sep 17 00:00:00 2001 From: sk89q Date: Fri, 8 Aug 2014 20:22:05 -0700 Subject: [PATCH] Rewrite region API. Migrations need to be re-implemented. This commit, however, adds: * Better support for background saves and loads. * Periodical writes of region changes rather than explicit save() calls. * Rewrite of the MySQL region code. * Support for partial saves when using MySQL. * Decoupling of region index implementations and RegionManager. What still needs to be done includes: * Background region modification operations (addmember, etc.). * Unit tests to verify correctness of the partial saving. * Migration support (to be re-added). * Better handling when regions are failing to save. * Testing of the /rg load and /rg save commands. * Verification of how server shutdown / world unload is handled. * Verification that thread-unsafe concurrent saves of data is not happening. --- .../bukkit/ConfigurationManager.java | 41 +- .../bukkit/LegacyWorldGuardMigration.java | 155 ---- .../sk89q/worldguard/bukkit/ReportWriter.java | 2 +- .../worldguard/bukkit/WorldGuardPlugin.java | 37 +- .../bukkit/commands/AsyncCommandHelper.java | 12 - .../bukkit/commands/RegionCommands.java | 75 +- .../bukkit/commands/RegionManagerLoad.java | 53 ++ .../bukkit/commands/RegionManagerSave.java | 53 ++ .../bukkit/commands/RegionMemberCommands.java | 24 +- .../commands/RegionPrintoutBuilder.java | 2 +- .../worldguard/domains/DefaultDomain.java | 20 +- .../sk89q/worldguard/domains/GroupDomain.java | 33 +- .../worldguard/domains/PlayerDomain.java | 44 +- .../protection/ApplicableRegionSet.java | 152 ++-- .../protection/GlobalRegionManager.java | 297 +------ .../protection/ManagerContainer.java | 174 ++++ .../UnsupportedIntersectionException.java | 8 +- .../AbstractAsynchronousDatabase.java | 226 ------ .../databases/AbstractProtectionDatabase.java | 60 -- .../protection/databases/CSVDatabase.java | 357 --------- .../databases/ProtectionDatabase.java | 110 --- .../protection/databases/RegionDBUtil.java | 2 +- .../migrator/AbstractDatabaseMigrator.java | 62 -- .../migrator/MySQLToYAMLMigrator.java | 97 --- .../migrator/YAMLToMySQLMigrator.java | 86 -- .../databases/mysql/AbstractJob.java | 78 -- .../databases/mysql/MySQLDatabaseImpl.java | 305 ------- .../databases/mysql/RegionLoader.java | 456 ----------- .../databases/mysql/RegionWriter.java | 608 -------------- .../worldguard/protection/flags/EnumFlag.java | 9 + .../worldguard/protection/flags/Flag.java | 11 +- .../protection/flags/RegionGroupFlag.java | 5 +- .../worldguard/protection/flags/SetFlag.java | 16 +- .../managers/FlatRegionManager.java | 210 ----- .../managers/PRTreeRegionManager.java | 243 ------ .../managers/RegionCollectionConsumer.java | 70 ++ .../protection/managers/RegionDifference.java | 83 ++ .../protection/managers/RegionManager.java | 405 +++++++--- .../protection/managers/RemovalStrategy.java | 40 + .../index/AbstractRegionIndex.java} | 10 +- .../index/ConcurrentRegionIndex.java} | 17 +- .../managers/index/HashMapIndex.java | 263 ++++++ .../managers/index/PriorityRTreeIndex.java | 102 +++ .../managers/index/RegionIndex.java | 138 ++++ .../storage/DifferenceSaveException.java | 41 + .../managers/storage/MemoryRegionStore.java | 52 ++ .../managers/storage/RegionStore.java | 69 ++ .../storage/RegionStoreException.java} | 20 +- .../managers/storage/RegionStoreUtils.java | 125 +++ .../storage/driver/DirectoryYamlDriver.java | 103 +++ .../managers/storage/driver/DriverType.java | 52 ++ .../storage/driver/RegionStoreDriver.java} | 35 +- .../managers/storage/driver/SQLDriver.java | 101 +++ .../storage/file/YamlFileStore.java} | 754 ++++++++---------- .../managers/storage/sql/DataLoader.java | 335 ++++++++ .../managers/storage/sql/DataUpdater.java | 168 ++++ .../storage/sql/DomainTableCache.java | 53 ++ .../managers/storage/sql/RegionInserter.java | 189 +++++ .../managers/storage/sql/RegionRemover.java | 88 ++ .../managers/storage/sql/RegionUpdater.java | 341 ++++++++ .../managers/storage/sql/SQLRegionStore.java | 375 +++++++++ .../storage/sql/StatementBatch.java} | 45 +- .../storage/sql/TableCache.java} | 110 ++- .../regions/GlobalProtectedRegion.java | 34 +- .../regions/ProtectedCuboidRegion.java | 90 +-- .../regions/ProtectedPolygonalRegion.java | 102 +-- .../protection/regions/ProtectedRegion.java | 300 ++++--- .../protection/regions/RegionType.java | 52 ++ .../util/DomainInputResolver.java | 2 +- .../util/UnresolvedNamesException.java | 2 +- .../migrator/MigrationException.java | 7 +- .../migrator/UUIDMigrator.java | 2 +- .../sk89q/worldguard/util/ChangeTracked.java | 42 + .../com/sk89q/worldguard/util/Normal.java | 119 +++ .../worldguard/util/sql/DataSourceConfig.java | 145 ++++ .../region/mysql/V2__Bug_fix_and_UUID.sql | 12 +- .../migrations/region/sqlite/V1__Initial.sql | 160 ++++ .../protection/FlatRegionOverlapTest.java | 29 - ...est.java => HashMapIndexPriorityTest.java} | 10 +- ...ava => HashMapIndexRegionOverlapTest.java} | 10 +- ...tryExitTest.java => HashMapIndexTest.java} | 9 +- .../protection/MockApplicableRegionSet.java | 2 +- ...yTest.java => PriorityRTreeIndexTest.java} | 10 +- .../PriorityRTreeRegionEntryExitTest.java | 33 + ...va => PriorityRTreeRegionOverlapTest.java} | 10 +- .../PriorityRTreeRegionPriorityTest.java | 33 + .../protection/RegionEntryExitTest.java | 1 + 87 files changed, 5098 insertions(+), 4425 deletions(-) delete mode 100644 src/main/java/com/sk89q/worldguard/bukkit/LegacyWorldGuardMigration.java create mode 100644 src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerLoad.java create mode 100644 src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerSave.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/CSVDatabase.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabase.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/migrator/AbstractDatabaseMigrator.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/migrator/MySQLToYAMLMigrator.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/migrator/YAMLToMySQLMigrator.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/mysql/AbstractJob.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/mysql/MySQLDatabaseImpl.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionLoader.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionWriter.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/FlatRegionManager.java delete mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/PRTreeRegionManager.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java rename src/main/java/com/sk89q/worldguard/protection/{databases/migrator/DatabaseMigrator.java => managers/index/AbstractRegionIndex.java} (76%) rename src/{test/java/com/sk89q/worldguard/protection/FlatRegionManagerTest.java => main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java} (65%) create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionStore.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStore.java rename src/main/java/com/sk89q/worldguard/protection/{databases/ProtectionDatabaseException.java => managers/storage/RegionStoreException.java} (67%) create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreUtils.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DirectoryYamlDriver.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DriverType.java rename src/main/java/com/sk89q/worldguard/protection/{databases/MySQLDatabase.java => managers/storage/driver/RegionStoreDriver.java} (50%) mode change 100755 => 100644 create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/SQLDriver.java rename src/main/java/com/sk89q/worldguard/protection/{databases/YAMLDatabase.java => managers/storage/file/YamlFileStore.java} (56%) create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java create mode 100644 src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionStore.java rename src/main/java/com/sk89q/worldguard/protection/{databases/migrator/MigratorKey.java => managers/storage/sql/StatementBatch.java} (52%) rename src/main/java/com/sk89q/worldguard/protection/{databases/mysql/UserRowCache.java => managers/storage/sql/TableCache.java} (61%) create mode 100644 src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java rename src/main/java/com/sk89q/worldguard/protection/{databases => }/util/DomainInputResolver.java (98%) rename src/main/java/com/sk89q/worldguard/protection/{databases => }/util/UnresolvedNamesException.java (95%) rename src/main/java/com/sk89q/worldguard/protection/{databases => util}/migrator/MigrationException.java (91%) rename src/main/java/com/sk89q/worldguard/protection/{databases => util}/migrator/UUIDMigrator.java (99%) create mode 100644 src/main/java/com/sk89q/worldguard/util/ChangeTracked.java create mode 100644 src/main/java/com/sk89q/worldguard/util/Normal.java create mode 100644 src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java create mode 100644 src/main/resources/migrations/region/sqlite/V1__Initial.sql delete mode 100644 src/test/java/com/sk89q/worldguard/protection/FlatRegionOverlapTest.java rename src/test/java/com/sk89q/worldguard/protection/{PRTreeRegionPriorityTest.java => HashMapIndexPriorityTest.java} (76%) rename src/test/java/com/sk89q/worldguard/protection/{PRTreeRegionManagerTest.java => HashMapIndexRegionOverlapTest.java} (76%) rename src/test/java/com/sk89q/worldguard/protection/{PRTreeRegionEntryExitTest.java => HashMapIndexTest.java} (77%) rename src/test/java/com/sk89q/worldguard/protection/{FlatRegionPriorityTest.java => PriorityRTreeIndexTest.java} (75%) create mode 100644 src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java rename src/test/java/com/sk89q/worldguard/protection/{PRTreeRegionOverlapTest.java => PriorityRTreeRegionOverlapTest.java} (75%) create mode 100644 src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java diff --git a/src/main/java/com/sk89q/worldguard/bukkit/ConfigurationManager.java b/src/main/java/com/sk89q/worldguard/bukkit/ConfigurationManager.java index 29e4dfcc..43b34fda 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/ConfigurationManager.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/ConfigurationManager.java @@ -66,30 +66,11 @@ public class ConfigurationManager { "# - Lines starting with # are comments and so they are ignored.\r\n" + "#\r\n"; - /** - * Reference to the plugin. - */ private WorldGuardPlugin plugin; - - /** - * Holds configurations for different worlds. - */ private ConcurrentMap worlds; - - /** - * The global configuration for use when loading worlds - */ private YAMLProcessor config; - - /** - * List of people with god mode. - */ @Deprecated private Set hasGodMode = new HashSet(); - - /** - * List of people who can breathe underwater. - */ private Set hasAmphibious = new HashSet(); private boolean hasCommandBookGodMode = false; @@ -125,6 +106,25 @@ public ConfigurationManager(WorldGuardPlugin plugin) { this.worlds = new ConcurrentHashMap(); } + /** + * Get the folder for storing data files and configuration. + * + * @return the data folder + */ + public File getDataFolder() { + return plugin.getDataFolder(); + } + + /** + * Get the folder for storing data files and configuration for each + * world. + * + * @return the data folder + */ + public File getWorldsDataFolder() { + return new File(getDataFolder(), "worlds"); + } + /** * Load the configuration. */ @@ -167,8 +167,7 @@ public void load() { } } - useSqlDatabase = config.getBoolean( - "regions.sql.use", false); + useSqlDatabase = config.getBoolean("regions.sql.use", false); sqlDsn = config.getString("regions.sql.dsn", "jdbc:mysql://localhost/worldguard"); sqlUsername = config.getString("regions.sql.username", "worldguard"); diff --git a/src/main/java/com/sk89q/worldguard/bukkit/LegacyWorldGuardMigration.java b/src/main/java/com/sk89q/worldguard/bukkit/LegacyWorldGuardMigration.java deleted file mode 100644 index f540e0d1..00000000 --- a/src/main/java/com/sk89q/worldguard/bukkit/LegacyWorldGuardMigration.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.bukkit; - -import com.sk89q.worldguard.protection.databases.CSVDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.managers.RegionManager; -import org.bukkit.World; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Utility methods for porting from legacy versions. - */ -public final class LegacyWorldGuardMigration { - - private LegacyWorldGuardMigration() { - } - - /** - * Port over the blacklist. - * - * @param plugin The plugin instance - */ - public static void migrateBlacklist(WorldGuardPlugin plugin) { - World mainWorld = plugin.getServer().getWorlds().get(0); - String mainWorldName = mainWorld.getName(); - String newPath = "worlds/" + mainWorldName + "/blacklist.txt"; - - File oldFile = new File(plugin.getDataFolder(), "blacklist.txt"); - File newFile = new File(plugin.getDataFolder(), newPath); - - if (!newFile.exists() && oldFile.exists()) { - plugin.getLogger().warning("WorldGuard will now update your blacklist " - + "from an older version of WorldGuard."); - - // Need to make root directories - newFile.getParentFile().mkdirs(); - - if (copyFile(oldFile, newFile)) { - oldFile.renameTo(new File(plugin.getDataFolder(), - "blacklist.txt.old")); - } else { - plugin.getLogger().warning("blacklist.txt has been converted " + - "for the main world at " + newPath + ""); - plugin.getLogger().warning("Your other worlds currently have no " + - "blacklist defined!"); - } - - } - } - - /** - * Migrate region settings. - * - * @param plugin The plugin instance - */ - public static void migrateRegions(WorldGuardPlugin plugin) { - try { - File oldDatabase = new File(plugin.getDataFolder(), "regions.txt"); - if (!oldDatabase.exists()) return; - - plugin.getLogger().info("The regions database has changed in 5.x. " - + "Your old regions database will be converted to the new format " - + "and set as your primary world's database."); - - World w = plugin.getServer().getWorlds().get(0); - RegionManager mgr = plugin.getGlobalRegionManager().get(w); - - // First load up the old database using the CSV loader - CSVDatabase db = new CSVDatabase(oldDatabase, plugin.getLogger()); - db.load(); - - // Then save the new database - mgr.setRegions(db.getRegions()); - mgr.save(); - - oldDatabase.renameTo(new File(plugin.getDataFolder(), "regions.txt.old")); - - plugin.getLogger().info("Regions database converted!"); - } catch (ProtectionDatabaseException e) { - plugin.getLogger().warning("Failed to load regions: " - + e.getMessage()); - } - } - - /** - * Copies a file. - * - * @param from The source file - * @param to The destination file - * @return true if successful - */ - private static boolean copyFile(File from, File to) { - InputStream in = null; - OutputStream out = null; - - try { - in = new FileInputStream(from); - out = new FileOutputStream(to); - - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - - in.close(); - out.close(); - - return true; - } catch (FileNotFoundException ignore) { - } catch (IOException ignore) { - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException ignore) { - } - } - - if (out != null) { - try { - out.close(); - } catch (IOException ignore) { - } - } - } - - return false; - } -} diff --git a/src/main/java/com/sk89q/worldguard/bukkit/ReportWriter.java b/src/main/java/com/sk89q/worldguard/bukkit/ReportWriter.java index c25d234f..82fa737b 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/ReportWriter.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/ReportWriter.java @@ -300,7 +300,7 @@ private void appendWorldConfigurations(WorldGuardPlugin plugin, List worl regionsLog.put("Number of regions", worldRegions.getRegions().size()); LogListBlock globalRegionLog = regionsLog.putChild("Global region"); - ProtectedRegion globalRegion = worldRegions.getRegion("__global__"); + ProtectedRegion globalRegion = worldRegions.matchRegion("__global__"); if (globalRegion == null) { globalRegionLog.put("Status", "UNDEFINED"); } else { diff --git a/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java b/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java index fca4e7dc..a6ad15d9 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java @@ -53,10 +53,9 @@ import com.sk89q.worldguard.internal.listener.ChestProtectionListener; import com.sk89q.worldguard.internal.listener.RegionProtectionListener; import com.sk89q.worldguard.protection.GlobalRegionManager; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.databases.migrator.MigrationException; -import com.sk89q.worldguard.protection.databases.migrator.UUIDMigrator; -import com.sk89q.worldguard.protection.databases.util.UnresolvedNamesException; +import com.sk89q.worldguard.protection.util.migrator.MigrationException; +import com.sk89q.worldguard.protection.util.migrator.UUIDMigrator; +import com.sk89q.worldguard.protection.util.UnresolvedNamesException; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.util.FatalConfigurationLoadingException; import org.bukkit.ChatColor; @@ -98,8 +97,8 @@ public class WorldGuardPlugin extends JavaPlugin { private static WorldGuardPlugin inst; private final CommandsManager commands; - private final GlobalRegionManager globalRegionManager; - private final ConfigurationManager configuration; + private GlobalRegionManager globalRegionManager; + private ConfigurationManager configuration; private FlagStateManager flagStateManager; private final Supervisor supervisor = new SimpleSupervisor(); private ListeningExecutorService executorService; @@ -111,9 +110,6 @@ public class WorldGuardPlugin extends JavaPlugin { * this merely instantiates the objects. */ public WorldGuardPlugin() { - configuration = new ConfigurationManager(this); - globalRegionManager = new GlobalRegionManager(this); - final WorldGuardPlugin plugin = inst = this; commands = new CommandsManager() { @Override @@ -137,6 +133,7 @@ public static WorldGuardPlugin inst() { @Override @SuppressWarnings("deprecation") public void onEnable() { + configuration = new ConfigurationManager(this); executorService = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 20)); // Set the proper command injector @@ -176,14 +173,12 @@ public void run() { PermissionsResolverManager.initialize(this); - // This must be done before configuration is loaded - LegacyWorldGuardMigration.migrateBlacklist(this); - try { // Load the configuration configuration.load(); getLogger().info("Loading region data..."); + globalRegionManager = new GlobalRegionManager(this); globalRegionManager.preload(); migrateRegionUniqueIds(); // Migrate to UUIDs @@ -202,10 +197,6 @@ public void run() { "******************************************************\n"); } - // Migrate regions after the regions were loaded because - // the migration code reuses the loaded region managers - LegacyWorldGuardMigration.migrateRegions(this); - flagStateManager = new FlagStateManager(this); if (configuration.useRegionsScheduler) { @@ -306,8 +297,8 @@ private void migrateRegionUniqueIds() { } } catch (MigrationException e) { getLogger().log(Level.WARNING, "Failed to migrate regions to use UUIDs instead of player names", e); - } catch (ProtectionDatabaseException e) { - getLogger().log(Level.WARNING, "Failed to save regions after UUID conversion", e); + } catch (IOException e) { + getLogger().log(Level.WARNING, "Failed to save region data", e); } } @@ -343,16 +334,14 @@ public String convertThrowable(@Nullable Throwable throwable) { } else if (throwable instanceof RejectedExecutionException) { return "There are currently too many tasks queued to add yours. Use /wg running to list queued and running tasks."; } else if (throwable instanceof CancellationException) { - return "Task was cancelled"; + return "WorldGuard: Task was cancelled"; } else if (throwable instanceof InterruptedException) { - return "Task was interrupted"; + return "WorldGuard: Task was interrupted"; } else if (throwable instanceof UnresolvedNamesException) { return throwable.getMessage(); - } else if (throwable instanceof Exception) { - getLogger().log(Level.WARNING, "WorldGuard encountered an unexpected error", throwable); - return "Unexpected error occurred: " + ((Exception) throwable).getMessage(); } else { - return "Unknown error"; + getLogger().log(Level.WARNING, "WorldGuard encountered an unexpected error", throwable); + return "WorldGuard: An unexpected error occurred! Please see the server console."; } } diff --git a/src/main/java/com/sk89q/worldguard/bukkit/commands/AsyncCommandHelper.java b/src/main/java/com/sk89q/worldguard/bukkit/commands/AsyncCommandHelper.java index 184e9306..a3ebaabc 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/commands/AsyncCommandHelper.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/commands/AsyncCommandHelper.java @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.sk89q.odeum.task.FutureForwardingTask; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.managers.RegionManager; import org.bukkit.World; import org.bukkit.command.CommandSender; @@ -129,17 +128,6 @@ public AsyncCommandHelper forRegionDataSave(World world, boolean silent) { return this; } - public AsyncCommandHelper thenSaveRegionData(RegionManager manager, World world) { - checkNotNull(manager); - checkNotNull(world); - - ListenableFuture future = manager.save(true); - - AsyncCommandHelper.wrap(future, plugin, sender).forRegionDataSave(world, true); - - return this; - } - public static AsyncCommandHelper wrap(ListenableFuture future, WorldGuardPlugin plugin, CommandSender sender) { return new AsyncCommandHelper(future, plugin, sender); } diff --git a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionCommands.java b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionCommands.java index 84f8f5bf..fff71fe0 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionCommands.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionCommands.java @@ -39,18 +39,15 @@ import com.sk89q.worldguard.bukkit.WorldConfiguration; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; import com.sk89q.worldguard.protection.databases.RegionDBUtil; -import com.sk89q.worldguard.protection.databases.migrator.AbstractDatabaseMigrator; -import com.sk89q.worldguard.protection.databases.migrator.MigrationException; -import com.sk89q.worldguard.protection.databases.migrator.MigratorKey; -import com.sk89q.worldguard.protection.databases.migrator.UUIDMigrator; import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.flags.Flag; import com.sk89q.worldguard.protection.flags.InvalidFlagFormat; import com.sk89q.worldguard.protection.flags.RegionGroup; import com.sk89q.worldguard.protection.flags.RegionGroupFlag; import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.util.migrator.MigrationException; +import com.sk89q.worldguard.protection.util.migrator.UUIDMigrator; import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; @@ -62,10 +59,9 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import java.lang.reflect.InvocationTargetException; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -78,9 +74,6 @@ public final class RegionCommands { private final WorldGuardPlugin plugin; - private MigratorKey migrateDBRequest; - private Date migrateDBRequestDate; - public RegionCommands(WorldGuardPlugin plugin) { this.plugin = plugin; } @@ -95,17 +88,6 @@ private static RegionPermissionModel getPermissionModel(CommandSender sender) { return new RegionPermissionModel(WorldGuardPlugin.inst(), sender); } - /** - * Save the regions asynchronously and alert the sender if any errors - * occur during save. - * - * @param manager the manager to save - * @param sender the sender - */ - private void saveRegionsSilently(RegionManager manager, World world, CommandSender sender) { - AsyncCommandHelper.wrap(Futures.immediateFuture(null), plugin, sender).thenSaveRegionData(manager, world); - } - /** * Gets the world from the given flag, or falling back to the the current player * if the sender is a player, otherwise reporting an error. @@ -167,7 +149,7 @@ private static ProtectedRegion findExistingRegion(RegionManager regionManager, S // Validate the id validateRegionId(id, allowGlobal); - ProtectedRegion region = regionManager.getRegionExact(id); + ProtectedRegion region = regionManager.getRegion(id); // No region found! if (region == null) { @@ -414,9 +396,6 @@ public void define(CommandContext args, CommandSender sender) throws CommandExce // Add region regionManager.addRegion(region); - - // Save regions - saveRegionsSilently(regionManager, player.getWorld(), sender); } /** @@ -470,9 +449,6 @@ public void redefine(CommandContext args, CommandSender sender) throws CommandEx sender.sendMessage(ChatColor.YELLOW + "Region '" + id + "' updated with new area."); regionManager.addRegion(region); // Replace region - - // Save regions - saveRegionsSilently(regionManager, world, sender); } /** @@ -529,7 +505,7 @@ public void claim(CommandContext args, CommandSender sender) throws CommandExcep } } - ProtectedRegion existing = regionManager.getRegionExact(id); + ProtectedRegion existing = regionManager.getRegion(id); // Check for an existing region if (existing != null) { @@ -570,9 +546,6 @@ public void claim(CommandContext args, CommandSender sender) throws CommandExcep // Replace region regionManager.addRegion(region); - - // Save regions - saveRegionsSilently(regionManager, player.getWorld(), sender); } /** @@ -890,9 +863,6 @@ public void flag(CommandContext args, CommandSender sender) throws CommandExcept printout.appendFlagsList(false); printout.append(")"); printout.send(sender); - - // Save regions - saveRegionsSilently(regionManager, world, sender); } /** @@ -927,9 +897,6 @@ public void setPriority(CommandContext args, CommandSender sender) sender.sendMessage(ChatColor.YELLOW + "Priority of '" + existing.getId() + "' set to " + priority + " (higher numbers override)."); - - // Save regions - saveRegionsSilently(regionManager, world, sender); } /** @@ -994,9 +961,6 @@ public void setParent(CommandContext args, CommandSender sender) throws CommandE printout.append(")"); } printout.send(sender); - - // Save regions - saveRegionsSilently(regionManager, world, sender); } /** @@ -1027,9 +991,6 @@ public void remove(CommandContext args, CommandSender sender) throws CommandExce regionManager.removeRegion(existing.getId()); sender.sendMessage(ChatColor.YELLOW + "Region '" + existing.getId() + "' removed."); - - // Save regions - saveRegionsSilently(regionManager, world, sender); } /** @@ -1063,20 +1024,22 @@ public void load(CommandContext args, final CommandSender sender) throws Command throw new CommandException("No region manager exists for world '" + world.getName() + "'."); } - ListenableFuture future = manager.load(true); + ListenableFuture future = plugin.getExecutorService().submit(new RegionManagerLoad(manager)); AsyncCommandHelper.wrap(future, plugin, sender) .forRegionDataLoad(world, false); } else { // Load regions for all worlds - List> futures = new ArrayList>(); + List managers = new ArrayList(); + for (World w : Bukkit.getServer().getWorlds()) { RegionManager manager = plugin.getGlobalRegionManager().get(w); if (manager != null) { - futures.add(manager.load(true)); + managers.add(manager); } } - ListenableFuture future = Futures.successfulAsList(futures); + + ListenableFuture future = plugin.getExecutorService().submit(new RegionManagerLoad(managers)); AsyncCommandHelper.wrap(future, plugin, sender) .registerWithSupervisor("Loading regions for all worlds") @@ -1118,20 +1081,22 @@ public void save(CommandContext args, final CommandSender sender) throws Command throw new CommandException("No region manager exists for world '" + world.getName() + "'."); } - ListenableFuture future = manager.save(true); + ListenableFuture future = plugin.getExecutorService().submit(new RegionManagerSave(manager)); AsyncCommandHelper.wrap(future, plugin, sender) .forRegionDataSave(world, false); } else { // Save for all worlds - List> futures = new ArrayList>(); + List managers = new ArrayList(); + for (World w : Bukkit.getServer().getWorlds()) { RegionManager manager = plugin.getGlobalRegionManager().get(w); if (manager != null) { - futures.add(manager.save(true)); + managers.add(manager); } } - ListenableFuture future = Futures.successfulAsList(futures); + + ListenableFuture future = plugin.getExecutorService().submit(new RegionManagerSave(managers)); AsyncCommandHelper.wrap(future, plugin, sender) .registerWithSupervisor("Saving regions for all worlds") @@ -1156,7 +1121,8 @@ public void migrateDB(CommandContext args, CommandSender sender) throws CommandE if (!getPermissionModel(sender).mayMigrateRegionStore()) { throw new CommandPermissionsException(); } - + + /* String from = args.getString(0).toLowerCase().trim(); String to = args.getString(1).toLowerCase().trim(); @@ -1202,6 +1168,7 @@ public void migrateDB(CommandContext args, CommandSender sender) throws CommandE sender.sendMessage(ChatColor.YELLOW + "Regions have been migrated successfully.\n" + "If you wish to use the destination format as your new backend, please update your config and reload WorldGuard."); + */ } /** @@ -1246,7 +1213,7 @@ public void migrateUuid(CommandContext args, CommandSender sender) throws Comman } else { sender.sendMessage(ChatColor.YELLOW + "There were no names to migrate."); } - } catch (ProtectionDatabaseException e) { + } catch (IOException e) { plugin.getLogger().log(Level.WARNING, "Failed to save", e); throw new CommandException("Error encountered while saving: " + e.getMessage()); } catch (MigrationException e) { diff --git a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerLoad.java b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerLoad.java new file mode 100644 index 00000000..c36de61c --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerLoad.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.commands; + +import com.sk89q.worldguard.protection.managers.RegionManager; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class RegionManagerLoad implements Callable> { + + private final Collection managers; + + RegionManagerLoad(Collection managers) { + checkNotNull(managers); + this.managers = managers; + } + + RegionManagerLoad(RegionManager... manager) { + this(Arrays.asList(manager)); + } + + @Override + public Collection call() throws IOException { + for (RegionManager manager : managers) { + manager.load(); + } + + return managers; + } + +} \ No newline at end of file diff --git a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerSave.java b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerSave.java new file mode 100644 index 00000000..19fbf4d8 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionManagerSave.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.commands; + +import com.sk89q.worldguard.protection.managers.RegionManager; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +class RegionManagerSave implements Callable> { + + private final Collection managers; + + RegionManagerSave(Collection managers) { + checkNotNull(managers); + this.managers = managers; + } + + RegionManagerSave(RegionManager... manager) { + this(Arrays.asList(manager)); + } + + @Override + public Collection call() throws IOException { + for (RegionManager manager : managers) { + manager.save(); + } + + return managers; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionMemberCommands.java b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionMemberCommands.java index a817a05b..f9732f02 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionMemberCommands.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionMemberCommands.java @@ -27,8 +27,8 @@ import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.databases.util.DomainInputResolver; -import com.sk89q.worldguard.protection.databases.util.DomainInputResolver.UserLocatorPolicy; +import com.sk89q.worldguard.protection.util.DomainInputResolver; +import com.sk89q.worldguard.protection.util.DomainInputResolver.UserLocatorPolicy; import com.sk89q.worldguard.protection.flags.DefaultFlag; import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; @@ -72,7 +72,7 @@ public void addMember(CommandContext args, CommandSender sender) throws CommandE String id = args.getString(0); RegionManager manager = plugin.getGlobalRegionManager().get(world); - ProtectedRegion region = manager.getRegion(id); + ProtectedRegion region = manager.matchRegion(id); if (region == null) { throw new CommandException("Could not find a region by that ID."); @@ -104,8 +104,7 @@ public void addMember(CommandContext args, CommandSender sender) throws CommandE .formatUsing(region.getId(), world.getName()) .registerWithSupervisor("Adding members to the region '%s' on '%s'") .sendMessageAfterDelay("(Please wait... querying player names...)") - .thenRespondWith("Region '%s' updated with new members.", "Failed to add new members") - .thenSaveRegionData(manager, world); + .thenRespondWith("Region '%s' updated with new members.", "Failed to add new members"); } @Command(aliases = {"addowner", "addowner", "ao"}, @@ -134,7 +133,7 @@ public void addOwner(CommandContext args, CommandSender sender) throws CommandEx String id = args.getString(0); RegionManager manager = plugin.getGlobalRegionManager().get(world); - ProtectedRegion region = manager.getRegion(id); + ProtectedRegion region = manager.matchRegion(id); if (region == null) { throw new CommandException("Could not find a region by that ID."); @@ -179,8 +178,7 @@ public void addOwner(CommandContext args, CommandSender sender) throws CommandEx .formatUsing(region.getId(), world.getName()) .registerWithSupervisor("Adding owners to the region '%s' on '%s'") .sendMessageAfterDelay("(Please wait... querying player names...)") - .thenRespondWith("Region '%s' updated with new owners.", "Failed to add new owners") - .thenSaveRegionData(manager, world); + .thenRespondWith("Region '%s' updated with new owners.", "Failed to add new owners"); } @Command(aliases = {"removemember", "remmember", "removemem", "remmem", "rm"}, @@ -209,7 +207,7 @@ public void removeMember(CommandContext args, CommandSender sender) throws Comma String id = args.getString(0); RegionManager manager = plugin.getGlobalRegionManager().get(world); - ProtectedRegion region = manager.getRegion(id); + ProtectedRegion region = manager.matchRegion(id); if (region == null) { throw new CommandException("Could not find a region by that ID."); @@ -253,8 +251,7 @@ public void removeMember(CommandContext args, CommandSender sender) throws Comma .formatUsing(region.getId(), world.getName()) .registerWithSupervisor("Removing members from the region '%s' on '%s'") .sendMessageAfterDelay("(Please wait... querying player names...)") - .thenRespondWith("Region '%s' updated with members removed.", "Failed to remove members") - .thenSaveRegionData(manager, world); + .thenRespondWith("Region '%s' updated with members removed.", "Failed to remove members"); } @Command(aliases = {"removeowner", "remowner", "ro"}, @@ -284,7 +281,7 @@ public void removeOwner(CommandContext args, String id = args.getString(0); RegionManager manager = plugin.getGlobalRegionManager().get(world); - ProtectedRegion region = manager.getRegion(id); + ProtectedRegion region = manager.matchRegion(id); if (region == null) { throw new CommandException("Could not find a region by that ID."); @@ -328,7 +325,6 @@ public void removeOwner(CommandContext args, .formatUsing(region.getId(), world.getName()) .registerWithSupervisor("Removing owners from the region '%s' on '%s'") .sendMessageAfterDelay("(Please wait... querying player names...)") - .thenRespondWith("Region '%s' updated with owners removed.", "Failed to remove owners") - .thenSaveRegionData(manager, world); + .thenRespondWith("Region '%s' updated with owners removed.", "Failed to remove owners"); } } diff --git a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionPrintoutBuilder.java b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionPrintoutBuilder.java index 5ada6da0..97eda335 100644 --- a/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionPrintoutBuilder.java +++ b/src/main/java/com/sk89q/worldguard/bukkit/commands/RegionPrintoutBuilder.java @@ -75,7 +75,7 @@ public void appendBasics() { builder.append(ChatColor.GRAY); builder.append(" (type="); - builder.append(region.getTypeName()); + builder.append(region.getType().getName()); builder.append(ChatColor.GRAY); builder.append(", priority="); diff --git a/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java b/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java index abe347b9..80670542 100644 --- a/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java +++ b/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java @@ -23,6 +23,7 @@ import com.sk89q.squirrelid.Profile; import com.sk89q.squirrelid.cache.ProfileCache; import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.util.ChangeTracked; import javax.annotation.Nullable; import java.util.ArrayList; @@ -37,7 +38,7 @@ /** * A combination of a {@link PlayerDomain} and a {@link GroupDomain}. */ -public class DefaultDomain implements Domain { +public class DefaultDomain implements Domain, ChangeTracked { private PlayerDomain playerDomain = new PlayerDomain(); private GroupDomain groupDomain = new GroupDomain(); @@ -84,9 +85,7 @@ public void setGroupDomain(GroupDomain groupDomain) { * Add the given player to the domain, identified by the player's name. * * @param name the name of the player - * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ */ - @Deprecated public void addPlayer(String name) { playerDomain.addPlayer(name); } @@ -95,9 +94,7 @@ public void addPlayer(String name) { * Remove the given player from the domain, identified by the player's name. * * @param name the name of the player - * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ */ - @Deprecated public void removePlayer(String name) { playerDomain.removePlayer(name); } @@ -179,9 +176,7 @@ public void removeAll(DefaultDomain other) { * Get the set of player names. * * @return the set of player names - * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ */ - @Deprecated public Set getPlayers() { return playerDomain.getPlayers(); } @@ -252,7 +247,6 @@ public void removeAll() { clear(); } - @SuppressWarnings("deprecation") public String toPlayersString() { return toPlayersString(null); } @@ -340,4 +334,14 @@ public String toUserFriendlyString(ProfileCache cache) { return str.toString(); } + @Override + public boolean isDirty() { + return playerDomain.isDirty() || groupDomain.isDirty(); + } + + @Override + public void setDirty(boolean dirty) { + playerDomain.setDirty(dirty); + groupDomain.setDirty(dirty); + } } diff --git a/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java b/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java index 5118d762..0623dbb6 100644 --- a/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java +++ b/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java @@ -20,19 +20,23 @@ package com.sk89q.worldguard.domains; import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.util.ChangeTracked; +import java.util.Collections; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * Contains groups in a domain. */ -public class GroupDomain implements Domain { +public class GroupDomain implements Domain, ChangeTracked { private final Set groups = new CopyOnWriteArraySet(); + private boolean dirty = true; /** * Create a new instance. @@ -43,11 +47,11 @@ public GroupDomain() { /** * Create a new instance. * - * @param groupsy an array of groups + * @param groups an array of groups */ - public GroupDomain(String[] groupsy) { - checkNotNull(groupsy); - for (String group : groupsy) { + public GroupDomain(String[] groups) { + checkNotNull(groups); + for (String group : groups) { addGroup(group); } } @@ -59,7 +63,9 @@ public GroupDomain(String[] groupsy) { */ public void addGroup(String name) { checkNotNull(name); - groups.add(name.toLowerCase()); + checkArgument(!name.trim().isEmpty(), "Can't add empty group name"); + setDirty(true); + groups.add(name.trim().toLowerCase()); } /** @@ -69,7 +75,8 @@ public void addGroup(String name) { */ public void removeGroup(String name) { checkNotNull(name); - groups.remove(name.toLowerCase()); + setDirty(true); + groups.remove(name.trim().toLowerCase()); } @Override @@ -90,7 +97,7 @@ public boolean contains(LocalPlayer player) { * @return the set of group names */ public Set getGroups() { - return groups; + return Collections.unmodifiableSet(groups); } @Override @@ -113,4 +120,14 @@ public void clear() { groups.clear(); } + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + } diff --git a/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java b/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java index 3da2e3c6..f390a8db 100644 --- a/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java +++ b/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java @@ -20,20 +20,24 @@ package com.sk89q.worldguard.domains; import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.util.ChangeTracked; +import java.util.Collections; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * Stores players (only) in a domain. */ -public class PlayerDomain implements Domain { +public class PlayerDomain implements Domain, ChangeTracked { private final Set uniqueIds = new CopyOnWriteArraySet(); private final Set names = new CopyOnWriteArraySet(); + private boolean dirty = true; /** * Create a new instance. @@ -58,12 +62,15 @@ public PlayerDomain(String[] names) { * Add the given player to the domain, identified by the player's name. * * @param name the name of the player - * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ */ - @Deprecated public void addPlayer(String name) { checkNotNull(name); - names.add(name.toLowerCase()); + checkArgument(!name.trim().isEmpty(), "Can't add empty player name"); + setDirty(true); + names.add(name.trim().toLowerCase()); + // Trim because some names contain spaces (previously valid Minecraft + // names) and we cannot store these correctly in the SQL storage + // implementations } /** @@ -73,6 +80,7 @@ public void addPlayer(String name) { */ public void addPlayer(UUID uniqueId) { checkNotNull(uniqueId); + setDirty(true); uniqueIds.add(uniqueId); } @@ -83,6 +91,7 @@ public void addPlayer(UUID uniqueId) { */ public void addPlayer(LocalPlayer player) { checkNotNull(player); + setDirty(true); addPlayer(player.getUniqueId()); } @@ -90,12 +99,11 @@ public void addPlayer(LocalPlayer player) { * Remove the given player from the domain, identified by the player's name. * * @param name the name of the player - * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ */ - @Deprecated public void removePlayer(String name) { checkNotNull(name); - names.remove(name.toLowerCase()); + setDirty(true); + names.remove(name.trim().toLowerCase()); } /** @@ -116,8 +124,8 @@ public void removePlayer(UUID uuid) { */ public void removePlayer(LocalPlayer player) { checkNotNull(player); - names.remove(player.getName().toLowerCase()); - uniqueIds.remove(player.getUniqueId()); + removePlayer(player.getName()); + removePlayer(player.getUniqueId()); } @Override @@ -130,11 +138,9 @@ public boolean contains(LocalPlayer player) { * Get the set of player names. * * @return the set of player names - * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ */ - @Deprecated public Set getPlayers() { - return names; + return Collections.unmodifiableSet(names); } /** @@ -143,7 +149,7 @@ public Set getPlayers() { * @return the set of player UUIDs */ public Set getUniqueIds() { - return uniqueIds; + return Collections.unmodifiableSet(uniqueIds); } @Override @@ -155,7 +161,7 @@ public boolean contains(UUID uniqueId) { @Override public boolean contains(String playerName) { checkNotNull(playerName); - return names.contains(playerName.toLowerCase()); + return names.contains(playerName.trim().toLowerCase()); } @Override @@ -169,4 +175,14 @@ public void clear() { names.clear(); } + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java b/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java index fd9c966d..aac4e751 100644 --- a/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java +++ b/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java @@ -20,67 +20,87 @@ package com.sk89q.worldguard.protection; import com.sk89q.worldguard.LocalPlayer; -import com.sk89q.worldguard.protection.flags.*; +import com.sk89q.worldguard.protection.flags.DefaultFlag; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.RegionGroupFlag; +import com.sk89q.worldguard.protection.flags.StateFlag; import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import java.util.*; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import static com.google.common.base.Preconditions.checkNotNull; /** - * Represents a set of regions for a particular point or area and the rules - * that are represented by that set. An instance of this can be used to - * query the value of a flag or check if a player can build in the respective - * region or point. This object contains the list of applicable regions and so - * the expensive search of regions that are in the desired area has already - * been completed. - * - * @author sk89q + * Represents the effective set of flags, owners, and members for a given + * spatial query. + * + *

An instance of this can be created using the spatial query methods + * available on {@link RegionManager}.

*/ public class ApplicableRegionSet implements Iterable { - private Collection applicable; - private ProtectedRegion globalRegion; + private final SortedSet applicable; + @Nullable + private final ProtectedRegion globalRegion; + + /** + * Construct the object. + * + *

A sorted set will be created to include the collection of regions.

+ * + * @param applicable the regions contained in this set + * @param globalRegion the global region, set aside for special handling. + */ + public ApplicableRegionSet(Collection applicable, @Nullable ProtectedRegion globalRegion) { + this(new TreeSet(checkNotNull(applicable)), globalRegion); + } /** * Construct the object. * - * @param applicable The regions contained in this set - * @param globalRegion The global region, set aside for special handling. + * @param applicable the regions contained in this set + * @param globalRegion the global region, set aside for special handling. */ - public ApplicableRegionSet(Collection applicable, - ProtectedRegion globalRegion) { + public ApplicableRegionSet(SortedSet applicable, @Nullable ProtectedRegion globalRegion) { + checkNotNull(applicable); this.applicable = applicable; this.globalRegion = globalRegion; } /** - * Checks if a player can build in an area. + * Test whether a player can build in an area. * * @param player The player to check * @return build ability */ public boolean canBuild(LocalPlayer player) { + checkNotNull(player); return internalGetState(DefaultFlag.BUILD, player, null); } + /** + * Test whether the construct flag evaluates true for the given player. + * + * @param player the player + * @return true if true + */ public boolean canConstruct(LocalPlayer player) { + checkNotNull(player); final RegionGroup flag = getFlag(DefaultFlag.CONSTRUCT, player); return RegionGroupFlag.isMember(this, flag, player); } - /** - * Checks if a player can use buttons and such in an area. - * - * @param player The player to check - * @return able to use items - * @deprecated This method seems to be the opposite of its name - */ - @Deprecated - public boolean canUse(LocalPlayer player) { - return !allows(DefaultFlag.USE, player) - && !canBuild(player); - } - /** * Gets the state of a state flag. This cannot be used for the build flag. * @@ -89,9 +109,12 @@ public boolean canUse(LocalPlayer player) { * @throws IllegalArgumentException if the build flag is given */ public boolean allows(StateFlag flag) { + checkNotNull(flag); + if (flag == DefaultFlag.BUILD) { throw new IllegalArgumentException("Can't use build flag with allows()"); } + return internalGetState(flag, null, null); } @@ -103,7 +126,9 @@ public boolean allows(StateFlag flag) { * @return whether the state is allows for it * @throws IllegalArgumentException if the build flag is given */ - public boolean allows(StateFlag flag, LocalPlayer player) { + public boolean allows(StateFlag flag, @Nullable LocalPlayer player) { + checkNotNull(flag); + if (flag == DefaultFlag.BUILD) { throw new IllegalArgumentException("Can't use build flag with allows()"); } @@ -111,12 +136,14 @@ public boolean allows(StateFlag flag, LocalPlayer player) { } /** - * Indicates whether a player is an owner of all regions in this set. + * Test whether a player is an owner of all regions in this set. * - * @param player player + * @param player the player * @return whether the player is an owner of all regions */ public boolean isOwnerOfAll(LocalPlayer player) { + checkNotNull(player); + for (ProtectedRegion region : applicable) { if (!region.isOwner(player)) { return false; @@ -127,13 +154,14 @@ public boolean isOwnerOfAll(LocalPlayer player) { } /** - * Indicates whether a player is an owner or member of all regions in - * this set. + * Test whether a player is an owner or member of all regions in this set. * - * @param player player + * @param player the player * @return whether the player is a member of all regions */ public boolean isMemberOfAll(LocalPlayer player) { + checkNotNull(player); + for (ProtectedRegion region : applicable) { if (!region.isMember(player)) { return false; @@ -144,15 +172,16 @@ public boolean isMemberOfAll(LocalPlayer player) { } /** - * Checks to see if a flag is permitted. + * Test whether a flag tests true. * - * @param flag flag to check - * @param player null to not check owners and members - * @param groupPlayer player to use for the group flag check + * @param flag the flag to check + * @param player the player, or null to not check owners and members + * @param groupPlayer a player to use for the group flag check * @return the allow/deny state for the flag */ - private boolean internalGetState(StateFlag flag, LocalPlayer player, - LocalPlayer groupPlayer) { + private boolean internalGetState(StateFlag flag, @Nullable LocalPlayer player, @Nullable LocalPlayer groupPlayer) { + checkNotNull(flag); + boolean found = false; boolean hasFlagDefined = false; boolean allowed = false; // Used for ALLOW override @@ -254,6 +283,7 @@ private boolean internalGetState(StateFlag flag, LocalPlayer player, if (player != null) { hasFlagDefined = true; + //noinspection StatementWithEmptyBody if (hasCleared.contains(region)) { // Already cleared, so do nothing } else { @@ -269,19 +299,17 @@ private boolean internalGetState(StateFlag flag, LocalPlayer player, found = true; } - return !found ? def : - (allowed || (player != null && needsClear.size() == 0)); + return !found ? def : (allowed || (player != null && needsClear.isEmpty())); } /** * Clear a region's parents for isFlagAllowed(). * - * @param needsClear The regions that should be cleared - * @param hasCleared The regions already cleared - * @param region The region to start from + * @param needsClear the regions that should be cleared + * @param hasCleared the regions already cleared + * @param region the region to start from */ - private void clearParents(Set needsClear, - Set hasCleared, ProtectedRegion region) { + private void clearParents(Set needsClear, Set hasCleared, ProtectedRegion region) { ProtectedRegion parent = region.getParent(); while (parent != null) { @@ -294,10 +322,13 @@ private void clearParents(Set needsClear, } /** - * @see #getFlag(com.sk89q.worldguard.protection.flags.Flag, com.sk89q.worldguard.LocalPlayer) - * @param flag flag to check - * @return value of the flag + * Gets the value of a flag. Do not use this for state flags + * (use {@link #allows(StateFlag, LocalPlayer)} for that). + * + * @param flag the flag to check + * @return value of the flag, which may be null */ + @Nullable public , V> V getFlag(T flag) { return getFlag(flag, null); } @@ -308,10 +339,13 @@ public , V> V getFlag(T flag) { * * @param flag flag to check * @param groupPlayer player to check {@link RegionGroup}s against - * @return value of the flag + * @return value of the flag, which may be null * @throws IllegalArgumentException if a StateFlag is given */ - public , V> V getFlag(T flag, LocalPlayer groupPlayer) { + @Nullable + public , V> V getFlag(T flag, @Nullable LocalPlayer groupPlayer) { + checkNotNull(flag); + /* if (flag instanceof StateFlag) { throw new IllegalArgumentException("Cannot use StateFlag with getFlag()"); @@ -341,6 +375,7 @@ public , V> V getFlag(T flag, LocalPlayer groupPlayer) { } } + //noinspection StatementWithEmptyBody if (hasCleared.contains(region)) { // Already cleared, so do nothing } else if (region.getFlag(flag) != null) { @@ -388,16 +423,15 @@ private void clearParents(Map needsClear, /** * Get the number of regions that are included. * - * @return the size of this ApplicbleRegionSet + * @return the number of contained regions */ public int size() { return applicable.size(); } - - /** - * Get an iterator of affected regions. - */ + + @Override public Iterator iterator() { return applicable.iterator(); } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java b/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java index 5624e8ee..b5260092 100644 --- a/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java +++ b/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java @@ -24,289 +24,74 @@ import com.sk89q.worldguard.bukkit.ConfigurationManager; import com.sk89q.worldguard.bukkit.WorldConfiguration; import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.databases.MySQLDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.databases.YAMLDatabase; import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.managers.PRTreeRegionManager; import com.sk89q.worldguard.protection.managers.RegionManager; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; +import javax.annotation.Nullable; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; import static com.sk89q.worldguard.bukkit.BukkitUtil.toVector; -/** - * This class keeps track of region information for every world. It loads - * world region information as needed. - * - * @author sk89q - * @author Redecouverte - */ public class GlobalRegionManager { - /** - * Reference to the plugin. - */ - private WorldGuardPlugin plugin; + private final WorldGuardPlugin plugin; + private final ConfigurationManager config; + private final ManagerContainer container; - /** - * Reference to the global configuration. - */ - private ConfigurationManager config; - - /** - * Map of managers per-world. - */ - private ConcurrentHashMap managers; - - /** - * Stores the list of modification dates for the world files. This allows - * WorldGuard to reload files as needed. - */ - private HashMap lastModified; - - /** - * Construct the object. - * - * @param plugin The plugin instance - */ public GlobalRegionManager(WorldGuardPlugin plugin) { this.plugin = plugin; config = plugin.getGlobalStateManager(); - managers = new ConcurrentHashMap(); - lastModified = new HashMap(); - } - - /** - * Unload region information. - */ - public void unload() { - managers.clear(); - lastModified.clear(); - } - - /** - * Get the path for a world's regions file. - * - * @param name The name of the world - * @return The region file path for a world's region file - */ - protected File getPath(String name) { - return new File(plugin.getDataFolder(), - "worlds" + File.separator + name + File.separator + "regions.yml"); - } - - /** - * Unload region information for a world. - * - * @param name The name of the world to unload - */ - public void unload(String name) { - RegionManager manager = managers.remove(name); - - if (manager != null) { - lastModified.remove(name); - } - } - - /** - * Unload all region information. - */ - public void unloadAll() { - managers.clear(); - lastModified.clear(); + container = new ManagerContainer(config); } + @Nullable public RegionManager load(World world) { - RegionManager manager = create(world); - if (manager != null) { - managers.put(world.getName(), manager); - } - return manager; + return container.load(world.getName()); } - /** - * Load region information for a world. - * - * @param world The world to load a RegionManager for - * @return the loaded RegionManager - */ - public RegionManager create(World world) { - String name = world.getName(); - boolean sql = config.useSqlDatabase; - ProtectionDatabase database; - File file = null; - - try { - if (!sql) { - file = getPath(name); - database = new YAMLDatabase(file, plugin.getLogger()); - - // Store the last modification date so we can track changes - lastModified.put(name, file.lastModified()); - } else { - database = new MySQLDatabase(config, name, plugin.getLogger()); - } - - // Create a manager - RegionManager manager = new PRTreeRegionManager(database); - manager.load(); - - if (plugin.getGlobalStateManager().get(world).summaryOnStart) { - plugin.getLogger().info(manager.getRegions().size() - + " regions loaded for '" + name + "'"); - } - - return manager; - } catch (ProtectionDatabaseException e) { - String logStr = "Failed to load regions from "; - if (sql) { - logStr += "SQL Database <" + config.sqlDsn + "> "; - } else { - logStr += "file \"" + file + "\" "; - } - - plugin.getLogger().log(Level.SEVERE, logStr + " : " + e.getMessage()); - e.printStackTrace(); - } catch (FileNotFoundException e) { - plugin.getLogger().log(Level.SEVERE, "Error loading regions for world \"" - + name + "\": " + e.toString() + "\n\t" + e.getMessage()); - e.printStackTrace(); - } - - // @TODO: THIS CREATES PROBLEMS!!one!!1!!eleven!!1!!! - return null; - } - - /** - * Preloads region managers for all worlds. - */ public void preload() { - // Load regions for (World world : plugin.getServer().getWorlds()) { load(world); } } - /** - * Reloads the region information from file when region databases - * have changed. - */ - public void reloadChanged() { - if (config.useSqlDatabase) return; - - for (String name : managers.keySet()) { - File file = getPath(name); - - Long oldDate = lastModified.get(name); - - if (oldDate == null) { - oldDate = 0L; - } - - try { - if (file.lastModified() > oldDate) { - World world = plugin.getServer().getWorld(name); - - if (world != null) { - load(world); - } - } - } catch (Exception ignore) { - } - } + public void unload(String name) { + container.unload(name); } - /** - * Get the region manager for a particular world. - * - * @param world The world to get a RegionManager for - * @return The region manager. - */ + public void unload() { + container.unloadAll(); + } + + public void unloadAll() { + container.unloadAll(); + } + + @Nullable public RegionManager get(World world) { - RegionManager manager = managers.get(world.getName()); - RegionManager newManager = null; - - while (manager == null) { - if (newManager == null) { - newManager = create(world); - } - managers.putIfAbsent(world.getName(), newManager); - manager = managers.get(world.getName()); - } - - return manager; + return container.get(world.getName()); } - /** - * Get a list of loaded region managers. - * - * @return an unmodifiable list - */ public List getLoaded() { - List list = new ArrayList(); - for (RegionManager manager : managers.values()) { - if (manager != null) { - list.add(manager); - } - } - return Collections.unmodifiableList(list); + return container.getLoaded(); } - /** - * Returns whether the player can bypass. - * - * @param player The player to check - * @param world The world to check for - * @return Whether {@code player} has bypass permission for {@code world} - */ public boolean hasBypass(LocalPlayer player, World world) { - return player.hasPermission("worldguard.region.bypass." - + world.getName()); + return player.hasPermission("worldguard.region.bypass." + world.getName()); } - /** - * Returns whether the player can bypass. - * - * @param player The player to check - * @param world The world to check - * @return Whether {@code player} has bypass permission for {@code world} - */ public boolean hasBypass(Player player, World world) { - return plugin.hasPermission(player, "worldguard.region.bypass." - + world.getName()); + return plugin.hasPermission(player, "worldguard.region.bypass." + world.getName()); } - /** - * Check if a player has permission to build at a block. - * - * @param player The player to check - * @param block The block to check at - * @return Whether {@code player} can build at {@code block}'s location - */ public boolean canBuild(Player player, Block block) { return canBuild(player, block.getLocation()); } - /** - * Check if a player has permission to build at a location. - * - * @param player The player to check - * @param loc The location to check - * @return Whether {@code player} can build at {@code loc} - */ public boolean canBuild(Player player, Location loc) { World world = loc.getWorld(); WorldConfiguration worldConfig = config.get(world); @@ -320,8 +105,7 @@ public boolean canBuild(Player player, Location loc) { if (!hasBypass(player, world)) { RegionManager mgr = get(world); - if (!mgr.getApplicableRegions(BukkitUtil.toVector(loc)) - .canBuild(localPlayer)) { + if (mgr != null && !mgr.getApplicableRegions(BukkitUtil.toVector(loc)).canBuild(localPlayer)) { return false; } } @@ -346,39 +130,25 @@ public boolean canConstruct(Player player, Location loc) { if (!hasBypass(player, world)) { RegionManager mgr = get(world); - final ApplicableRegionSet applicableRegions = mgr.getApplicableRegions(BukkitUtil.toVector(loc)); - if (!applicableRegions.canBuild(localPlayer)) { - return false; - } - if (!applicableRegions.canConstruct(localPlayer)) { - return false; + if (mgr != null) { + final ApplicableRegionSet applicableRegions = mgr.getApplicableRegions(BukkitUtil.toVector(loc)); + if (!applicableRegions.canBuild(localPlayer)) { + return false; + } + if (!applicableRegions.canConstruct(localPlayer)) { + return false; + } } } return true; } - /** - * Checks to see whether a flag is allowed. - * - * @see #allows(com.sk89q.worldguard.protection.flags.StateFlag, org.bukkit.Location, com.sk89q.worldguard.LocalPlayer) - * @param flag The flag to check - * @param loc The location to check the flag at - * @return Whether the flag is allowed - */ public boolean allows(StateFlag flag, Location loc) { return allows(flag, loc, null); } - /** - * Checks to see whether a flag is allowed. - * - * @param flag The flag to check - * @param loc The location to check the flag at - * @param player The player to check for the flag's {@link com.sk89q.worldguard.protection.flags.RegionGroup} - * @return Whether the flag is allowed - */ - public boolean allows(StateFlag flag, Location loc, LocalPlayer player) { + public boolean allows(StateFlag flag, Location loc, @Nullable LocalPlayer player) { World world = loc.getWorld(); WorldConfiguration worldConfig = config.get(world); @@ -387,6 +157,7 @@ public boolean allows(StateFlag flag, Location loc, LocalPlayer player) { } RegionManager mgr = get(world); - return mgr.getApplicableRegions(toVector(loc)).allows(flag, player); + return mgr == null || mgr.getApplicableRegions(toVector(loc)).allows(flag, player); } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java b/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java new file mode 100644 index 00000000..521f0fc1 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java @@ -0,0 +1,174 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.google.common.base.Supplier; +import com.sk89q.worldguard.bukkit.ConfigurationManager; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex; +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.storage.RegionStore; +import com.sk89q.worldguard.protection.managers.storage.driver.DriverType; +import com.sk89q.worldguard.protection.managers.storage.driver.RegionStoreDriver; +import com.sk89q.worldguard.util.Normal; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Manages different {@link RegionManager}s for different worlds or dimensions. + */ +class ManagerContainer { + + private static final Logger log = Logger.getLogger(ManagerContainer.class.getCanonicalName()); + private static final int SAVE_INTERVAL = 1000 * 30; + + private final ConcurrentMap mapping = new ConcurrentHashMap(); + private final Object lock = new Object(); + private final EnumMap drivers = new EnumMap(DriverType.class); + private final RegionStoreDriver defaultDriver; + private final Supplier indexFactory = new PriorityRTreeIndex.Factory(); + private final Timer timer = new Timer(); + + ManagerContainer(ConfigurationManager config) { + checkNotNull(config); + + for (DriverType type : DriverType.values()) { + drivers.put(type, type.create(config)); + } + + if (config.useSqlDatabase) { + defaultDriver = drivers.get(DriverType.SQL); + } else { + defaultDriver = drivers.get(DriverType.YAML); + } + + timer.schedule(new BackgroundSaver(), SAVE_INTERVAL, SAVE_INTERVAL); + } + + @Nullable + public RegionManager load(String name) { + checkNotNull(name); + + Normal normal = Normal.normal(name); + + synchronized (lock) { + RegionManager manager = mapping.get(normal); + if (manager != null) { + return manager; + } else { + try { + manager = createAndLoad(name); + mapping.put(normal, manager); + return manager; + } catch (IOException e) { + log.log(Level.WARNING, "Failed to load the region data for '" + name + "'", e); + return null; + } + } + } + } + + private RegionManager createAndLoad(String name) throws IOException { + RegionStore store = defaultDriver.get(name); + RegionManager manager = new RegionManager(store, indexFactory); + manager.load(); // Try loading, although it may fail + return manager; + } + + public void unload(String name) { + checkNotNull(name); + + Normal normal = Normal.normal(name); + + synchronized (lock) { + RegionManager manager = mapping.get(normal); + if (manager != null) { + try { + manager.save(); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to save the region data for '" + name + "'", e); + } + } + mapping.remove(normal); + } + } + + public void unloadAll() { + synchronized (lock) { + for (Map.Entry entry : mapping.entrySet()) { + String name = entry.getKey().toString(); + RegionManager manager = entry.getValue(); + try { + manager.save(); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to save the region data for '" + name + "' while unloading the data for all worlds", e); + } + } + + mapping.clear(); + } + } + + @Nullable + public RegionManager get(String name) { + checkNotNull(name); + return mapping.get(Normal.normal(name)); + } + + public List getLoaded() { + return Collections.unmodifiableList(new ArrayList(mapping.values())); + } + + private class BackgroundSaver extends TimerTask { + @Override + public void run() { + synchronized (lock) { + // Block loading of new region managers + + for (Map.Entry entry : mapping.entrySet()) { + String name = entry.getKey().toString(); + RegionManager manager = entry.getValue(); + try { + manager.saveChanges(); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to save the region data for '" + name + "' during a periodical save", e); + } catch (Exception e) { + log.log(Level.WARNING, "An expected error occurred during a periodical save", e); + } + } + } + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/UnsupportedIntersectionException.java b/src/main/java/com/sk89q/worldguard/protection/UnsupportedIntersectionException.java index 2f631c1b..21a2ea28 100644 --- a/src/main/java/com/sk89q/worldguard/protection/UnsupportedIntersectionException.java +++ b/src/main/java/com/sk89q/worldguard/protection/UnsupportedIntersectionException.java @@ -19,6 +19,12 @@ package com.sk89q.worldguard.protection; +/** + * Thrown when an intersection between two different types of regions is not + * supported. + * + * @deprecated no longer utilized + */ +@Deprecated public class UnsupportedIntersectionException extends Exception { - private static final long serialVersionUID = 6423189392345575148L; } diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java deleted file mode 100644 index 72a1aa53..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/AbstractAsynchronousDatabase.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases; - -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.sk89q.odeum.concurrent.EvenMoreExecutors; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.RejectedExecutionException; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * An abstract implementation of a {@code RegionManager} that supports - * asynchronously loading and saving region data while only allowing one - * single operation (either load or save) occurring at a given time. - */ -public abstract class AbstractAsynchronousDatabase extends AbstractProtectionDatabase { - - private static final Logger log = Logger.getLogger(AbstractAsynchronousDatabase.class.getName()); - - private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 4)); - private final Object lock = new Object(); - private QueuedTask lastSave; - private QueuedTask lastLoad; - - @Override - public final void load() throws ProtectionDatabaseException, RejectedExecutionException { - blockOnSave(submitLoadTask(null)); - } - - @Override - public final ListenableFuture load(RegionManager manager, boolean async) throws RejectedExecutionException { - ListenableFuture future = submitLoadTask(manager); - if (!async) { - blockOnLoad(future); - } - return future; - } - - @Override - public final void save() throws ProtectionDatabaseException, RejectedExecutionException { - blockOnSave(submitSaveTask(new HashMap(getRegions()))); - } - - @Override - public final ListenableFuture save(RegionManager manager, boolean async) throws RejectedExecutionException { - ListenableFuture future = submitSaveTask(new HashMap(manager.getRegions())); - if (!async) { - blockOnSave(future); - } - return future; - } - - /** - * Submit a load task and return a future for it. - * - *

If a load task is already queued then that load task's future will - * be returned.

- * - * @param manager the manager - * @return a future - * @throws RejectedExecutionException thrown if there are too many load/save tasks queued - */ - private ListenableFuture submitLoadTask(@Nullable final RegionManager manager) throws RejectedExecutionException { - synchronized (lock) { - lastSave = null; // Void the pending queued save so that any future - // save() calls will submit a brand new save task - - QueuedTask last = lastLoad; - - // Check if there is already a queued task that has not yet started - // that we can return, rather than queue yet another task - if (last != null && !last.started) { - return last.future; - } else { - // Submit the task - final QueuedTask task = new QueuedTask(); - task.future = executor.submit(new Callable() { - @Override - public Object call() throws Exception { - task.started = true; - - performLoad(); - if (manager != null) { - manager.setRegions(getRegions()); - } - return AbstractAsynchronousDatabase.this; - } - }); - - this.lastLoad = task; - - return task.future; - } - } - } - - /** - * Submit a save task and return a future for it. - * - *

If a save task is already queued then that save task's future will - * be returned.

- * - * @param entries a map of regions - * @return a future - * @throws RejectedExecutionException thrown if there are too many load/save tasks queued - */ - private ListenableFuture submitSaveTask(final Map entries) throws RejectedExecutionException { - checkNotNull(entries); - - synchronized (lock) { - lastLoad = null; // Void the pending queued load so that any future - // load() calls will submit a brand new load task - - QueuedTask last = lastSave; - - // Check if there is already a queued task that has not yet started - // that we can return, rather than queue yet another task - if (last != null && !last.started) { - return last.future; - } else { - // Submit the task - final QueuedTask task = new QueuedTask(); - task.future = executor.submit(new Callable() { - @Override - public Object call() throws Exception { - task.started = true; - - setRegions(entries); - performSave(); - return AbstractAsynchronousDatabase.this; - } - }); - - this.lastSave = task; - - return task.future; - } - } - } - - /** - * Block on the given future and print error messages about failing to - * load the database on error. - * - * @param future the future - */ - private void blockOnLoad(Future future) { - try { - future.get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - log.log(Level.WARNING, "Failed to load the region database", e); - } - } - - /** - * Block on the given future and print error messages about failing to - * save the database on error. - * - * @param future the future - */ - private void blockOnSave(Future future) { - try { - future.get(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - log.log(Level.WARNING, "Failed to save the region database", e); - } - } - - /** - * Call to execute a load that may occur in any thread. However, no - * other save or load operation will be simultaneously ongoing. - */ - protected abstract void performLoad() throws ProtectionDatabaseException; - - /** - * Call to execute a save that may occur in any thread. However, no - * other save or load operation will be simultaneously ongoing. - * - *

{@link #setRegions(Map)} must not be called until loading - * has completed and the provided map is in its completed state.

- */ - protected abstract void performSave() throws ProtectionDatabaseException; - - /** - * Stores information about the a queued task. - */ - private static class QueuedTask { - private boolean started = false; - private ListenableFuture future; - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java deleted file mode 100644 index e62effaf..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/AbstractProtectionDatabase.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.sk89q.worldguard.protection.managers.RegionManager; - -public abstract class AbstractProtectionDatabase implements ProtectionDatabase { - - @Override - public void load(RegionManager manager) throws ProtectionDatabaseException { - load(); - manager.setRegions(getRegions()); - } - - @Override - public final void save(RegionManager manager) throws ProtectionDatabaseException { - save(manager, false); - } - - @Override - public ListenableFuture load(RegionManager manager, boolean async) { - try { - load(manager); - } catch (ProtectionDatabaseException e) { - return Futures.immediateFailedFuture(e); - } - return Futures.immediateCheckedFuture(this); - } - - @Override - public ListenableFuture save(RegionManager manager, boolean async) { - setRegions(manager.getRegions()); - try { - save(); - } catch (ProtectionDatabaseException e) { - return Futures.immediateFailedFuture(e); - } - return Futures.immediateCheckedFuture(this); - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/CSVDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/CSVDatabase.java deleted file mode 100644 index 75a72be7..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/CSVDatabase.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases; - -import au.com.bytecode.opencsv.CSVReader; -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.flags.DefaultFlag; -import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.flags.StateFlag.State; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; -import com.sk89q.worldguard.util.ArrayReader; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Represents a protected area database that uses CSV files. - * - * @deprecated no longer maintained - use {@link YAMLDatabase} - */ -@Deprecated -public class CSVDatabase extends AbstractProtectionDatabase { - - private static final Map legacyFlagCodes = new HashMap(); - static { - legacyFlagCodes.put("z", DefaultFlag.PASSTHROUGH); - legacyFlagCodes.put("b", DefaultFlag.BUILD); - legacyFlagCodes.put("p", DefaultFlag.PVP); - legacyFlagCodes.put("m", DefaultFlag.MOB_DAMAGE); - legacyFlagCodes.put("c", DefaultFlag.CREEPER_EXPLOSION); - legacyFlagCodes.put("t", DefaultFlag.TNT); - legacyFlagCodes.put("l", DefaultFlag.LIGHTER); - legacyFlagCodes.put("f", DefaultFlag.FIRE_SPREAD); - legacyFlagCodes.put("F", DefaultFlag.LAVA_FIRE); - legacyFlagCodes.put("C", DefaultFlag.CHEST_ACCESS); - } - - private final Logger logger; - - /** - * References the CSV file. - */ - private final File file; - /** - * Holds the list of regions. - */ - private Map regions; - - /** - * Construct the database with a path to a file. No file is read or - * written at this time. - * - * @param file The file in CSV format containing the region database - * @param logger The logger to log errors to - */ - public CSVDatabase(File file, Logger logger) { - this.file = file; - this.logger = logger; - } - - /** - * Saves the database. - */ - public void save() throws ProtectionDatabaseException { - throw new UnsupportedOperationException("CSV format is no longer implemented"); - } - - public void load() throws ProtectionDatabaseException { - Map regions = - new HashMap(); - Map parentSets = - new LinkedHashMap(); - - CSVReader reader = null; - try { - reader = new CSVReader(new FileReader(file)); - - String[] line; - - while ((line = reader.readNext()) != null) { - if (line.length < 2) { - logger.warning("Invalid region definition: " + line); - continue; - } - - String id = line[0].toLowerCase().replace(".", ""); - String type = line[1]; - ArrayReader entries = new ArrayReader(line); - - if (type.equalsIgnoreCase("cuboid")) { - if (line.length < 8) { - logger.warning("Invalid region definition: " + line); - continue; - } - - Vector pt1 = new Vector( - Integer.parseInt(line[2]), - Integer.parseInt(line[3]), - Integer.parseInt(line[4])); - Vector pt2 = new Vector( - Integer.parseInt(line[5]), - Integer.parseInt(line[6]), - Integer.parseInt(line[7])); - - BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); - BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); - - int priority = entries.get(8) == null ? 0 : Integer.parseInt(entries.get(8)); - String ownersData = entries.get(9); - String flagsData = entries.get(10); - //String enterMessage = nullEmptyString(entries.get(11)); - - ProtectedRegion region = new ProtectedCuboidRegion(id, min, max); - region.setPriority(priority); - parseFlags(region, flagsData); - region.setOwners(this.parseDomains(ownersData)); - regions.put(id, region); - } else if (type.equalsIgnoreCase("cuboid.2")) { - Vector pt1 = new Vector( - Integer.parseInt(line[2]), - Integer.parseInt(line[3]), - Integer.parseInt(line[4])); - Vector pt2 = new Vector( - Integer.parseInt(line[5]), - Integer.parseInt(line[6]), - Integer.parseInt(line[7])); - - BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); - BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); - - int priority = entries.get(8) == null ? 0 : Integer.parseInt(entries.get(8)); - String parentId = entries.get(9); - String ownersData = entries.get(10); - String membersData = entries.get(11); - String flagsData = entries.get(12); - //String enterMessage = nullEmptyString(entries.get(13)); - //String leaveMessage = nullEmptyString(entries.get(14)); - - ProtectedRegion region = new ProtectedCuboidRegion(id, min, max); - region.setPriority(priority); - parseFlags(region, flagsData); - region.setOwners(this.parseDomains(ownersData)); - region.setMembers(this.parseDomains(membersData)); - regions.put(id, region); - - // Link children to parents later - if (parentId.length() > 0) { - parentSets.put(region, parentId); - } - } - } - } catch (IOException e) { - throw new ProtectionDatabaseException(e); - } finally { - try { - reader.close(); - } catch (IOException ignored) { - } - } - - for (Map.Entry entry : parentSets.entrySet()) { - ProtectedRegion parent = regions.get(entry.getValue()); - if (parent != null) { - try { - entry.getKey().setParent(parent); - } catch (CircularInheritanceException e) { - logger.warning("Circular inheritance detect with '" - + entry.getValue() + "' detected as a parent"); - } - } else { - logger.warning("Unknown region parent: " + entry.getValue()); - } - } - - this.regions = regions; - } - - /** - * Used to parse the specified domain in the CSV file. - * - * @param data The domain data as a string - * @return The domain data as a DefaultDomain - */ - private DefaultDomain parseDomains(String data) { - if (data == null) { - return new DefaultDomain(); - } - - DefaultDomain domain = new DefaultDomain(); - Pattern pattern = Pattern.compile("^([A-Za-z]):(.*)$"); - - String[] parts = data.split(","); - - for (String part : parts) { - if (part.trim().length() == 0) { - continue; - } - - Matcher matcher = pattern.matcher(part); - - if (!matcher.matches()) { - logger.warning("Invalid owner specification: " + part); - continue; - } - - String type = matcher.group(1); - String id = matcher.group(2); - - if (type.equals("u")) { - domain.addPlayer(id); - } else if (type.equals("g")) { - domain.addGroup(id); - } else { - logger.warning("Unknown owner specification: " + type); - } - } - - return domain; - } - - /** - * Used to parse the list of flags. - * - * @param data The flag data in string format - */ - private void parseFlags(ProtectedRegion region, String data) { - if (data == null) { - return; - } - - State curState = State.ALLOW; - - for (int i = 0; i < data.length(); i++) { - char k = data.charAt(i); - if (k == '+') { - curState = State.ALLOW; - } else if (k == '-') { - curState = State.DENY; - } else { - String flagStr; - if (k == '_') { - if (i == data.length() - 1) { - logger.warning("_ read ahead fail"); - break; - } - flagStr = "_" + data.charAt(i + 1); - i++; - - logger.warning("_? custom flags are no longer supported"); - continue; - } else { - flagStr = String.valueOf(k); - } - - StateFlag flag = legacyFlagCodes.get(flagStr); - if (flag != null) { - region.setFlag(flag, curState); - } else { - logger.warning("Legacy flag '" + flagStr + "' is unsupported"); - } - } - } - } - - /** - * Used to write the list of domains. - * - * @param domain - * @return - */ -/* private String writeDomains(DefaultDomain domain) { - StringBuilder str = new StringBuilder(); - - for (String player : domain.getPlayers()) { - str.append("u:" + player + ","); - } - - for (String group : domain.getGroups()) { - str.append("g:" + group + ","); - } - - return str.length() > 0 ? - str.toString().substring(0, str.length() - 1) : ""; - }*/ - - /** - * Helper method to prepend '+' or '-' in front of a flag according - * to the flag's state. - * - * @param state - * @param flag - * @return - */ -/* - private String writeFlag(State state, String flag) { - if (state == State.ALLOW) { - return "+" + flag; - } else if (state == State.DENY) { - return "-" + flag; - } - - return ""; - } -*/ - - /** - * Returns a null if a string is null or empty. - * - * @param str The string to format - * @return null if the string is empty or null, otherwise the provided string - */ - protected String nullEmptyString(String str) { - if (str == null) { - return null; - } else if (str.length() == 0) { - return null; - } else { - return str; - } - } - - public Map getRegions() { - return regions; - } - - public void setRegions(Map regions) { - this.regions = regions; - } -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabase.java b/src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabase.java deleted file mode 100644 index 88ade282..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabase.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases; - -import com.google.common.util.concurrent.ListenableFuture; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import java.util.Map; -import java.util.concurrent.RejectedExecutionException; - -/** - * Represents a database to read and write lists of regions from and to. - */ -public interface ProtectionDatabase { - - /** - * Load the list of regions. The method should not modify the list returned - * by getRegions() unless the load finishes successfully. - * - * @throws ProtectionDatabaseException when an error occurs - */ - public void load() throws ProtectionDatabaseException, RejectedExecutionException; - - /** - * Load the list of regions into a region manager. - * - *

This call will block.

- * - * @param manager The manager to load regions into - * @throws ProtectionDatabaseException when an error occurs - */ - public void load(RegionManager manager) throws ProtectionDatabaseException, RejectedExecutionException; - - /** - * Load the list of regions into a region manager, optionally to suggest - * that the data be load in another thread without blocking the thread - * from which the call is made. - * - *

{@code async} is merely a suggestion and it may be ignored by - * implementations if it is not supported.

- * - * @param manager The manager to load regions into - * @param async true to attempt to save the data asynchronously if it is supported - */ - public ListenableFuture load(RegionManager manager, boolean async) throws RejectedExecutionException; - - /** - * Save the list of regions. - * - * @throws ProtectionDatabaseException when an error occurs - */ - public void save() throws ProtectionDatabaseException, RejectedExecutionException; - - /** - * Save the list of regions from a region manager. - * - *

This call will block.

- * - * @param manager The manager to load regions into - * @throws ProtectionDatabaseException when an error occurs - */ - public void save(RegionManager manager) throws ProtectionDatabaseException, RejectedExecutionException; - - /** - * Save the list of regions from a region manager, optionally to suggest - * that the data be saved in another thread without blocking the thread - * from which the call is made. - * - *

{@code async} is merely a suggestion and it may be ignored by - * implementations if it is not supported.

- * - * @param manager The manager to load regions into - * @param async true to attempt to save the data asynchronously if it is supported - * @throws RejectedExecutionException on rejection - */ - public ListenableFuture save(RegionManager manager, boolean async) throws RejectedExecutionException; - - /** - * Get a list of regions. - * - * @return the regions loaded by this ProtectionDatabase - */ - public Map getRegions(); - - /** - * Set the list of regions. - * - * @param regions The regions to be applied to this ProtectionDatabase - */ - public void setRegions(Map regions); - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/RegionDBUtil.java b/src/main/java/com/sk89q/worldguard/protection/databases/RegionDBUtil.java index 158d6567..2fcf9d8b 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/RegionDBUtil.java +++ b/src/main/java/com/sk89q/worldguard/protection/databases/RegionDBUtil.java @@ -20,7 +20,7 @@ package com.sk89q.worldguard.protection.databases; import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.databases.util.DomainInputResolver; +import com.sk89q.worldguard.protection.util.DomainInputResolver; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/AbstractDatabaseMigrator.java b/src/main/java/com/sk89q/worldguard/protection/databases/migrator/AbstractDatabaseMigrator.java deleted file mode 100644 index 1d12bf29..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/AbstractDatabaseMigrator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.migrator; - -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public abstract class AbstractDatabaseMigrator implements DatabaseMigrator { - - private static HashMap> migrators = - new HashMap>(); - - public static Map> getMigrators() { - if (!migrators.isEmpty()) return migrators; - - AbstractDatabaseMigrator.migrators.put(new MigratorKey("mysql", "yaml"), MySQLToYAMLMigrator.class); - AbstractDatabaseMigrator.migrators.put(new MigratorKey("yaml", "mysql"), YAMLToMySQLMigrator.class); - - return migrators; - } - - protected abstract Set getWorldsFromOld() throws MigrationException; - - protected abstract Map getRegionsForWorldFromOld(String world) throws MigrationException; - - protected abstract ProtectionDatabase getNewWorldStorage(String world) throws MigrationException; - - public void migrate() throws MigrationException { - for (String world : this.getWorldsFromOld()) { - ProtectionDatabase database = this.getNewWorldStorage(world); - database.setRegions(this.getRegionsForWorldFromOld(world)); - - try { - database.save(); - } catch (ProtectionDatabaseException e) { - throw new MigrationException(e); - } - } - } -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MySQLToYAMLMigrator.java b/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MySQLToYAMLMigrator.java deleted file mode 100644 index bdfafa5a..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MySQLToYAMLMigrator.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.migrator; - -import com.sk89q.worldguard.bukkit.ConfigurationManager; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.databases.MySQLDatabase; -import com.sk89q.worldguard.protection.databases.YAMLDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import java.io.File; -import java.io.FileNotFoundException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class MySQLToYAMLMigrator extends AbstractDatabaseMigrator { - - private WorldGuardPlugin plugin; - private Set worlds; - - public MySQLToYAMLMigrator(WorldGuardPlugin plugin) throws MigrationException { - this.plugin = plugin; - this.worlds = new HashSet(); - - ConfigurationManager config = plugin.getGlobalStateManager(); - - try { - Connection conn = DriverManager.getConnection(config.sqlDsn, config.sqlUsername, config.sqlPassword); - - ResultSet worlds = conn.prepareStatement("SELECT `name` FROM `world`;").executeQuery(); - - while(worlds.next()) { - this.worlds.add(worlds.getString(1)); - } - - conn.close(); - } catch (SQLException e) { - throw new MigrationException(e); - } - } - - @Override - protected Set getWorldsFromOld() { - return this.worlds; - } - - @Override - protected Map getRegionsForWorldFromOld(String world) throws MigrationException { - ProtectionDatabase oldDatabase; - try { - oldDatabase = new MySQLDatabase(plugin.getGlobalStateManager(), world, plugin.getLogger()); - oldDatabase.load(); - } catch (ProtectionDatabaseException e) { - throw new MigrationException(e); - } - - return oldDatabase.getRegions(); - } - - @Override - protected ProtectionDatabase getNewWorldStorage(String world) throws MigrationException { - try { - File file = new File(plugin.getDataFolder(), - "worlds" + File.separator + world + File.separator + "regions.yml"); - - return new YAMLDatabase(file, plugin.getLogger()); - } catch (FileNotFoundException e) { - throw new MigrationException(e); - } catch (ProtectionDatabaseException e) { - throw new MigrationException(e); - } - } -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/YAMLToMySQLMigrator.java b/src/main/java/com/sk89q/worldguard/protection/databases/migrator/YAMLToMySQLMigrator.java deleted file mode 100644 index 2a1d879f..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/YAMLToMySQLMigrator.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.migrator; - -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.databases.MySQLDatabase; -import com.sk89q.worldguard.protection.databases.YAMLDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class YAMLToMySQLMigrator extends AbstractDatabaseMigrator { - - private WorldGuardPlugin plugin; - private HashMap regionYamlFiles; - - public YAMLToMySQLMigrator(WorldGuardPlugin plugin) { - this.plugin = plugin; - - this.regionYamlFiles = new HashMap(); - - File files[] = new File(plugin.getDataFolder(), "worlds" + File.separator).listFiles(); - for (File item : files) { - if (item.isDirectory()) { - for (File subItem : item.listFiles()) { - if (subItem.getName().equals("regions.yml")) { - this.regionYamlFiles.put(item.getName(), subItem); - } - } - } - } - } - - @Override - protected Set getWorldsFromOld() { - return this.regionYamlFiles.keySet(); - } - - @Override - protected Map getRegionsForWorldFromOld(String world) throws MigrationException { - ProtectionDatabase oldDatabase; - try { - oldDatabase = new YAMLDatabase(this.regionYamlFiles.get(world), plugin.getLogger()); - oldDatabase.load(); - } catch (FileNotFoundException e) { - throw new MigrationException(e); - } catch (ProtectionDatabaseException e) { - throw new MigrationException(e); - } - - return oldDatabase.getRegions(); - } - - @Override - protected ProtectionDatabase getNewWorldStorage(String world) throws MigrationException { - try { - return new MySQLDatabase(plugin.getGlobalStateManager(), world, plugin.getLogger()); - } catch (ProtectionDatabaseException e) { - throw new MigrationException(e); - } - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/AbstractJob.java b/src/main/java/com/sk89q/worldguard/protection/databases/mysql/AbstractJob.java deleted file mode 100644 index 944a3b7b..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/AbstractJob.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.mysql; - -import com.sk89q.worldguard.bukkit.ConfigurationManager; -import org.yaml.snakeyaml.error.YAMLException; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.logging.Logger; - -import static com.google.common.base.Preconditions.checkNotNull; - -@SuppressWarnings("ProtectedField") -abstract class AbstractJob { - - protected final MySQLDatabaseImpl database; - protected final ConfigurationManager config; - protected final Connection conn; - protected final Logger logger; - - protected AbstractJob(MySQLDatabaseImpl database, Connection conn) { - checkNotNull(database); - checkNotNull(conn); - this.database = database; - this.config = database.getConfiguration(); - this.conn = conn; - this.logger = database.getLogger(); - } - - static void closeQuietly(ResultSet rs) { - if (rs != null) { - try { - rs.close(); - } catch (SQLException ignored) {} - } - } - - static void closeQuietly(Statement st) { - if (st != null) { - try { - st.close(); - } catch (SQLException ignored) {} - } - } - - protected Object sqlUnmarshal(String rawValue) { - try { - return database.getYaml().load(rawValue); - } catch (YAMLException e) { - return String.valueOf(rawValue); - } - } - - protected String sqlMarshal(Object rawObject) { - return database.getYaml().dump(rawObject); - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/MySQLDatabaseImpl.java b/src/main/java/com/sk89q/worldguard/protection/databases/mysql/MySQLDatabaseImpl.java deleted file mode 100644 index 3000f2af..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/MySQLDatabaseImpl.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.mysql; - -import com.jolbox.bonecp.BoneCP; -import com.jolbox.bonecp.BoneCPConfig; -import com.sk89q.worldguard.bukkit.ConfigurationManager; -import com.sk89q.worldguard.protection.databases.AbstractAsynchronousDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.util.io.Closer; -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.FlywayException; -import org.flywaydb.core.api.MigrationVersion; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.DumperOptions.FlowStyle; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.SafeConstructor; -import org.yaml.snakeyaml.representer.Representer; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * For internal use. Do not subclass. - */ -public class MySQLDatabaseImpl extends AbstractAsynchronousDatabase { - - private final ConfigurationManager config; - private final Logger logger; - - private final BoneCP connectionPool; - private final Yaml yaml = createYaml(); - private final int worldId; - - private Map regions = new HashMap(); - - public MySQLDatabaseImpl(ConfigurationManager config, String worldName, Logger logger) throws ProtectionDatabaseException { - checkNotNull(config); - checkNotNull(worldName); - checkNotNull(logger); - - this.config = config; - this.logger = logger; - - BoneCPConfig poolConfig = new BoneCPConfig(); - poolConfig.setJdbcUrl(config.sqlDsn); - poolConfig.setUsername(config.sqlUsername); - poolConfig.setPassword(config.sqlPassword); - - try { - connectionPool = new BoneCP(poolConfig); - } catch (SQLException e) { - throw new ProtectionDatabaseException("Failed to connect to the database", e); - } - - try { - migrate(); - } catch (FlywayException e) { - throw new ProtectionDatabaseException("Failed to migrate tables", e); - } catch (SQLException e) { - throw new ProtectionDatabaseException("Failed to migrate tables", e); - } - - try { - worldId = chooseWorldId(worldName); - } catch (SQLException e) { - throw new ProtectionDatabaseException("Failed to choose the ID for this world", e); - } - } - - private boolean tryQuery(Connection conn, String sql) throws SQLException { - Closer closer = Closer.create(); - try { - Statement statement = closer.register(conn.createStatement()); - statement.executeQuery(sql); - return true; - } catch (SQLException ex) { - return false; - } finally { - closer.closeQuietly(); - } - } - - /** - * Migrate the tables to the latest version. - * - * @throws SQLException thrown if a connection can't be opened - * @throws ProtectionDatabaseException thrown on other error - */ - private void migrate() throws SQLException, ProtectionDatabaseException { - Closer closer = Closer.create(); - Connection conn = closer.register(getConnection()); - - // Check some tables - boolean tablesExist; - boolean isRecent; - boolean isBeforeMigrations; - boolean hasMigrations; - - try { - tablesExist = tryQuery(conn, "SELECT * FROM `" + config.sqlTablePrefix + "region_cuboid` LIMIT 1"); - isRecent = tryQuery(conn, "SELECT `world_id` FROM `" + config.sqlTablePrefix + "region_cuboid` LIMIT 1"); - isBeforeMigrations = !tryQuery(conn, "SELECT `uuid` FROM `" + config.sqlTablePrefix + "user` LIMIT 1"); - hasMigrations = tryQuery(conn, "SELECT * FROM `" + config.sqlTablePrefix + "migrations` LIMIT 1"); - } finally { - closer.closeQuietly(); - } - - // We don't bother with migrating really old tables - if (tablesExist && !isRecent) { - throw new ProtectionDatabaseException( - "Sorry, your tables are too old for the region SQL auto-migration system. " + - "Please run region_manual_update_20110325.sql on your database, which comes " + - "with WorldGuard or can be found in http://github.com/sk89q/worldguard"); - } - - // Our placeholders - Map placeHolders = new HashMap(); - placeHolders.put("tablePrefix", config.sqlTablePrefix); - - BoneCPConfig boneConfig = connectionPool.getConfig(); - - Flyway flyway = new Flyway(); - - // The MySQL support predates the usage of Flyway, so let's do some - // checks and issue messages appropriately - if (!hasMigrations) { - flyway.setInitOnMigrate(true); - - if (tablesExist) { - // Detect if this is before migrations - if (isBeforeMigrations) { - flyway.setInitVersion(MigrationVersion.fromVersion("1")); - } - - logger.log(Level.INFO, "The MySQL region tables exist but the migrations table seems to not exist yet. Creating the migrations table..."); - } else { - // By default, if Flyway sees any tables at all in the schema, it - // will assume that we are up to date, so we have to manually - // check ourselves and then ask Flyway to start from the beginning - // if our test table doesn't exist - flyway.setInitVersion(MigrationVersion.fromVersion("0")); - - logger.log(Level.INFO, "MySQL region tables do not exist: creating..."); - } - } - - flyway.setClassLoader(getClass().getClassLoader()); - flyway.setLocations("migrations/region/mysql"); - flyway.setDataSource(boneConfig.getJdbcUrl(), boneConfig.getUser(), boneConfig.getPassword()); - flyway.setTable(config.sqlTablePrefix + "migrations"); - flyway.setPlaceholders(placeHolders); - flyway.setValidateOnMigrate(false); - flyway.migrate(); - } - - /** - * Get the ID for this world from the database or pick a new one if - * an entry does not exist yet. - * - * @param worldName the world name - * @return a world ID - * @throws SQLException on a database access error - */ - private int chooseWorldId(String worldName) throws SQLException { - Closer closer = Closer.create(); - try { - Connection conn = closer.register(getConnection()); - PreparedStatement worldStmt = closer.register(conn.prepareStatement( - "SELECT `id` FROM `" + config.sqlTablePrefix + "world` WHERE `name` = ? LIMIT 0, 1" - )); - - worldStmt.setString(1, worldName); - ResultSet worldResult = closer.register(worldStmt.executeQuery()); - - if (worldResult.first()) { - return worldResult.getInt("id"); - } else { - PreparedStatement insertWorldStatement = closer.register(conn.prepareStatement( - "INSERT INTO " + - "`" + config.sqlTablePrefix + "world` " + - "(`id`, `name`) VALUES (null, ?)", - Statement.RETURN_GENERATED_KEYS - )); - - insertWorldStatement.setString(1, worldName); - insertWorldStatement.execute(); - ResultSet generatedKeys = insertWorldStatement.getGeneratedKeys(); - - if (generatedKeys.first()) { - return generatedKeys.getInt(1); - } else { - throw new SQLException("Expected result, got none"); - } - } - } finally { - closer.closeQuietly(); - } - } - - private static Yaml createYaml() { - DumperOptions options = new DumperOptions(); - options.setIndent(2); - options.setDefaultFlowStyle(FlowStyle.FLOW); - Representer representer = new Representer(); - representer.setDefaultFlowStyle(FlowStyle.FLOW); - - // We have to use this in order to properly save non-string values - return new Yaml(new SafeConstructor(), new Representer(), options); - } - - ConfigurationManager getConfiguration() { - return this.config; - } - - Logger getLogger() { - return logger; - } - - Yaml getYaml() { - return yaml; - } - - int getWorldId() { - return worldId; - } - - private Connection getConnection() throws SQLException { - return connectionPool.getConnection(); - } - - @Override - public Map getRegions() { - return regions; - } - - @Override - public void setRegions(Map regions) { - this.regions = regions; - } - - @Override - protected void performLoad() throws ProtectionDatabaseException { - Connection connection = null; - try { - connection = getConnection(); - setRegions(new RegionLoader(this, connection).load()); - } catch (SQLException e) { - throw new ProtectionDatabaseException("Failed to load regions database", e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException ignored) { - } - } - } - } - - @Override - protected void performSave() throws ProtectionDatabaseException { - Connection connection = null; - try { - connection = getConnection(); - new RegionWriter(this, connection, getRegions()).save(); - } catch (SQLException e) { - throw new ProtectionDatabaseException("Failed to save regions database", e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException ignored) { - } - } - } - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionLoader.java b/src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionLoader.java deleted file mode 100644 index e4793837..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionLoader.java +++ /dev/null @@ -1,456 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.mysql; - -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.BlockVector2D; -import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.flags.DefaultFlag; -import com.sk89q.worldguard.protection.flags.Flag; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -class RegionLoader extends AbstractJob { - - /* - ========= Everything below is a nightmare. ========= - */ - - private final int worldId; - - private Map cuboidRegions; - private Map poly2dRegions; - private Map globalRegions; - private Map parentSets; - - RegionLoader(MySQLDatabaseImpl database, Connection conn) { - super(database, conn); - this.worldId = database.getWorldId(); - } - - public Map load() { - parentSets = new HashMap(); - - // We load the cuboid regions first, as this is likely to be the - // largest dataset. This should save time in regards to the putAll()s - this.loadCuboid(); - Map regions = this.cuboidRegions; - this.cuboidRegions = null; - - this.loadPoly2d(); - regions.putAll(this.poly2dRegions); - this.poly2dRegions = null; - - this.loadGlobal(); - regions.putAll(this.globalRegions); - this.globalRegions = null; - - // Relink parents // Taken verbatim from YAMLDatabase - for (Map.Entry entry : parentSets.entrySet()) { - ProtectedRegion parent = regions.get(entry.getValue()); - if (parent != null) { - try { - entry.getKey().setParent(parent); - } catch (CircularInheritanceException e) { - logger.warning("Circular inheritance detect with '" - + entry.getValue() + "' detected as a parent"); - } - } else { - logger.warning("Unknown region parent: " + entry.getValue()); - } - } - - return regions; - } - - private void loadFlags(ProtectedRegion region) { - // @TODO: Iterate _ONCE_ - PreparedStatement flagsStatement = null; - ResultSet flagsResultSet = null; - try { - flagsStatement = this.conn.prepareStatement( - "SELECT " + - "`region_flag`.`flag`, " + - "`region_flag`.`value` " + - "FROM `" + config.sqlTablePrefix + "region_flag` AS `region_flag` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId - ); - - flagsStatement.setString(1, region.getId().toLowerCase()); - flagsResultSet = flagsStatement.executeQuery(); - - Map regionFlags = new HashMap(); - while (flagsResultSet.next()) { - regionFlags.put( - flagsResultSet.getString("flag"), - sqlUnmarshal(flagsResultSet.getString("value")) - ); - } - - // @TODO: Make this better - for (Flag flag : DefaultFlag.getFlags()) { - Object o = regionFlags.get(flag.getName()); - if (o != null) { - setFlag(region, flag, o); - } - } - } catch (SQLException ex) { - logger.warning( - "Unable to load flags for region " - + region.getId().toLowerCase() + ": " + ex.getMessage() - ); - } finally { - closeQuietly(flagsResultSet); - closeQuietly(flagsStatement); - } - } - - private void setFlag(ProtectedRegion region, Flag flag, Object rawValue) { - T val = flag.unmarshal(rawValue); - if (val == null) { - logger.warning("Failed to parse flag '" + flag.getName() + "' with value '" + rawValue + "'"); - return; - } - region.setFlag(flag, val); - } - - private void loadOwnersAndMembers(ProtectedRegion region) { - DefaultDomain owners = new DefaultDomain(); - DefaultDomain members = new DefaultDomain(); - - ResultSet userSet = null; - PreparedStatement usersStatement = null; - try { - usersStatement = this.conn.prepareStatement( - "SELECT " + - "`user`.`name`, `user`.`uuid`, " + - "`region_players`.`owner` " + - "FROM `" + config.sqlTablePrefix + "region_players` AS `region_players` " + - "LEFT JOIN `" + config.sqlTablePrefix + "user` AS `user` ON ( " + - "`region_players`.`user_id` = " + - "`user`.`id`) " + - "WHERE `region_players`.`region_id` = ? " + - "AND `region_players`.`world_id` = " + this.worldId - ); - - usersStatement.setString(1, region.getId().toLowerCase()); - userSet = usersStatement.executeQuery(); - while (userSet.next()) { - DefaultDomain domain; - if (userSet.getBoolean("owner")) { - domain = owners; - } else { - domain = members; - } - - String name = userSet.getString("name"); - String uuid = userSet.getString("uuid"); - if (name != null) { - domain.addPlayer(name); - } else if (uuid != null) { - try { - domain.addPlayer(UUID.fromString(uuid)); - } catch (IllegalArgumentException e) { - logger.warning("Invalid UUID in database: " + uuid); - } - } - } - } catch (SQLException ex) { - logger.warning("Unable to load users for region " + region.getId().toLowerCase() + ": " + ex.getMessage()); - } finally { - closeQuietly(userSet); - closeQuietly(usersStatement); - } - - PreparedStatement groupsStatement = null; - ResultSet groupSet = null; - try { - groupsStatement = this.conn.prepareStatement( - "SELECT " + - "`group`.`name`, " + - "`region_groups`.`owner` " + - "FROM `" + config.sqlTablePrefix + "region_groups` AS `region_groups` " + - "LEFT JOIN `" + config.sqlTablePrefix + "group` AS `group` ON ( " + - "`region_groups`.`group_id` = " + - "`group`.`id`) " + - "WHERE `region_groups`.`region_id` = ? " + - "AND `region_groups`.`world_id` = " + this.worldId - ); - - groupsStatement.setString(1, region.getId().toLowerCase()); - groupSet = groupsStatement.executeQuery(); - while (groupSet.next()) { - if (groupSet.getBoolean("owner")) { - owners.addGroup(groupSet.getString("name")); - } else { - members.addGroup(groupSet.getString("name")); - } - } - } catch (SQLException ex) { - logger.warning("Unable to load groups for region " + region.getId().toLowerCase() + ": " + ex.getMessage()); - } finally { - closeQuietly(groupSet); - closeQuietly(groupsStatement); - } - - region.setOwners(owners); - region.setMembers(members); - } - - private void loadGlobal() { - Map regions = - new HashMap(); - - PreparedStatement globalRegionStatement = null; - ResultSet globalResultSet = null; - try { - globalRegionStatement = this.conn.prepareStatement( - "SELECT " + - "`region`.`id`, " + - "`region`.`priority`, " + - "`parent`.`id` AS `parent` " + - "FROM `" + config.sqlTablePrefix + "region` AS `region` " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + - "ON (`region`.`parent` = `parent`.`id` " + - "AND `region`.`world_id` = `parent`.`world_id`) " + - "WHERE `region`.`type` = 'global' " + - "AND `region`.`world_id` = ? " - ); - - globalRegionStatement.setInt(1, this.worldId); - globalResultSet = globalRegionStatement.executeQuery(); - - while (globalResultSet.next()) { - ProtectedRegion region = new GlobalProtectedRegion(globalResultSet.getString("id")); - - region.setPriority(globalResultSet.getInt("priority")); - - this.loadFlags(region); - this.loadOwnersAndMembers(region); - - regions.put(globalResultSet.getString("id"), region); - - String parentId = globalResultSet.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Unable to load regions from sql database: " + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning("\t\tCause: " + t.getMessage()); - t = t.getCause(); - } - } finally { - closeQuietly(globalResultSet); - closeQuietly(globalRegionStatement); - } - - globalRegions = regions; - } - - private void loadCuboid() { - Map regions = new HashMap(); - - PreparedStatement cuboidRegionStatement = null; - ResultSet cuboidResultSet = null; - try { - cuboidRegionStatement = this.conn.prepareStatement( - "SELECT " + - "`region_cuboid`.`min_z`, " + - "`region_cuboid`.`min_y`, " + - "`region_cuboid`.`min_x`, " + - "`region_cuboid`.`max_z`, " + - "`region_cuboid`.`max_y`, " + - "`region_cuboid`.`max_x`, " + - "`region`.`id`, " + - "`region`.`priority`, " + - "`parent`.`id` AS `parent` " + - "FROM `" + config.sqlTablePrefix + "region_cuboid` AS `region_cuboid` " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `region` " + - "ON (`region_cuboid`.`region_id` = `region`.`id` " + - "AND `region_cuboid`.`world_id` = `region`.`world_id`) " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + - "ON (`region`.`parent` = `parent`.`id` " + - "AND `region`.`world_id` = `parent`.`world_id`) " + - "WHERE `region`.`world_id` = ? " - ); - - cuboidRegionStatement.setInt(1, this.worldId); - cuboidResultSet = cuboidRegionStatement.executeQuery(); - - while (cuboidResultSet.next()) { - Vector pt1 = new Vector( - cuboidResultSet.getInt("min_x"), - cuboidResultSet.getInt("min_y"), - cuboidResultSet.getInt("min_z") - ); - Vector pt2 = new Vector( - cuboidResultSet.getInt("max_x"), - cuboidResultSet.getInt("max_y"), - cuboidResultSet.getInt("max_z") - ); - - BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); - BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); - ProtectedRegion region = new ProtectedCuboidRegion( - cuboidResultSet.getString("id"), - min, - max - ); - - region.setPriority(cuboidResultSet.getInt("priority")); - - this.loadFlags(region); - this.loadOwnersAndMembers(region); - - regions.put(cuboidResultSet.getString("id"), region); - - String parentId = cuboidResultSet.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } - - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Unable to load regions from sql database: " + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning("\t\tCause: " + t.getMessage()); - t = t.getCause(); - } - } finally { - closeQuietly(cuboidResultSet); - closeQuietly(cuboidRegionStatement); - } - - cuboidRegions = regions; - } - - private void loadPoly2d() { - Map regions = new HashMap(); - - PreparedStatement poly2dRegionStatement = null; - ResultSet poly2dResultSet = null; - PreparedStatement poly2dVectorStatement = null; - try { - poly2dRegionStatement = this.conn.prepareStatement( - "SELECT " + - "`region_poly2d`.`min_y`, " + - "`region_poly2d`.`max_y`, " + - "`region`.`id`, " + - "`region`.`priority`, " + - "`parent`.`id` AS `parent` " + - "FROM `" + config.sqlTablePrefix + "region_poly2d` AS `region_poly2d` " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `region` " + - "ON (`region_poly2d`.`region_id` = `region`.`id` " + - "AND `region_poly2d`.`world_id` = `region`.`world_id`) " + - "LEFT JOIN `" + config.sqlTablePrefix + "region` AS `parent` " + - "ON (`region`.`parent` = `parent`.`id` " + - "AND `region`.`world_id` = `parent`.`world_id`) " + - "WHERE `region`.`world_id` = ? " - ); - - poly2dRegionStatement.setInt(1, this.worldId); - poly2dResultSet = poly2dRegionStatement.executeQuery(); - - poly2dVectorStatement = this.conn.prepareStatement( - "SELECT " + - "`region_poly2d_point`.`x`, " + - "`region_poly2d_point`.`z` " + - "FROM `" + config.sqlTablePrefix + "region_poly2d_point` AS `region_poly2d_point` " + - "WHERE `region_poly2d_point`.`region_id` = ? " + - "AND `region_poly2d_point`.`world_id` = " + this.worldId - ); - - while (poly2dResultSet.next()) { - String id = poly2dResultSet.getString("id"); - - Integer minY = poly2dResultSet.getInt("min_y"); - Integer maxY = poly2dResultSet.getInt("max_y"); - List points = new ArrayList(); - - poly2dVectorStatement.setString(1, id); - ResultSet poly2dVectorResultSet = poly2dVectorStatement.executeQuery(); - - while (poly2dVectorResultSet.next()) { - points.add(new BlockVector2D( - poly2dVectorResultSet.getInt("x"), - poly2dVectorResultSet.getInt("z") - )); - } - - if (points.size() < 3) { - logger.warning(String.format("Invalid polygonal region '%s': region only has %d point(s). Ignoring.", id, points.size())); - continue; - } - - closeQuietly(poly2dVectorResultSet); - - ProtectedRegion region = new ProtectedPolygonalRegion(id, points, minY, maxY); - - region.setPriority(poly2dResultSet.getInt("priority")); - - this.loadFlags(region); - this.loadOwnersAndMembers(region); - - regions.put(poly2dResultSet.getString("id"), region); - - String parentId = poly2dResultSet.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } - } catch (SQLException ex) { - ex.printStackTrace(); - logger.warning("Unable to load regions from sql database: " + ex.getMessage()); - Throwable t = ex.getCause(); - while (t != null) { - logger.warning("\t\tCause: " + t.getMessage()); - t = t.getCause(); - } - } finally { - closeQuietly(poly2dResultSet); - closeQuietly(poly2dRegionStatement); - closeQuietly(poly2dVectorStatement); - } - - poly2dRegions = regions; - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionWriter.java b/src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionWriter.java deleted file mode 100644 index c3294638..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/RegionWriter.java +++ /dev/null @@ -1,608 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases.mysql; - -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.BlockVector2D; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.internal.util.sql.StatementUtils; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; -import com.sk89q.worldguard.protection.flags.Flag; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.sk89q.worldguard.protection.databases.mysql.UserRowCache.NameRowCache; -import static com.sk89q.worldguard.protection.databases.mysql.UserRowCache.UUIDRowCache; - -class RegionWriter extends AbstractJob { - - /* - ========= Everything below is a nightmare. ========= - */ - - private final Map regions; - private final NameRowCache usernameCache; - private final UUIDRowCache uuidCache; - private final int worldId; - - RegionWriter(MySQLDatabaseImpl database, Connection conn, Map regions) { - super(database, conn); - checkNotNull(regions); - this.regions = regions; - this.worldId = database.getWorldId(); - usernameCache = new NameRowCache(database, conn); - uuidCache = new UUIDRowCache(database, conn); - } - - public void save() throws SQLException, ProtectionDatabaseException { - - /* - * As we don't get notified on the creation/removal of regions: - * 1) We get a list of all of the in-database regions - * 2) We iterate over all of the in-memory regions - * 2a) If the region is in the database, we update the database and - * remove the region from the in-database list - * b) If the region is not in the database, we insert it - * 3) We iterate over what remains of the in-database list and remove - * them from the database - * - * TODO: Look at adding/removing/updating the database when the in - * memory region is created/remove/updated - * - * @see com.sk89q.worldguard.protection.databases.ProtectionDatabase#save() - */ - - List regionsInDatabase = new ArrayList(); - - PreparedStatement getAllRegionsStatement = null; - ResultSet getAllRegionsResult = null; - try { - getAllRegionsStatement = this.conn.prepareStatement( - "SELECT `region`.`id` FROM " + - "`" + config.sqlTablePrefix + "region` AS `region` " + - "WHERE `world_id` = ? " - ); - - getAllRegionsStatement.setInt(1, this.worldId); - getAllRegionsResult = getAllRegionsStatement.executeQuery(); - - while(getAllRegionsResult.next()) { - regionsInDatabase.add(getAllRegionsResult.getString("id")); - } - } catch (SQLException ex) { - logger.warning("Could not get region list for save comparison: " + ex.getMessage()); - } finally { - closeQuietly(getAllRegionsResult); - closeQuietly(getAllRegionsStatement); - } - - for (Map.Entry entry : regions.entrySet()) { - String name = entry.getKey(); - ProtectedRegion region = entry.getValue(); - - try { - if (regionsInDatabase.contains(name)) { - regionsInDatabase.remove(name); - - if (region instanceof ProtectedCuboidRegion) { - updateRegionCuboid( (ProtectedCuboidRegion) region ); - } else if (region instanceof ProtectedPolygonalRegion) { - updateRegionPoly2D( (ProtectedPolygonalRegion) region ); - } else if (region instanceof GlobalProtectedRegion) { - updateRegionGlobal( (GlobalProtectedRegion) region ); - } else { - this.updateRegion(region, region.getClass().getCanonicalName()); - } - } else { - if (region instanceof ProtectedCuboidRegion) { - insertRegionCuboid( (ProtectedCuboidRegion) region ); - } else if (region instanceof ProtectedPolygonalRegion) { - insertRegionPoly2D( (ProtectedPolygonalRegion) region ); - } else if (region instanceof GlobalProtectedRegion) { - insertRegionGlobal( (GlobalProtectedRegion) region ); - } else { - this.insertRegion(region, region.getClass().getCanonicalName()); - } - } - } catch (SQLException ex) { - logger.warning("Could not save region " + region.getId().toLowerCase() + ": " + ex.getMessage()); - throw new ProtectionDatabaseException(ex); - } - } - - for (Map.Entry entry : regions.entrySet()) { - PreparedStatement setParentStatement = null; - try { - if (entry.getValue().getParent() == null) continue; - - setParentStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region` SET " + - "`parent` = ? " + - "WHERE `id` = ? AND `world_id` = " + this.worldId - ); - - setParentStatement.setString(1, entry.getValue().getParent().getId().toLowerCase()); - setParentStatement.setString(2, entry.getValue().getId().toLowerCase()); - - setParentStatement.execute(); - } catch (SQLException ex) { - logger.warning("Could not save region parents " + entry.getValue().getId().toLowerCase() + ": " + ex.getMessage()); - throw new ProtectionDatabaseException(ex); - } finally { - closeQuietly(setParentStatement); - } - } - - for (String name : regionsInDatabase) { - PreparedStatement removeRegion = null; - try { - removeRegion = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region` WHERE `id` = ? " - ); - - removeRegion.setString(1, name); - removeRegion.execute(); - } catch (SQLException ex) { - logger.warning("Could not remove region from database " + name + ": " + ex.getMessage()); - } finally { - closeQuietly(removeRegion); - } - } - } - - /* - * Returns the database id for the groups - * If it doesn't exits it adds the group and returns the id. - */ - private Map getGroupIds(String... groupnames) { - Map groups = new HashMap(); - - if (groupnames.length < 1) return groups; - - PreparedStatement findGroupsStatement = null; - ResultSet findGroupsResults = null; - PreparedStatement insertGroupStatement = null; - try { - findGroupsStatement = this.conn.prepareStatement( - String.format( - "SELECT " + - "`group`.`id`, " + - "`group`.`name` " + - "FROM `" + config.sqlTablePrefix + "group` AS `group` " + - "WHERE `name` IN (%s)", - StatementUtils.preparePlaceHolders(groupnames.length) - ) - ); - - StatementUtils.setValues(findGroupsStatement, groupnames); - - findGroupsResults = findGroupsStatement.executeQuery(); - - while(findGroupsResults.next()) { - groups.put(findGroupsResults.getString("name"), findGroupsResults.getInt("id")); - } - - insertGroupStatement = this.conn.prepareStatement( - "INSERT INTO " + - "`" + config.sqlTablePrefix + "group` ( " + - "`id`, " + - "`name`" + - ") VALUES (null, ?)", - Statement.RETURN_GENERATED_KEYS - ); - - for (String groupname : groupnames) { - if (!groups.containsKey(groupname)) { - insertGroupStatement.setString(1, groupname); - insertGroupStatement.execute(); - ResultSet generatedKeys = insertGroupStatement.getGeneratedKeys(); - if (generatedKeys.first()) { - groups.put(groupname, generatedKeys.getInt(1)); - } else { - logger.warning("Could not get the database id for user " + groupname); - } - } - } - } catch (SQLException ex) { - logger.warning("Could not get the database id for the groups " + groupnames.toString() + ex.getMessage()); - } finally { - closeQuietly(findGroupsResults); - closeQuietly(findGroupsStatement); - closeQuietly(insertGroupStatement); - } - - return groups; - } - - private void updateFlags(ProtectedRegion region) throws SQLException { - PreparedStatement clearCurrentFlagStatement = null; - try { - clearCurrentFlagStatement = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_flag` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId - ); - - clearCurrentFlagStatement.setString(1, region.getId().toLowerCase()); - clearCurrentFlagStatement.execute(); - - for (Map.Entry, Object> entry : region.getFlags().entrySet()) { - if (entry.getValue() == null) continue; - - Object flag = sqlMarshal(marshalFlag(entry.getKey(), entry.getValue())); - - PreparedStatement insertFlagStatement = null; - try { - insertFlagStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_flag` ( " + - "`id`, " + - "`region_id`, " + - "`world_id`, " + - "`flag`, " + - "`value` " + - ") VALUES (null, ?, " + this.worldId + ", ?, ?)" - ); - - insertFlagStatement.setString(1, region.getId().toLowerCase()); - insertFlagStatement.setString(2, entry.getKey().getName()); - insertFlagStatement.setObject(3, flag); - - insertFlagStatement.execute(); - } finally { - closeQuietly(insertFlagStatement); - } - } - } finally { - closeQuietly(clearCurrentFlagStatement); - } - } - - private void updatePlayerAndGroups(ProtectedRegion region, Boolean owners) throws SQLException { - DefaultDomain domain; - - if (owners) { - domain = region.getOwners(); - } else { - domain = region.getMembers(); - } - - PreparedStatement deleteUsersForRegion = null; - PreparedStatement insertUsersForRegion = null; - PreparedStatement deleteGroupsForRegion = null; - PreparedStatement insertGroupsForRegion = null; - - try { - deleteUsersForRegion = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_players` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId + " " + - "AND `owner` = ?" - ); - - deleteUsersForRegion.setString(1, region.getId().toLowerCase()); - deleteUsersForRegion.setBoolean(2, owners); - deleteUsersForRegion.execute(); - - insertUsersForRegion = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_players` " + - "(`region_id`, `world_id`, `user_id`, `owner`) " + - "VALUES (?, " + this.worldId + ", ?, ?)" - ); - - // Map players to IDs - usernameCache.fetch(domain.getPlayers()); - uuidCache.fetch(domain.getUniqueIds()); - - for (String name : domain.getPlayers()) { - Integer id = usernameCache.find(name); - if (id != null) { - insertUsersForRegion.setString(1, region.getId().toLowerCase()); - insertUsersForRegion.setInt(2, id); - insertUsersForRegion.setBoolean(3, owners); - insertUsersForRegion.execute(); - } else { - logger.log(Level.WARNING, "Did not find an ID for the user identified as '" + name + "'"); - } - } - - for (UUID uuid : domain.getUniqueIds()) { - Integer id = uuidCache.find(uuid); - if (id != null) { - insertUsersForRegion.setString(1, region.getId().toLowerCase()); - insertUsersForRegion.setInt(2, id); - insertUsersForRegion.setBoolean(3, owners); - insertUsersForRegion.execute(); - } else { - logger.log(Level.WARNING, "Did not find an ID for the user identified by '" + uuid + "'"); - } - } - - deleteGroupsForRegion = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_groups` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId + " " + - "AND `owner` = ?" - ); - - deleteGroupsForRegion.setString(1, region.getId().toLowerCase()); - deleteGroupsForRegion.setBoolean(2, owners); - deleteGroupsForRegion.execute(); - - insertGroupsForRegion = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_groups` " + - "(`region_id`, `world_id`, `group_id`, `owner`) " + - "VALUES (?, " + this.worldId + ", ?, ?)" - ); - - Set groupVar = domain.getGroups(); - for (Integer group : getGroupIds(groupVar.toArray(new String[groupVar.size()])).values()) { - insertGroupsForRegion.setString(1, region.getId().toLowerCase()); - insertGroupsForRegion.setInt(2, group); - insertGroupsForRegion.setBoolean(3, owners); - - insertGroupsForRegion.execute(); - } - } finally { - closeQuietly(deleteGroupsForRegion); - closeQuietly(deleteUsersForRegion); - closeQuietly(insertGroupsForRegion); - closeQuietly(insertUsersForRegion); - } - } - - @SuppressWarnings("unchecked") - private Object marshalFlag(Flag flag, Object val) { - return flag.marshal( (V) val ); - } - - private void insertRegion(ProtectedRegion region, String type) throws SQLException { - PreparedStatement insertRegionStatement = null; - try { - insertRegionStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region` (" + - "`id`, " + - "`world_id`, " + - "`type`, " + - "`priority`, " + - "`parent` " + - ") VALUES (?, ?, ?, ?, null)" - ); - - insertRegionStatement.setString(1, region.getId().toLowerCase()); - insertRegionStatement.setInt(2, this.worldId); - insertRegionStatement.setString(3, type); - insertRegionStatement.setInt(4, region.getPriority()); - - insertRegionStatement.execute(); - } finally { - closeQuietly(insertRegionStatement); - } - - updateFlags(region); - - updatePlayerAndGroups(region, false); - updatePlayerAndGroups(region, true); - } - - private void insertRegionCuboid(ProtectedCuboidRegion region) throws SQLException { - insertRegion(region, "cuboid"); - - PreparedStatement insertCuboidRegionStatement = null; - try { - insertCuboidRegionStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_cuboid` (" + - "`region_id`, " + - "`world_id`, " + - "`min_z`, " + - "`min_y`, " + - "`min_x`, " + - "`max_z`, " + - "`max_y`, " + - "`max_x` " + - ") VALUES (?, " + this.worldId + ", ?, ?, ?, ?, ?, ?)" - ); - - BlockVector min = region.getMinimumPoint(); - BlockVector max = region.getMaximumPoint(); - - insertCuboidRegionStatement.setString(1, region.getId().toLowerCase()); - insertCuboidRegionStatement.setInt(2, min.getBlockZ()); - insertCuboidRegionStatement.setInt(3, min.getBlockY()); - insertCuboidRegionStatement.setInt(4, min.getBlockX()); - insertCuboidRegionStatement.setInt(5, max.getBlockZ()); - insertCuboidRegionStatement.setInt(6, max.getBlockY()); - insertCuboidRegionStatement.setInt(7, max.getBlockX()); - - insertCuboidRegionStatement.execute(); - } finally { - closeQuietly(insertCuboidRegionStatement); - } - } - - private void insertRegionPoly2D(ProtectedPolygonalRegion region) throws SQLException { - insertRegion(region, "poly2d"); - - PreparedStatement insertPoly2dRegionStatement = null; - try { - insertPoly2dRegionStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_poly2d` (" + - "`region_id`, " + - "`world_id`, " + - "`max_y`, " + - "`min_y` " + - ") VALUES (?, " + this.worldId + ", ?, ?)" - ); - - insertPoly2dRegionStatement.setString(1, region.getId().toLowerCase()); - insertPoly2dRegionStatement.setInt(2, region.getMaximumPoint().getBlockY()); - insertPoly2dRegionStatement.setInt(3, region.getMinimumPoint().getBlockY()); - - insertPoly2dRegionStatement.execute(); - } finally { - closeQuietly(insertPoly2dRegionStatement); - } - - updatePoly2dPoints(region); - } - - private void updatePoly2dPoints(ProtectedPolygonalRegion region) throws SQLException { - PreparedStatement clearPoly2dPointsForRegionStatement = null; - PreparedStatement insertPoly2dPointStatement = null; - - try { - clearPoly2dPointsForRegionStatement = this.conn.prepareStatement( - "DELETE FROM `" + config.sqlTablePrefix + "region_poly2d_point` " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId - ); - - clearPoly2dPointsForRegionStatement.setString(1, region.getId().toLowerCase()); - - clearPoly2dPointsForRegionStatement.execute(); - - insertPoly2dPointStatement = this.conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "region_poly2d_point` (" + - "`id`, " + - "`region_id`, " + - "`world_id`, " + - "`z`, " + - "`x` " + - ") VALUES (null, ?, " + this.worldId + ", ?, ?)" - ); - - String lowerId = region.getId().toLowerCase(); - for (BlockVector2D point : region.getPoints()) { - insertPoly2dPointStatement.setString(1, lowerId); - insertPoly2dPointStatement.setInt(2, point.getBlockZ()); - insertPoly2dPointStatement.setInt(3, point.getBlockX()); - - insertPoly2dPointStatement.execute(); - } - } finally { - closeQuietly(clearPoly2dPointsForRegionStatement); - closeQuietly(insertPoly2dPointStatement); - } - } - - private void insertRegionGlobal(GlobalProtectedRegion region) throws SQLException { - insertRegion(region, "global"); - } - - private void updateRegion(ProtectedRegion region, String type) throws SQLException { - PreparedStatement updateRegionStatement = null; - try { - updateRegionStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region` SET " + - "`priority` = ? WHERE `id` = ? AND `world_id` = " + this.worldId - ); - - updateRegionStatement.setInt(1, region.getPriority()); - updateRegionStatement.setString(2, region.getId().toLowerCase()); - - updateRegionStatement.execute(); - } finally { - closeQuietly(updateRegionStatement); - } - - updateFlags(region); - - updatePlayerAndGroups(region, false); - updatePlayerAndGroups(region, true); - } - - private void updateRegionCuboid(ProtectedCuboidRegion region) throws SQLException { - updateRegion(region, "cuboid"); - - PreparedStatement updateCuboidRegionStatement = null; - try { - updateCuboidRegionStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region_cuboid` SET " + - "`min_z` = ?, " + - "`min_y` = ?, " + - "`min_x` = ?, " + - "`max_z` = ?, " + - "`max_y` = ?, " + - "`max_x` = ? " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId - ); - - BlockVector min = region.getMinimumPoint(); - BlockVector max = region.getMaximumPoint(); - - updateCuboidRegionStatement.setInt(1, min.getBlockZ()); - updateCuboidRegionStatement.setInt(2, min.getBlockY()); - updateCuboidRegionStatement.setInt(3, min.getBlockX()); - updateCuboidRegionStatement.setInt(4, max.getBlockZ()); - updateCuboidRegionStatement.setInt(5, max.getBlockY()); - updateCuboidRegionStatement.setInt(6, max.getBlockX()); - updateCuboidRegionStatement.setString(7, region.getId().toLowerCase()); - - updateCuboidRegionStatement.execute(); - } finally { - closeQuietly(updateCuboidRegionStatement); - } - } - - private void updateRegionPoly2D(ProtectedPolygonalRegion region) throws SQLException { - updateRegion(region, "poly2d"); - - PreparedStatement updatePoly2dRegionStatement = null; - try { - updatePoly2dRegionStatement = this.conn.prepareStatement( - "UPDATE `" + config.sqlTablePrefix + "region_poly2d` SET " + - "`max_y` = ?, " + - "`min_y` = ? " + - "WHERE `region_id` = ? " + - "AND `world_id` = " + this.worldId - ); - - updatePoly2dRegionStatement.setInt(1, region.getMaximumPoint().getBlockY()); - updatePoly2dRegionStatement.setInt(2, region.getMinimumPoint().getBlockY()); - updatePoly2dRegionStatement.setString(3, region.getId().toLowerCase()); - - updatePoly2dRegionStatement.execute(); - } finally { - closeQuietly(updatePoly2dRegionStatement); - } - updatePoly2dPoints(region); - } - - private void updateRegionGlobal(GlobalProtectedRegion region) throws SQLException { - updateRegion(region, "global"); - } - -} diff --git a/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java b/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java index d4cff48d..b1633ad4 100644 --- a/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java +++ b/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java @@ -41,6 +41,15 @@ public EnumFlag(String name, Class enumClass) { this.enumClass = enumClass; } + /** + * Get the enum class. + * + * @return the enum class + */ + public Class getEnumClass() { + return enumClass; + } + private T findValue(String input) throws IllegalArgumentException { if (input != null) { input = input.toUpperCase(); diff --git a/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java b/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java index 6ecf5bf0..f3ee58c6 100644 --- a/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java +++ b/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java @@ -23,6 +23,8 @@ import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import javax.annotation.Nullable; + /** * * @author sk89q @@ -56,7 +58,14 @@ public RegionGroupFlag getRegionGroupFlag() { public abstract T parseInput(WorldGuardPlugin plugin, CommandSender sender, String input) throws InvalidFlagFormat; - public abstract T unmarshal(Object o); + public abstract T unmarshal(@Nullable Object o); public abstract Object marshal(T o); + + @Override + public String toString() { + return getClass().getName() + "{" + + "name='" + name + '\'' + + '}'; + } } diff --git a/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java b/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java index 8003bd76..772eb529 100644 --- a/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java +++ b/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java @@ -23,6 +23,8 @@ import com.sk89q.worldguard.protection.ApplicableRegionSet; import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import javax.annotation.Nullable; + /** * * @author sk89q @@ -85,8 +87,7 @@ public static boolean isMember(ProtectedRegion region, RegionGroup group, LocalP return false; } - public static boolean isMember(ApplicableRegionSet set, - RegionGroup group, LocalPlayer player) { + public static boolean isMember(ApplicableRegionSet set, @Nullable RegionGroup group, LocalPlayer player) { if (group == null || group == RegionGroup.ALL) { return true; } else if (group == RegionGroup.OWNERS) { diff --git a/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java b/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java index 6d2d5639..adb44706 100644 --- a/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java +++ b/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java @@ -19,16 +19,15 @@ package com.sk89q.worldguard.protection.flags; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import org.bukkit.command.CommandSender; + import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.bukkit.command.CommandSender; - -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; - /** * Represents a flag that consists of a set. * @@ -48,6 +47,15 @@ public SetFlag(String name, Flag subFlag) { this.subFlag = subFlag; } + /** + * Get the flag that is stored in this flag. + * + * @return the stored flag type + */ + public Flag getType() { + return subFlag; + } + @Override public Set parseInput(WorldGuardPlugin plugin, CommandSender sender, String input) throws InvalidFlagFormat { diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/FlatRegionManager.java b/src/main/java/com/sk89q/worldguard/protection/managers/FlatRegionManager.java deleted file mode 100644 index aa929763..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/managers/FlatRegionManager.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.managers; - -import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.LocalPlayer; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.UnsupportedIntersectionException; -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * A very simple implementation of the region manager that uses a flat list - * and iterates through the list to identify applicable regions. This method - * is not very efficient. - */ -public class FlatRegionManager extends RegionManager { - - /** - * List of protected regions. - */ - private ConcurrentMap regions = new ConcurrentHashMap(); - - /** - * Construct the manager. - * - * @param regionLoader The loader for regions - */ - public FlatRegionManager(ProtectionDatabase regionLoader) { - super(regionLoader); - } - - @Override - public Map getRegions() { - return regions; - } - - @Override - public void setRegions(Map regions) { - this.regions = new ConcurrentHashMap(regions); - } - - @Override - public void addRegion(ProtectedRegion region) { - regions.put(region.getId().toLowerCase(), region); - } - - @Override - public void removeRegion(String id) { - ProtectedRegion region = regions.get(id.toLowerCase()); - regions.remove(id.toLowerCase()); - - if (region != null) { - List removeRegions = new ArrayList(); - for (ProtectedRegion curRegion : regions.values()) { - if (curRegion.getParent() == region) { - removeRegions.add(curRegion.getId().toLowerCase()); - } - } - - for (String remId : removeRegions) { - removeRegion(remId); - } - } - } - - @Override - public boolean hasRegion(String id) { - return regions.containsKey(id.toLowerCase()); - } - - @Override - public ApplicableRegionSet getApplicableRegions(Vector pt) { - TreeSet appRegions = - new TreeSet(); - - for (ProtectedRegion region : regions.values()) { - if (region.contains(pt)) { - appRegions.add(region); - - ProtectedRegion parent = region.getParent(); - - while (parent != null) { - if (!appRegions.contains(parent)) { - appRegions.add(parent); - } - - parent = parent.getParent(); - } - } - } - - return new ApplicableRegionSet(appRegions, regions.get("__global__")); - } - - /** - * Get an object for a region for rules to be applied with. - * - * @return - */ - /*@Override - public ApplicableRegionSet getApplicableRegions(ProtectedRegion checkRegion) { - - List appRegions = new ArrayList(); - appRegions.addAll(regions.values()); - - List intersectRegions; - try { - intersectRegions = checkRegion.getIntersectingRegions(appRegions); - } catch (Exception e) { - intersectRegions = new ArrayList(); - } - - return new ApplicableRegionSet(intersectRegions, regions.get("__global__")); - }*/ - - @Override - public List getApplicableRegionsIDs(Vector pt) { - List applicable = new ArrayList(); - - for (Map.Entry entry : regions.entrySet()) { - if (entry.getValue().contains(pt)) { - applicable.add(entry.getKey()); - } - } - - return applicable; - } - - @Override - public ApplicableRegionSet getApplicableRegions(ProtectedRegion checkRegion) { - - List appRegions = new ArrayList(); - appRegions.addAll(regions.values()); - - List intersectRegions; - - try { - intersectRegions = checkRegion.getIntersectingRegions(appRegions); - } catch (Exception e) { - intersectRegions = new ArrayList(); - } - - return new ApplicableRegionSet(intersectRegions, regions.get("__global__")); - } - - @Override - public boolean overlapsUnownedRegion(ProtectedRegion checkRegion, LocalPlayer player) { - List appRegions = new ArrayList(); - - for (ProtectedRegion other : regions.values()) { - if (other.getOwners().contains(player)) { - continue; - } - - appRegions.add(other); - } - - List intersectRegions; - try { - intersectRegions = checkRegion.getIntersectingRegions(appRegions); - } catch (UnsupportedIntersectionException e) { - intersectRegions = new ArrayList(); - } - - return !intersectRegions.isEmpty(); - } - - @Override - public int size() { - return regions.size(); - } - - @Override - public int getRegionCountOfPlayer(LocalPlayer player) { - int count = 0; - - for (ProtectedRegion region : regions.values()) { - if (region.getOwners().contains(player)) { - count++; - } - } - - return count; - } -} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/PRTreeRegionManager.java b/src/main/java/com/sk89q/worldguard/protection/managers/PRTreeRegionManager.java deleted file mode 100644 index 487e5e29..00000000 --- a/src/main/java/com/sk89q/worldguard/protection/managers/PRTreeRegionManager.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.managers; - -import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.LocalPlayer; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegionMBRConverter; -import org.khelekore.prtree.MBR; -import org.khelekore.prtree.MBRConverter; -import org.khelekore.prtree.PRTree; -import org.khelekore.prtree.SimpleMBR; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -public class PRTreeRegionManager extends RegionManager { - - private static final int BRANCH_FACTOR = 30; - - private MBRConverter converter = new ProtectedRegionMBRConverter(); - private RegionsContainer data = new RegionsContainer(); - - /** - * Construct the manager. - * - * @param regionLoader The region loader to use - */ - public PRTreeRegionManager(ProtectionDatabase regionLoader) { - super(regionLoader); - } - - @Override - public Map getRegions() { - return data.regions; - } - - @Override - public void setRegions(Map regions) { - ConcurrentMap newRegions = new ConcurrentHashMap(regions); - PRTree tree = new PRTree(converter, BRANCH_FACTOR); - tree.load(newRegions.values()); - this.data = new RegionsContainer(newRegions, tree); - } - - @Override - public void addRegion(ProtectedRegion region) { - RegionsContainer data = this.data; - data.regions.put(region.getId().toLowerCase(), region); - PRTree tree = new PRTree(converter, BRANCH_FACTOR); - tree.load(data.regions.values()); - this.data = new RegionsContainer(data.regions, tree); - } - - @Override - public boolean hasRegion(String id) { - return data.regions.containsKey(id.toLowerCase()); - } - - @Override - public void removeRegion(String id) { - RegionsContainer data = this.data; - - ProtectedRegion region = data.regions.get(id.toLowerCase()); - - data.regions.remove(id.toLowerCase()); - - if (region != null) { - List removeRegions = new ArrayList(); - for (ProtectedRegion curRegion : data.regions.values()) { - if (curRegion.getParent() == region) { - removeRegions.add(curRegion.getId().toLowerCase()); - } - } - - for (String remId : removeRegions) { - removeRegion(remId); - } - } - - PRTree tree = new PRTree(converter, BRANCH_FACTOR); - tree.load(data.regions.values()); - this.data = new RegionsContainer(data.regions, tree); - } - - @Override - public ApplicableRegionSet getApplicableRegions(Vector pt) { - RegionsContainer data = this.data; - - // Floor the vector to ensure we get accurate points - pt = pt.floor(); - - List appRegions = new ArrayList(); - MBR pointMBR = new SimpleMBR(pt.getX(), pt.getX(), pt.getY(), pt.getY(), pt.getZ(), pt.getZ()); - - for (ProtectedRegion region : data.tree.find(pointMBR)) { - if (region.contains(pt) && !appRegions.contains(region)) { - appRegions.add(region); - - ProtectedRegion parent = region.getParent(); - - while (parent != null) { - if (!appRegions.contains(parent)) { - appRegions.add(parent); - } - - parent = parent.getParent(); - } - } - } - - Collections.sort(appRegions); - - return new ApplicableRegionSet(appRegions, data.regions.get("__global__")); - } - - @Override - public ApplicableRegionSet getApplicableRegions(ProtectedRegion checkRegion) { - RegionsContainer data = this.data; - - List appRegions = new ArrayList(); - appRegions.addAll(data.regions.values()); - - List intersectRegions; - try { - intersectRegions = checkRegion.getIntersectingRegions(appRegions); - } catch (Exception e) { - intersectRegions = new ArrayList(); - } - - return new ApplicableRegionSet(intersectRegions, data.regions.get("__global__")); - } - - @Override - public List getApplicableRegionsIDs(Vector pt) { - RegionsContainer data = this.data; - - // Floor the vector to ensure we get accurate points - pt = pt.floor(); - - List applicable = new ArrayList(); - MBR pointMBR = new SimpleMBR(pt.getX(), pt.getX(), pt.getY(), pt.getY(), pt.getZ(), pt.getZ()); - - for (ProtectedRegion region : data.tree.find(pointMBR)) { - if (region.contains(pt) && !applicable.contains(region.getId())) { - applicable.add(region.getId()); - - ProtectedRegion parent = region.getParent(); - - while (parent != null) { - if (!applicable.contains(parent.getId())) { - applicable.add(parent.getId()); - } - - parent = parent.getParent(); - } - } - } - - return applicable; - } - - @Override - public boolean overlapsUnownedRegion(ProtectedRegion checkRegion, LocalPlayer player) { - RegionsContainer data = this.data; - - List appRegions = new ArrayList(); - - for (ProtectedRegion other : data.regions.values()) { - if (other.getOwners().contains(player)) { - continue; - } - - appRegions.add(other); - } - - List intersectRegions; - try { - intersectRegions = checkRegion.getIntersectingRegions(appRegions); - } catch (Exception e) { - intersectRegions = new ArrayList(); - } - - return !intersectRegions.isEmpty(); - } - - @Override - public int size() { - return data.regions.size(); - } - - @Override - public int getRegionCountOfPlayer(LocalPlayer player) { - int count = 0; - - for (Map.Entry entry : data.regions.entrySet()) { - if (entry.getValue().getOwners().contains(player)) { - count++; - } - } - - return count; - } - - private class RegionsContainer { - private final ConcurrentMap regions; - private final PRTree tree; - - private RegionsContainer() { - regions = new ConcurrentHashMap(); - tree = new PRTree(converter, BRANCH_FACTOR); - } - - private RegionsContainer(ConcurrentMap regions, PRTree tree) { - this.regions = regions; - this.tree = tree; - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java b/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java new file mode 100644 index 00000000..0ab9d6fd --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java @@ -0,0 +1,70 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import com.google.common.base.Predicate; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A consumer predicate that adds regions to a collection. + * + *

This class can also add the parents of regions that are visited + * to the collection, although it may result in duplicates in the collection + * if the collection is not a set.

+ */ +class RegionCollectionConsumer implements Predicate { + + private final Collection collection; + private final boolean addParents; + + /** + * Create a new instance. + * + * @param collection the collection to add regions to + * @param addParents true to also add the parents to the collection + */ + RegionCollectionConsumer(Collection collection, boolean addParents) { + checkNotNull(collection); + + this.collection = collection; + this.addParents = addParents; + } + + @Override + public boolean apply(ProtectedRegion region) { + collection.add(region); + + if (addParents) { + ProtectedRegion parent = region.getParent(); + + while (parent != null) { + collection.add(parent); + parent = parent.getParent(); + } + } + + return true; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java b/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java new file mode 100644 index 00000000..29411f27 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java @@ -0,0 +1,83 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Collections; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Describes the difference in region data. + */ +public final class RegionDifference { + + private final Set changed; + private final Set removed; + + /** + * Create a new instance. + * + * @param changed a set of regions that were changed or added + * @param removed a set of regions that were removed + */ + public RegionDifference(Set changed, Set removed) { + checkNotNull(changed); + checkNotNull(removed); + + this.changed = changed; + this.removed = removed; + } + + /** + * Get the regions that were changed or added. + * + * @return regions + */ + public Set getChanged() { + return Collections.unmodifiableSet(changed); + } + + /** + * Get the regions that were removed. + * + * @return regions + */ + public Set getRemoved() { + return Collections.unmodifiableSet(removed); + } + + /** + * Test whether there are changes or removals. + * + * @return true if there are changes + */ + public boolean containsChanges() { + return !changed.isEmpty() || !removed.isEmpty(); + } + + public void addAll(RegionDifference diff) { + changed.addAll(diff.getChanged()); + removed.addAll(diff.getRemoved()); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java b/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java index 51ad8698..3ccfe844 100644 --- a/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java @@ -19,123 +19,204 @@ package com.sk89q.worldguard.protection.managers; -import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; import com.sk89q.worldedit.Vector; import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.databases.ProtectionDatabase; -import com.sk89q.worldguard.protection.databases.ProtectionDatabaseException; +import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex; +import com.sk89q.worldguard.protection.managers.index.RegionIndex; +import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; +import com.sk89q.worldguard.protection.managers.storage.RegionStore; import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.Normal; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; -/** - * An abstract class for getting, setting, and looking up regions. The most - * simple implementation uses a flat list and iterates through the entire list - * to look for applicable regions, but a more complicated (and more efficient) - * implementation may use space partitioning techniques. - * - * @author sk89q - */ -public abstract class RegionManager { +import static com.google.common.base.Preconditions.checkNotNull; - protected ProtectionDatabase loader; +public final class RegionManager { + + private final RegionStore store; + private final Supplier indexFactory; + private ConcurrentRegionIndex index; /** - * Construct the object. + * Create a new index. * - * @param loader The loader for this region + * @param store the region store + * @param indexFactory the factory for creating new instances of the index */ - public RegionManager(ProtectionDatabase loader) { - this.loader = loader; + public RegionManager(RegionStore store, Supplier indexFactory) { + checkNotNull(store); + checkNotNull(indexFactory); + + this.store = store; + this.indexFactory = indexFactory; + this.index = indexFactory.get(); } /** - * Load the list of regions. If the regions do not load properly, then - * the existing list should be used (as stored previously). + * Load regions from storage and replace the index on this manager with + * the regions loaded from the store. * - * @throws ProtectionDatabaseException when an error occurs + *

This method will block until the save completes, but it will + * not block access to the region data from other threads, nor will it + * prevent the creation or modification of regions in the index while + * a new collection of regions is loaded from storage.

+ * + * @throws IOException thrown when loading fails */ - public void load() throws ProtectionDatabaseException { - loader.load(this); + public void load() throws IOException { + setRegions(store.loadAll()); } /** - * Load the list of regions. If the regions do not load properly, then - * the existing list should be used (as stored previously). + * Save a snapshot of all the regions as it is right now to storage. * - * @param async true to attempt to save the data asynchronously + * @throws IOException */ - public ListenableFuture load(boolean async) { - return loader.load(this, async); + public void save() throws IOException { + store.saveAll(new HashSet(getValuesCopy())); } /** - * Save the list of regions. + * Save changes to the region index to disk, preferring to only save + * the changes (rather than the whole index), but choosing to save the + * whole index if the underlying store does not support partial saves. * - * @throws ProtectionDatabaseException when an error occurs while saving + *

This method does nothing if there are no changes.

+ * + * @throws IOException thrown on save error */ - public void save() throws ProtectionDatabaseException { - loader.save(this); + public void saveChanges() throws IOException { + RegionDifference diff = index.getAndClearDifference(); + + if (diff.containsChanges()) { + try { + store.saveChanges(diff); + } catch (DifferenceSaveException e) { + save(); // Partial save is not supported + } + } } /** - * Save the list of regions. + * Get an unmodifiable map of regions containing the state of the + * index at the time of call. * - * @param async true to attempt to save the data asynchronously + *

This call is relatively heavy (and may block other threads), + * so refrain from calling it frequently.

+ * + * @return a map of regions */ - public ListenableFuture save(boolean async) { - return loader.save(this, async); + public Map getRegions() { + Map map = new HashMap(); + for (ProtectedRegion region : index.values()) { + map.put(Normal.normalize(region.getId()), region); + } + return Collections.unmodifiableMap(map); } /** - * Get a map of protected regions. Use one of the region manager methods - * if possible if working with regions. + * Replace the index with the regions in the given map. * - * @return map of regions, with keys being region IDs (lowercase) + *

The parents of the regions will also be added to the index, even + * if they are not in the provided map.

+ * + * @param regions a map of regions */ - public abstract Map getRegions(); + public void setRegions(Map regions) { + checkNotNull(regions); + + setRegions(regions.values()); + } /** - * Set a list of protected regions. Keys should be lowercase in the given - * map fo regions. + * Replace the index with the regions in the given collection. * - * @param regions map of regions + *

The parents of the regions will also be added to the index, even + * if they are not in the provided map.

+ * + * @param regions a collection of regions */ - public abstract void setRegions(Map regions); + public void setRegions(Collection regions) { + checkNotNull(regions); + + ConcurrentRegionIndex newIndex = indexFactory.get(); + newIndex.addAll(regions); + newIndex.getAndClearDifference(); // Clear changes + this.index = newIndex; + } /** - * Adds a region. If a region by the given name already exists, then - * the existing region will be replaced. + * Aad a region to the manager. * - * @param region region to add + *

The parents of the region will also be added to the index.

+ * + * @param region the region */ - public abstract void addRegion(ProtectedRegion region); + public void addRegion(ProtectedRegion region) { + checkNotNull(region); + index.add(region); + } /** - * Return whether a region exists by an ID. + * Return whether the index contains a region by the given name, + * with equality determined by {@link Normal}. * - * @param id id of the region, can be mixed-case - * @return whether the region exists + * @param id the name of the region + * @return true if this index contains the region */ - public abstract boolean hasRegion(String id); + public boolean hasRegion(String id) { + return index.contains(id); + } /** - * Get a region by its ID. Includes symbolic names like #<index> + * Get the region named by the given name (equality determined using + * {@link Normal}). * - * @param id id of the region, can be mixed-case - * @return region or null if it doesn't exist + * @param id the name of the region + * @return a region or {@code null} + * @deprecated use {@link #matchRegion(String)} */ + @Nullable public ProtectedRegion getRegion(String id) { - if (id.startsWith("#")) { + checkNotNull(id); + return index.get(id); + } + + /** + * Matches a region using either the pattern {@code #{region_index}} or + * simply by the exact name of the region. + * + * @param pattern the pattern + * @return a region + */ + @Nullable + public ProtectedRegion matchRegion(String pattern) { + checkNotNull(pattern); + + if (pattern.startsWith("#")) { int index; try { - index = Integer.parseInt(id.substring(1)) - 1; + index = Integer.parseInt(pattern.substring(1)) - 1; } catch (NumberFormatException e) { return null; } - for (ProtectedRegion region : getRegions().values()) { + for (ProtectedRegion region : this.index.values()) { if (index == 0) { return region; } @@ -144,87 +225,167 @@ public ProtectedRegion getRegion(String id) { return null; } - return getRegionExact(id); + return getRegion(pattern); } /** - * Get a region by its ID. + * Remove a region from the index with the given name, opting to remove + * the children of the removed region. * - * @param id id of the region, can be mixed-case - * @return region or null if it doesn't exist + * @param id the name of the region + * @return a list of removed regions where the first entry is the region specified by {@code id} */ - public ProtectedRegion getRegionExact(String id) { - return getRegions().get(id.toLowerCase()); + @Nullable + public Set removeRegion(String id) { + return removeRegion(id, RemovalStrategy.REMOVE_CHILDREN); } /** - * Removes a region, including inheriting children. + * Remove a region from the index with the given name. * - * @param id id of the region, can be mixed-case + * @param id the name of the region + * @param strategy what to do with children + * @return a list of removed regions where the first entry is the region specified by {@code id} */ - public abstract void removeRegion(String id); + @Nullable + public Set removeRegion(String id, RemovalStrategy strategy) { + return index.remove(id, strategy); + } /** - * Get an object for a point for rules to be applied with. Use this in order - * to query for flag data or membership data for a given point. + * Query for effective flags and owners for the given positive. * - * @param loc Bukkit location - * @return applicable region set + * @param position the position + * @return the query object + */ + public ApplicableRegionSet getApplicableRegions(Vector position) { + checkNotNull(position); + + TreeSet regions = new TreeSet(); + index.applyContaining(position, new RegionCollectionConsumer(regions, true)); + return new ApplicableRegionSet(regions, index.get("__global__")); + } + + /** + * Query for effective flags and owners for the area represented + * by the given region. + * + * @param region the region + * @return the query object + */ + public ApplicableRegionSet getApplicableRegions(ProtectedRegion region) { + checkNotNull(region); + + TreeSet regions = new TreeSet(); + index.applyIntersecting(region, new RegionCollectionConsumer(regions, true)); + return new ApplicableRegionSet(regions, index.get("__global__")); + } + + /** + * Get a list of region names for regions that contain the given position. + * + * @param position the position + * @return a list of names + */ + public List getApplicableRegionsIDs(Vector position) { + checkNotNull(position); + + final List names = new ArrayList(); + + index.applyContaining(position, new Predicate() { + @Override + public boolean apply(ProtectedRegion region) { + return names.add(region.getId()); + } + }); + + return names; + } + + /** + * Return whether there are any regions intersecting the given region that + * are not owned by the given player. + * + * @param region the region + * @param player the player + * @return true if there are such intersecting regions + */ + public boolean overlapsUnownedRegion(ProtectedRegion region, final LocalPlayer player) { + checkNotNull(region); + checkNotNull(player); + + RegionIndex index = this.index; + + final AtomicBoolean overlapsUnowned = new AtomicBoolean(); + + index.applyIntersecting(region, new Predicate() { + @Override + public boolean apply(ProtectedRegion test) { + if (!test.getOwners().contains(player)) { + overlapsUnowned.set(true); + return false; + } else { + return true; + } + } + }); + + return overlapsUnowned.get(); + } + + /** + * Get the number of regions. + * + * @return the number of regions + */ + public int size() { + return index.size(); + } + + /** + * Get the number of regions that are owned by the given player. + * + * @param player the player + * @return name number of regions that a player owns + */ + public int getRegionCountOfPlayer(final LocalPlayer player) { + checkNotNull(player); + + final AtomicInteger count = new AtomicInteger(); + + index.apply(new Predicate() { + @Override + public boolean apply(ProtectedRegion test) { + if (test.getOwners().contains(player)) { + count.incrementAndGet(); + } + return true; + } + }); + + return count.get(); + } + + /** + * Get an {@link ArrayList} copy of regions in the index. + * + * @return a list + */ + private List getValuesCopy() { + return new ArrayList(index.values()); + } + + // =============== HELPER METHODS =============== + + /** + * Helper method for {@link #getApplicableRegions(Vector)} using Bukkit + * locations. + * + * @param loc the location + * @return an {@code ApplicableRegionSet} */ public ApplicableRegionSet getApplicableRegions(org.bukkit.Location loc) { return getApplicableRegions(com.sk89q.worldedit.bukkit.BukkitUtil.toVector(loc)); } - /** - * Get an object for a point for rules to be applied with. Use this in order - * to query for flag data or membership data for a given point. - * - * @param pt point - * @return applicable region set - */ - public abstract ApplicableRegionSet getApplicableRegions(Vector pt); - - /** - * Get an object for a point for rules to be applied with. This gets - * a set for the given reason. - * - * @param region region - * @return regino set - */ - public abstract ApplicableRegionSet getApplicableRegions( - ProtectedRegion region); - - /** - * Get a list of region IDs that contain a point. - * - * @param pt point - * @return list of region Ids - */ - public abstract List getApplicableRegionsIDs(Vector pt); - - /** - * Returns true if the provided region overlaps with any other region that - * is not owned by the player. - * - * @param region region to check - * @param player player to check against - * @return whether there is an overlap - */ - public abstract boolean overlapsUnownedRegion(ProtectedRegion region, - LocalPlayer player); - - /** - * Get the number of regions. - * - * @return number of regions - */ - public abstract int size(); - - /** - * Get the number of regions for a player. - * - * @param player player - * @return name number of regions that a player owns - */ - public abstract int getRegionCountOfPlayer(LocalPlayer player); } diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java b/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java new file mode 100644 index 00000000..5b136651 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java @@ -0,0 +1,40 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import com.sk89q.worldguard.protection.managers.index.RegionIndex; + +/** + * Determines the strategy regarding child regions when regions are + * removed from a {@link RegionIndex}. + */ +public enum RemovalStrategy { + + /** + * Unset the parent in children regions. + */ + UNSET_PARENT_IN_CHILDREN, + + /** + * Remove any children under the removed regions. + */ + REMOVE_CHILDREN + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/DatabaseMigrator.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java similarity index 76% rename from src/main/java/com/sk89q/worldguard/protection/databases/migrator/DatabaseMigrator.java rename to src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java index 56623766..c66e9b67 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/DatabaseMigrator.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java @@ -17,15 +17,11 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.migrator; +package com.sk89q.worldguard.protection.managers.index; /** - * Represents a method of converting from one database to another. - * - * @author Nicholas Steicke + * An abstract implementation of a region index to make implementations easier. */ -public interface DatabaseMigrator { +abstract class AbstractRegionIndex implements RegionIndex { - public void migrate() throws MigrationException; } - diff --git a/src/test/java/com/sk89q/worldguard/protection/FlatRegionManagerTest.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java similarity index 65% rename from src/test/java/com/sk89q/worldguard/protection/FlatRegionManagerTest.java rename to src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java index 91336e57..0f8ff47a 100644 --- a/src/test/java/com/sk89q/worldguard/protection/FlatRegionManagerTest.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java @@ -17,13 +17,16 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection; +package com.sk89q.worldguard.protection.managers.index; -import com.sk89q.worldguard.protection.managers.FlatRegionManager; -import com.sk89q.worldguard.protection.managers.RegionManager; +import java.util.concurrent.ConcurrentMap; -public class FlatRegionManagerTest extends RegionOverlapTest { - protected RegionManager createRegionManager() throws Exception { - return new FlatRegionManager(null); - } +/** + * An implementation of a region index that supports concurrent access. + * + *

The mechanics of concurrent access should be similar to that of + * {@link ConcurrentMap}. Spatial queries can lag behind changes on the data + * for brief periods of time.

+ */ +public interface ConcurrentRegionIndex extends RegionIndex { } diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java new file mode 100644 index 00000000..aaa86a93 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java @@ -0,0 +1,263 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldguard.util.Normal.normalize; + +/** + * An index that stores regions in a hash map, which allows for fast lookup + * by ID but O(n) performance for spatial queries. + * + *

This implementation supports concurrency to the extent that + * a {@link ConcurrentMap} does.

+ */ +public class HashMapIndex extends AbstractRegionIndex implements ConcurrentRegionIndex { + + private final ConcurrentMap regions = new ConcurrentHashMap(); + private Set removed = new HashSet(); + private final Object lock = new Object(); + + /** + * Called to rebuild the index after changes. + */ + protected void rebuildIndex() { + // Can be implemented by subclasses + } + + /** + * Perform the add operation. + * + * @param region the region + */ + private void performAdd(ProtectedRegion region) { + checkNotNull(region); + + region.setDirty(true); + + synchronized (lock) { + String normalId = normalize(region.getId()); + + ProtectedRegion existing = regions.get(normalId); + + // Casing / form of ID has changed + if (existing != null && !existing.getId().equals(region.getId())) { + removed.add(existing); + } + + regions.put(normalId, region); + + removed.remove(region); + + ProtectedRegion parent = region.getParent(); + if (parent != null) { + performAdd(parent); + } + } + } + + @Override + public void addAll(Collection regions) { + checkNotNull(regions); + + synchronized (lock) { + for (ProtectedRegion region : regions) { + performAdd(region); + } + + rebuildIndex(); + } + } + + @Override + public void add(ProtectedRegion region) { + synchronized (lock) { + performAdd(region); + + rebuildIndex(); + } + } + + @Override + public Set remove(String id, RemovalStrategy strategy) { + checkNotNull(id); + checkNotNull(strategy); + + Set removedSet = new HashSet(); + + synchronized (lock) { + ProtectedRegion removed = regions.remove(normalize(id)); + + if (removed != null) { + removedSet.add(removed); + + Iterator it = regions.values().iterator(); + + // Handle children + while (it.hasNext()) { + ProtectedRegion current = it.next(); + ProtectedRegion parent = current.getParent(); + + if (parent != null && parent == removed) { + switch (strategy) { + case REMOVE_CHILDREN: + removedSet.add(current); + it.remove(); + break; + case UNSET_PARENT_IN_CHILDREN: + current.clearParent(); + } + } + } + } + + this.removed.addAll(removedSet); + + rebuildIndex(); + } + + return removedSet; + } + + @Override + public boolean contains(String id) { + return regions.containsKey(normalize(id)); + } + + @Nullable + @Override + public ProtectedRegion get(String id) { + return regions.get(normalize(id)); + } + + @Override + public void apply(Predicate consumer) { + for (ProtectedRegion region : regions.values()) { + if (!consumer.apply(region)) { + break; + } + } + } + + @Override + public void applyContaining(final Vector position, final Predicate consumer) { + apply(new Predicate() { + @Override + public boolean apply(ProtectedRegion region) { + return !region.contains(position) || consumer.apply(region); + } + }); + } + + @Override + public void applyIntersecting(ProtectedRegion region, Predicate consumer) { + for (ProtectedRegion found : region.getIntersectingRegions(regions.values())) { + if (!consumer.apply(found)) { + break; + } + } + } + + @Override + public int size() { + return regions.size(); + } + + @Override + public RegionDifference getAndClearDifference() { + synchronized (lock) { + Set changed = new HashSet(); + Set removed = this.removed; + + for (ProtectedRegion region : regions.values()) { + if (region.isDirty()) { + changed.add(region); + region.setDirty(false); + } + } + + this.removed = new HashSet(); + + return new RegionDifference(changed, removed); + } + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(regions.values()); + } + + @Override + public boolean isDirty() { + synchronized (lock) { + if (!removed.isEmpty()) { + return true; + } + + for (ProtectedRegion region : regions.values()) { + if (region.isDirty()) { + return true; + } + } + } + + return false; + } + + @Override + public void setDirty(boolean dirty) { + synchronized (lock) { + if (!dirty) { + removed.clear(); + } + + for (ProtectedRegion region : regions.values()) { + region.setDirty(dirty); + } + } + } + + /** + * A factory for new instances using this index. + */ + public static final class Factory implements Supplier { + @Override + public HashMapIndex get() { + return new HashMapIndex(); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java new file mode 100644 index 00000000..ea54da7e --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java @@ -0,0 +1,102 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import com.google.common.base.Predicate; +import com.google.common.base.Supplier; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegionMBRConverter; +import org.khelekore.prtree.MBR; +import org.khelekore.prtree.MBRConverter; +import org.khelekore.prtree.PRTree; +import org.khelekore.prtree.SimpleMBR; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * An implementation of an index that uses {@link HashMapIndex} for queries + * by region name and a priority R-tree for spatial queries. + * + *

At the moment, the R-tree is only utilized for the + * {@link #applyContaining(Vector, Predicate)} method, and the underlying + * hash map-based index is used for the other spatial queries. In addition, + * every modification to the index requires the entire R-tree to be rebuilt, + * although this operation is reasonably quick.

+ * + *

This implementation is as thread-safe as the underlying + * {@link HashMapIndex}, although spatial queries may lag behind changes + * for very brief periods of time as the tree is rebuilt.

+ */ +public class PriorityRTreeIndex extends HashMapIndex { + + private static final int BRANCH_FACTOR = 30; + private static final MBRConverter CONVERTER = new ProtectedRegionMBRConverter(); + + private PRTree tree; + + public PriorityRTreeIndex() { + tree = new PRTree(CONVERTER, BRANCH_FACTOR); + tree.load(Collections.emptyList()); + } + + @Override + protected void rebuildIndex() { + PRTree newTree = new PRTree(CONVERTER, BRANCH_FACTOR); + newTree.load(values()); + this.tree = newTree; + } + + @Override + public void applyContaining(Vector position, Predicate consumer) { + // Floor the vector to ensure we get accurate points + position = position.floor(); + + Set seen = new HashSet(); + MBR pointMBR = new SimpleMBR(position.getX(), position.getX(), position.getY(), position.getY(), position.getZ(), position.getZ()); + + for (ProtectedRegion region : tree.find(pointMBR)) { + if (region.contains(position) && !seen.contains(region)) { + seen.add(region); + if (!consumer.apply(region)) { + break; + } + } + } + } + + @Override + public void applyIntersecting(ProtectedRegion region, Predicate consumer) { + super.applyIntersecting(region, consumer); + } + + /** + * A factory for new instances using this index. + */ + public static final class Factory implements Supplier { + @Override + public PriorityRTreeIndex get() { + return new PriorityRTreeIndex(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java new file mode 100644 index 00000000..76e43812 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java @@ -0,0 +1,138 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import com.google.common.base.Predicate; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.ChangeTracked; +import com.sk89q.worldguard.util.Normal; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Set; + +/** + * An index of regions to allow for fast lookups of regions by their ID and + * through spatial queries. + * + *

Indexes may be thread-unsafe.

+ */ +public interface RegionIndex extends ChangeTracked { + + /** + * Add a region to this index, replacing any existing one with the same + * name (equality determined using {@link Normal}). + * + *

The parents of the region will also be added to the index.

+ * + * @param region the region + */ + void add(ProtectedRegion region); + + /** + * Add a list of regions to this index, replacing any existing one + * with the same name (equality determined using {@link Normal}). + * + *

The parents of the region will also be added to the index.

+ * + * @param regions a collections of regions + */ + void addAll(Collection regions); + + /** + * Remove a region from the index with the given name. + * + * @param id the name of the region + * @param strategy what to do with children + * @return a list of removed regions where the first entry is the region specified by {@code id} + */ + Set remove(String id, RemovalStrategy strategy); + + /** + * Test whether the index contains a region named by the given name + * (equality determined using {@link Normal}). + * + * @param id the name of the region + * @return true if the index contains the region + */ + boolean contains(String id); + + /** + * Get the region named by the given name (equality determined using + * {@link Normal}). + * + * @param id the name of the region + * @return a region or {@code null} + */ + @Nullable + ProtectedRegion get(String id); + + /** + * Apply the given predicate to all the regions in the index + * until there are no more regions or the predicate returns false. + * + * @param consumer a predicate that returns true to continue iterating + */ + void apply(Predicate consumer); + + /** + * Apply the given predicate to all regions that contain the given + * position until there are no more regions or the predicate returns false. + * + * @param position the position + * @param consumer a predicate that returns true to continue iterating + */ + void applyContaining(Vector position, Predicate consumer); + + /** + * Apply the given predicate to all regions that intersect the given + * region until there are no more regions or the predicate returns false. + * + * @param region the intersecting region + * @param consumer a predicate that returns true to continue iterating + */ + void applyIntersecting(ProtectedRegion region, Predicate consumer); + + /** + * Return the number of regions in the index. + * + * @return the number of regions + */ + int size(); + + /** + * Get the list of changed or removed regions since last call and + * clear those lists. + * + * @return the difference + */ + RegionDifference getAndClearDifference(); + + /** + * Get an unmodifiable collection of regions stored in this index. + * + * @return a collection of regions + */ + Collection values(); + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java new file mode 100644 index 00000000..9620c172 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java @@ -0,0 +1,41 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +/** + * Thrown when a partial save is not supported. + */ +public class DifferenceSaveException extends RegionStoreException { + + public DifferenceSaveException() { + } + + public DifferenceSaveException(String message) { + super(message); + } + + public DifferenceSaveException(String message, Throwable cause) { + super(message, cause); + } + + public DifferenceSaveException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionStore.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionStore.java new file mode 100644 index 00000000..149634ea --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionStore.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A memory store that saves the memory to a temporary variable. + */ +public class MemoryRegionStore implements RegionStore { + + private Set regions = Collections.emptySet(); + + @Override + public Set loadAll() throws IOException { + return regions; + } + + @Override + public void saveAll(Set regions) throws IOException { + this.regions = Collections.unmodifiableSet(new HashSet(regions)); + } + + @Override + public void saveChanges(RegionDifference difference) throws DifferenceSaveException, IOException { + throw new DifferenceSaveException(); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStore.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStore.java new file mode 100644 index 00000000..2d6da8be --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStore.java @@ -0,0 +1,69 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.io.IOException; +import java.util.Set; + +/** + * An object that persists region data to a persistent store. + */ +public interface RegionStore { + + /** + * Load all regions from storage and place them into the passed map. + * + *

The map will only be modified from one thread. The keys of + * each map entry will be in respect to the ID of the region but + * transformed to be lowercase. Until this method returns, the map may not + * be modified by any other thread simultaneously. If an exception is + * thrown, then the state in which the map is left is undefined.

+ * + *

The provided map should have reasonably efficient + * {@code get()} and {@code put()} calls in order to maximize performance. + *

+ * + * @return a setf loaded regions + * @throws IOException thrown on read error + */ + Set loadAll() throws IOException; + + /** + * Replace all the data in the store with the given collection of regions. + * + * @param regions a set of regions + * @throws IOException thrown on write error + */ + void saveAll(Set regions) throws IOException; + + /** + * Perform a partial save that only commits changes, rather than the + * entire region index. + * + * @param difference the difference + * @throws DifferenceSaveException thrown if partial saves are not supported + * @throws IOException thrown on write error + */ + void saveChanges(RegionDifference difference) throws DifferenceSaveException, IOException; + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabaseException.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreException.java similarity index 67% rename from src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabaseException.java rename to src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreException.java index b4f2f883..c818e096 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/ProtectionDatabaseException.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreException.java @@ -17,26 +17,26 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases; +package com.sk89q.worldguard.protection.managers.storage; -import java.lang.Exception; +/** + * Exceptions related to region stores inherit from this exception. + */ +public class RegionStoreException extends Exception { -public class ProtectionDatabaseException extends Exception { - private static final long serialVersionUID = 1L; - - public ProtectionDatabaseException() { - super(); + public RegionStoreException() { } - public ProtectionDatabaseException(String message) { + public RegionStoreException(String message) { super(message); } - public ProtectionDatabaseException(String message, Throwable cause) { + public RegionStoreException(String message, Throwable cause) { super(message, cause); } - public ProtectionDatabaseException(Throwable cause) { + public RegionStoreException(Throwable cause) { super(cause); } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreUtils.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreUtils.java new file mode 100644 index 00000000..c533ae9e --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionStoreUtils.java @@ -0,0 +1,125 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import com.sk89q.worldguard.protection.flags.DefaultFlag; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utility methods for region stores. + */ +public final class RegionStoreUtils { + + private static final Logger log = Logger.getLogger(RegionStoreUtils.class.getCanonicalName()); + + private RegionStoreUtils() { + } + + /** + * Try setting the given map of flags onto the region. + * + * @param region the region + * @param flagData the map of flag data + */ + public static void trySetFlagMap(ProtectedRegion region, Map flagData) { + checkNotNull(region); + checkNotNull(flagData); + + for (Flag flag : DefaultFlag.getFlags()) { + if (flag == null) { + // Some plugins that add custom flags to WorldGuard are doing + // something very wrong -- see WORLDGUARD-3094 + continue; + } + + Object o = flagData.get(flag.getName()); + if (o != null) { + RegionStoreUtils.trySetFlag(region, flag, o); + } + + // Set group + if (flag.getRegionGroupFlag() != null) { + Object o2 = flagData.get(flag.getRegionGroupFlag().getName()); + if (o2 != null) { + RegionStoreUtils.trySetFlag(region, flag.getRegionGroupFlag(), o2); + } + } + } + } + + /** + * Try to set a flag on the region. + * + * @param region the region + * @param flag the flag + * @param value the value of the flag, which may be {@code null} + * @param the flag's type + * @return true if the set succeeded + */ + public static boolean trySetFlag(ProtectedRegion region, Flag flag, @Nullable Object value) { + checkNotNull(region); + checkNotNull(flag); + + T val = flag.unmarshal(value); + + if (val != null) { + region.setFlag(flag, val); + return true; + } else { + log.warning("Failed to parse flag '" + flag.getName() + "' with value '" + value + "'"); + return false; + } + } + + /** + * Re-link parent regions on each provided region using the two + * provided maps. + * + * @param regions the map of regions from which parent regions are found + * @param parentSets a mapping of region to parent name + */ + public static void relinkParents(Map regions, Map parentSets) { + checkNotNull(regions); + checkNotNull(parentSets); + + for (Map.Entry entry : parentSets.entrySet()) { + ProtectedRegion target = entry.getKey(); + ProtectedRegion parent = regions.get(entry.getValue()); + if (parent != null) { + try { + target.setParent(parent); + } catch (CircularInheritanceException e) { + log.warning("Circular inheritance detected! Can't set the parent of '" + target + "' to parent '" + parent.getId() + "'"); + } + } else { + log.warning("Unknown region parent: " + entry.getValue()); + } + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DirectoryYamlDriver.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DirectoryYamlDriver.java new file mode 100644 index 00000000..28e06bd6 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DirectoryYamlDriver.java @@ -0,0 +1,103 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.driver; + +import com.sk89q.worldguard.protection.managers.storage.RegionStore; +import com.sk89q.worldguard.protection.managers.storage.file.YamlFileStore; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores region data in a {root_dir}/{id}/{filename} pattern on disk + * using {@link YamlFileStore}. + */ +public class DirectoryYamlDriver implements RegionStoreDriver { + + private final File rootDir; + private final String filename; + + /** + * Create a new instance. + * + * @param rootDir the directory where the world folders reside + * @param filename the filename (i.e. "regions.yml") + */ + public DirectoryYamlDriver(File rootDir, String filename) { + checkNotNull(rootDir); + checkNotNull(filename); + this.rootDir = rootDir; + this.filename = filename; + } + + /** + * Get the path for the given ID. + * + * @param id the ID + * @return the file path + */ + private File getPath(String id) { + checkNotNull(id); + + File f = new File(rootDir, id + File.separator + filename); + try { + f.getCanonicalPath(); + return f; + } catch (IOException e) { + throw new IllegalArgumentException("Invalid file path for the world's regions file"); + } + } + + @Override + public RegionStore get(String id) throws IOException { + checkNotNull(id); + + File file = getPath(id); + File parentDir = file.getParentFile(); + if (!parentDir.exists()) { + if (!parentDir.mkdirs()) { + throw new IOException("Failed to create the parent directory (" + parentDir.getAbsolutePath() + ") to store the regions file in"); + } + } + + return new YamlFileStore(file); + } + + @Override + public List fetchAllExisting() { + List names = new ArrayList(); + + File files[] = rootDir.listFiles(); + if (files != null) { + for (File dir : files) { + if (dir.isDirectory() && new File(dir, "regions.yml").isFile()) { + names.add(dir.getName()); + } + } + } + + return names; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DriverType.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DriverType.java new file mode 100644 index 00000000..9e45691b --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/DriverType.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.driver; + +import com.sk89q.worldguard.bukkit.ConfigurationManager; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +/** + * An enumeration of supported drivers. + */ +public enum DriverType { + + YAML { + @Override + public RegionStoreDriver create(ConfigurationManager config) { + return new DirectoryYamlDriver(config.getWorldsDataFolder(), "regions.yml"); + } + }, + SQL { + @Override + public RegionStoreDriver create(ConfigurationManager config) { + DataSourceConfig dataSourceConfig = new DataSourceConfig(config.sqlDsn, config.sqlUsername, config.sqlPassword, config.sqlTablePrefix); + return new SQLDriver(dataSourceConfig); + } + }; + + /** + * Create a driver instance from a configuration. + * + * @param config a configuration + * @return a driver + */ + public abstract RegionStoreDriver create(ConfigurationManager config); + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/RegionStoreDriver.java old mode 100755 new mode 100644 similarity index 50% rename from src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java rename to src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/RegionStoreDriver.java index aff2c284..55ba195a --- a/src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/RegionStoreDriver.java @@ -17,30 +17,33 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases; +package com.sk89q.worldguard.protection.managers.storage.driver; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.sk89q.worldguard.bukkit.ConfigurationManager; -import com.sk89q.worldguard.protection.databases.mysql.MySQLDatabaseImpl; +import com.sk89q.worldguard.protection.managers.storage.RegionStore; -import java.util.logging.Logger; +import java.io.IOException; +import java.util.List; /** - * A store that persists regions in a MySQL database. + * A driver is able to create new {@code RegionStore}s for named worlds. */ -public class MySQLDatabase extends MySQLDatabaseImpl { +public interface RegionStoreDriver { /** - * Create a new instance. + * Get a region store for the named world. * - * @param executor the executor to perform loads and saves in - * @param config the configuration - * @param worldName the world name - * @param logger a logger - * @throws ProtectionDatabaseException thrown on error + * @param name the name + * @return the world + * @throws IOException thrown if the region store can't be created due to an I/O error */ - public MySQLDatabase(ConfigurationManager config, String worldName, Logger logger) throws ProtectionDatabaseException { - super(config, worldName, logger); - } + RegionStore get(String name) throws IOException; + + /** + * Fetch the names of all worlds that are stored with this driver. + * + * @return a list of names + * @throws IOException thrown if the fetch operation fails + */ + List fetchAllExisting() throws IOException; } diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/SQLDriver.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/SQLDriver.java new file mode 100644 index 00000000..04c9d25e --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/driver/SQLDriver.java @@ -0,0 +1,101 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.driver; + +import com.jolbox.bonecp.BoneCP; +import com.sk89q.worldguard.protection.managers.storage.RegionStore; +import com.sk89q.worldguard.protection.managers.storage.sql.SQLRegionStore; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores regions using {@link SQLRegionStore}. + */ +public class SQLDriver implements RegionStoreDriver { + + private final DataSourceConfig config; + private final Object lock = new Object(); + private BoneCP connectionPool; + + /** + * Create a new instance. + * + * @param config a configuration + */ + public SQLDriver(DataSourceConfig config) { + checkNotNull(config); + this.config = config; + } + + /** + * Get an instance of the connection pool. + * + * @return the connection pool + * @throws SQLException occurs when the connection pool can't be created + */ + protected BoneCP getConnectionPool() throws SQLException { + synchronized (lock) { + if (connectionPool == null) { + connectionPool = new BoneCP(config.createBoneCPConfig()); + } + + return connectionPool; + } + } + + @Override + public RegionStore get(String name) throws IOException { + try { + return new SQLRegionStore(config, getConnectionPool(), name); + } catch (SQLException e) { + throw new IOException("Failed to get a connection pool for storing regions (are the SQL details correct?)"); + } + } + + @Override + public List fetchAllExisting() throws IOException { + Closer closer = Closer.create(); + try { + List names = new ArrayList(); + Connection connection = closer.register(getConnectionPool().getConnection()); + Statement stmt = connection.createStatement(); + ResultSet rs = closer.register(stmt.executeQuery("SELECT name FROM world")); + while (rs.next()) { + names.add(rs.getString(1)); + } + return names; + } catch (SQLException e) { + throw new IOException("Failed to fetch list of worlds", e); + } finally { + closer.close(); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlFileStore.java similarity index 56% rename from src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java rename to src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlFileStore.java index a1e26b4a..b8c98018 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/YAMLDatabase.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlFileStore.java @@ -1,405 +1,349 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection.databases; - -import com.sk89q.util.yaml.YAMLFormat; -import com.sk89q.util.yaml.YAMLNode; -import com.sk89q.util.yaml.YAMLProcessor; -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.BlockVector2D; -import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.flags.DefaultFlag; -import com.sk89q.worldguard.protection.flags.Flag; -import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.DumperOptions.FlowStyle; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.SafeConstructor; -import org.yaml.snakeyaml.representer.Representer; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A store that persists regions in a YAML-encoded file. - */ -public class YAMLDatabase extends AbstractAsynchronousDatabase { - - /** - * Used to dump YAML when an error occurs - */ - private static Yaml yaml; - - private Map regions; - private final File file; - private final Logger logger; - - /** - * Create a new instance. - * - * @param file the file - * @param logger a logger - * @throws ProtectionDatabaseException - * @throws FileNotFoundException - */ - public YAMLDatabase(File file, Logger logger) throws ProtectionDatabaseException, FileNotFoundException { - this.logger = logger; - this.file = file; - - if (!file.exists()) { // shouldn't be necessary, but check anyways - try { - file.createNewFile(); - } catch (IOException e) { - throw new FileNotFoundException(file.getAbsolutePath()); - } - } - } - - private YAMLProcessor createYamlProcessor(File file) { - return new YAMLProcessor(file, false, YAMLFormat.COMPACT); - } - - @Override - public void performLoad() throws ProtectionDatabaseException { - YAMLProcessor config = createYamlProcessor(file); - - try { - config.load(); - } catch (IOException e) { - throw new ProtectionDatabaseException(e); - } - - Map regionData = config.getNodes("regions"); - - // No regions are even configured - if (regionData == null) { - this.regions = new HashMap(); - return; - } - - // Warning for WORLDGUARD-3094 - for (Flag flag : DefaultFlag.getFlags()) { - if (flag == null) { - logger.severe("Some 3rd-party plugin has registered an invalid 'null' custom flag with WorldGuard, though we can't tell you which plugin did it - this may cause major problems in other places"); - break; - } - } - - Map regions = new HashMap(); - Map parentSets = new LinkedHashMap(); - - for (Map.Entry entry : regionData.entrySet()) { - String id = entry.getKey().toLowerCase().replace(".", ""); - YAMLNode node = entry.getValue(); - - String type = node.getString("type"); - ProtectedRegion region; - - try { - if (type == null) { - logger.warning("Undefined region type for region '" + id + "'!\n" + - "Here is what the region data looks like:\n\n" + dumpAsYaml(entry.getValue().getMap()) + "\n"); - continue; - } else if (type.equals("cuboid")) { - Vector pt1 = checkNonNull(node.getVector("min")); - Vector pt2 = checkNonNull(node.getVector("max")); - BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); - BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); - region = new ProtectedCuboidRegion(id, min, max); - } else if (type.equals("poly2d")) { - Integer minY = checkNonNull(node.getInt("min-y")); - Integer maxY = checkNonNull(node.getInt("max-y")); - List points = node.getBlockVector2dList("points", null); - region = new ProtectedPolygonalRegion(id, points, minY, maxY); - } else if (type.equals("global")) { - region = new GlobalProtectedRegion(id); - } else { - logger.warning("Unknown region type for region '" + id + "'!\n" + - "Here is what the region data looks like:\n\n" + dumpAsYaml(entry.getValue().getMap()) + "\n"); - continue; - } - - Integer priority = checkNonNull(node.getInt("priority")); - region.setPriority(priority); - setFlags(region, node.getNode("flags")); - region.setOwners(parseDomain(node.getNode("owners"))); - region.setMembers(parseDomain(node.getNode("members"))); - regions.put(id, region); - - String parentId = node.getString("parent"); - if (parentId != null) { - parentSets.put(region, parentId); - } - } catch (NullPointerException e) { - logger.log(Level.WARNING, - "Unexpected NullPointerException encountered during parsing for the region '" + id + "'!\n" + - "Here is what the region data looks like:\n\n" + dumpAsYaml(entry.getValue().getMap()) + - "\n\nNote: This region will disappear as a result!", e); - } - } - - // Relink parents - for (Map.Entry entry : parentSets.entrySet()) { - ProtectedRegion parent = regions.get(entry.getValue()); - if (parent != null) { - try { - entry.getKey().setParent(parent); - } catch (CircularInheritanceException e) { - logger.warning("Circular inheritance detect with '" + entry.getValue() + "' detected as a parent"); - } - } else { - logger.warning("Unknown region parent: " + entry.getValue()); - } - } - - this.regions = regions; - } - - private V checkNonNull(V val) throws NullPointerException { - if (val == null) { - throw new NullPointerException(); - } - - return val; - } - - private void setFlags(ProtectedRegion region, YAMLNode flagsData) { - if (flagsData == null) { - return; - } - - // @TODO: Make this better - for (Flag flag : DefaultFlag.getFlags()) { - if (flag == null) { - // Some plugins that add custom flags to WorldGuard are doing - // something very wrong -- see WORLDGUARD-3094 - continue; - } - - Object o = flagsData.getProperty(flag.getName()); - if (o != null) { - setFlag(region, flag, o); - } - - if (flag.getRegionGroupFlag() != null) { - Object o2 = flagsData.getProperty(flag.getRegionGroupFlag().getName()); - if (o2 != null) { - setFlag(region, flag.getRegionGroupFlag(), o2); - } - } - } - } - - private void setFlag(ProtectedRegion region, Flag flag, Object rawValue) { - T val = flag.unmarshal(rawValue); - if (val == null) { - logger.warning("Failed to parse flag '" + flag.getName() - + "' with value '" + rawValue.toString() + "'"); - return; - } - region.setFlag(flag, val); - } - - @SuppressWarnings("deprecation") - private DefaultDomain parseDomain(YAMLNode node) { - if (node == null) { - return new DefaultDomain(); - } - - DefaultDomain domain = new DefaultDomain(); - - for (String name : node.getStringList("players", null)) { - domain.addPlayer(name); - } - - for (String stringId : node.getStringList("unique-ids", null)) { - try { - domain.addPlayer(UUID.fromString(stringId)); - } catch (IllegalArgumentException e) { - logger.log(Level.WARNING, "Failed to parse UUID '" + stringId +"'", e); - } - } - - for (String name : node.getStringList("groups", null)) { - domain.addGroup(name); - } - - return domain; - } - - @Override - protected void performSave() throws ProtectionDatabaseException { - File tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); - YAMLProcessor config = createYamlProcessor(tempFile); - - config.clear(); - - for (Map.Entry entry : regions.entrySet()) { - ProtectedRegion region = entry.getValue(); - YAMLNode node = config.addNode("regions." + entry.getKey()); - - if (region instanceof ProtectedCuboidRegion) { - ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region; - node.setProperty("type", "cuboid"); - node.setProperty("min", cuboid.getMinimumPoint()); - node.setProperty("max", cuboid.getMaximumPoint()); - } else if (region instanceof ProtectedPolygonalRegion) { - ProtectedPolygonalRegion poly = (ProtectedPolygonalRegion) region; - node.setProperty("type", "poly2d"); - node.setProperty("min-y", poly.getMinimumPoint().getBlockY()); - node.setProperty("max-y", poly.getMaximumPoint().getBlockY()); - - List> points = new ArrayList>(); - for (BlockVector2D point : poly.getPoints()) { - Map data = new HashMap(); - data.put("x", point.getBlockX()); - data.put("z", point.getBlockZ()); - points.add(data); - } - - node.setProperty("points", points); - } else if (region instanceof GlobalProtectedRegion) { - node.setProperty("type", "global"); - } else { - node.setProperty("type", region.getClass().getCanonicalName()); - } - - node.setProperty("priority", region.getPriority()); - node.setProperty("flags", getFlagData(region)); - node.setProperty("owners", getDomainData(region.getOwners())); - node.setProperty("members", getDomainData(region.getMembers())); - ProtectedRegion parent = region.getParent(); - if (parent != null) { - node.setProperty("parent", parent.getId()); - } - } - - config.setHeader("#\r\n" + - "# WorldGuard regions file\r\n" + - "#\r\n" + - "# WARNING: THIS FILE IS AUTOMATICALLY GENERATED. If you modify this file by\r\n" + - "# hand, be aware that A SINGLE MISTYPED CHARACTER CAN CORRUPT THE FILE. If\r\n" + - "# WorldGuard is unable to parse the file, your regions will FAIL TO LOAD and\r\n" + - "# the contents of this file will reset. Please use a YAML validator such as\r\n" + - "# http://yaml-online-parser.appspot.com (for smaller files).\r\n" + - "#\r\n" + - "# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" + - "#"); - config.save(); - - file.delete(); - if (!tempFile.renameTo(file)) { - throw new ProtectionDatabaseException("Failed to rename temporary regions file to " + file.getAbsolutePath()); - } - } - - private Map getFlagData(ProtectedRegion region) { - Map flagData = new HashMap(); - - for (Map.Entry, Object> entry : region.getFlags().entrySet()) { - Flag flag = entry.getKey(); - addMarshalledFlag(flagData, flag, entry.getValue()); - } - - return flagData; - } - - @SuppressWarnings("unchecked") - private void addMarshalledFlag(Map flagData, Flag flag, Object val) { - if (val == null) { - return; - } - - flagData.put(flag.getName(), flag.marshal((V) val)); - } - - @SuppressWarnings("deprecation") - private Map getDomainData(DefaultDomain domain) { - Map domainData = new HashMap(); - - setDomainData(domainData, "players", domain.getPlayers()); - setDomainData(domainData, "unique-ids", domain.getUniqueIds()); - setDomainData(domainData, "groups", domain.getGroups()); - - return domainData; - } - - private void setDomainData(Map domainData, String key, Set domain) { - if (domain.isEmpty()) { - return; - } - - List list = new ArrayList(); - - for (Object str : domain) { - list.add(String.valueOf(str)); - } - - domainData.put(key, list); - } - - @Override - public Map getRegions() { - return regions; - } - - @Override - public void setRegions(Map regions) { - this.regions = regions; - } - - /** - * Dump the given object as YAML for debugging purposes. - * - * @param object the object - * @return the YAML string or an error string if dumping fals - */ - private static String dumpAsYaml(Object object) { - if (yaml == null) { - DumperOptions options = new DumperOptions(); - options.setIndent(4); - options.setDefaultFlowStyle(FlowStyle.AUTO); - - yaml = new Yaml(new SafeConstructor(), new Representer(), options); - } - - try { - return yaml.dump(object).replaceAll("(?m)^", "\t"); - } catch (Throwable t) { - return ""; - } - } - -} +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.util.yaml.YAMLFormat; +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; +import com.sk89q.worldguard.protection.managers.storage.RegionStore; +import com.sk89q.worldguard.protection.managers.storage.RegionStoreUtils; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A store that persists regions in a YAML-encoded file. + */ +public class YamlFileStore implements RegionStore { + + private static final Logger log = Logger.getLogger(YamlFileStore.class.getCanonicalName()); + private static final Yaml ERROR_DUMP_YAML; + + private static final String FILE_HEADER = "#\r\n" + + "# WorldGuard regions file\r\n" + + "#\r\n" + + "# WARNING: THIS FILE IS AUTOMATICALLY GENERATED. If you modify this file by\r\n" + + "# hand, be aware that A SINGLE MISTYPED CHARACTER CAN CORRUPT THE FILE. If\r\n" + + "# WorldGuard is unable to parse the file, your regions will FAIL TO LOAD and\r\n" + + "# the contents of this file will reset. Please use a YAML validator such as\r\n" + + "# http://yaml-online-parser.appspot.com (for smaller files).\r\n" + + "#\r\n" + + "# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" + + "#"; + + private final File file; + + static { + DumperOptions options = new DumperOptions(); + options.setIndent(4); + options.setDefaultFlowStyle(FlowStyle.AUTO); + + ERROR_DUMP_YAML = new Yaml(new SafeConstructor(), new Representer(), options); + } + + /** + * Create a new instance. + * + * @param file the file + * @throws IOException thrown if the file cannot be written to + */ + public YamlFileStore(File file) throws IOException { + checkNotNull(file); + this.file = file; + if (!file.exists()) { + //noinspection ResultOfMethodCallIgnored + file.createNewFile(); + } + } + + @Override + public Set loadAll() throws IOException { + Map loaded = new HashMap(); + + YAMLProcessor config = createYamlProcessor(file); + config.load(); + + Map regionData = config.getNodes("regions"); + + if (regionData == null) { + return Collections.emptySet(); // No regions are even configured + } + + Map parentSets = new LinkedHashMap(); + + for (Map.Entry entry : regionData.entrySet()) { + String id = entry.getKey(); + YAMLNode node = entry.getValue(); + + String type = node.getString("type"); + ProtectedRegion region; + + try { + if (type == null) { + log.warning("Undefined region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); + continue; + } else if (type.equals("cuboid")) { + Vector pt1 = checkNotNull(node.getVector("min")); + Vector pt2 = checkNotNull(node.getVector("max")); + BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); + BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); + region = new ProtectedCuboidRegion(id, min, max); + } else if (type.equals("poly2d")) { + Integer minY = checkNotNull(node.getInt("min-y")); + Integer maxY = checkNotNull(node.getInt("max-y")); + List points = node.getBlockVector2dList("points", null); + region = new ProtectedPolygonalRegion(id, points, minY, maxY); + } else if (type.equals("global")) { + region = new GlobalProtectedRegion(id); + } else { + log.warning("Unknown region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); + continue; + } + + Integer priority = checkNotNull(node.getInt("priority")); + region.setPriority(priority); + setFlags(region, node.getNode("flags")); + region.setOwners(parseDomain(node.getNode("owners"))); + region.setMembers(parseDomain(node.getNode("members"))); + + loaded.put(id, region); + + String parentId = node.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } catch (NullPointerException e) { + log.log(Level.WARNING, + "Unexpected NullPointerException encountered during parsing for the region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + + "\n\nNote: This region will disappear as a result!", e); + } + } + + // Relink parents + RegionStoreUtils.relinkParents(loaded, parentSets); + + return new HashSet(loaded.values()); + } + + @Override + public void saveAll(Set regions) throws IOException { + checkNotNull(regions); + + File tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); + YAMLProcessor config = createYamlProcessor(tempFile); + + config.clear(); + + YAMLNode regionsNode = config.addNode("regions"); + Map map = regionsNode.getMap(); + + for (ProtectedRegion region : regions) { + Map nodeMap = new HashMap(); + map.put(region.getId(), nodeMap); + YAMLNode node = new YAMLNode(nodeMap, false); + + if (region instanceof ProtectedCuboidRegion) { + ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region; + node.setProperty("type", "cuboid"); + node.setProperty("min", cuboid.getMinimumPoint()); + node.setProperty("max", cuboid.getMaximumPoint()); + } else if (region instanceof ProtectedPolygonalRegion) { + ProtectedPolygonalRegion poly = (ProtectedPolygonalRegion) region; + node.setProperty("type", "poly2d"); + node.setProperty("min-y", poly.getMinimumPoint().getBlockY()); + node.setProperty("max-y", poly.getMaximumPoint().getBlockY()); + + List> points = new ArrayList>(); + for (BlockVector2D point : poly.getPoints()) { + Map data = new HashMap(); + data.put("x", point.getBlockX()); + data.put("z", point.getBlockZ()); + points.add(data); + } + + node.setProperty("points", points); + } else if (region instanceof GlobalProtectedRegion) { + node.setProperty("type", "global"); + } else { + node.setProperty("type", region.getClass().getCanonicalName()); + } + + node.setProperty("priority", region.getPriority()); + node.setProperty("flags", getFlagData(region)); + node.setProperty("owners", getDomainData(region.getOwners())); + node.setProperty("members", getDomainData(region.getMembers())); + + ProtectedRegion parent = region.getParent(); + if (parent != null) { + node.setProperty("parent", parent.getId()); + } + } + + config.setHeader(FILE_HEADER); + config.save(); + + //noinspection ResultOfMethodCallIgnored + file.delete(); + if (!tempFile.renameTo(file)) { + throw new IOException("Failed to rename temporary regions file to " + file.getAbsolutePath()); + } + } + + @Override + public void saveChanges(RegionDifference difference) throws DifferenceSaveException { + throw new DifferenceSaveException("Not supported"); + } + + @SuppressWarnings("deprecation") + private DefaultDomain parseDomain(YAMLNode node) { + if (node == null) { + return new DefaultDomain(); + } + + DefaultDomain domain = new DefaultDomain(); + + for (String name : node.getStringList("players", null)) { + domain.addPlayer(name); + } + + for (String stringId : node.getStringList("unique-ids", null)) { + try { + domain.addPlayer(UUID.fromString(stringId)); + } catch (IllegalArgumentException e) { + log.log(Level.WARNING, "Failed to parse UUID '" + stringId + "'", e); + } + } + + for (String name : node.getStringList("groups", null)) { + domain.addGroup(name); + } + + return domain; + } + + private Map getFlagData(ProtectedRegion region) { + Map flagData = new HashMap(); + + for (Map.Entry, Object> entry : region.getFlags().entrySet()) { + Flag flag = entry.getKey(); + addMarshalledFlag(flagData, flag, entry.getValue()); + } + + return flagData; + } + + private void setFlags(ProtectedRegion region, YAMLNode flagsData) { + if (flagsData != null) { + RegionStoreUtils.trySetFlagMap(region, flagsData.getMap()); + } + } + + @SuppressWarnings("unchecked") + private void addMarshalledFlag(Map flagData, Flag flag, Object val) { + if (val == null) { + return; + } + + flagData.put(flag.getName(), flag.marshal((V) val)); + } + + private Map getDomainData(DefaultDomain domain) { + Map domainData = new HashMap(); + + //noinspection deprecation + setDomainData(domainData, "players", domain.getPlayers()); + setDomainData(domainData, "unique-ids", domain.getUniqueIds()); + setDomainData(domainData, "groups", domain.getGroups()); + + return domainData; + } + + private void setDomainData(Map domainData, String key, Set domain) { + if (domain.isEmpty()) { + return; + } + + List list = new ArrayList(); + + for (Object str : domain) { + list.add(String.valueOf(str)); + } + + domainData.put(key, list); + } + + /** + * Create a YAML processer instance. + * + * @param file the file + * @return a processor instance + */ + private YAMLProcessor createYamlProcessor(File file) { + checkNotNull(file); + return new YAMLProcessor(file, false, YAMLFormat.COMPACT); + } + + /** + * Dump the given object as YAML for debugging purposes. + * + * @param object the object + * @return the YAML string or an error string if dumping fals + */ + private static String toYamlOutput(Object object) { + try { + return ERROR_DUMP_YAML.dump(object).replaceAll("(?m)^", "\t"); + } catch (Throwable t) { + return ""; + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java new file mode 100644 index 00000000..33fc1cdb --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java @@ -0,0 +1,335 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Table; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.managers.storage.RegionStoreUtils; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +class DataLoader { + + private static final Logger log = Logger.getLogger(DataLoader.class.getCanonicalName()); + + final Connection conn; + final DataSourceConfig config; + final int worldId; + + private final Map loaded = new HashMap(); + private final Map parentSets = new HashMap(); + private final Yaml yaml = SQLRegionStore.createYaml(); + + DataLoader(SQLRegionStore regionStore, Connection conn) throws SQLException { + checkNotNull(regionStore); + + this.conn = conn; + this.config = regionStore.getDataSourceConfig(); + this.worldId = regionStore.getWorldId(); + } + + public Set load() throws SQLException { + loadCuboids(); + loadPolygons(); + loadGlobals(); + + loadFlags(); + loadDomainUsers(); + loadDomainGroups(); + + RegionStoreUtils.relinkParents(loaded, parentSets); + + return new HashSet(loaded.values()); + } + + private void loadCuboids() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT g.min_z, g.min_y, g.min_x, " + + " g.max_z, g.max_y, g.max_x, " + + " r.id, r.priority, p.id AS parent " + + "FROM " + config.getTablePrefix() + "region_cuboid AS g " + + "LEFT JOIN " + config.getTablePrefix() + "region AS r " + + " ON (g.region_id = r.id AND g.world_id = r.world_id) " + + "LEFT JOIN " + config.getTablePrefix() + "region AS p " + + " ON (r.parent = p.id AND r.world_id = p.world_id) " + + "WHERE r.world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + Vector pt1 = new Vector(rs.getInt("min_x"), rs.getInt("min_y"), rs.getInt("min_z")); + Vector pt2 = new Vector(rs.getInt("max_x"), rs.getInt("max_y"), rs.getInt("max_z")); + + BlockVector min = Vector.getMinimum(pt1, pt2).toBlockVector(); + BlockVector max = Vector.getMaximum(pt1, pt2).toBlockVector(); + ProtectedRegion region = new ProtectedCuboidRegion(rs.getString("id"), min, max); + + region.setPriority(rs.getInt("priority")); + + loaded.put(rs.getString("id"), region); + + String parentId = rs.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadGlobals() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT r.id, r.priority, p.id AS parent " + + "FROM " + config.getTablePrefix() + "region AS r " + + "LEFT JOIN " + config.getTablePrefix() + "region AS p " + + " ON (r.parent = p.id AND r.world_id = p.world_id) " + + "WHERE r.type = 'global' AND r.world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + ProtectedRegion region = new GlobalProtectedRegion(rs.getString("id")); + + region.setPriority(rs.getInt("priority")); + + loaded.put(rs.getString("id"), region); + + String parentId = rs.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadPolygons() throws SQLException { + ListMultimap pointsCache = ArrayListMultimap.create(); + + // First get all the vertices and store them in memory + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT region_id, x, z " + + "FROM " + config.getTablePrefix() + "region_poly2d_point " + + "WHERE world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + pointsCache.put(rs.getString("region_id"), new BlockVector2D(rs.getInt("x"), rs.getInt("z"))); + } + } finally { + closer.closeQuietly(); + } + + // Now we pull the regions themselves + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT g.min_y, g.max_y, r.id, r.priority, p.id AS parent " + + "FROM " + config.getTablePrefix() + "region_poly2d AS g " + + "LEFT JOIN " + config.getTablePrefix() + "region AS r " + + " ON (g.region_id = r.id AND g.world_id = r.world_id) " + + "LEFT JOIN " + config.getTablePrefix() + "region AS p " + + " ON (r.parent = p.id AND r.world_id = p.world_id) " + + "WHERE r.world_id = " + worldId + )); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + String id = rs.getString("id"); + + // Get the points from the cache + List points = pointsCache.get(id); + + if (points.size() < 3) { + log.log(Level.WARNING, "Invalid polygonal region '" + id + "': region has " + points.size() + " point(s) (less than the required 3). Skipping this region."); + continue; + } + + Integer minY = rs.getInt("min_y"); + Integer maxY = rs.getInt("max_y"); + + ProtectedRegion region = new ProtectedPolygonalRegion(id, points, minY, maxY); + region.setPriority(rs.getInt("priority")); + + loaded.put(id, region); + + String parentId = rs.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadFlags() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT region_id, flag, value " + + "FROM " + config.getTablePrefix() + "region_flag " + + "WHERE world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + Table data = HashBasedTable.create(); + while (rs.next()) { + data.put( + rs.getString("region_id"), + rs.getString("flag"), + unmarshalFlagValue(rs.getString("value"))); + } + + for (Map.Entry> entry : data.rowMap().entrySet()) { + ProtectedRegion region = loaded.get(entry.getKey()); + if (region != null) { + RegionStoreUtils.trySetFlagMap(region, entry.getValue()); + } else { + throw new RuntimeException("An unexpected error occurred (loaded.get() returned null)"); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadDomainUsers() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT p.region_id, u.name, u.uuid, p.owner " + + "FROM " + config.getTablePrefix() + "region_players AS p " + + "LEFT JOIN " + config.getTablePrefix() + "user AS u " + + " ON (p.user_id = u.id) " + + "WHERE p.world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + ProtectedRegion region = loaded.get(rs.getString("region_id")); + + if (region != null) { + DefaultDomain domain; + + if (rs.getBoolean("owner")) { + domain = region.getOwners(); + } else { + domain = region.getMembers(); + } + + String name = rs.getString("name"); + String uuid = rs.getString("uuid"); + + if (name != null) { + //noinspection deprecation + domain.addPlayer(name); + } else if (uuid != null) { + try { + domain.addPlayer(UUID.fromString(uuid)); + } catch (IllegalArgumentException e) { + log.warning("Invalid UUID '" + uuid + "' for region '" + region.getId() + "'"); + } + } + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadDomainGroups() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT rg.region_id, g.name, rg.owner " + + "FROM `" + config.getTablePrefix() + "region_groups` AS rg " + + "INNER JOIN `" + config.getTablePrefix() + "group` AS g ON (rg.group_id = g.id) " + + // LEFT JOIN is returning NULLS for reasons unknown + "AND rg.world_id = " + this.worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + ProtectedRegion region = loaded.get(rs.getString("region_id")); + + if (region != null) { + DefaultDomain domain; + + if (rs.getBoolean("owner")) { + domain = region.getOwners(); + } else { + domain = region.getMembers(); + } + + domain.addGroup(rs.getString("name")); + } + } + } finally { + closer.closeQuietly(); + } + } + + private Object unmarshalFlagValue(String rawValue) { + try { + return yaml.load(rawValue); + } catch (YAMLException e) { + return String.valueOf(rawValue); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java new file mode 100644 index 00000000..2420f9be --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java @@ -0,0 +1,168 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import javax.annotation.Nullable; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +class DataUpdater { + + final Connection conn; + final DataSourceConfig config; + final int worldId; + final DomainTableCache domainTableCache; + + DataUpdater(SQLRegionStore regionStore, Connection conn) throws SQLException { + checkNotNull(regionStore); + + this.conn = conn; + this.config = regionStore.getDataSourceConfig(); + this.worldId = regionStore.getWorldId(); + this.domainTableCache = new DomainTableCache(config, conn); + } + + /** + * Save the given set of regions to the database. + * + * @param regions a set of regions to save + * @throws SQLException thrown on a fatal SQL error + */ + public void saveAll(Set regions) throws SQLException { + executeSave(regions, null); + } + + /** + * Save the given set of regions to the database. + * + * @param changed a set of changed regions + * @param removed a set of removed regions + * @throws SQLException thrown on a fatal SQL error + */ + public void saveChanges(Set changed, Set removed) throws SQLException { + executeSave(changed, removed); + } + + /** + * Execute the save operation. + * + * @param toUpdate a list of regions to update + * @param toRemove a list of regions to remove, or {@code null} to remove + * regions in the database that were not in {@code toUpdate} + * @throws SQLException thrown on a fatal SQL error + */ + private void executeSave(Set toUpdate, @Nullable Set toRemove) throws SQLException { + Map existing = getExistingRegions(); // Map of regions that already exist in the database + + // WARNING: The database uses utf8_bin for its collation, so + // we have to remove the exact same ID (it is case-sensitive!) + + try { + conn.setAutoCommit(false); + + RegionUpdater updater = new RegionUpdater(this); + RegionInserter inserter = new RegionInserter(this); + RegionRemover remover = new RegionRemover(this); + + for (ProtectedRegion region : toUpdate) { + if (toRemove != null && toRemove.contains(region)) { + continue; + } + + String currentType = existing.get(region.getId()); + + // Check if the region + if (currentType != null) { // Region exists in the database + existing.remove(region.getId()); + + updater.updateRegionType(region); + remover.removeGeometry(region, currentType); + } else { + inserter.insertRegionType(region); + } + + inserter.insertGeometry(region); + updater.updateRegionProperties(region); + } + + if (toRemove != null) { + List removeNames = new ArrayList(); + for (ProtectedRegion region : toRemove) { + removeNames.add(region.getId()); + } + remover.removeRegionsEntirely(removeNames); + } else { + remover.removeRegionsEntirely(existing.keySet()); + } + + remover.apply(); + inserter.apply(); + updater.apply(); + + conn.commit(); + } catch (SQLException e) { + conn.rollback(); + throw e; + } catch (RuntimeException e) { + conn.rollback(); + throw e; + } finally { + conn.setAutoCommit(true); + } + + // Connection to be closed by caller + } + + private Map getExistingRegions() throws SQLException { + Map existing = new HashMap(); + + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT id, type " + + "FROM " + config.getTablePrefix() + "region " + + "WHERE world_id = " + worldId)); + + ResultSet resultSet = closer.register(stmt.executeQuery()); + + while (resultSet.next()) { + existing.put(resultSet.getString("id"), resultSet.getString("type")); + } + + return existing; + } finally { + closer.closeQuietly(); + } + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java new file mode 100644 index 00000000..9ac4cf35 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.managers.storage.sql.TableCache.GroupNameCache; +import com.sk89q.worldguard.protection.managers.storage.sql.TableCache.UserNameCache; +import com.sk89q.worldguard.protection.managers.storage.sql.TableCache.UserUuidCache; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.sql.Connection; + +class DomainTableCache { + + private final UserNameCache userNameCache; + private final UserUuidCache userUuidCache; + private final GroupNameCache groupNameCache; + + DomainTableCache(DataSourceConfig config, Connection conn) { + userNameCache = new UserNameCache(config, conn); + userUuidCache = new UserUuidCache(config, conn); + groupNameCache = new GroupNameCache(config, conn); + } + + public UserNameCache getUserNameCache() { + return userNameCache; + } + + public UserUuidCache getUserUuidCache() { + return userUuidCache; + } + + public GroupNameCache getGroupNameCache() { + return groupNameCache; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java new file mode 100644 index 00000000..8886e458 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java @@ -0,0 +1,189 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.google.common.collect.Lists; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Insert regions that don't exist in the database yet. + */ +class RegionInserter { + + private final DataSourceConfig config; + private final Connection conn; + private final int worldId; + private final List all = new ArrayList(); + private final List cuboids = new ArrayList(); + private final List polygons = new ArrayList(); + + RegionInserter(DataUpdater updater) { + this.config = updater.config; + this.conn = updater.conn; + this.worldId = updater.worldId; + } + + public void insertRegionType(ProtectedRegion region) throws SQLException { + all.add(region); + } + + @SuppressWarnings("StatementWithEmptyBody") + public void insertGeometry(ProtectedRegion region) throws SQLException { + if (region instanceof ProtectedCuboidRegion) { + cuboids.add((ProtectedCuboidRegion) region); + + } else if (region instanceof ProtectedPolygonalRegion) { + polygons.add((ProtectedPolygonalRegion) region); + + } else if (region instanceof GlobalProtectedRegion) { + // Nothing special to do about them + + } else { + throw new IllegalArgumentException("Unknown type of region: " + region.getClass().getName()); + } + } + + private void insertRegionTypes() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region " + + "(id, world_id, type, priority, parent) " + + "VALUES " + + "(?, ?, ?, ?, NULL)")); + + for (List partition : Lists.partition(all, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.setInt(2, worldId); + stmt.setString(3, SQLRegionStore.getRegionTypeName(region)); + stmt.setInt(4, region.getPriority()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void insertCuboids() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_cuboid " + + "(region_id, world_id, min_z, min_y, min_x, max_z, max_y, max_x ) " + + "VALUES " + + "(?, " + worldId + ", ?, ?, ?, ?, ?, ?)")); + + for (List partition : Lists.partition(cuboids, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedCuboidRegion region : partition) { + BlockVector min = region.getMinimumPoint(); + BlockVector max = region.getMaximumPoint(); + + stmt.setString(1, region.getId()); + stmt.setInt(2, min.getBlockZ()); + stmt.setInt(3, min.getBlockY()); + stmt.setInt(4, min.getBlockX()); + stmt.setInt(5, max.getBlockZ()); + stmt.setInt(6, max.getBlockY()); + stmt.setInt(7, max.getBlockX()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void insertPolygons() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_poly2d " + + "(region_id, world_id, max_y, min_y) " + + "VALUES " + + "(?, " + worldId + ", ?, ?)")); + + for (List partition : Lists.partition(polygons, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedPolygonalRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.setInt(2, region.getMaximumPoint().getBlockY()); + stmt.setInt(3, region.getMinimumPoint().getBlockY()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void insertPolygonVertices() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_poly2d_point" + + "(region_id, world_id, z, x) " + + "VALUES " + + "(?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedPolygonalRegion region : polygons) { + for (BlockVector2D point : region.getPoints()) { + stmt.setString(1, region.getId()); + stmt.setInt(2, point.getBlockZ()); + stmt.setInt(3, point.getBlockX()); + batch.addBatch(); + } + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + public void apply() throws SQLException { + insertRegionTypes(); + insertCuboids(); + insertPolygons(); + insertPolygonVertices(); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java new file mode 100644 index 00000000..f6de8ccd --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java @@ -0,0 +1,88 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class RegionRemover { + + private final DataSourceConfig config; + private final Connection conn; + private final int worldId; + private final List regionQueue = new ArrayList(); + private final List cuboidGeometryQueue = new ArrayList(); + private final List polygonGeometryQueue = new ArrayList(); + + RegionRemover(DataUpdater updater) { + this.config = updater.config; + this.conn = updater.conn; + this.worldId = updater.worldId; + } + + public void removeRegionsEntirely(Collection names) { + regionQueue.addAll(names); + } + + public void removeGeometry(ProtectedRegion region, String currentType) { + if (currentType.equals("cuboid")) { + cuboidGeometryQueue.add(region.getId()); + } else if (currentType.equals("poly2d")) { + polygonGeometryQueue.add(region.getId()); + } else if (currentType.equals("global")) { + // Nothing to do + } else { + throw new RuntimeException("Unknown type of region in the database: " + currentType); + } + + } + + private void removeRows(Collection names, String table, String field) throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + table + " WHERE " + field + " = ? AND world_id = " + worldId)); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + for (String name : names) { + stmt.setString(1, name); + batch.addBatch(); + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + public void apply() throws SQLException { + removeRows(regionQueue, "region", "id"); + removeRows(cuboidGeometryQueue, "region_cuboid", "region_id"); + removeRows(polygonGeometryQueue, "region_poly2d", "region_id"); + } +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java new file mode 100644 index 00000000..7f15ba63 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java @@ -0,0 +1,341 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.google.common.collect.Lists; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.yaml.snakeyaml.Yaml; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Updates region data that needs to be updated for both inserts and updates. + */ +class RegionUpdater { + + private static final Logger log = Logger.getLogger(RegionUpdater.class.getCanonicalName()); + private final DataSourceConfig config; + private final Connection conn; + private final int worldId; + private final DomainTableCache domainTableCache; + + private final Set userNames = new HashSet(); + private final Set userUuids = new HashSet(); + private final Set groupNames = new HashSet(); + + private final Yaml yaml = SQLRegionStore.createYaml(); + + private final List typesToUpdate = new ArrayList(); + private final List parentsToSet = new ArrayList(); + private final List flagsToReplace = new ArrayList(); + private final List domainsToReplace = new ArrayList(); + + RegionUpdater(DataUpdater updater) { + this.config = updater.config; + this.conn = updater.conn; + this.worldId = updater.worldId; + this.domainTableCache = updater.domainTableCache; + } + + public void updateRegionType(ProtectedRegion region) { + typesToUpdate.add(region); + } + + public void updateRegionProperties(ProtectedRegion region) { + if (region.getParent() != null) { + parentsToSet.add(region); + } + + flagsToReplace.add(region); + domainsToReplace.add(region); + + addDomain(region.getOwners()); + addDomain(region.getMembers()); + } + + private void addDomain(DefaultDomain domain) { + //noinspection deprecation + for (String name : domain.getPlayers()) { + userNames.add(name.toLowerCase()); + } + + for (UUID uuid : domain.getUniqueIds()) { + userUuids.add(uuid); + } + + for (String name : domain.getGroups()) { + groupNames.add(name.toLowerCase()); + } + } + + private void setParents() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "UPDATE " + config.getTablePrefix() + "region " + + "SET parent = ? " + + "WHERE id = ? AND world_id = " + worldId)); + + for (List partition : Lists.partition(parentsToSet, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + ProtectedRegion parent = region.getParent(); + if (parent != null) { // Parent would be null due to a race condition + stmt.setString(1, parent.getId()); + stmt.setString(2, region.getId()); + stmt.addBatch(); + } + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void replaceFlags() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + "region_flag " + + "WHERE region_id = ? " + + "AND world_id = " + worldId)); + + for (List partition : Lists.partition(flagsToReplace, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_flag " + + "(id, region_id, world_id, flag, value) " + + "VALUES " + + "(null, ?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedRegion region : flagsToReplace) { + for (Map.Entry, Object> entry : region.getFlags().entrySet()) { + if (entry.getValue() == null) continue; + + Object flag = marshalFlagValue(entry.getKey(), entry.getValue()); + + stmt.setString(1, region.getId()); + stmt.setString(2, entry.getKey().getName()); + stmt.setObject(3, flag); + batch.addBatch(); + } + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + private void replaceDomainUsers() throws SQLException { + // Remove users + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + "region_players " + + "WHERE region_id = ? " + + "AND world_id = " + worldId)); + + for (List partition : Lists.partition(domainsToReplace, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + + // Add users + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_players " + + "(region_id, world_id, user_id, owner) " + + "VALUES (?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedRegion region : domainsToReplace) { + insertDomainUsers(stmt, batch, region, region.getMembers(), false); // owner = false + insertDomainUsers(stmt, batch, region, region.getOwners(), true); // owner = true + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + private void insertDomainUsers(PreparedStatement stmt, StatementBatch batch, ProtectedRegion region, DefaultDomain domain, boolean owner) throws SQLException { + //noinspection deprecation + for (String name : domain.getPlayers()) { + Integer id = domainTableCache.getUserNameCache().find(name); + if (id != null) { + stmt.setString(1, region.getId()); + stmt.setInt(2, id); + stmt.setBoolean(3, owner); + batch.addBatch(); + } else { + log.log(Level.WARNING, "Did not find an ID for the user identified as '" + name + "'"); + } + } + + for (UUID uuid : domain.getUniqueIds()) { + Integer id = domainTableCache.getUserUuidCache().find(uuid); + if (id != null) { + stmt.setString(1, region.getId()); + stmt.setInt(2, id); + stmt.setBoolean(3, owner); + batch.addBatch(); + } else { + log.log(Level.WARNING, "Did not find an ID for the user identified by '" + uuid + "'"); + } + } + } + + private void replaceDomainGroups() throws SQLException { + // Remove groups + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + "region_groups " + + "WHERE region_id = ? " + + "AND world_id = " + worldId)); + + for (List partition : Lists.partition(domainsToReplace, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + + // Add groups + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_groups " + + "(region_id, world_id, group_id, owner) " + + "VALUES (?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedRegion region : domainsToReplace) { + insertDomainGroups(stmt, batch, region, region.getMembers(), false); // owner = false + insertDomainGroups(stmt, batch, region, region.getOwners(), true); // owner = true + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + private void insertDomainGroups(PreparedStatement stmt, StatementBatch batch, ProtectedRegion region, DefaultDomain domain, boolean owner) throws SQLException { + for (String name : domain.getGroups()) { + Integer id = domainTableCache.getGroupNameCache().find(name); + if (id != null) { + stmt.setString(1, region.getId()); + stmt.setInt(2, id); + stmt.setBoolean(3, owner); + batch.addBatch(); + } else { + log.log(Level.WARNING, "Did not find an ID for the group identified as '" + name + "'"); + } + } + } + + private void updateRegionTypes() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "UPDATE " + config.getTablePrefix() + "region " + + "SET type = ?, priority = ?, parent = NULL " + + "WHERE id = ? AND world_id = " + worldId)); + + for (List partition : Lists.partition(typesToUpdate, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, SQLRegionStore.getRegionTypeName(region)); + stmt.setInt(2, region.getPriority()); + stmt.setString(3, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + public void apply() throws SQLException { + domainTableCache.getUserNameCache().fetch(userNames); + domainTableCache.getUserUuidCache().fetch(userUuids); + domainTableCache.getGroupNameCache().fetch(groupNames); + + updateRegionTypes(); + setParents(); + replaceFlags(); + replaceDomainUsers(); + replaceDomainGroups(); + } + + @SuppressWarnings("unchecked") + private Object marshalFlagValue(Flag flag, Object val) { + return yaml.dump(flag.marshal((V) val)); + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionStore.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionStore.java new file mode 100644 index 00000000..2da61c0a --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionStore.java @@ -0,0 +1,375 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.jolbox.bonecp.BoneCP; +import com.jolbox.bonecp.BoneCPConfig; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; +import com.sk89q.worldguard.protection.managers.storage.RegionStore; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.FlywayException; +import org.flywaydb.core.api.MigrationVersion; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores region data into a SQL database in a highly normalized fashion. + */ +public class SQLRegionStore implements RegionStore { + + private static final Logger log = Logger.getLogger(SQLRegionStore.class.getCanonicalName()); + + private final BoneCP connectionPool; + private final DataSourceConfig config; + private final int worldId; + + /** + * Create a new instance. + * + * @param config a configuration object that configures a {@link Connection} + * @param connectionPool a connection pool + * @param worldName the name of the world to store regions by + * @throws IOException thrown on error + */ + public SQLRegionStore(DataSourceConfig config, BoneCP connectionPool, String worldName) throws IOException { + checkNotNull(config); + checkNotNull(connectionPool); + checkNotNull(worldName); + + this.config = config; + this.connectionPool = connectionPool; + + try { + migrate(); + } catch (FlywayException e) { + throw new IOException("Failed to migrate tables", e); + } catch (SQLException e) { + throw new IOException("Failed to migrate tables", e); + } + + try { + worldId = chooseWorldId(worldName); + } catch (SQLException e) { + throw new IOException("Failed to choose the ID for this world", e); + } + } + + /** + * Attempt to migrate the tables to the latest version. + * + * @throws SQLException thrown on SQL errors + */ + private void migrate() throws SQLException { + Closer closer = Closer.create(); + Connection conn = closer.register(getConnection()); + + try { + // Check some tables + boolean tablesExist; + boolean isRecent; + boolean isBeforeMigrations; + boolean hasMigrations; + + try { + tablesExist = tryQuery(conn, "SELECT * FROM " + config.getTablePrefix() + "region_cuboid LIMIT 1"); + isRecent = tryQuery(conn, "SELECT world_id FROM " + config.getTablePrefix() + "region_cuboid LIMIT 1"); + isBeforeMigrations = !tryQuery(conn, "SELECT uuid FROM " + config.getTablePrefix() + "user LIMIT 1"); + hasMigrations = tryQuery(conn, "SELECT * FROM " + config.getTablePrefix() + "migrations LIMIT 1"); + } finally { + closer.closeQuietly(); + } + + // We don't bother with migrating really old tables + if (tablesExist && !isRecent) { + throw new SQLException( + "Sorry, your tables are too old for the region SQL auto-migration system. " + + "Please run region_manual_update_20110325.sql on your database, which comes " + + "with WorldGuard or can be found in http://github.com/sk89q/worldguard"); + } + + // Our placeholders + Map placeHolders = new HashMap(); + placeHolders.put("tablePrefix", config.getTablePrefix()); + + BoneCPConfig boneConfig = connectionPool.getConfig(); + + Flyway flyway = new Flyway(); + + // The SQL support predates the usage of Flyway, so let's do some + // checks and issue messages appropriately + if (!hasMigrations) { + flyway.setInitOnMigrate(true); + + if (tablesExist) { + // Detect if this is before migrations + if (isBeforeMigrations) { + flyway.setInitVersion(MigrationVersion.fromVersion("1")); + } + + log.log(Level.INFO, "The SQL region tables exist but the migrations table seems to not exist yet. Creating the migrations table..."); + } else { + // By default, if Flyway sees any tables at all in the schema, it + // will assume that we are up to date, so we have to manually + // check ourselves and then ask Flyway to start from the beginning + // if our test table doesn't exist + flyway.setInitVersion(MigrationVersion.fromVersion("0")); + + log.log(Level.INFO, "SQL region tables do not exist: creating..."); + } + } + + flyway.setClassLoader(getClass().getClassLoader()); + flyway.setLocations("migrations/region/" + getMigrationFolderName()); + flyway.setDataSource(boneConfig.getJdbcUrl(), boneConfig.getUser(), boneConfig.getPassword()); + flyway.setTable(config.getTablePrefix() + "migrations"); + flyway.setPlaceholders(placeHolders); + flyway.setValidateOnMigrate(false); + flyway.migrate(); + } finally { + closer.closeQuietly(); + } + } + + /** + * Get the ID for this world from the database or pick a new one if + * an entry does not exist yet. + * + * @param worldName the world name + * @return a world ID + * @throws SQLException on a database access error + */ + private int chooseWorldId(String worldName) throws SQLException { + Closer closer = Closer.create(); + try { + Connection conn = closer.register(getConnection()); + + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT id FROM " + config.getTablePrefix() + "world WHERE name = ? LIMIT 0, 1")); + + stmt.setString(1, worldName); + ResultSet worldResult = closer.register(stmt.executeQuery()); + + if (worldResult.next()) { + return worldResult.getInt("id"); + } else { + PreparedStatement stmt2 = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "world (id, name) VALUES (null, ?)", + Statement.RETURN_GENERATED_KEYS)); + + stmt2.setString(1, worldName); + stmt2.execute(); + ResultSet generatedKeys = stmt2.getGeneratedKeys(); + + if (generatedKeys.next()) { + return generatedKeys.getInt(1); + } else { + throw new SQLException("Expected result, got none"); + } + } + } finally { + closer.closeQuietly(); + } + } + + /** + * Return a new database connection. + * + * @return a connection + * @throws SQLException thrown if the connection could not be created + */ + private Connection getConnection() throws SQLException { + return connectionPool.getConnection(); + } + + /** + * Get the data source config. + * + * @return the data source config + */ + public DataSourceConfig getDataSourceConfig() { + return config; + } + + /** + * Get the world ID. + * + * @return the world ID + */ + public int getWorldId() { + return worldId; + } + + /** + * Try to execute a query and return true if it did not fail. + * + * @param conn the connection to run the query on + * @param sql the SQL query + * @return true if the query did not end in error + */ + private boolean tryQuery(Connection conn, String sql) { + Closer closer = Closer.create(); + try { + Statement statement = closer.register(conn.createStatement()); + statement.executeQuery(sql); + return true; + } catch (SQLException ex) { + return false; + } finally { + closer.closeQuietly(); + } + } + + /** + * Get the identifier string for a region's type. + * + * @param region the region + * @return the ID of the region type + */ + static String getRegionTypeName(ProtectedRegion region) { + if (region instanceof ProtectedCuboidRegion) { + return "cuboid"; + } else if (region instanceof ProtectedPolygonalRegion) { + return "poly2d"; // Differs from getTypeName() on ProtectedRegion + } else if (region instanceof GlobalProtectedRegion) { + return "global"; + } else { + throw new IllegalArgumentException("Unexpected region type: " + region.getClass().getName()); + } + } + + /** + * Create a YAML dumper / parser. + * + * @return a YAML dumper / parser + */ + static Yaml createYaml() { + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setDefaultFlowStyle(FlowStyle.FLOW); + Representer representer = new Representer(); + representer.setDefaultFlowStyle(FlowStyle.FLOW); + + // We have to use this in order to properly save non-string values + return new Yaml(new SafeConstructor(), new Representer(), options); + } + + /** + * Get the name of the folder in migrations/region containing the migration files. + * + * @return the migration folder name + */ + public String getMigrationFolderName() { + return "mysql"; + } + + @Override + public Set loadAll() throws IOException { + Closer closer = Closer.create(); + DataLoader loader; + + try { + try { + loader = new DataLoader(this, closer.register(getConnection())); + } catch (SQLException e) { + throw new IOException("Failed to get a connection to the database", e); + } + + try { + return loader.load(); + } catch (SQLException e) { + throw new IOException("Failed to save the region data to the database", e); + } + } finally { + closer.closeQuietly(); + } + } + + @Override + public void saveAll(Set regions) throws IOException { + checkNotNull(regions); + + Closer closer = Closer.create(); + DataUpdater updater; + + try { + try { + updater = new DataUpdater(this, closer.register(getConnection())); + } catch (SQLException e) { + throw new IOException("Failed to get a connection to the database", e); + } + + try { + updater.saveAll(regions); + } catch (SQLException e) { + throw new IOException("Failed to save the region data to the database", e); + } + } finally { + closer.closeQuietly(); + } + } + + @Override + public void saveChanges(RegionDifference difference) throws DifferenceSaveException, IOException { + checkNotNull(difference); + + Closer closer = Closer.create(); + DataUpdater updater; + + try { + try { + updater = new DataUpdater(this, closer.register(getConnection())); + } catch (SQLException e) { + throw new IOException("Failed to get a connection to the database", e); + } + + try { + updater.saveChanges(difference.getChanged(), difference.getRemoved()); + } catch (SQLException e) { + throw new IOException("Failed to save the region data to the database", e); + } + } finally { + closer.closeQuietly(); + } + } +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MigratorKey.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java similarity index 52% rename from src/main/java/com/sk89q/worldguard/protection/databases/migrator/MigratorKey.java rename to src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java index da85cdc4..7a43c3cd 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MigratorKey.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java @@ -17,27 +17,38 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.migrator; +package com.sk89q.worldguard.protection.managers.storage.sql; -public class MigratorKey { - public final String from; - public final String to; +import java.sql.PreparedStatement; +import java.sql.SQLException; - public MigratorKey(String from, String to) { - this.from = from; - this.to = to; +class StatementBatch { + + public static final int MAX_BATCH_SIZE = 100; + + private final PreparedStatement stmt; + private final int batchSize; + private int count = 0; + + StatementBatch(PreparedStatement stmt, int batchSize) { + this.stmt = stmt; + this.batchSize = batchSize; } - public boolean equals(Object o) { - MigratorKey other = (MigratorKey) o; - - return other.from.equals(this.from) && other.to.equals(this.to); + public void addBatch() throws SQLException { + stmt.addBatch(); + count++; + if (count > batchSize) { + stmt.executeBatch(); + count = 0; + } } - public int hashCode() { - int hash = 17; - hash = hash * 31 + this.from.hashCode(); - hash = hash * 31 + this.to.hashCode(); - return hash; + public void executeRemaining() throws SQLException { + if (count > 0) { + count = 0; + stmt.executeBatch(); + } } -} \ No newline at end of file + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/UserRowCache.java b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java similarity index 61% rename from src/main/java/com/sk89q/worldguard/protection/databases/mysql/UserRowCache.java rename to src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java index 0ae2ebd7..b0730359 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/mysql/UserRowCache.java +++ b/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java @@ -17,11 +17,12 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.mysql; +package com.sk89q.worldguard.protection.managers.storage.sql; import com.google.common.collect.Lists; import com.sk89q.worldguard.internal.util.sql.StatementUtils; import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; import javax.annotation.Nullable; import java.sql.Connection; @@ -35,26 +36,66 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.logging.Logger; import static com.google.common.base.Preconditions.checkNotNull; -abstract class UserRowCache extends AbstractJob { +/** + * Stores a cache of entries from a table for fast lookup and + * creates new rows whenever required. + * + * @param the type of entry + */ +abstract class TableCache { + + private static final Logger log = Logger.getLogger(TableCache.class.getCanonicalName()); private static final int MAX_NUMBER_PER_QUERY = 100; private static final Object LOCK = new Object(); private final Map cache = new HashMap(); + private final DataSourceConfig config; + private final Connection conn; + private final String tableName; private final String fieldName; - protected UserRowCache(MySQLDatabaseImpl database, Connection conn, String fieldName) { - super(database, conn); + /** + * Create a new instance. + * + * @param config the data source config + * @param conn the connection to use + * @param tableName the table name + * @param fieldName the field name + */ + protected TableCache(DataSourceConfig config, Connection conn, String tableName, String fieldName) { + this.config = config; + this.conn = conn; + this.tableName = tableName; this.fieldName = fieldName; } + /** + * Convert from the type to the string representation. + * + * @param o the object + * @return the string representation + */ protected abstract String fromType(V o); + /** + * Convert from the string representation to the type. + * + * @param o the string + * @return the object + */ protected abstract V toType(String o); + /** + * Convert the object to the version that is stored as a key in the map. + * + * @param object the object + * @return the key version + */ protected abstract V toKey(V object); @Nullable @@ -62,6 +103,13 @@ public Integer find(V object) { return cache.get(object); } + /** + * Fetch from the database rows that match the given entries, otherwise + * create new entries and assign them an ID. + * + * @param entries a list of entries + * @throws SQLException thrown on SQL error + */ public void fetch(Collection entries) throws SQLException { synchronized (LOCK) { // Lock across all cache instances checkNotNull(entries); @@ -84,9 +132,9 @@ public void fetch(Collection entries) throws SQLException { try { PreparedStatement statement = closer.register(conn.prepareStatement( String.format( - "SELECT `user`.`id`, `user`.`" + fieldName + "` " + - "FROM `" + config.sqlTablePrefix + "user` AS `user` " + - "WHERE `" + fieldName + "` IN (%s)", + "SELECT id, " + fieldName + " " + + "FROM `" + config.getTablePrefix() + tableName + "` " + + "WHERE " + fieldName + " IN (%s)", StatementUtils.preparePlaceHolders(partition.size())))); String[] values = new String[partition.size()]; @@ -118,7 +166,7 @@ public void fetch(Collection entries) throws SQLException { Closer closer = Closer.create(); try { PreparedStatement statement = closer.register(conn.prepareStatement( - "INSERT INTO `" + config.sqlTablePrefix + "user` (`id`, `" + fieldName + "`) VALUES (null, ?)", + "INSERT INTO `" + config.getTablePrefix() + tableName + "` (id, " + fieldName + ") VALUES (null, ?)", Statement.RETURN_GENERATED_KEYS)); for (V entry : missing) { @@ -126,10 +174,10 @@ public void fetch(Collection entries) throws SQLException { statement.execute(); ResultSet generatedKeys = statement.getGeneratedKeys(); - if (generatedKeys.first()) { + if (generatedKeys.next()) { cache.put(toKey(entry), generatedKeys.getInt(1)); } else { - logger.warning("Could not get the database ID for user " + entry); + log.warning("Could not get the database ID for entry " + entry); } } } finally { @@ -139,9 +187,12 @@ public void fetch(Collection entries) throws SQLException { } } - static class NameRowCache extends UserRowCache { - protected NameRowCache(MySQLDatabaseImpl database, Connection conn) { - super(database, conn, "name"); + /** + * An index of user rows that utilize the name field. + */ + static class UserNameCache extends TableCache { + protected UserNameCache(DataSourceConfig config, Connection conn) { + super(config, conn, "user", "name"); } @Override @@ -160,9 +211,12 @@ protected String toKey(String object) { } } - static class UUIDRowCache extends UserRowCache { - protected UUIDRowCache(MySQLDatabaseImpl database, Connection conn) { - super(database, conn, "uuid"); + /** + * An index of user rows that utilize the UUID field. + */ + static class UserUuidCache extends TableCache { + protected UserUuidCache(DataSourceConfig config, Connection conn) { + super(config, conn, "user", "uuid"); } @Override @@ -181,4 +235,28 @@ protected UUID toKey(UUID object) { } } + /** + * An index of group rows. + */ + static class GroupNameCache extends TableCache { + protected GroupNameCache(DataSourceConfig config, Connection conn) { + super(config, conn, "group", "name"); + } + + @Override + protected String fromType(String o) { + return o; + } + + @Override + protected String toType(String o) { + return o; + } + + @Override + protected String toKey(String object) { + return object.toLowerCase(); + } + } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java b/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java index 7b32b6bc..32fe8944 100644 --- a/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java +++ b/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java @@ -19,25 +19,39 @@ package com.sk89q.worldguard.protection.regions; -import java.util.ArrayList; -import java.util.List; - import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.BlockVector2D; import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.protection.UnsupportedIntersectionException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A special region that is not quite "anywhere" (its volume is 0, it + * contains no positions, and it does not intersect with any other region). + * + *

Global regions, however, are used to specify a region with flags that + * are applied with the lowest priority.

+ */ public class GlobalProtectedRegion extends ProtectedRegion { + /** + * Create a new instance. + * + * @param id the ID + */ public GlobalProtectedRegion(String id) { super(id); min = new BlockVector(0, 0, 0); max = new BlockVector(0, 0, 0); } + @Override public List getPoints() { + // This doesn't make sense List pts = new ArrayList(); - pts.add(new BlockVector2D(min.getBlockX(),min.getBlockZ())); + pts.add(new BlockVector2D(min.getBlockX(), min.getBlockZ())); return pts; } @@ -48,18 +62,18 @@ public int volume() { @Override public boolean contains(Vector pt) { + // Global regions are handled separately so it must not contain any positions return false; } @Override - public String getTypeName() { - return "global"; + public RegionType getType() { + return RegionType.GLOBAL; } @Override - public List getIntersectingRegions( - List regions) - throws UnsupportedIntersectionException { + public List getIntersectingRegions(Collection regions) { + // Global regions are handled separately so it must not contain any positions return new ArrayList(); } diff --git a/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java b/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java index 767054bc..dd178b75 100644 --- a/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java +++ b/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java @@ -19,13 +19,15 @@ package com.sk89q.worldguard.protection.regions; -import java.util.ArrayList; -import java.util.List; - import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.BlockVector2D; import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.protection.UnsupportedIntersectionException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; /** * Represents a cuboid region that can be protected. @@ -37,9 +39,9 @@ public class ProtectedCuboidRegion extends ProtectedRegion { /** * Construct a new instance of this cuboid region. * - * @param id The region id - * @param pt1 The first point of this region - * @param pt2 The second point of this region + * @param id the region id + * @param pt1 the first point of this region + * @param pt2 the second point of this region */ public ProtectedCuboidRegion(String id, BlockVector pt1, BlockVector pt2) { super(id); @@ -47,34 +49,37 @@ public ProtectedCuboidRegion(String id, BlockVector pt1, BlockVector pt2) { } /** - * Given any two points, sets the minimum and maximum points + * Given any two points, sets the minimum and maximum points. * - * @param pt1 The first point of this region - * @param pt2 The second point of this region + * @param position1 the first point of this region + * @param position2 the second point of this region */ - private void setMinMaxPoints(BlockVector pt1, BlockVector pt2) { + private void setMinMaxPoints(BlockVector position1, BlockVector position2) { + checkNotNull(position1); + checkNotNull(position2); + List points = new ArrayList(); - points.add(pt1); - points.add(pt2); + points.add(position1); + points.add(position2); setMinMaxPoints(points); } /** * Set the lower point of the cuboid. * - * @param pt The point to set as the minimum point + * @param position the point to set as the minimum point */ - public void setMinimumPoint(BlockVector pt) { - setMinMaxPoints(pt, max); + public void setMinimumPoint(BlockVector position) { + setMinMaxPoints(position, max); } /** * Set the upper point of the cuboid. * - * @param pt The point to set as the maximum point + * @param position the point to set as the maximum point */ - public void setMaximumPoint(BlockVector pt) { - setMinMaxPoints(min, pt); + public void setMaximumPoint(BlockVector position) { + setMinMaxPoints(min, position); } @Override @@ -103,34 +108,15 @@ public boolean contains(Vector pt) { && z >= min.getBlockZ() && z < max.getBlockZ()+1; } - - /* - public boolean intersectsWith(ProtectedRegion region) throws UnsupportedIntersectionException { - - if (region instanceof ProtectedCuboidRegion) { - ProtectedCuboidRegion r1 = (ProtectedCuboidRegion) this; - ProtectedCuboidRegion r2 = (ProtectedCuboidRegion) region; - BlockVector min1 = r1.getMinimumPoint(); - BlockVector max1 = r1.getMaximumPoint(); - BlockVector min2 = r2.getMinimumPoint(); - BlockVector max2 = r2.getMaximumPoint(); - - return !(min1.getBlockX() > max2.getBlockX() - || min1.getBlockY() > max2.getBlockY() - || min1.getBlockZ() > max2.getBlockZ() - || max1.getBlockX() < min2.getBlockX() - || max1.getBlockY() < min2.getBlockY() - || max1.getBlockZ() < min2.getBlockZ()); - } else if (region instanceof ProtectedPolygonalRegion) { - throw new UnsupportedIntersectionException(); - } else { - throw new UnsupportedIntersectionException(); - } + @Override + public RegionType getType() { + return RegionType.CUBOID; } - */ @Override - public List getIntersectingRegions(List regions) throws UnsupportedIntersectionException { + public List getIntersectingRegions(Collection regions) { + checkNotNull(regions); + List intersectingRegions = new ArrayList(); for (ProtectedRegion region : regions) { @@ -139,28 +125,21 @@ public List getIntersectingRegions(List region // If both regions are Cuboids and their bounding boxes intersect, they intersect if (region instanceof ProtectedCuboidRegion) { intersectingRegions.add(region); - continue; } else if (region instanceof ProtectedPolygonalRegion) { // If either region contains the points of the other, // or if any edges intersect, the regions intersect - if (containsAny(region.getPoints()) - || region.containsAny(getPoints()) - || intersectsEdges(region)) { + if (containsAny(region.getPoints()) || region.containsAny(getPoints()) || intersectsEdges(region)) { intersectingRegions.add(region); - continue; } + } else if (region instanceof GlobalProtectedRegion) { + // Never intersects } else { - throw new UnsupportedOperationException("Not supported yet."); + throw new IllegalArgumentException("Not supported yet."); } } return intersectingRegions; } - @Override - public String getTypeName() { - return "cuboid"; - } - @Override public int volume() { int xLength = max.getBlockX() - min.getBlockX() + 1; @@ -169,4 +148,5 @@ public int volume() { return xLength * yLength * zLength; } + } \ No newline at end of file diff --git a/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java b/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java index 7b6d474b..0f9c8cbc 100644 --- a/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java +++ b/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java @@ -19,23 +19,25 @@ package com.sk89q.worldguard.protection.regions; -import java.util.ArrayList; -import java.util.List; - import com.sk89q.worldedit.BlockVector2D; import com.sk89q.worldedit.Vector; -import com.sk89q.worldguard.protection.UnsupportedIntersectionException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; public class ProtectedPolygonalRegion extends ProtectedRegion { - protected List points; - protected int minY; - protected int maxY; + private List points; + private int minY; + private int maxY; public ProtectedPolygonalRegion(String id, List points, int minY, int maxY) { super(id); - this.points = points; setMinMaxPoints(points, minY, maxY); + this.points = points; this.minY = min.getBlockY(); this.maxY = max.getBlockY(); } @@ -48,6 +50,8 @@ public ProtectedPolygonalRegion(String id, List points, int minY, * @param maxY The maximum y coordinate */ private void setMinMaxPoints(List points2D, int minY, int maxY) { + checkNotNull(points2D); + List points = new ArrayList(); int y = minY; for (BlockVector2D point2D : points2D) { @@ -57,18 +61,18 @@ private void setMinMaxPoints(List points2D, int minY, int maxY) { setMinMaxPoints(points); } + @Override public List getPoints() { return points; } - /** - * Checks to see if a point is inside this region. - */ @Override - public boolean contains(Vector pt) { - int targetX = pt.getBlockX(); //wide - int targetY = pt.getBlockY(); //height - int targetZ = pt.getBlockZ(); //depth + public boolean contains(Vector position) { + checkNotNull(position); + + int targetX = position.getBlockX(); // Width + int targetY = position.getBlockY(); // Height + int targetZ = position.getBlockZ(); // Depth if (targetY < minY || targetY > maxY) { return false; @@ -124,7 +128,14 @@ public boolean contains(Vector pt) { } @Override - public List getIntersectingRegions(List regions) throws UnsupportedIntersectionException { + public RegionType getType() { + return RegionType.POLYGON; + } + + @Override + public List getIntersectingRegions(Collection regions) { + checkNotNull(regions); + List intersectingRegions = new ArrayList(); for (ProtectedRegion region : regions) { @@ -133,67 +144,22 @@ public List getIntersectingRegions(List region if (region instanceof ProtectedPolygonalRegion || region instanceof ProtectedCuboidRegion) { // If either region contains the points of the other, // or if any edges intersect, the regions intersect - if (containsAny(region.getPoints()) - || region.containsAny(getPoints()) - || intersectsEdges(region)) { + if (containsAny(region.getPoints()) || region.containsAny(getPoints()) || intersectsEdges(region)) { intersectingRegions.add(region); - continue; } + } else if (region instanceof GlobalProtectedRegion) { + // Never intersects } else { - throw new UnsupportedOperationException("Not supported yet."); + throw new IllegalArgumentException("Not supported yet."); } } return intersectingRegions; } - - /** - * Return the type of region as a user-friendly name. - * - * @return type of region - */ - @Override - public String getTypeName() { - return "polygon"; - } - @Override public int volume() { - int volume = 0; - // TODO: Fix this - /*int numPoints = points.size(); - if (numPoints < 3) { - return 0; - } - - double area = 0; - int xa, z1, z2; - - for (int i = 0; i < numPoints; i++) { - xa = points.get(i).getBlockX(); - //za = points.get(i).getBlockZ(); - - if (points.get(i + 1) == null) { - z1 = points.get(0).getBlockZ(); - } else { - z1 = points.get(i + 1).getBlockZ(); - } - if (points.get(i - 1) == null) { - z2 = points.get(numPoints - 1).getBlockZ(); - } else { - z2 = points.get(i - 1).getBlockZ(); - } - - area = area + (xa * (z1 - z2)); - } - - xa = points.get(0).getBlockX(); - //za = points.get(0).getBlockZ(); - - area = area + (xa * (points.get(1).getBlockZ() - points.get(numPoints - 1).getBlockZ())); - - volume = (Math.abs(maxY - minY) + 1) * (int) Math.ceil((Math.abs(area) / 2));*/ - - return volume; + // TODO: Fix this -- the previous algorithm returned incorrect results, but the current state of this method is even worse + return 0; } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java b/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java index e3355391..01744050 100644 --- a/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java +++ b/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java @@ -24,45 +24,62 @@ import com.sk89q.worldedit.Vector; import com.sk89q.worldguard.LocalPlayer; import com.sk89q.worldguard.domains.DefaultDomain; -import com.sk89q.worldguard.protection.UnsupportedIntersectionException; import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.util.ChangeTracked; +import com.sk89q.worldguard.util.Normal; +import javax.annotation.Nullable; import java.awt.geom.Line2D; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; +import static com.google.common.base.Preconditions.checkNotNull; + /** - * Represents a region of any shape and size that can be protected. + * Represents a region that can be indexed and have spatial queries performed + * against it. + * + *

Instances can be modified and access from several threads at a time.

*/ -public abstract class ProtectedRegion implements Comparable { +public abstract class ProtectedRegion implements ChangeTracked, Comparable { + + private static final Pattern VALID_ID_PATTERN = Pattern.compile("^[A-Za-z0-9_,'\\-\\+/]{1,}$"); protected BlockVector min; protected BlockVector max; - private static final Pattern idPattern = Pattern.compile("^[A-Za-z0-9_,'\\-\\+/]{1,}$"); - - private String id; + private final String id; private int priority = 0; private ProtectedRegion parent; private DefaultDomain owners = new DefaultDomain(); private DefaultDomain members = new DefaultDomain(); - private Map, Object> flags = new ConcurrentHashMap, Object>(); + private ConcurrentMap, Object> flags = new ConcurrentHashMap, Object>(); + private boolean dirty = true; /** * Construct a new instance of this region. * - * @param id The id (name) of this region. + * @param id the name of this region + * @throws IllegalArgumentException thrown if the ID is invalid (see {@link #isValidId(String)} */ - public ProtectedRegion(String id) { - this.id = id; + ProtectedRegion(String id) { // Package private because we can't have people creating their own region types + checkNotNull(id); + + if (!isValidId(id)) { + throw new IllegalArgumentException("Invalid region ID: " + id); + } + + this.id = Normal.normalize(id); } /** - * Sets the minimum and maximum points of the bounding box for a region + * Set the minimum and maximum points of the bounding box for a region * - * @param points The points to set. Must have at least one element. + * @param points the points to set with at least one entry */ protected void setMinMaxPoints(List points) { int minX = points.get(0).getBlockX(); @@ -85,39 +102,45 @@ protected void setMinMaxPoints(List points) { if (y > maxY) maxY = y; if (z > maxZ) maxZ = z; } - + + setDirty(true); min = new BlockVector(minX, minY, minZ); max = new BlockVector(maxX, maxY, maxZ); } /** - * Gets the id of this region + * Gets the name of this region * - * @return the id + * @return the name */ public String getId() { return id; } /** - * Get the lower point of the cuboid. + * Get a vector containing the smallest X, Y, and Z components for the + * corner of the axis-aligned bounding box that contains this region. * - * @return min point + * @return the minimum point */ public BlockVector getMinimumPoint() { return min; } /** - * Get the upper point of the cuboid. + * Get a vector containing the highest X, Y, and Z components for the + * corner of the axis-aligned bounding box that contains this region. * - * @return max point + * @return the maximum point */ public BlockVector getMaximumPoint() { return max; } /** + * Get the priority of the region, where higher numbers indicate a higher + * priority. + * * @return the priority */ public int getPriority() { @@ -125,27 +148,36 @@ public int getPriority() { } /** - * @param priority the priority to setFlag + * Set the priority of the region, where higher numbers indicate a higher + * priority. + * + * @param priority the priority to set */ public void setPriority(int priority) { + setDirty(true); this.priority = priority; } /** - * @return the curParent + * Get the parent of the region, if one exists. + * + * @return the parent, or {@code null} */ + @Nullable public ProtectedRegion getParent() { return parent; } /** - * Set the curParent. This checks to make sure that it will not result - * in circular inheritance. + * Set the parent of this region. This checks to make sure that it will + * not result in circular inheritance. * - * @param parent the curParent to setFlag + * @param parent the new parent * @throws CircularInheritanceException when circular inheritance is detected */ - public void setParent(ProtectedRegion parent) throws CircularInheritanceException { + public void setParent(@Nullable ProtectedRegion parent) throws CircularInheritanceException { + setDirty(true); + if (parent == null) { this.parent = null; return; @@ -166,23 +198,38 @@ public void setParent(ProtectedRegion parent) throws CircularInheritanceExceptio this.parent = parent; } + /** + * Clear the parent (set the parent to {@code null}). + */ + public void clearParent() { + setDirty(true); + this.parent = null; + } /** - * @return the owners + * Get the domain that contains the owners of this region. + * + * @return the domain */ public DefaultDomain getOwners() { - return owners; } /** - * @param owners the owners to setFlag + * Set the owner domain. + * + * @param owners the new domain */ public void setOwners(DefaultDomain owners) { + checkNotNull(owners); + setDirty(true); this.owners = owners; } /** + * Get the domain that contains the members of this region, which does + * not automatically include the owners. + * * @return the members */ public DefaultDomain getMembers() { @@ -190,9 +237,13 @@ public DefaultDomain getMembers() { } /** - * @param members the members to setFlag + * Set the members domain. + * + * @param members the new domain */ public void setMembers(DefaultDomain members) { + checkNotNull(members); + setDirty(true); this.members = members; } @@ -212,6 +263,8 @@ public boolean hasMembersOrOwners() { * @return whether an owner */ public boolean isOwner(LocalPlayer player) { + checkNotNull(player); + if (owners.contains(player)) { return true; } @@ -233,8 +286,12 @@ public boolean isOwner(LocalPlayer player) { * * @param playerName player name to check * @return whether an owner + * @deprecated Names are deprecated */ + @Deprecated public boolean isOwner(String playerName) { + checkNotNull(playerName); + if (owners.contains(playerName)) { return true; } @@ -259,6 +316,8 @@ public boolean isOwner(String playerName) { * @return whether an owner or member */ public boolean isMember(LocalPlayer player) { + checkNotNull(player); + if (isOwner(player)) { return true; } @@ -285,8 +344,12 @@ public boolean isMember(LocalPlayer player) { * * @param playerName player name to check * @return whether an owner or member + * @deprecated Names are deprecated */ + @Deprecated public boolean isMember(String playerName) { + checkNotNull(playerName); + if (isOwner(playerName)) { return true; } @@ -308,13 +371,14 @@ public boolean isMember(String playerName) { } /** - * Checks whether a player is a member of the region - * or any of its parents. + * Checks whether a player is a member of the region or any of its parents. * * @param player player to check * @return whether an member */ public boolean isMemberOnly(LocalPlayer player) { + checkNotNull(player); + if (members.contains(player)) { return true; } @@ -334,32 +398,40 @@ public boolean isMemberOnly(LocalPlayer player) { /** * Get a flag's value. * - * @param The flag type - * @param The type of the flag's value - * @param flag The flag to check - * @return value or null if isn't defined + * @param flag the flag to check + * @return the value or null if isn't defined + * @param the flag type + * @param the type of the flag's value */ @SuppressWarnings("unchecked") + @Nullable public , V> V getFlag(T flag) { + checkNotNull(flag); + Object obj = flags.get(flag); V val; + if (obj != null) { val = (V) obj; } else { return null; } + return val; } /** * Set a flag's value. * - * @param The flag type - * @param The type of the flag's value - * @param flag The flag to check - * @param val The value to set + * @param flag the flag to check + * @param val the value to set + * @param the flag type + * @param the type of the flag's value */ - public , V> void setFlag(T flag, V val) { + public , V> void setFlag(T flag, @Nullable V val) { + checkNotNull(flag); + setDirty(true); + if (val == null) { flags.remove(flag); } else { @@ -370,30 +442,35 @@ public , V> void setFlag(T flag, V val) { /** * Get the map of flags. * - * @return The map of flags currently used for this region + * @return the map of flags currently used for this region */ public Map, Object> getFlags() { return flags; } /** - * Get the map of flags. + * Set the map of flags. * - * @param flags The flags to set + *

A copy of the map will be used.

+ * + * @param flags the flags to set */ public void setFlags(Map, Object> flags) { - this.flags = flags; + checkNotNull(flags); + + setDirty(true); + this.flags = new ConcurrentHashMap, Object>(flags); } /** - * Gets the 2D points for this region + * Get points of the region projected onto the X-Z plane. * - * @return The points for this region as (x, z) coordinates + * @return the points */ public abstract List getPoints(); /** - * Get the number of blocks in this region + * Get the number of blocks in this region. * * @return the volume of this region in blocks */ @@ -408,84 +485,79 @@ public void setFlags(Map, Object> flags) { public abstract boolean contains(Vector pt); /** - * Check to see if a point is inside this region. + * Check to see if a position is contained within this region. * - * @param pt The point to check - * @return Whether {@code pt} is in this region + * @param position the position to check + * @return whether {@code position} is in this region */ - public boolean contains(BlockVector2D pt) { - return contains(new Vector(pt.getBlockX(), min.getBlockY(), pt.getBlockZ())); + public boolean contains(BlockVector2D position) { + checkNotNull(position); + return contains(new Vector(position.getBlockX(), min.getBlockY(), position.getBlockZ())); } /** * Check to see if a point is inside this region. * - * @param x The x coordinate to check - * @param y The y coordinate to check - * @param z The z coordinate to check - * @return Whether this region contains the points at the given coordinate + * @param x the x coordinate to check + * @param y the y coordinate to check + * @param z the z coordinate to check + * @return whether this region contains the point */ public boolean contains(int x, int y, int z) { return contains(new Vector(x, y, z)); } /** - * Check to see if any of the 2D points are inside this region. + * Check to see if any of the points are inside this region projected + * onto the X-Z plane. * - * @param pts a list positions + * @param positions a list of positions * @return true if contained */ - public boolean containsAny(List pts) { - for (BlockVector2D pt : pts) { + public boolean containsAny(List positions) { + checkNotNull(positions); + + for (BlockVector2D pt : positions) { if (contains(pt)) { return true; } } + return false; } /** - * Compares to another region.
- *
- * Orders primarily by the priority, descending
- * Orders secondarily by the id, ascending + * Get the type of region. * - * @param other The region to compare to + * @return the type */ - @Override - public int compareTo(ProtectedRegion other) { - if (priority > other.priority) { - return -1; - } else if (priority < other.priority) { - return 1; - } - - return id.compareTo(other.id); - } + public abstract RegionType getType(); /** * Return the type of region as a user-friendly, lowercase name. * * @return type of region + * @deprecated use {@link #getType()} */ - public abstract String getTypeName(); + @Deprecated + public final String getTypeName() { + return getType().getName(); + } /** - * Get a list of intersecting regions. + * Return a list of regions from the given list of regions that intersect + * with this region. * - * @param regions The list of regions to source from - * @return The elements of {@code regions} that intersect with this region - * @throws UnsupportedIntersectionException if an invalid intersection is detected + * @param regions a list of regions to source from + * @return the elements of {@code regions} that intersect with this region */ - public abstract List getIntersectingRegions( - List regions) - throws UnsupportedIntersectionException; + public abstract List getIntersectingRegions(Collection regions); /** * Checks if the bounding box of a region intersects with with the bounding - * box of this region + * box of this region. * - * @param region The region to check + * @param region the region to check * @return whether the given region intersects */ protected boolean intersectsBoundingBox(ProtectedRegion region) { @@ -507,9 +579,9 @@ protected boolean intersectsBoundingBox(ProtectedRegion region) { } /** - * Compares all edges of two regions to see if any of them intersect + * Compares all edges of two regions to see if any of them intersect. * - * @param region The region to check + * @param region the region to check * @return whether any edges of a region intersect */ protected boolean intersectsEdges(ProtectedRegion region) { @@ -540,15 +612,27 @@ protected boolean intersectsEdges(ProtectedRegion region) { return false; } - /** - * Checks to see if the given ID is accurate. - * - * @param id The id to check - * @see #idPattern - * @return Whether the region id given is valid - */ - public static boolean isValidId(String id) { - return idPattern.matcher(id).matches(); + @Override + public boolean isDirty() { + return dirty || owners.isDirty() || members.isDirty(); + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + owners.setDirty(dirty); + members.setDirty(dirty); + } + + @Override + public int compareTo(ProtectedRegion other) { + if (getPriority() > other.getPriority()) { + return -1; + } else if (getPriority() < other.getPriority()) { + return 1; + } + + return getId().compareTo(other.getId()); } @Override @@ -556,9 +640,6 @@ public int hashCode(){ return id.hashCode(); } - /** - * Returns whether this region has the same ID as another region. - */ @Override public boolean equals(Object obj) { if (!(obj instanceof ProtectedRegion)) { @@ -569,8 +650,27 @@ public boolean equals(Object obj) { return other.getId().equals(getId()); } + @Override + public String toString() { + return "ProtectedRegion{" + + "id='" + id + "', " + + "type='" + getType() + '\'' + + '}'; + } + /** - * Thrown when setting a curParent would create a circular inheritance + * Checks to see if the given ID is a valid ID. + * + * @param id the id to check + * @return whether the region id given is valid + */ + public static boolean isValidId(String id) { + checkNotNull(id); + return VALID_ID_PATTERN.matcher(id).matches(); + } + + /** + * Thrown when setting a parent would create a circular inheritance * situation. */ public static class CircularInheritanceException extends Exception { diff --git a/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java b/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java new file mode 100644 index 00000000..5e57e7c3 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +/** + * An enum of supported region types. + */ +public enum RegionType { + + // Do not change the names + CUBOID("cuboid"), + POLYGON("poly2d"), + GLOBAL("global"); + + private final String name; + + /** + * Create a new instance. + * + * @param name the region name + */ + RegionType(String name) { + this.name = name; + } + + /** + * Get the name of the region. + * + * @return the name of the region + */ + public String getName() { + return name; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/util/DomainInputResolver.java b/src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java similarity index 98% rename from src/main/java/com/sk89q/worldguard/protection/databases/util/DomainInputResolver.java rename to src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java index c4ff7a75..57dc66b4 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/util/DomainInputResolver.java +++ b/src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.util; +package com.sk89q.worldguard.protection.util; import com.google.common.base.Function; import com.google.common.base.Joiner; diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/util/UnresolvedNamesException.java b/src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java similarity index 95% rename from src/main/java/com/sk89q/worldguard/protection/databases/util/UnresolvedNamesException.java rename to src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java index 25f586c7..c2a28777 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/util/UnresolvedNamesException.java +++ b/src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.util; +package com.sk89q.worldguard.protection.util; /** * Thrown when there are unresolved names. diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MigrationException.java b/src/main/java/com/sk89q/worldguard/protection/util/migrator/MigrationException.java similarity index 91% rename from src/main/java/com/sk89q/worldguard/protection/databases/migrator/MigrationException.java rename to src/main/java/com/sk89q/worldguard/protection/util/migrator/MigrationException.java index 84aef164..9ea57a4a 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/MigrationException.java +++ b/src/main/java/com/sk89q/worldguard/protection/util/migrator/MigrationException.java @@ -17,10 +17,12 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.migrator; +package com.sk89q.worldguard.protection.util.migrator; +/** + * Thrown when a migration fails. + */ public class MigrationException extends Exception { - private static final long serialVersionUID = 1L; public MigrationException() { super(); @@ -37,4 +39,5 @@ public MigrationException(String message, Throwable cause) { public MigrationException(Throwable cause) { super(cause); } + } diff --git a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/UUIDMigrator.java b/src/main/java/com/sk89q/worldguard/protection/util/migrator/UUIDMigrator.java similarity index 99% rename from src/main/java/com/sk89q/worldguard/protection/databases/migrator/UUIDMigrator.java rename to src/main/java/com/sk89q/worldguard/protection/util/migrator/UUIDMigrator.java index 1788c4cd..1071916a 100644 --- a/src/main/java/com/sk89q/worldguard/protection/databases/migrator/UUIDMigrator.java +++ b/src/main/java/com/sk89q/worldguard/protection/util/migrator/UUIDMigrator.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package com.sk89q.worldguard.protection.databases.migrator; +package com.sk89q.worldguard.protection.util.migrator; import com.google.common.base.Predicate; import com.sk89q.squirrelid.Profile; diff --git a/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java b/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java new file mode 100644 index 00000000..c7baaf77 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java @@ -0,0 +1,42 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +/** + * An object that keeps track of a dirty flag that is set to true when changes + * are made to this object. + */ +public interface ChangeTracked { + + /** + * Tests whether changes have been made. + * + * @return true if changes have been made + */ + boolean isDirty(); + + /** + * Set whether changes have been made. + * + * @param dirty a new dirty state + */ + void setDirty(boolean dirty); + +} diff --git a/src/main/java/com/sk89q/worldguard/util/Normal.java b/src/main/java/com/sk89q/worldguard/util/Normal.java new file mode 100644 index 00000000..50d60d0f --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/Normal.java @@ -0,0 +1,119 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import javax.annotation.Nullable; +import java.text.Normalizer; +import java.text.Normalizer.Form; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Normal names are strings that are considered equal after they have been + * normalized using Unicode's NFC form and made lowercase. + */ +public final class Normal { + + private final String name; + @Nullable + private final String normal; + + /** + * Create a new instance. + * + * @param name a new instance + */ + private Normal(String name) { + checkNotNull(name); + + this.name = name; + String normal = normalize(name); + if (!normal.equals(name)) { // Simple comparison + this.normal = normal; + } else { + this.normal = null; + } + } + + /** + * Get the original name before normalization. + * + * @return the original name before normalization + */ + public String getName() { + return name; + } + + /** + * Get the normalized name. + * + * @return the normal name + */ + public String getNormal() { + return normal != null ? normal : name; + } + + /** + * Normalize a string according to the rules of this class. + * + * @param name an string + * @return the normalized string + */ + public static String normalize(String name) { + return Normalizer.normalize(name.toLowerCase(), Form.NFC); + } + + /** + * Create a new instance. + * + * @param name the name + * @return an instance + */ + public static Normal normal(String name) { + return new Normal(name); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Normal that = (Normal) o; + + return getNormal().equals(that.getNormal()); + + } + + @Override + public int hashCode() { + return getNormal().hashCode(); + } + + /** + * Return the un-normalized name. + * + * @return the un-normalized name + */ + @Override + public String toString() { + return name; + } + +} diff --git a/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java b/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java new file mode 100644 index 00000000..144a7ee5 --- /dev/null +++ b/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java @@ -0,0 +1,145 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.sql; + +import com.jolbox.bonecp.BoneCPConfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Describes a data source. + */ +public class DataSourceConfig { + + private final String dsn; + private final String username; + private final String password; + private final String tablePrefix; + + /** + * Create a new instance. + * + * @param dsn the DSN + * @param username the username + * @param password the password + * @param tablePrefix the table prefix + */ + public DataSourceConfig(String dsn, String username, String password, String tablePrefix) { + checkNotNull(dsn); + checkNotNull(username); + checkNotNull(password); + checkNotNull(tablePrefix); + + this.dsn = dsn; + this.username = username; + this.password = password; + this.tablePrefix = tablePrefix; + } + + /** + * Get the DSN. + * + * @return the DSN + */ + public String getDsn() { + return dsn; + } + + /** + * Get the username. + * + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Get the password. + * + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * Get the table prefix. + * + * @return the table prefix + */ + public String getTablePrefix() { + return tablePrefix; + } + + /** + * Create a new instance with a new DSN. + * + * @param dsn a new DSN string + * @return a new instance + */ + public DataSourceConfig setDsn(String dsn) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new instance with a new username. + * + * @param username a new username + * @return a new instance + */ + public DataSourceConfig setUsername(String username) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new instance with a new password. + * + * @param password a new password + * @return a new instance + */ + public DataSourceConfig setPassword(String password) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new instance with a new table prefix. + * + * @param tablePrefix the new table prefix + * @return a new instance + */ + public DataSourceConfig setTablePrefix(String tablePrefix) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new BoneCP configuration object. + * + * @return a new configuration object + */ + public BoneCPConfig createBoneCPConfig() { + BoneCPConfig config = new BoneCPConfig(); + config.setJdbcUrl(dsn); + config.setUsername(username); + config.setPassword(password); + return config; + } + +} diff --git a/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql b/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql index 46789055..b1df899f 100644 --- a/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql +++ b/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql @@ -5,6 +5,10 @@ ALTER TABLE `${tablePrefix}region_players` DROP PRIMARY KEY, ADD PRIMARY KEY (`region_id`, `world_id`, `user_id`, `owner`); +ALTER TABLE `${tablePrefix}region_groups` + DROP PRIMARY KEY, + ADD PRIMARY KEY (`region_id`, `world_id`, `group_id`, `owner`); + -- Fix WORLDGUARD-3030 -- Adds UUID support @@ -14,4 +18,10 @@ ALTER TABLE `${tablePrefix}user` ALTER TABLE `${tablePrefix}user` CHANGE COLUMN `name` `name` VARCHAR(64) NULL COLLATE 'utf8_bin' AFTER `id`, ADD COLUMN `uuid` CHAR(36) NULL AFTER `name`, - ADD UNIQUE INDEX `uuid` (`uuid`); \ No newline at end of file + ADD UNIQUE INDEX `uuid` (`uuid`); + +-- Strings with differing numbers of trailing spaces are equal in MySQL +-- The domains have been updated to trim strings + +UPDATE `${tablePrefix}user` SET `name` = TRIM(`name`); +UPDATE `${tablePrefix}group` SET `name` = TRIM(`name`); \ No newline at end of file diff --git a/src/main/resources/migrations/region/sqlite/V1__Initial.sql b/src/main/resources/migrations/region/sqlite/V1__Initial.sql new file mode 100644 index 00000000..2b5b99e3 --- /dev/null +++ b/src/main/resources/migrations/region/sqlite/V1__Initial.sql @@ -0,0 +1,160 @@ + +CREATE TABLE "${tablePrefix}world" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + name TEXT NOT NULL + UNIQUE +); + + +CREATE TABLE "${tablePrefix}region" ( + id TEXT NOT NULL, + world_id INTEGER NOT NULL + REFERENCES "${tablePrefix}world" ( id ) ON DELETE CASCADE + ON UPDATE CASCADE, + type TEXT NOT NULL, + priority INTEGER NOT NULL, + parent TEXT DEFAULT ( NULL ) + --REFERENCES "${tablePrefix}region" ( id ) ON DELETE SET NULL + -- ON UPDATE CASCADE -- Not supported + , + PRIMARY KEY ( id, world_id ) +); + + +CREATE TABLE "${tablePrefix}user" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + name TEXT UNIQUE + DEFAULT ( NULL ), + uuid TEXT UNIQUE + DEFAULT ( NULL ) +); + + +CREATE TABLE "${tablePrefix}group" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + name TEXT NOT NULL + UNIQUE +); + + +CREATE TABLE "${tablePrefix}region_cuboid" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + min_x INTEGER NOT NULL, + min_y INTEGER NOT NULL, + min_z INTEGER NOT NULL, + max_x INTEGER NOT NULL, + max_y INTEGER NOT NULL, + max_z INTEGER NOT NULL, + PRIMARY KEY ( region_id, world_id ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_poly2d" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + min_y INTEGER NOT NULL, + max_y INTEGER NOT NULL, + PRIMARY KEY ( region_id, world_id ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_poly2d_point" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + x INTEGER NOT NULL, + z INTEGER NOT NULL, + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region_poly2d" ( region_id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_groups" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + group_id INTEGER NOT NULL + REFERENCES "${tablePrefix}group" ( id ) ON DELETE CASCADE + ON UPDATE CASCADE, + owner BOOLEAN NOT NULL, + PRIMARY KEY ( region_id, world_id, group_id ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_flag" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + flag TEXT NOT NULL, + value TEXT NOT NULL, + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_players" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + user_id INTEGER NOT NULL + REFERENCES "${tablePrefix}user" ( id ) ON DELETE CASCADE + ON UPDATE CASCADE, + owner BOOLEAN NOT NULL, + PRIMARY KEY ( region_id, world_id, user_id, owner ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE INDEX "idx_${tablePrefix}region_cuboid_region_id" ON "${tablePrefix}region_cuboid" ( + region_id +); + + +CREATE INDEX "idx_${tablePrefix}region_world_id" ON "${tablePrefix}region" ( + world_id +); + + +CREATE INDEX "idx_${tablePrefix}region_parent" ON "${tablePrefix}region" ( + parent +); + + +CREATE INDEX "idx_${tablePrefix}region_poly2d_region_id" ON "${tablePrefix}region_poly2d" ( + region_id +); + + +CREATE INDEX "idx_${tablePrefix}region_poly2d_point_region_world_id" ON "${tablePrefix}region_poly2d_point" ( + region_id, + world_id +); + + +CREATE INDEX "idx_${tablePrefix}region_groups_region_id" ON "${tablePrefix}region_groups" ( + region_id +); + + +CREATE INDEX "idx_${tablePrefix}region_groups_group_id" ON "${tablePrefix}region_groups" ( + group_id +); + + +CREATE INDEX "idx_${tablePrefix}region_flag_region_world_id" ON "${tablePrefix}region_flag" ( + region_id, + world_id, + flag +); + diff --git a/src/test/java/com/sk89q/worldguard/protection/FlatRegionOverlapTest.java b/src/test/java/com/sk89q/worldguard/protection/FlatRegionOverlapTest.java deleted file mode 100644 index cd26c221..00000000 --- a/src/test/java/com/sk89q/worldguard/protection/FlatRegionOverlapTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * WorldGuard, a suite of tools for Minecraft - * Copyright (C) sk89q - * Copyright (C) WorldGuard team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldguard.protection; - -import com.sk89q.worldguard.protection.managers.FlatRegionManager; -import com.sk89q.worldguard.protection.managers.RegionManager; - -public class FlatRegionOverlapTest extends RegionOverlapTest { - protected RegionManager createRegionManager() throws Exception { - return new FlatRegionManager(null); - } -} diff --git a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionPriorityTest.java b/src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java similarity index 76% rename from src/test/java/com/sk89q/worldguard/protection/PRTreeRegionPriorityTest.java rename to src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java index e5744c7d..3ae63cc9 100644 --- a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionPriorityTest.java +++ b/src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java @@ -19,11 +19,15 @@ package com.sk89q.worldguard.protection; -import com.sk89q.worldguard.protection.managers.PRTreeRegionManager; +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; -public class PRTreeRegionPriorityTest extends RegionPriorityTest { +public class HashMapIndexPriorityTest extends RegionPriorityTest { + + @Override protected RegionManager createRegionManager() throws Exception { - return new PRTreeRegionManager(null); + return new RegionManager(new MemoryRegionStore(), new HashMapIndex.Factory()); } + } diff --git a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionManagerTest.java b/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java similarity index 76% rename from src/test/java/com/sk89q/worldguard/protection/PRTreeRegionManagerTest.java rename to src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java index e83ebb6f..7a00755c 100644 --- a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionManagerTest.java +++ b/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java @@ -19,11 +19,15 @@ package com.sk89q.worldguard.protection; -import com.sk89q.worldguard.protection.managers.PRTreeRegionManager; +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; -public class PRTreeRegionManagerTest extends RegionOverlapTest { +public class HashMapIndexRegionOverlapTest extends RegionOverlapTest { + + @Override protected RegionManager createRegionManager() throws Exception { - return new PRTreeRegionManager(null); + return new RegionManager(new MemoryRegionStore(), new HashMapIndex.Factory()); } + } diff --git a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionEntryExitTest.java b/src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java similarity index 77% rename from src/test/java/com/sk89q/worldguard/protection/PRTreeRegionEntryExitTest.java rename to src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java index e7e4061c..6ff2a728 100644 --- a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionEntryExitTest.java +++ b/src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java @@ -19,12 +19,15 @@ package com.sk89q.worldguard.protection; -import com.sk89q.worldguard.protection.managers.PRTreeRegionManager; +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; + +public class HashMapIndexTest extends RegionOverlapTest { -public class PRTreeRegionEntryExitTest extends RegionEntryExitTest { @Override protected RegionManager createRegionManager() throws Exception { - return new PRTreeRegionManager(null); + return new RegionManager(new MemoryRegionStore(), new HashMapIndex.Factory()); } + } diff --git a/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java b/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java index 543794a2..24939297 100644 --- a/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java +++ b/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java @@ -79,7 +79,7 @@ public ApplicableRegionSet getApplicableSet() { private String getNextId() { id++; - return "#REGION_" + id; + return "REGION_" + id; } } diff --git a/src/test/java/com/sk89q/worldguard/protection/FlatRegionPriorityTest.java b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java similarity index 75% rename from src/test/java/com/sk89q/worldguard/protection/FlatRegionPriorityTest.java rename to src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java index 9536b43b..c67fb0c4 100644 --- a/src/test/java/com/sk89q/worldguard/protection/FlatRegionPriorityTest.java +++ b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java @@ -19,11 +19,15 @@ package com.sk89q.worldguard.protection; -import com.sk89q.worldguard.protection.managers.FlatRegionManager; import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; -public class FlatRegionPriorityTest extends RegionPriorityTest { +public class PriorityRTreeIndexTest extends RegionOverlapTest { + + @Override protected RegionManager createRegionManager() throws Exception { - return new FlatRegionManager(null); + return new RegionManager(new MemoryRegionStore(), new PriorityRTreeIndex.Factory()); } + } diff --git a/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java new file mode 100644 index 00000000..facb384c --- /dev/null +++ b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; + +public class PriorityRTreeRegionEntryExitTest extends RegionEntryExitTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionStore(), new PriorityRTreeIndex.Factory()); + } + +} diff --git a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionOverlapTest.java b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java similarity index 75% rename from src/test/java/com/sk89q/worldguard/protection/PRTreeRegionOverlapTest.java rename to src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java index 923ee3bd..1ebac794 100644 --- a/src/test/java/com/sk89q/worldguard/protection/PRTreeRegionOverlapTest.java +++ b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java @@ -19,11 +19,15 @@ package com.sk89q.worldguard.protection; -import com.sk89q.worldguard.protection.managers.PRTreeRegionManager; +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; -public class PRTreeRegionOverlapTest extends RegionOverlapTest { +public class PriorityRTreeRegionOverlapTest extends RegionOverlapTest { + + @Override protected RegionManager createRegionManager() throws Exception { - return new PRTreeRegionManager(null); + return new RegionManager(new MemoryRegionStore(), new PriorityRTreeIndex.Factory()); } + } diff --git a/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java new file mode 100644 index 00000000..0fddcaf5 --- /dev/null +++ b/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionStore; + +public class PriorityRTreeRegionPriorityTest extends RegionPriorityTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionStore(), new PriorityRTreeIndex.Factory()); + } + +} diff --git a/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java b/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java index ba17582f..d7bad72a 100644 --- a/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java +++ b/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java @@ -37,6 +37,7 @@ import static org.junit.Assert.assertTrue; public abstract class RegionEntryExitTest { + static String ENTRY_ID = "entry_rg"; static String EXIT_ID = "exit_rg"; static String BUILDER_GROUP = "builder";