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.
This commit is contained in:
sk89q 2014-08-08 20:22:05 -07:00
parent b5dfed9e01
commit 7a01168781
87 changed files with 5098 additions and 4425 deletions

View File

@ -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<String, WorldConfiguration> worlds;
/**
* The global configuration for use when loading worlds
*/
private YAMLProcessor config;
/**
* List of people with god mode.
*/
@Deprecated
private Set<String> hasGodMode = new HashSet<String>();
/**
* List of people who can breathe underwater.
*/
private Set<String> hasAmphibious = new HashSet<String>();
private boolean hasCommandBookGodMode = false;
@ -125,6 +106,25 @@ public ConfigurationManager(WorldGuardPlugin plugin) {
this.worlds = new ConcurrentHashMap<String, WorldConfiguration>();
}
/**
* 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");

View File

@ -1,155 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -300,7 +300,7 @@ private void appendWorldConfigurations(WorldGuardPlugin plugin, List<World> 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 {

View File

@ -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<CommandSender> 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<CommandSender>() {
@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.";
}
}

View File

@ -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);
}

View File

@ -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<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>();
List<RegionManager> managers = new ArrayList<RegionManager>();
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<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>();
List<RegionManager> managers = new ArrayList<RegionManager>();
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")
@ -1157,6 +1122,7 @@ public void migrateDB(CommandContext args, CommandSender sender) throws CommandE
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) {

View File

@ -0,0 +1,53 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Collection<RegionManager>> {
private final Collection<RegionManager> managers;
RegionManagerLoad(Collection<RegionManager> managers) {
checkNotNull(managers);
this.managers = managers;
}
RegionManagerLoad(RegionManager... manager) {
this(Arrays.asList(manager));
}
@Override
public Collection<RegionManager> call() throws IOException {
for (RegionManager manager : managers) {
manager.load();
}
return managers;
}
}

View File

@ -0,0 +1,53 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Collection<RegionManager>> {
private final Collection<RegionManager> managers;
RegionManagerSave(Collection<RegionManager> managers) {
checkNotNull(managers);
this.managers = managers;
}
RegionManagerSave(RegionManager... manager) {
this(Arrays.asList(manager));
}
@Override
public Collection<RegionManager> call() throws IOException {
for (RegionManager manager : managers) {
manager.save();
}
return managers;
}
}

View File

@ -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");
}
}

View File

@ -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=");

View File

@ -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<String> 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);
}
}

View File

@ -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<String> groups = new CopyOnWriteArraySet<String>();
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<String> 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;
}
}

View File

@ -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<UUID> uniqueIds = new CopyOnWriteArraySet<UUID>();
private final Set<String> names = new CopyOnWriteArraySet<String>();
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<String> getPlayers() {
return names;
return Collections.unmodifiableSet(names);
}
/**
@ -143,7 +149,7 @@ public Set<String> getPlayers() {
* @return the set of player UUIDs
*/
public Set<UUID> 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;
}
}

View File

@ -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.
* Represents the effective set of flags, owners, and members for a given
* spatial query.
*
* @author sk89q
* <p>An instance of this can be created using the spatial query methods
* available on {@link RegionManager}.</p>
*/
public class ApplicableRegionSet implements Iterable<ProtectedRegion> {
private Collection<ProtectedRegion> applicable;
private ProtectedRegion globalRegion;
private final SortedSet<ProtectedRegion> applicable;
@Nullable
private final ProtectedRegion globalRegion;
/**
* Construct the object.
*
* @param applicable The regions contained in this set
* @param globalRegion The global region, set aside for special handling.
* <p>A sorted set will be created to include the collection of regions.</p>
*
* @param applicable the regions contained in this set
* @param globalRegion the global region, set aside for special handling.
*/
public ApplicableRegionSet(Collection<ProtectedRegion> applicable,
ProtectedRegion globalRegion) {
public ApplicableRegionSet(Collection<ProtectedRegion> applicable, @Nullable ProtectedRegion globalRegion) {
this(new TreeSet<ProtectedRegion>(checkNotNull(applicable)), globalRegion);
}
/**
* Construct the object.
*
* @param applicable the regions contained in this set
* @param globalRegion the global region, set aside for special handling.
*/
public ApplicableRegionSet(SortedSet<ProtectedRegion> 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<ProtectedRegion> needsClear,
Set<ProtectedRegion> hasCleared, ProtectedRegion region) {
private void clearParents(Set<ProtectedRegion> needsClear, Set<ProtectedRegion> hasCleared, ProtectedRegion region) {
ProtectedRegion parent = region.getParent();
while (parent != null) {
@ -294,10 +322,13 @@ private void clearParents(Set<ProtectedRegion> 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 <T extends Flag<V>, V> V getFlag(T flag) {
return getFlag(flag, null);
}
@ -308,10 +339,13 @@ public <T extends Flag<V>, 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 <T extends Flag<V>, V> V getFlag(T flag, LocalPlayer groupPlayer) {
@Nullable
public <T extends Flag<V>, 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 <T extends Flag<V>, 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<ProtectedRegion, ?> 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<ProtectedRegion> iterator() {
return applicable.iterator();
}
}

View File

@ -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<String, RegionManager> managers;
/**
* Stores the list of modification dates for the world files. This allows
* WorldGuard to reload files as needed.
*/
private HashMap<String, Long> lastModified;
/**
* Construct the object.
*
* @param plugin The plugin instance
*/
public GlobalRegionManager(WorldGuardPlugin plugin) {
this.plugin = plugin;
config = plugin.getGlobalStateManager();
managers = new ConcurrentHashMap<String, RegionManager>();
lastModified = new HashMap<String, Long>();
}
/**
* 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;
public void unload(String name) {
container.unload(name);
}
try {
if (file.lastModified() > oldDate) {
World world = plugin.getServer().getWorld(name);
if (world != null) {
load(world);
}
}
} catch (Exception ignore) {
}
}
public void unload() {
container.unloadAll();
}
/**
* Get the region manager for a particular world.
*
* @param world The world to get a RegionManager for
* @return The region manager.
*/
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 container.get(world.getName());
}
return manager;
}
/**
* Get a list of loaded region managers.
*
* @return an unmodifiable list
*/
public List<RegionManager> getLoaded() {
List<RegionManager> list = new ArrayList<RegionManager>();
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,6 +130,7 @@ public boolean canConstruct(Player player, Location loc) {
if (!hasBypass(player, world)) {
RegionManager mgr = get(world);
if (mgr != null) {
final ApplicableRegionSet applicableRegions = mgr.getApplicableRegions(BukkitUtil.toVector(loc));
if (!applicableRegions.canBuild(localPlayer)) {
return false;
@ -354,31 +139,16 @@ public boolean canConstruct(Player player, Location loc) {
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);
}
}

View File

@ -0,0 +1,174 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<Normal, RegionManager> mapping = new ConcurrentHashMap<Normal, RegionManager>();
private final Object lock = new Object();
private final EnumMap<DriverType, RegionStoreDriver> drivers = new EnumMap<DriverType, RegionStoreDriver>(DriverType.class);
private final RegionStoreDriver defaultDriver;
private final Supplier<? extends ConcurrentRegionIndex> 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<Normal, RegionManager> 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<RegionManager> getLoaded() {
return Collections.unmodifiableList(new ArrayList<RegionManager>(mapping.values()));
}
private class BackgroundSaver extends TimerTask {
@Override
public void run() {
synchronized (lock) {
// Block loading of new region managers
for (Map.Entry<Normal, RegionManager> 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);
}
}
}
}
}
}

View File

@ -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;
}

View File

@ -1,226 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion>(getRegions())));
}
@Override
public final ListenableFuture<?> save(RegionManager manager, boolean async) throws RejectedExecutionException {
ListenableFuture<?> future = submitSaveTask(new HashMap<String, ProtectedRegion>(manager.getRegions()));
if (!async) {
blockOnSave(future);
}
return future;
}
/**
* Submit a load task and return a future for it.
*
* <p>If a load task is already queued then that load task's future will
* be returned.</p>
*
* @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<Object>() {
@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.
*
* <p>If a save task is already queued then that save task's future will
* be returned.</p>
*
* @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<String, ProtectedRegion> 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<Object>() {
@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.
*
* <p>{@link #setRegions(Map)} must not be called until loading
* has completed and the provided map is in its completed state.</p>
*/
protected abstract void performSave() throws ProtectionDatabaseException;
/**
* Stores information about the a queued task.
*/
private static class QueuedTask {
private boolean started = false;
private ListenableFuture<?> future;
}
}

View File

@ -1,60 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -1,357 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, StateFlag> legacyFlagCodes = new HashMap<String, StateFlag>();
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<String,ProtectedRegion> 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<String,ProtectedRegion> regions =
new HashMap<String,ProtectedRegion>();
Map<ProtectedRegion,String> parentSets =
new LinkedHashMap<ProtectedRegion, String>();
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<String> entries = new ArrayReader<String>(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<ProtectedRegion, String> 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<String,ProtectedRegion> getRegions() {
return regions;
}
public void setRegions(Map<String,ProtectedRegion> regions) {
this.regions = regions;
}
}

View File

@ -1,110 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>This call will block.</p>
*
* @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.
*
* <p>{@code async} is merely a suggestion and it may be ignored by
* implementations if it is not supported.</p>
*
* @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.
*
* <p>This call will block.</p>
*
* @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.
*
* <p>{@code async} is merely a suggestion and it may be ignored by
* implementations if it is not supported.</p>
*
* @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<String,ProtectedRegion> getRegions();
/**
* Set the list of regions.
*
* @param regions The regions to be applied to this ProtectionDatabase
*/
public void setRegions(Map<String,ProtectedRegion> regions);
}

View File

@ -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;

View File

@ -1,62 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<MigratorKey, Class<? extends AbstractDatabaseMigrator>> migrators =
new HashMap<MigratorKey, Class<? extends AbstractDatabaseMigrator>>();
public static Map<MigratorKey, Class<? extends AbstractDatabaseMigrator>> 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<String> getWorldsFromOld() throws MigrationException;
protected abstract Map<String, ProtectedRegion> 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);
}
}
}
}

View File

@ -1,97 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String> worlds;
public MySQLToYAMLMigrator(WorldGuardPlugin plugin) throws MigrationException {
this.plugin = plugin;
this.worlds = new HashSet<String>();
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<String> getWorldsFromOld() {
return this.worlds;
}
@Override
protected Map<String, ProtectedRegion> 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);
}
}
}

View File

@ -1,86 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String,File> regionYamlFiles;
public YAMLToMySQLMigrator(WorldGuardPlugin plugin) {
this.plugin = plugin;
this.regionYamlFiles = new HashMap<String,File>();
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<String> getWorldsFromOld() {
return this.regionYamlFiles.keySet();
}
@Override
protected Map<String, ProtectedRegion> 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);
}
}
}

View File

@ -1,78 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -1,305 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion> regions = new HashMap<String, ProtectedRegion>();
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<String, String> placeHolders = new HashMap<String, String>();
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<String, ProtectedRegion> getRegions() {
return regions;
}
@Override
public void setRegions(Map<String, ProtectedRegion> 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) {
}
}
}
}
}

View File

@ -1,456 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion> cuboidRegions;
private Map<String, ProtectedRegion> poly2dRegions;
private Map<String, ProtectedRegion> globalRegions;
private Map<ProtectedRegion, String> parentSets;
RegionLoader(MySQLDatabaseImpl database, Connection conn) {
super(database, conn);
this.worldId = database.getWorldId();
}
public Map<String, ProtectedRegion> load() {
parentSets = new HashMap<ProtectedRegion, String>();
// 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<String, ProtectedRegion> 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<ProtectedRegion, String> 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<String, Object> regionFlags = new HashMap<String, Object>();
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 <T> void setFlag(ProtectedRegion region, Flag<T> 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<String, ProtectedRegion> regions =
new HashMap<String, ProtectedRegion>();
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<String, ProtectedRegion> regions = new HashMap<String, ProtectedRegion>();
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<String, ProtectedRegion> regions = new HashMap<String, ProtectedRegion>();
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<BlockVector2D> points = new ArrayList<BlockVector2D>();
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;
}
}

View File

@ -1,608 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion> regions;
private final NameRowCache usernameCache;
private final UUIDRowCache uuidCache;
private final int worldId;
RegionWriter(MySQLDatabaseImpl database, Connection conn, Map<String, ProtectedRegion> 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<String> regionsInDatabase = new ArrayList<String>();
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<String, ProtectedRegion> 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<String, ProtectedRegion> 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<String,Integer> getGroupIds(String... groupnames) {
Map<String,Integer> groups = new HashMap<String,Integer>();
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<Flag<?>, 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<String> 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 <V> Object marshalFlag(Flag<V> 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");
}
}

View File

@ -41,6 +41,15 @@ public EnumFlag(String name, Class<T> enumClass) {
this.enumClass = enumClass;
}
/**
* Get the enum class.
*
* @return the enum class
*/
public Class<T> getEnumClass() {
return enumClass;
}
private T findValue(String input) throws IllegalArgumentException {
if (input != null) {
input = input.toUpperCase();

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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) {

View File

@ -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<T> subFlag) {
this.subFlag = subFlag;
}
/**
* Get the flag that is stored in this flag.
*
* @return the stored flag type
*/
public Flag<T> getType() {
return subFlag;
}
@Override
public Set<T> parseInput(WorldGuardPlugin plugin, CommandSender sender,
String input) throws InvalidFlagFormat {

View File

@ -1,210 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion> regions = new ConcurrentHashMap<String, ProtectedRegion>();
/**
* Construct the manager.
*
* @param regionLoader The loader for regions
*/
public FlatRegionManager(ProtectionDatabase regionLoader) {
super(regionLoader);
}
@Override
public Map<String, ProtectedRegion> getRegions() {
return regions;
}
@Override
public void setRegions(Map<String, ProtectedRegion> regions) {
this.regions = new ConcurrentHashMap<String, ProtectedRegion>(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<String> removeRegions = new ArrayList<String>();
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<ProtectedRegion> appRegions =
new TreeSet<ProtectedRegion>();
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<ProtectedRegion> appRegions = new ArrayList<ProtectedRegion>();
appRegions.addAll(regions.values());
List<ProtectedRegion> intersectRegions;
try {
intersectRegions = checkRegion.getIntersectingRegions(appRegions);
} catch (Exception e) {
intersectRegions = new ArrayList<ProtectedRegion>();
}
return new ApplicableRegionSet(intersectRegions, regions.get("__global__"));
}*/
@Override
public List<String> getApplicableRegionsIDs(Vector pt) {
List<String> applicable = new ArrayList<String>();
for (Map.Entry<String, ProtectedRegion> entry : regions.entrySet()) {
if (entry.getValue().contains(pt)) {
applicable.add(entry.getKey());
}
}
return applicable;
}
@Override
public ApplicableRegionSet getApplicableRegions(ProtectedRegion checkRegion) {
List<ProtectedRegion> appRegions = new ArrayList<ProtectedRegion>();
appRegions.addAll(regions.values());
List<ProtectedRegion> intersectRegions;
try {
intersectRegions = checkRegion.getIntersectingRegions(appRegions);
} catch (Exception e) {
intersectRegions = new ArrayList<ProtectedRegion>();
}
return new ApplicableRegionSet(intersectRegions, regions.get("__global__"));
}
@Override
public boolean overlapsUnownedRegion(ProtectedRegion checkRegion, LocalPlayer player) {
List<ProtectedRegion> appRegions = new ArrayList<ProtectedRegion>();
for (ProtectedRegion other : regions.values()) {
if (other.getOwners().contains(player)) {
continue;
}
appRegions.add(other);
}
List<ProtectedRegion> intersectRegions;
try {
intersectRegions = checkRegion.getIntersectingRegions(appRegions);
} catch (UnsupportedIntersectionException e) {
intersectRegions = new ArrayList<ProtectedRegion>();
}
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;
}
}

View File

@ -1,243 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<ProtectedRegion> 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<String, ProtectedRegion> getRegions() {
return data.regions;
}
@Override
public void setRegions(Map<String, ProtectedRegion> regions) {
ConcurrentMap<String, ProtectedRegion> newRegions = new ConcurrentHashMap<String, ProtectedRegion>(regions);
PRTree<ProtectedRegion> tree = new PRTree<ProtectedRegion>(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<ProtectedRegion> tree = new PRTree<ProtectedRegion>(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<String> removeRegions = new ArrayList<String>();
for (ProtectedRegion curRegion : data.regions.values()) {
if (curRegion.getParent() == region) {
removeRegions.add(curRegion.getId().toLowerCase());
}
}
for (String remId : removeRegions) {
removeRegion(remId);
}
}
PRTree<ProtectedRegion> tree = new PRTree<ProtectedRegion>(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<ProtectedRegion> appRegions = new ArrayList<ProtectedRegion>();
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<ProtectedRegion> appRegions = new ArrayList<ProtectedRegion>();
appRegions.addAll(data.regions.values());
List<ProtectedRegion> intersectRegions;
try {
intersectRegions = checkRegion.getIntersectingRegions(appRegions);
} catch (Exception e) {
intersectRegions = new ArrayList<ProtectedRegion>();
}
return new ApplicableRegionSet(intersectRegions, data.regions.get("__global__"));
}
@Override
public List<String> getApplicableRegionsIDs(Vector pt) {
RegionsContainer data = this.data;
// Floor the vector to ensure we get accurate points
pt = pt.floor();
List<String> applicable = new ArrayList<String>();
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<ProtectedRegion> appRegions = new ArrayList<ProtectedRegion>();
for (ProtectedRegion other : data.regions.values()) {
if (other.getOwners().contains(player)) {
continue;
}
appRegions.add(other);
}
List<ProtectedRegion> intersectRegions;
try {
intersectRegions = checkRegion.getIntersectingRegions(appRegions);
} catch (Exception e) {
intersectRegions = new ArrayList<ProtectedRegion>();
}
return !intersectRegions.isEmpty();
}
@Override
public int size() {
return data.regions.size();
}
@Override
public int getRegionCountOfPlayer(LocalPlayer player) {
int count = 0;
for (Map.Entry<String, ProtectedRegion> entry : data.regions.entrySet()) {
if (entry.getValue().getOwners().contains(player)) {
count++;
}
}
return count;
}
private class RegionsContainer {
private final ConcurrentMap<String, ProtectedRegion> regions;
private final PRTree<ProtectedRegion> tree;
private RegionsContainer() {
regions = new ConcurrentHashMap<String, ProtectedRegion>();
tree = new PRTree<ProtectedRegion>(converter, BRANCH_FACTOR);
}
private RegionsContainer(ConcurrentMap<String, ProtectedRegion> regions, PRTree<ProtectedRegion> tree) {
this.regions = regions;
this.tree = tree;
}
}
}

View File

@ -0,0 +1,70 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.</p>
*/
class RegionCollectionConsumer implements Predicate<ProtectedRegion> {
private final Collection<ProtectedRegion> 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<ProtectedRegion> 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;
}
}

View File

@ -0,0 +1,83 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<ProtectedRegion> changed;
private final Set<ProtectedRegion> 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<ProtectedRegion> changed, Set<ProtectedRegion> removed) {
checkNotNull(changed);
checkNotNull(removed);
this.changed = changed;
this.removed = removed;
}
/**
* Get the regions that were changed or added.
*
* @return regions
*/
public Set<ProtectedRegion> getChanged() {
return Collections.unmodifiableSet(changed);
}
/**
* Get the regions that were removed.
*
* @return regions
*/
public Set<ProtectedRegion> 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());
}
}

View File

@ -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;
import static com.google.common.base.Preconditions.checkNotNull;
public final class RegionManager {
private final RegionStore store;
private final Supplier<? extends ConcurrentRegionIndex> indexFactory;
private ConcurrentRegionIndex index;
/**
* 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.
* Create a new index.
*
* @author sk89q
* @param store the region store
* @param indexFactory the factory for creating new instances of the index
*/
public abstract class RegionManager {
public RegionManager(RegionStore store, Supplier<? extends ConcurrentRegionIndex> indexFactory) {
checkNotNull(store);
checkNotNull(indexFactory);
protected ProtectionDatabase loader;
/**
* Construct the object.
*
* @param loader The loader for this region
*/
public RegionManager(ProtectionDatabase loader) {
this.loader = loader;
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
* <p>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.</p>
*
* @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<ProtectedRegion>(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
* <p>This method does nothing if there are no changes.</p>
*
* @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
* <p>This call is relatively heavy (and may block other threads),
* so refrain from calling it frequently.</p>
*
* @return a map of regions
*/
public ListenableFuture<?> save(boolean async) {
return loader.save(this, async);
public Map<String, ProtectedRegion> getRegions() {
Map<String, ProtectedRegion> map = new HashMap<String, ProtectedRegion>();
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)
* <p>The parents of the regions will also be added to the index, even
* if they are not in the provided map.</p>
*
* @param regions a map of regions
*/
public abstract Map<String, ProtectedRegion> getRegions();
public void setRegions(Map<String, ProtectedRegion> 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
* <p>The parents of the regions will also be added to the index, even
* if they are not in the provided map.</p>
*
* @param regions a collection of regions
*/
public abstract void setRegions(Map<String, ProtectedRegion> regions);
public void setRegions(Collection<ProtectedRegion> 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
* <p>The parents of the region will also be added to the index.</p>
*
* @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 #&lt;index&gt;
* 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<ProtectedRegion> 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<ProtectedRegion> 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<ProtectedRegion> regions = new TreeSet<ProtectedRegion>();
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<ProtectedRegion> regions = new TreeSet<ProtectedRegion>();
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<String> getApplicableRegionsIDs(Vector position) {
checkNotNull(position);
final List<String> names = new ArrayList<String>();
index.applyContaining(position, new Predicate<ProtectedRegion>() {
@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<ProtectedRegion>() {
@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<ProtectedRegion>() {
@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<ProtectedRegion> getValuesCopy() {
return new ArrayList<ProtectedRegion>(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<String> 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);
}

View File

@ -0,0 +1,40 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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
}

View File

@ -17,15 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -17,13 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.</p>
*/
public interface ConcurrentRegionIndex extends RegionIndex {
}

View File

@ -0,0 +1,263 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>This implementation supports concurrency to the extent that
* a {@link ConcurrentMap} does.</p>
*/
public class HashMapIndex extends AbstractRegionIndex implements ConcurrentRegionIndex {
private final ConcurrentMap<String, ProtectedRegion> regions = new ConcurrentHashMap<String, ProtectedRegion>();
private Set<ProtectedRegion> removed = new HashSet<ProtectedRegion>();
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<ProtectedRegion> 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<ProtectedRegion> remove(String id, RemovalStrategy strategy) {
checkNotNull(id);
checkNotNull(strategy);
Set<ProtectedRegion> removedSet = new HashSet<ProtectedRegion>();
synchronized (lock) {
ProtectedRegion removed = regions.remove(normalize(id));
if (removed != null) {
removedSet.add(removed);
Iterator<ProtectedRegion> 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<ProtectedRegion> consumer) {
for (ProtectedRegion region : regions.values()) {
if (!consumer.apply(region)) {
break;
}
}
}
@Override
public void applyContaining(final Vector position, final Predicate<ProtectedRegion> consumer) {
apply(new Predicate<ProtectedRegion>() {
@Override
public boolean apply(ProtectedRegion region) {
return !region.contains(position) || consumer.apply(region);
}
});
}
@Override
public void applyIntersecting(ProtectedRegion region, Predicate<ProtectedRegion> 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<ProtectedRegion> changed = new HashSet<ProtectedRegion>();
Set<ProtectedRegion> removed = this.removed;
for (ProtectedRegion region : regions.values()) {
if (region.isDirty()) {
changed.add(region);
region.setDirty(false);
}
}
this.removed = new HashSet<ProtectedRegion>();
return new RegionDifference(changed, removed);
}
}
@Override
public Collection<ProtectedRegion> 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<HashMapIndex> {
@Override
public HashMapIndex get() {
return new HashMapIndex();
}
}
}

View File

@ -0,0 +1,102 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.</p>
*
* <p>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.</p>
*/
public class PriorityRTreeIndex extends HashMapIndex {
private static final int BRANCH_FACTOR = 30;
private static final MBRConverter<ProtectedRegion> CONVERTER = new ProtectedRegionMBRConverter();
private PRTree<ProtectedRegion> tree;
public PriorityRTreeIndex() {
tree = new PRTree<ProtectedRegion>(CONVERTER, BRANCH_FACTOR);
tree.load(Collections.<ProtectedRegion>emptyList());
}
@Override
protected void rebuildIndex() {
PRTree<ProtectedRegion> newTree = new PRTree<ProtectedRegion>(CONVERTER, BRANCH_FACTOR);
newTree.load(values());
this.tree = newTree;
}
@Override
public void applyContaining(Vector position, Predicate<ProtectedRegion> consumer) {
// Floor the vector to ensure we get accurate points
position = position.floor();
Set<ProtectedRegion> seen = new HashSet<ProtectedRegion>();
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<ProtectedRegion> consumer) {
super.applyIntersecting(region, consumer);
}
/**
* A factory for new instances using this index.
*/
public static final class Factory implements Supplier<PriorityRTreeIndex> {
@Override
public PriorityRTreeIndex get() {
return new PriorityRTreeIndex();
}
}
}

View File

@ -0,0 +1,138 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>Indexes may be thread-unsafe.</p>
*/
public interface RegionIndex extends ChangeTracked {
/**
* Add a region to this index, replacing any existing one with the same
* name (equality determined using {@link Normal}).
*
* <p>The parents of the region will also be added to the index.</p>
*
* @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}).
*
* <p>The parents of the region will also be added to the index.</p>
*
* @param regions a collections of regions
*/
void addAll(Collection<ProtectedRegion> 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<ProtectedRegion> 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<ProtectedRegion> 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<ProtectedRegion> 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<ProtectedRegion> 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<ProtectedRegion> values();
}

View File

@ -0,0 +1,41 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,52 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<ProtectedRegion> regions = Collections.emptySet();
@Override
public Set<ProtectedRegion> loadAll() throws IOException {
return regions;
}
@Override
public void saveAll(Set<ProtectedRegion> regions) throws IOException {
this.regions = Collections.unmodifiableSet(new HashSet<ProtectedRegion>(regions));
}
@Override
public void saveChanges(RegionDifference difference) throws DifferenceSaveException, IOException {
throw new DifferenceSaveException();
}
}

View File

@ -0,0 +1,69 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <p>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.</p>
*
* <p>The provided map should have reasonably efficient
* {@code get()} and {@code put()} calls in order to maximize performance.
* </p>
*
* @return a setf loaded regions
* @throws IOException thrown on read error
*/
Set<ProtectedRegion> 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<ProtectedRegion> 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;
}

View File

@ -17,26 +17,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,125 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, Object> 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 <T> the flag's type
* @return true if the set succeeded
*/
public static <T> boolean trySetFlag(ProtectedRegion region, Flag<T> 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<String, ProtectedRegion> regions, Map<ProtectedRegion, String> parentSets) {
checkNotNull(regions);
checkNotNull(parentSets);
for (Map.Entry<ProtectedRegion, String> 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());
}
}
}
}

View File

@ -0,0 +1,103 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String> fetchAllExisting() {
List<String> names = new ArrayList<String>();
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;
}
}

View File

@ -0,0 +1,52 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@ -17,30 +17,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<String> fetchAllExisting() throws IOException;
}

View File

@ -0,0 +1,101 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String> fetchAllExisting() throws IOException {
Closer closer = Closer.create();
try {
List<String> names = new ArrayList<String>();
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();
}
}
}

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.protection.databases;
package com.sk89q.worldguard.protection.managers.storage.file;
import com.sk89q.util.yaml.YAMLFormat;
import com.sk89q.util.yaml.YAMLNode;
@ -26,13 +26,15 @@
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.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 com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
@ -40,10 +42,11 @@
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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -52,76 +55,70 @@
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 YAMLDatabase extends AbstractAsynchronousDatabase {
public class YamlFileStore implements RegionStore {
/**
* Used to dump YAML when an error occurs
*/
private static Yaml yaml;
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 Map<String, ProtectedRegion> regions;
private final File file;
private final Logger logger;
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
* @param logger a logger
* @throws ProtectionDatabaseException
* @throws FileNotFoundException
* @throws IOException thrown if the file cannot be written to
*/
public YAMLDatabase(File file, Logger logger) throws ProtectionDatabaseException, FileNotFoundException {
this.logger = logger;
public YamlFileStore(File file) throws IOException {
checkNotNull(file);
this.file = file;
if (!file.exists()) { // shouldn't be necessary, but check anyways
try {
if (!file.exists()) {
//noinspection ResultOfMethodCallIgnored
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);
public Set<ProtectedRegion> loadAll() throws IOException {
Map<String, ProtectedRegion> loaded = new HashMap<String, ProtectedRegion>();
try {
YAMLProcessor config = createYamlProcessor(file);
config.load();
} catch (IOException e) {
throw new ProtectionDatabaseException(e);
}
Map<String, YAMLNode> regionData = config.getNodes("regions");
// No regions are even configured
if (regionData == null) {
this.regions = new HashMap<String, ProtectedRegion>();
return;
return Collections.emptySet(); // No regions are even configured
}
// 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<String,ProtectedRegion> regions = new HashMap<String,ProtectedRegion>();
Map<ProtectedRegion, String> parentSets = new LinkedHashMap<ProtectedRegion, String>();
for (Map.Entry<String, YAMLNode> entry : regionData.entrySet()) {
String id = entry.getKey().toLowerCase().replace(".", "");
String id = entry.getKey();
YAMLNode node = entry.getValue();
String type = node.getString("type");
@ -129,146 +126,70 @@ public void performLoad() throws ProtectionDatabaseException {
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");
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 = checkNonNull(node.getVector("min"));
Vector pt2 = checkNonNull(node.getVector("max"));
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 = checkNonNull(node.getInt("min-y"));
Integer maxY = checkNonNull(node.getInt("max-y"));
Integer minY = checkNotNull(node.getInt("min-y"));
Integer maxY = checkNotNull(node.getInt("max-y"));
List<BlockVector2D> 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");
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 = checkNonNull(node.getInt("priority"));
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")));
regions.put(id, region);
loaded.put(id, region);
String parentId = node.getString("parent");
if (parentId != null) {
parentSets.put(region, parentId);
}
} catch (NullPointerException e) {
logger.log(Level.WARNING,
log.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()) +
"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
for (Map.Entry<ProtectedRegion, String> 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());
}
}
RegionStoreUtils.relinkParents(loaded, parentSets);
this.regions = regions;
}
private <V> 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 <T> void setFlag(ProtectedRegion region, Flag<T> 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;
return new HashSet<ProtectedRegion>(loaded.values());
}
@Override
protected void performSave() throws ProtectionDatabaseException {
public void saveAll(Set<ProtectedRegion> regions) throws IOException {
checkNotNull(regions);
File tempFile = new File(file.getParentFile(), file.getName() + ".tmp");
YAMLProcessor config = createYamlProcessor(tempFile);
config.clear();
for (Map.Entry<String, ProtectedRegion> entry : regions.entrySet()) {
ProtectedRegion region = entry.getValue();
YAMLNode node = config.addNode("regions." + entry.getKey());
YAMLNode regionsNode = config.addNode("regions");
Map<String, Object> map = regionsNode.getMap();
for (ProtectedRegion region : regions) {
Map<String, Object> nodeMap = new HashMap<String, Object>();
map.put(region.getId(), nodeMap);
YAMLNode node = new YAMLNode(nodeMap, false);
if (region instanceof ProtectedCuboidRegion) {
ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region;
@ -300,31 +221,55 @@ protected void performSave() throws ProtectionDatabaseException {
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.setHeader(FILE_HEADER);
config.save();
//noinspection ResultOfMethodCallIgnored
file.delete();
if (!tempFile.renameTo(file)) {
throw new ProtectionDatabaseException("Failed to rename temporary regions file to " + file.getAbsolutePath());
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<String, Object> getFlagData(ProtectedRegion region) {
Map<String, Object> flagData = new HashMap<String, Object>();
@ -336,6 +281,12 @@ private Map<String, Object> getFlagData(ProtectedRegion region) {
return flagData;
}
private void setFlags(ProtectedRegion region, YAMLNode flagsData) {
if (flagsData != null) {
RegionStoreUtils.trySetFlagMap(region, flagsData.getMap());
}
}
@SuppressWarnings("unchecked")
private <V> void addMarshalledFlag(Map<String, Object> flagData, Flag<V> flag, Object val) {
if (val == null) {
@ -345,10 +296,10 @@ private <V> void addMarshalledFlag(Map<String, Object> flagData, Flag<V> flag, O
flagData.put(flag.getName(), flag.marshal((V) val));
}
@SuppressWarnings("deprecation")
private Map<String, Object> getDomainData(DefaultDomain domain) {
Map<String, Object> domainData = new HashMap<String, Object>();
//noinspection deprecation
setDomainData(domainData, "players", domain.getPlayers());
setDomainData(domainData, "unique-ids", domain.getUniqueIds());
setDomainData(domainData, "groups", domain.getGroups());
@ -370,14 +321,15 @@ private void setDomainData(Map<String, Object> domainData, String key, Set<?> do
domainData.put(key, list);
}
@Override
public Map<String, ProtectedRegion> getRegions() {
return regions;
}
@Override
public void setRegions(Map<String, ProtectedRegion> regions) {
this.regions = regions;
/**
* 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);
}
/**
@ -386,17 +338,9 @@ public void setRegions(Map<String, ProtectedRegion> regions) {
* @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);
}
private static String toYamlOutput(Object object) {
try {
return yaml.dump(object).replaceAll("(?m)^", "\t");
return ERROR_DUMP_YAML.dump(object).replaceAll("(?m)^", "\t");
} catch (Throwable t) {
return "<error while dumping object>";
}

View File

@ -0,0 +1,335 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, ProtectedRegion> loaded = new HashMap<String, ProtectedRegion>();
private final Map<ProtectedRegion, String> parentSets = new HashMap<ProtectedRegion, String>();
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<ProtectedRegion> load() throws SQLException {
loadCuboids();
loadPolygons();
loadGlobals();
loadFlags();
loadDomainUsers();
loadDomainGroups();
RegionStoreUtils.relinkParents(loaded, parentSets);
return new HashSet<ProtectedRegion>(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<String, BlockVector2D> 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<BlockVector2D> 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<String, String, Object> data = HashBasedTable.create();
while (rs.next()) {
data.put(
rs.getString("region_id"),
rs.getString("flag"),
unmarshalFlagValue(rs.getString("value")));
}
for (Map.Entry<String, Map<String, Object>> 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);
}
}
}

View File

@ -0,0 +1,168 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<ProtectedRegion> 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<ProtectedRegion> changed, Set<ProtectedRegion> 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<ProtectedRegion> toUpdate, @Nullable Set<ProtectedRegion> toRemove) throws SQLException {
Map<String, String> 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<String> removeNames = new ArrayList<String>();
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<String, String> getExistingRegions() throws SQLException {
Map<String, String> existing = new HashMap<String, String>();
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();
}
}
}

View File

@ -0,0 +1,53 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,189 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<ProtectedRegion> all = new ArrayList<ProtectedRegion>();
private final List<ProtectedCuboidRegion> cuboids = new ArrayList<ProtectedCuboidRegion>();
private final List<ProtectedPolygonalRegion> polygons = new ArrayList<ProtectedPolygonalRegion>();
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<ProtectedRegion> 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<ProtectedCuboidRegion> 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<ProtectedPolygonalRegion> 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();
}
}

View File

@ -0,0 +1,88 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String> regionQueue = new ArrayList<String>();
private final List<String> cuboidGeometryQueue = new ArrayList<String>();
private final List<String> polygonGeometryQueue = new ArrayList<String>();
RegionRemover(DataUpdater updater) {
this.config = updater.config;
this.conn = updater.conn;
this.worldId = updater.worldId;
}
public void removeRegionsEntirely(Collection<String> 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<String> 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");
}
}

View File

@ -0,0 +1,341 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String> userNames = new HashSet<String>();
private final Set<UUID> userUuids = new HashSet<UUID>();
private final Set<String> groupNames = new HashSet<String>();
private final Yaml yaml = SQLRegionStore.createYaml();
private final List<ProtectedRegion> typesToUpdate = new ArrayList<ProtectedRegion>();
private final List<ProtectedRegion> parentsToSet = new ArrayList<ProtectedRegion>();
private final List<ProtectedRegion> flagsToReplace = new ArrayList<ProtectedRegion>();
private final List<ProtectedRegion> domainsToReplace = new ArrayList<ProtectedRegion>();
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<ProtectedRegion> 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<ProtectedRegion> 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<Flag<?>, 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<ProtectedRegion> 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<ProtectedRegion> 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<ProtectedRegion> 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 <V> Object marshalFlagValue(Flag<V> flag, Object val) {
return yaml.dump(flag.marshal((V) val));
}
}

View File

@ -0,0 +1,375 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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<String, String> placeHolders = new HashMap<String, String>();
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<ProtectedRegion> 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<ProtectedRegion> 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();
}
}
}

View File

@ -17,27 +17,38 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}
}

View File

@ -17,11 +17,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<V> extends AbstractJob {
/**
* Stores a cache of entries from a table for fast lookup and
* creates new rows whenever required.
*
* @param <V> the type of entry
*/
abstract class TableCache<V> {
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<V, Integer> cache = new HashMap<V, Integer>();
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<V> entries) throws SQLException {
synchronized (LOCK) { // Lock across all cache instances
checkNotNull(entries);
@ -84,9 +132,9 @@ public void fetch(Collection<V> 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<V> 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<V> 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<V> entries) throws SQLException {
}
}
static class NameRowCache extends UserRowCache<String> {
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<String> {
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<UUID> {
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<UUID> {
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<String> {
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();
}
}
}

View File

@ -19,23 +19,37 @@
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).
*
* <p>Global regions, however, are used to specify a region with flags that
* are applied with the lowest priority.</p>
*/
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<BlockVector2D> getPoints() {
// This doesn't make sense
List<BlockVector2D> pts = new ArrayList<BlockVector2D>();
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<ProtectedRegion> getIntersectingRegions(
List<ProtectedRegion> regions)
throws UnsupportedIntersectionException {
public List<ProtectedRegion> getIntersectingRegions(Collection<ProtectedRegion> regions) {
// Global regions are handled separately so it must not contain any positions
return new ArrayList<ProtectedRegion>();
}

View File

@ -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<Vector> points = new ArrayList<Vector>();
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<ProtectedRegion> getIntersectingRegions(List<ProtectedRegion> regions) throws UnsupportedIntersectionException {
public List<ProtectedRegion> getIntersectingRegions(Collection<ProtectedRegion> regions) {
checkNotNull(regions);
List<ProtectedRegion> intersectingRegions = new ArrayList<ProtectedRegion>();
for (ProtectedRegion region : regions) {
@ -139,28 +125,21 @@ public List<ProtectedRegion> getIntersectingRegions(List<ProtectedRegion> 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;
}
}

View File

@ -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<BlockVector2D> points;
protected int minY;
protected int maxY;
private List<BlockVector2D> points;
private int minY;
private int maxY;
public ProtectedPolygonalRegion(String id, List<BlockVector2D> 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<BlockVector2D> points, int minY,
* @param maxY The maximum y coordinate
*/
private void setMinMaxPoints(List<BlockVector2D> points2D, int minY, int maxY) {
checkNotNull(points2D);
List<Vector> points = new ArrayList<Vector>();
int y = minY;
for (BlockVector2D point2D : points2D) {
@ -57,18 +61,18 @@ private void setMinMaxPoints(List<BlockVector2D> points2D, int minY, int maxY) {
setMinMaxPoints(points);
}
@Override
public List<BlockVector2D> 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<ProtectedRegion> getIntersectingRegions(List<ProtectedRegion> regions) throws UnsupportedIntersectionException {
public RegionType getType() {
return RegionType.POLYGON;
}
@Override
public List<ProtectedRegion> getIntersectingRegions(Collection<ProtectedRegion> regions) {
checkNotNull(regions);
List<ProtectedRegion> intersectingRegions = new ArrayList<ProtectedRegion>();
for (ProtectedRegion region : regions) {
@ -133,67 +144,22 @@ public List<ProtectedRegion> getIntersectingRegions(List<ProtectedRegion> 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) {
// TODO: Fix this -- the previous algorithm returned incorrect results, but the current state of this method is even worse
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;
}
}

View File

@ -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.
*
* <p>Instances can be modified and access from several threads at a time.</p>
*/
public abstract class ProtectedRegion implements Comparable<ProtectedRegion> {
public abstract class ProtectedRegion implements ChangeTracked, Comparable<ProtectedRegion> {
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<Flag<?>, Object> flags = new ConcurrentHashMap<Flag<?>, Object>();
private ConcurrentMap<Flag<?>, Object> flags = new ConcurrentHashMap<Flag<?>, 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<Vector> points) {
int minX = points.get(0).getBlockX();
@ -86,38 +103,44 @@ protected void setMinMaxPoints(List<Vector> points) {
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 <T> The flag type
* @param <V> 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 <T> the flag type
* @param <V> the type of the flag's value
*/
@SuppressWarnings("unchecked")
@Nullable
public <T extends Flag<V>, 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 <T> The flag type
* @param <V> 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 <T> the flag type
* @param <V> the type of the flag's value
*/
public <T extends Flag<V>, V> void setFlag(T flag, V val) {
public <T extends Flag<V>, V> void setFlag(T flag, @Nullable V val) {
checkNotNull(flag);
setDirty(true);
if (val == null) {
flags.remove(flag);
} else {
@ -370,30 +442,35 @@ public <T extends Flag<V>, 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<Flag<?>, Object> getFlags() {
return flags;
}
/**
* Get the map of flags.
* Set the map of flags.
*
* @param flags The flags to set
* <p>A copy of the map will be used.</p>
*
* @param flags the flags to set
*/
public void setFlags(Map<Flag<?>, Object> flags) {
this.flags = flags;
checkNotNull(flags);
setDirty(true);
this.flags = new ConcurrentHashMap<Flag<?>, 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<BlockVector2D> 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<Flag<?>, 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<BlockVector2D> pts) {
for (BlockVector2D pt : pts) {
public boolean containsAny(List<BlockVector2D> positions) {
checkNotNull(positions);
for (BlockVector2D pt : positions) {
if (contains(pt)) {
return true;
}
}
return false;
}
/**
* Compares to another region.<br>
*<br>
* Orders primarily by the priority, descending<br>
* 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<ProtectedRegion> getIntersectingRegions(
List<ProtectedRegion> regions)
throws UnsupportedIntersectionException;
public abstract List<ProtectedRegion> getIntersectingRegions(Collection<ProtectedRegion> 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 {

View File

@ -0,0 +1,52 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldguard.protection.databases.util;
package com.sk89q.worldguard.protection.util;
/**
* Thrown when there are unresolved names.

View File

@ -17,10 +17,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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;

View File

@ -0,0 +1,42 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}

View File

@ -0,0 +1,119 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,145 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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
@ -15,3 +19,9 @@ 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`);
-- 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`);

View File

@ -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
);

View File

@ -1,29 +0,0 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -79,7 +79,7 @@ public ApplicableRegionSet getApplicableSet() {
private String getNextId() {
id++;
return "#REGION_" + id;
return "REGION_" + id;
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,33 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,33 @@
/*
* WorldGuard, a suite of tools for Minecraft
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@ -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";