com.sk89q.worldguard.bukkit.commands.RegionCommands.java Source code

Java tutorial

Introduction

Here is the source code for com.sk89q.worldguard.bukkit.commands.RegionCommands.java

Source

/*
 * 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.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.Location;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.bukkit.BukkitUtil;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.selections.CuboidSelection;
import com.sk89q.worldedit.bukkit.selections.Polygonal2DSelection;
import com.sk89q.worldedit.bukkit.selections.Selection;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.bukkit.RegionPermissionModel;
import com.sk89q.worldguard.bukkit.WorldConfiguration;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.databases.RegionDBUtil;
import com.sk89q.worldguard.protection.databases.migrators.AbstractDatabaseMigrator;
import com.sk89q.worldguard.protection.databases.migrators.MigrationException;
import com.sk89q.worldguard.protection.databases.migrators.MigratorKey;
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.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.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
import java.util.logging.Level;

/**
 * Implements the /region commands for WorldGuard.
 */
public final class RegionCommands {

    private final WorldGuardPlugin plugin;

    private MigratorKey migrateDBRequest;
    private Date migrateDBRequestDate;

    public RegionCommands(WorldGuardPlugin plugin) {
        this.plugin = plugin;
    }

    /**
     * Get the permission model to lookup permissions.
     * 
     * @param sender the sender
     * @return the permission model
     */
    private static RegionPermissionModel getPermissionModel(CommandSender sender) {
        return new RegionPermissionModel(WorldGuardPlugin.inst(), sender);
    }

    /**
     * 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.
     * 
     * @param args the arguments
     * @param sender the sender
     * @param flag the flag (such as 'w')
     * @return a world
     * @throws CommandException on error
     */
    private static World getWorld(CommandContext args, CommandSender sender, char flag) throws CommandException {
        if (args.hasFlag(flag)) {
            return WorldGuardPlugin.inst().matchWorld(sender, args.getFlag(flag));
        } else {
            if (sender instanceof Player) {
                return WorldGuardPlugin.inst().checkPlayer(sender).getWorld();
            } else {
                throw new CommandException("Please specify " + "the world with -" + flag + " world_name.");
            }
        }
    }

    /**
     * Validate a region ID.
     * 
     * @param id the id
     * @param allowGlobal whether __global__ is allowed
     * @return the id given
     * @throws CommandException thrown on an error
     */
    private static String validateRegionId(String id, boolean allowGlobal) throws CommandException {
        if (!ProtectedRegion.isValidId(id)) {
            throw new CommandException("The region name of '" + id + "' contains characters that are not allowed.");
        }

        if (!allowGlobal && id.equalsIgnoreCase("__global__")) { // Sorry, no global
            throw new CommandException("Sorry, you can't use __global__ here.");
        }

        return id;
    }

    /**
     * Get a protected region by a given name, otherwise throw a
     * {@link CommandException}.
     * 
     * <p>This also validates the region ID.</p>
     * 
     * @param regionManager the region manager
     * @param id the name to search
     * @param allowGlobal true to allow selecting __global__
     * @throws CommandException thrown if no region is found by the given name
     */
    private static ProtectedRegion findExistingRegion(RegionManager regionManager, String id, boolean allowGlobal)
            throws CommandException {
        // Validate the id
        validateRegionId(id, allowGlobal);

        ProtectedRegion region = regionManager.getRegionExact(id);

        // No region found!
        if (region == null) {
            // But we want a __global__, so let's create one
            if (id.equalsIgnoreCase("__global__")) {
                region = new GlobalProtectedRegion(id);
                regionManager.addRegion(region);
                return region;
            }

            throw new CommandException("No region could be found with the name of '" + id + "'.");
        }

        return region;
    }

    /**
     * Get the region at the player's location, if possible.
     * 
     * <p>If the player is standing in several regions, an error will be raised
     * and a list of regions will be provided.</p>
     * 
     * @param regionManager the region manager
     * @param player the player
     * @return a region
     * @throws CommandException thrown if no region was found
     */
    private static ProtectedRegion findRegionStandingIn(RegionManager regionManager, Player player)
            throws CommandException {
        return findRegionStandingIn(regionManager, player, false);
    }

    /**
     * Get the region at the player's location, if possible.
     * 
     * <p>If the player is standing in several regions, an error will be raised
     * and a list of regions will be provided.</p>
     * 
     * <p>If the player is not standing in any regions, the global region will
     * returned if allowGlobal is true and it exists.</p>
     * 
     * @param regionManager the region manager
     * @param player the player
     * @param allowGlobal whether to search for a global region if no others are found
     * @return a region
     * @throws CommandException thrown if no region was found
     */
    private static ProtectedRegion findRegionStandingIn(RegionManager regionManager, Player player,
            boolean allowGlobal) throws CommandException {
        ApplicableRegionSet set = regionManager.getApplicableRegions(player.getLocation());

        if (set.size() == 0) {
            if (allowGlobal) {
                ProtectedRegion global = findExistingRegion(regionManager, "__global__", true);
                player.sendMessage(ChatColor.GRAY + "You're not standing in any "
                        + "regions. Using the global region for this world instead.");
                return global;
            }
            throw new CommandException(
                    "You're not standing in a region." + "Specify an ID if you want to select a specific region.");
        } else if (set.size() > 1) {
            StringBuilder builder = new StringBuilder();
            boolean first = true;

            for (ProtectedRegion region : set) {
                if (!first) {
                    builder.append(", ");
                }
                first = false;
                builder.append(region.getId());
            }

            throw new CommandException("You're standing in several regions, and "
                    + "WorldGuard is not sure what you want.\nYou're in: " + builder.toString());
        }

        return set.iterator().next();
    }

    /**
     * Get a WorldEdit selection for a player, or emit an exception if there is none
     * available.
     * 
     * @param player the player
     * @return the selection
     * @throws CommandException thrown on an error
     */
    private static Selection getSelection(Player player) throws CommandException {
        WorldEditPlugin worldEdit = WorldGuardPlugin.inst().getWorldEdit();
        Selection selection = worldEdit.getSelection(player);

        if (selection == null) {
            throw new CommandException("Please select an area first. " + "Use WorldEdit to make a selection! "
                    + "(wiki: http://wiki.sk89q.com/wiki/WorldEdit).");
        }

        return selection;
    }

    /**
     * Create a {@link ProtectedRegion} from the player's selection.
     * 
     * @param player the player
     * @param id the ID of the new region
     * @return a new region
     * @throws CommandException thrown on an error
     */
    private static ProtectedRegion createRegionFromSelection(Player player, String id) throws CommandException {

        Selection selection = getSelection(player);

        // Detect the type of region from WorldEdit
        if (selection instanceof Polygonal2DSelection) {
            Polygonal2DSelection polySel = (Polygonal2DSelection) selection;
            int minY = polySel.getNativeMinimumPoint().getBlockY();
            int maxY = polySel.getNativeMaximumPoint().getBlockY();
            return new ProtectedPolygonalRegion(id, polySel.getNativePoints(), minY, maxY);
        } else if (selection instanceof CuboidSelection) {
            BlockVector min = selection.getNativeMinimumPoint().toBlockVector();
            BlockVector max = selection.getNativeMaximumPoint().toBlockVector();
            return new ProtectedCuboidRegion(id, min, max);
        } else {
            throw new CommandException("Sorry, you can only use cuboids and polygons for WorldGuard regions.");
        }
    }

    /**
     * Save the region database.
     * 
     * @param sender the sender
     * @param regionManager the region manager
     * @param silent whether to suppress messages sent to the player
     * @throws CommandException throw on an error
     */
    private ListenableFuture<?> commitChanges(final CommandSender sender, RegionManager regionManager,
            final World world, final boolean silent) throws CommandException {
        ListenableFuture<?> future;

        try {
            future = regionManager.save(true);
        } catch (RejectedExecutionException e) {
            throw new CommandException(
                    "There are too many interleaved load and save tasks currently queued. Please try again later.");
        }

        if (!silent) {
            sender.sendMessage(ChatColor.GRAY + "(A save of the region data has been queued.)");
        }

        Futures.addCallback(future, new FutureCallback<Object>() {
            @Override
            public void onSuccess(Object o) {
                if (!silent) {
                    sender.sendMessage(ChatColor.YELLOW + "Successfully saved the region data for the '"
                            + world.getName() + "' world.");
                }
            }

            @Override
            public void onFailure(Throwable throwable) {
                sender.sendMessage(ChatColor.RED + "Failed to save region data for the '" + world.getName()
                        + "' world: " + throwable.getMessage());
                plugin.getLogger().log(Level.WARNING, "Failed to save region data", throwable);
            }
        });

        return future;
    }

    /**
     * Load the region database.
     * 
     * @param sender the sender
     * @param regionManager the region manager
     * @param silent whether to suppress messages sent to the player
     * @throws CommandException throw on an error
     */
    private ListenableFuture<?> reloadChanges(final CommandSender sender, RegionManager regionManager,
            final World world, final boolean silent) throws CommandException {
        ListenableFuture<?> future;

        try {
            future = regionManager.load(true);
        } catch (RejectedExecutionException e) {
            throw new CommandException(
                    "There are too many interleaved load and save tasks currently queued. Please try again later.");
        }

        if (!silent) {
            sender.sendMessage(ChatColor.GRAY + "(A load of the region data has been queued.)");
        }

        Futures.addCallback(future, new FutureCallback<Object>() {
            @Override
            public void onSuccess(Object o) {
                if (!silent) {
                    sender.sendMessage(ChatColor.YELLOW + "Successfully loaded the region data for the '"
                            + world.getName() + "' world.");
                }
            }

            @Override
            public void onFailure(Throwable throwable) {
                sender.sendMessage(ChatColor.RED + "Failed to load region data for the '" + world.getName()
                        + "' world: " + throwable.getMessage());
                plugin.getLogger().log(Level.WARNING, "Failed to load region data", throwable);
            }
        });

        return future;
    }

    /**
     * Set a player's selection to a given region.
     * 
     * @param player the player
     * @param region the region
     * @throws CommandException thrown on a command error
     */
    private static void setPlayerSelection(Player player, ProtectedRegion region) throws CommandException {
        WorldEditPlugin worldEdit = WorldGuardPlugin.inst().getWorldEdit();

        World world = player.getWorld();

        // Set selection
        if (region instanceof ProtectedCuboidRegion) {
            ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region;
            Vector pt1 = cuboid.getMinimumPoint();
            Vector pt2 = cuboid.getMaximumPoint();
            CuboidSelection selection = new CuboidSelection(world, pt1, pt2);
            worldEdit.setSelection(player, selection);
            player.sendMessage(ChatColor.YELLOW + "Region selected as a cuboid.");

        } else if (region instanceof ProtectedPolygonalRegion) {
            ProtectedPolygonalRegion poly2d = (ProtectedPolygonalRegion) region;
            Polygonal2DSelection selection = new Polygonal2DSelection(world, poly2d.getPoints(),
                    poly2d.getMinimumPoint().getBlockY(), poly2d.getMaximumPoint().getBlockY());
            worldEdit.setSelection(player, selection);
            player.sendMessage(ChatColor.YELLOW + "Region selected as a polygon.");

        } else if (region instanceof GlobalProtectedRegion) {
            throw new CommandException("Can't select global regions! " + "That would cover the entire world.");

        } else {
            throw new CommandException("Unknown region type: " + region.getClass().getCanonicalName());
        }
    }

    /**
     * Utility method to set a flag.
     * 
     * @param region the region
     * @param flag the flag
     * @param sender the sender
     * @param value the value
     * @throws InvalidFlagFormat thrown if the value is invalid
     */
    private static <V> void setFlag(ProtectedRegion region, Flag<V> flag, CommandSender sender, String value)
            throws InvalidFlagFormat {
        region.setFlag(flag, flag.parseInput(WorldGuardPlugin.inst(), sender, value));
    }

    /**
     * Defines a new region.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "define", "def", "d",
            "create" }, usage = "<id> [<owner1> [<owner2> [<owners...>]]]", desc = "Defines a region", min = 1)
    public void define(CommandContext args, CommandSender sender) throws CommandException {
        Player player = plugin.checkPlayer(sender);

        // Check permissions
        if (!getPermissionModel(sender).mayDefine()) {
            throw new CommandPermissionsException();
        }

        // Get and validate the region ID
        String id = validateRegionId(args.getString(0), false);

        // Can't replace regions with this command
        RegionManager regionManager = plugin.getGlobalRegionManager().get(player.getWorld());
        if (regionManager.hasRegion(id)) {
            throw new CommandException(
                    "That region is already defined. To change the shape, use " + "/region redefine " + id);
        }

        // Make a region from the user's selection
        ProtectedRegion region = createRegionFromSelection(player, id);

        // Get the list of region owners
        if (args.argsLength() > 1) {
            region.setOwners(RegionDBUtil.parseDomainString(args.getSlice(1), 1));
        }

        regionManager.addRegion(region);
        commitChanges(sender, regionManager, player.getWorld(), true); // Save to disk

        // Issue a warning about height
        int height = region.getMaximumPoint().getBlockY() - region.getMinimumPoint().getBlockY();
        if (height <= 2) {
            sender.sendMessage(
                    ChatColor.GOLD + "(Warning: The height of the region was " + (height + 1) + " block(s).)");
        }

        // Hint
        if (regionManager.getRegions().size() <= 2) {
            sender.sendMessage(ChatColor.GRAY + "(This region is NOW PROTECTED from modification from others. "
                    + "Don't want that? Use " + ChatColor.AQUA + "/rg flag " + id + " passthrough allow"
                    + ChatColor.GRAY + ")");
        }

        // Tell the user
        sender.sendMessage(ChatColor.YELLOW + "A new region has been made named '" + id + "'.");
    }

    /**
     * Re-defines a region with a new selection.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "redefine", "update",
            "move" }, usage = "<id>", desc = "Re-defines the shape of a region", min = 1, max = 1)
    public void redefine(CommandContext args, CommandSender sender) throws CommandException {
        Player player = plugin.checkPlayer(sender);
        World world = player.getWorld();

        // Get and validate the region ID
        String id = validateRegionId(args.getString(0), false);

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
        ProtectedRegion existing = findExistingRegion(regionManager, id, false);

        // Check permissions
        if (!getPermissionModel(sender).mayRedefine(existing)) {
            throw new CommandPermissionsException();
        }

        // Make a region from the user's selection
        ProtectedRegion region = createRegionFromSelection(player, id);

        // Copy details from the old region to the new one
        region.setMembers(existing.getMembers());
        region.setOwners(existing.getOwners());
        region.setFlags(existing.getFlags());
        region.setPriority(existing.getPriority());
        try {
            region.setParent(existing.getParent());
        } catch (CircularInheritanceException ignore) {
            // This should not be thrown
        }

        regionManager.addRegion(region); // Replace region
        commitChanges(sender, regionManager, player.getWorld(), true); // Save to disk

        // Issue a warning about height
        int height = region.getMaximumPoint().getBlockY() - region.getMinimumPoint().getBlockY();
        if (height <= 2) {
            sender.sendMessage(
                    ChatColor.GOLD + "(Warning: The height of the region was " + (height + 1) + " block(s).)");
        }

        sender.sendMessage(ChatColor.YELLOW + "Region '" + id + "' updated with new area.");
    }

    /**
     * Claiming command for users.
     * 
     * <p>This command is a joke and it needs to be rewritten. It was contributed
     * code :(</p>
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = {
            "claim" }, usage = "<id> [<owner1> [<owner2> [<owners...>]]]", desc = "Claim a region", min = 1)
    public void claim(CommandContext args, CommandSender sender) throws CommandException {
        Player player = plugin.checkPlayer(sender);
        LocalPlayer localPlayer = plugin.wrapPlayer(player);
        RegionPermissionModel permModel = getPermissionModel(sender);

        // Check permissions
        if (!permModel.mayClaim()) {
            throw new CommandPermissionsException();
        }

        // Get and validate the region ID
        String id = validateRegionId(args.getString(0), false);

        // Can't replace existing regions
        RegionManager mgr = plugin.getGlobalRegionManager().get(player.getWorld());
        if (mgr.hasRegion(id)) {
            throw new CommandException("That region already exists. Please choose a different name.");
        }

        // Make a region from the user's selection
        ProtectedRegion region = createRegionFromSelection(player, id);

        // Get the list of region owners
        if (args.argsLength() > 1) {
            region.setOwners(RegionDBUtil.parseDomainString(args.getSlice(1), 1));
        }

        WorldConfiguration wcfg = plugin.getGlobalStateManager().get(player.getWorld());

        // Check whether the player has created too many regions
        if (!permModel.mayClaimRegionsUnbounded()) {
            int maxRegionCount = wcfg.getMaxRegionCount(player);
            if (maxRegionCount >= 0 && mgr.getRegionCountOfPlayer(localPlayer) >= maxRegionCount) {
                throw new CommandException("You own too many regions, delete one first to claim a new one.");
            }
        }

        ProtectedRegion existing = mgr.getRegionExact(id);

        // Check for an existing region
        if (existing != null) {
            if (!existing.getOwners().contains(localPlayer)) {
                throw new CommandException("This region already exists and you don't own it.");
            }
        }

        // We have to check whether this region violates the space of any other reion
        ApplicableRegionSet regions = mgr.getApplicableRegions(region);

        // Check if this region overlaps any other region
        if (regions.size() > 0) {
            if (!regions.isOwnerOfAll(localPlayer)) {
                throw new CommandException("This region overlaps with someone else's region.");
            }
        } else {
            if (wcfg.claimOnlyInsideExistingRegions) {
                throw new CommandException(
                        "You may only claim regions inside " + "existing regions that you or your group own.");
            }
        }

        // Check claim volume
        if (!permModel.mayClaimRegionsUnbounded()) {
            if (region.volume() > wcfg.maxClaimVolume) {
                player.sendMessage(ChatColor.RED + "This region is too large to claim.");
                player.sendMessage(ChatColor.RED + "Max. volume: " + wcfg.maxClaimVolume + ", your volume: "
                        + region.volume());
                return;
            }
        }

        /*if (plugin.getGlobalConfiguration().getiConomy() != null && wcfg.useiConomy && wcfg.buyOnClaim) {
        if (iConomy.getBank().hasAccount(player.getName())) {
            Account account = iConomy.getBank().getAccount(player.getName());
            double balance = account.getBalance();
            double regionCosts = region.countBlocks() * wcfg.buyOnClaimPrice;
            if (balance >= regionCosts) {
                account.subtract(regionCosts);
                player.sendMessage(ChatColor.YELLOW + "You have bought that region for "
                        + iConomy.getBank().format(regionCosts));
                account.save();
            } else {
                player.sendMessage(ChatColor.RED + "You have not enough money.");
                player.sendMessage(ChatColor.RED + "The region you want to claim costs "
                        + iConomy.getBank().format(regionCosts));
                player.sendMessage(ChatColor.RED + "You have " + iConomy.getBank().format(balance));
                return;
            }
        } else {
            player.sendMessage(ChatColor.YELLOW + "You have not enough money.");
            return;
        }
        }*/

        region.getOwners().addPlayer(player.getName());

        mgr.addRegion(region);
        commitChanges(sender, mgr, player.getWorld(), true); // Save to disk
        sender.sendMessage(ChatColor.YELLOW + "Region '" + id + "' updated with new area.");
    }

    /**
     * Get a WorldEdit selection from a region.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "select", "sel",
            "s" }, usage = "[id]", desc = "Load a region as a WorldEdit selection", min = 0, max = 1)
    public void select(CommandContext args, CommandSender sender) throws CommandException {
        Player player = plugin.checkPlayer(sender);
        World world = player.getWorld();
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
        ProtectedRegion existing;

        // If no arguments were given, get the region that the player is inside
        if (args.argsLength() == 0) {
            existing = findRegionStandingIn(regionManager, player);
        } else {
            existing = findExistingRegion(regionManager, args.getString(0), false);
        }

        // Check permissions
        if (!getPermissionModel(sender).maySelect(existing)) {
            throw new CommandPermissionsException();
        }

        // Select
        setPlayerSelection(player, existing);
    }

    /**
     * Get information about a region.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "info",
            "i" }, usage = "[id]", flags = "sw:", desc = "Get information about a region", min = 0, max = 1)
    public void info(CommandContext args, CommandSender sender) throws CommandException {
        World world = getWorld(args, sender, 'w'); // Get the world
        RegionPermissionModel permModel = getPermissionModel(sender);

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
        ProtectedRegion existing;

        if (args.argsLength() == 0) { // Get region from where the player is
            if (!(sender instanceof Player)) {
                throw new CommandException(
                        "Please specify " + "the region with /region info -w world_name region_name.");
            }

            existing = findRegionStandingIn(regionManager, (Player) sender, true);
        } else { // Get region from the ID
            existing = findExistingRegion(regionManager, args.getString(0), true);
        }

        // Check permissions
        if (!permModel.mayLookup(existing)) {
            throw new CommandPermissionsException();
        }

        // Print region information
        RegionPrintoutBuilder printout = new RegionPrintoutBuilder(existing);
        printout.appendRegionInfo();
        printout.send(sender);

        // Let the player also select the region
        if (args.hasFlag('s')) {
            // Check permissions
            if (!permModel.maySelect(existing)) {
                throw new CommandPermissionsException();
            }

            setPlayerSelection(plugin.checkPlayer(sender), existing);
        }
    }

    /**
     * List regions.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "list" }, usage = "[page]", desc = "Get a list of regions", flags = "p:w:", max = 1)
    public void list(CommandContext args, CommandSender sender) throws CommandException {
        World world = getWorld(args, sender, 'w'); // Get the world
        String ownedBy;

        // Get page
        int page = args.getInteger(0, 1) - 1;
        if (page < 0) {
            page = 0;
        }

        // -p flag to lookup a player's regions
        if (args.hasFlag('p')) {
            ownedBy = args.getFlag('p');
        } else {
            ownedBy = null; // List all regions
        }

        // Check permissions
        if (!getPermissionModel(sender).mayList(ownedBy)) {
            ownedBy = sender.getName(); // assume they only want their own
            if (!getPermissionModel(sender).mayList(ownedBy)) {
                throw new CommandPermissionsException();
            }
        }

        RegionManager mgr = plugin.getGlobalRegionManager().get(world);
        Map<String, ProtectedRegion> regions = mgr.getRegions();

        // Build a list of regions to show
        List<RegionListEntry> entries = new ArrayList<RegionListEntry>();

        int index = 0;
        for (String id : regions.keySet()) {
            RegionListEntry entry = new RegionListEntry(id, index++);

            // Filtering by owner?
            if (ownedBy != null) {
                entry.isOwner = regions.get(id).isOwner(ownedBy);
                entry.isMember = regions.get(id).isMember(ownedBy);

                if (!entry.isOwner && !entry.isMember) {
                    continue; // Skip
                }
            }

            entries.add(entry);
        }

        Collections.sort(entries);

        final int totalSize = entries.size();
        final int pageSize = 10;
        final int pages = (int) Math.ceil(totalSize / (float) pageSize);

        sender.sendMessage(
                ChatColor.RED + (ownedBy == null ? "Regions (page " : "Regions for " + ownedBy + " (page ")
                        + (page + 1) + " of " + pages + "):");

        if (page < pages) {
            // Print
            for (int i = page * pageSize; i < page * pageSize + pageSize; i++) {
                if (i >= totalSize) {
                    break;
                }

                sender.sendMessage(ChatColor.YELLOW.toString() + entries.get(i));
            }
        }
    }

    /**
     * Set a flag.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "flag",
            "f" }, usage = "<id> <flag> [-w world] [-g group] [value]", flags = "g:w:", desc = "Set flags", min = 2)
    public void flag(CommandContext args, CommandSender sender) throws CommandException {
        World world = getWorld(args, sender, 'w'); // Get the world
        String flagName = args.getString(1);
        String value = args.argsLength() >= 3 ? args.getJoinedStrings(2) : null;
        RegionGroup groupValue = null;
        RegionPermissionModel permModel = getPermissionModel(sender);

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
        ProtectedRegion existing = findExistingRegion(regionManager, args.getString(0), true);

        // Check permissions
        if (!permModel.maySetFlag(existing)) {
            throw new CommandPermissionsException();
        }

        Flag<?> foundFlag = DefaultFlag.fuzzyMatchFlag(flagName);

        // We didn't find the flag, so let's print a list of flags that the user
        // can use, and do nothing afterwards
        if (foundFlag == null) {
            StringBuilder list = new StringBuilder();

            // Need to build a list
            for (Flag<?> flag : DefaultFlag.getFlags()) {
                // Can the user set this flag?
                if (!permModel.maySetFlag(existing, flag)) {
                    continue;
                }

                if (list.length() > 0) {
                    list.append(", ");
                }

                list.append(flag.getName());
            }

            sender.sendMessage(ChatColor.RED + "Unknown flag specified: " + flagName);
            sender.sendMessage(ChatColor.RED + "Available " + "flags: " + list);

            return;
        }

        // Also make sure that we can use this flag
        // This permission is confusing and probably should be replaced, but
        // but not here -- in the model
        if (!permModel.maySetFlag(existing, foundFlag)) {
            throw new CommandPermissionsException();
        }

        // -g for group flag
        if (args.hasFlag('g')) {
            String group = args.getFlag('g');
            RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag();

            if (groupFlag == null) {
                throw new CommandException("Region flag '" + foundFlag.getName() + "' does not have a group flag!");
            }

            // Parse the [-g group] separately so entire command can abort if parsing
            // the [value] part throws an error.
            try {
                groupValue = groupFlag.parseInput(plugin, sender, group);
            } catch (InvalidFlagFormat e) {
                throw new CommandException(e.getMessage());
            }

        }

        // Set the flag value if a value was set
        if (value != null) {
            // Set the flag if [value] was given even if [-g group] was given as well
            try {
                setFlag(existing, foundFlag, sender, value);
            } catch (InvalidFlagFormat e) {
                throw new CommandException(e.getMessage());
            }

            sender.sendMessage(ChatColor.YELLOW + "Region flag " + foundFlag.getName() + " set on '"
                    + existing.getId() + "' to '" + value + "'.");

            // No value? Clear the flag, if -g isn't specified
        } else if (!args.hasFlag('g')) {
            // Clear the flag only if neither [value] nor [-g group] was given
            existing.setFlag(foundFlag, null);

            // Also clear the associated group flag if one exists
            RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag();
            if (groupFlag != null) {
                existing.setFlag(groupFlag, null);
            }

            sender.sendMessage(ChatColor.YELLOW + "Region flag " + foundFlag.getName() + " removed from '"
                    + existing.getId() + "'. (Any -g(roups) were also removed.)");
        }

        // Now set the group
        if (groupValue != null) {
            RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag();

            // If group set to the default, then clear the group flag
            if (groupValue == groupFlag.getDefault()) {
                existing.setFlag(groupFlag, null);
                sender.sendMessage(ChatColor.YELLOW + "Region group flag for '" + foundFlag.getName()
                        + "' reset to " + "default.");
            } else {
                existing.setFlag(groupFlag, groupValue);
                sender.sendMessage(ChatColor.YELLOW + "Region group flag for '" + foundFlag.getName() + "' set.");
            }
        }

        commitChanges(sender, regionManager, world, true); // Save to disk

        // Print region information
        RegionPrintoutBuilder printout = new RegionPrintoutBuilder(existing);
        printout.append(ChatColor.GRAY);
        printout.append("(Current flags: ");
        printout.appendFlagsList(false);
        printout.append(")");
        printout.send(sender);
    }

    /**
     * Set the priority of a region.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "setpriority", "priority",
            "pri" }, usage = "<id> <priority>", flags = "w:", desc = "Set the priority of a region", min = 2, max = 2)
    public void setPriority(CommandContext args, CommandSender sender) throws CommandException {
        World world = getWorld(args, sender, 'w'); // Get the world
        int priority = args.getInteger(1);

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
        ProtectedRegion existing = findExistingRegion(regionManager, args.getString(0), false);

        // Check permissions
        if (!getPermissionModel(sender).maySetPriority(existing)) {
            throw new CommandPermissionsException();
        }

        existing.setPriority(priority);
        commitChanges(sender, regionManager, world, true); // Save to disk

        sender.sendMessage(ChatColor.YELLOW + "Priority of '" + existing.getId() + "' set to " + priority
                + " (higher numbers override).");
    }

    /**
     * Set the parent of a region.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "setparent", "parent",
            "par" }, usage = "<id> [parent-id]", flags = "w:", desc = "Set the parent of a region", min = 1, max = 2)
    public void setParent(CommandContext args, CommandSender sender) throws CommandException {
        World world = getWorld(args, sender, 'w'); // Get the world
        ProtectedRegion parent;
        ProtectedRegion child;

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);

        // Get parent and child
        child = findExistingRegion(regionManager, args.getString(0), false);
        if (args.argsLength() == 2) {
            parent = findExistingRegion(regionManager, args.getString(1), false);
        } else {
            parent = null;
        }

        // Check permissions
        if (!getPermissionModel(sender).maySetParent(child, parent)) {
            throw new CommandPermissionsException();
        }

        try {
            child.setParent(parent);
        } catch (CircularInheritanceException e) {
            // Tell the user what's wrong
            RegionPrintoutBuilder printout = new RegionPrintoutBuilder(parent);
            printout.append(ChatColor.RED);
            printout.append("Uh oh! Setting '" + parent.getId() + "' to be the parent " + "of '" + child.getId()
                    + "' would cause circular inheritance.\n");
            printout.append(ChatColor.GRAY);
            printout.append("(Current inheritance on '" + parent.getId() + "':\n");
            printout.appendParentTree(true);
            printout.append(ChatColor.GRAY);
            printout.append(")");
            printout.send(sender);
            return;
        }

        commitChanges(sender, regionManager, world, true); // Save to disk

        // Tell the user the current inheritance
        RegionPrintoutBuilder printout = new RegionPrintoutBuilder(child);
        printout.append(ChatColor.YELLOW);
        printout.append("Inheritance set for region '" + child.getId() + "'.\n");
        if (parent != null) {
            printout.append(ChatColor.GRAY);
            printout.append("(Current inheritance:\n");
            printout.appendParentTree(true);
            printout.append(ChatColor.GRAY);
            printout.append(")");
        }
        printout.send(sender);
        return;
    }

    /**
     * Remove a region.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "remove", "delete", "del",
            "rem" }, usage = "<id>", flags = "w:", desc = "Remove a region", min = 1, max = 1)
    public void remove(CommandContext args, CommandSender sender) throws CommandException {
        World world = getWorld(args, sender, 'w'); // Get the world

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
        ProtectedRegion existing = findExistingRegion(regionManager, args.getString(0), true);

        // Check permissions
        if (!getPermissionModel(sender).mayDelete(existing)) {
            throw new CommandPermissionsException();
        }

        regionManager.removeRegion(existing.getId());
        commitChanges(sender, regionManager, world, true); // Save to disk

        sender.sendMessage(ChatColor.YELLOW + "Region '" + existing.getId() + "' removed.");
    }

    /**
     * Reload the region database.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "load", "reload" }, usage = "[world]", desc = "Reload regions from file", flags = "w:")
    public void load(CommandContext args, final CommandSender sender) throws CommandException {
        World world = null;
        try {
            world = getWorld(args, sender, 'w'); // Get the world
        } catch (CommandException e) {
            // assume the user wants to reload all worlds
        }

        // Check permissions
        if (!getPermissionModel(sender).mayForceLoadRegions()) {
            throw new CommandPermissionsException();
        }

        if (world != null) {
            RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
            if (regionManager == null) {
                throw new CommandException("No region manager exists for world '" + world.getName() + "'.");
            }
            reloadChanges(sender, regionManager, world, false);
        } else {
            List<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>();
            for (World w : plugin.getServer().getWorlds()) {
                RegionManager regionManager = plugin.getGlobalRegionManager().get(w);
                if (regionManager == null) {
                    continue;
                }
                futures.add(reloadChanges(sender, regionManager, world, true));
            }

            Futures.addCallback(Futures.allAsList(futures), new FutureCallback<Object>() {
                @Override
                public void onSuccess(Object o) {
                    sender.sendMessage(ChatColor.YELLOW + "Successfully loaded region data for all worlds.");
                }

                @Override
                public void onFailure(Throwable throwable) {
                    sender.sendMessage(
                            ChatColor.RED + "Failed to load region data for all worlds: " + throwable.getMessage());
                    plugin.getLogger().log(Level.WARNING, "Failed to load region data", throwable);
                }
            });
        }
    }

    /**
     * Re-save the region database.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "save", "write" }, usage = "[world]", desc = "Re-save regions to file", flags = "w:")
    public void save(CommandContext args, final CommandSender sender) throws CommandException {
        World world = null;
        try {
            world = getWorld(args, sender, 'w'); // Get the world
        } catch (CommandException e) {
            // assume user wants to save all worlds
        }

        // Check permissions
        if (!getPermissionModel(sender).mayForceSaveRegions()) {
            throw new CommandPermissionsException();
        }

        if (world != null) {
            RegionManager regionManager = plugin.getGlobalRegionManager().get(world);
            if (regionManager == null) {
                throw new CommandException("No region manager exists for world '" + world.getName() + "'.");
            }
            commitChanges(sender, regionManager, world, false);
        } else {
            sender.sendMessage(ChatColor.YELLOW + "Saving all region databases... This might take a bit.");
            List<ListenableFuture<?>> futures = new ArrayList<ListenableFuture<?>>();
            for (World w : plugin.getServer().getWorlds()) {
                RegionManager regionManager = plugin.getGlobalRegionManager().get(w);
                if (regionManager == null) {
                    continue;
                }
                futures.add(commitChanges(sender, regionManager, world, true));
            }

            Futures.addCallback(Futures.allAsList(futures), new FutureCallback<Object>() {
                @Override
                public void onSuccess(Object o) {
                    sender.sendMessage(ChatColor.YELLOW + "Successfully saved region data for all worlds.");
                }

                @Override
                public void onFailure(Throwable throwable) {
                    sender.sendMessage(
                            ChatColor.RED + "Failed to save region data for all worlds: " + throwable.getMessage());
                    plugin.getLogger().log(Level.WARNING, "Failed to save region data", throwable);
                }
            });
        }
    }

    /**
     * Migrate the region database.
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = {
            "migratedb" }, usage = "<from> <to>", desc = "Migrate from one Protection Database to another.", min = 2, max = 2)
    public void migrateDB(CommandContext args, CommandSender sender) throws CommandException {
        // Check permissions
        if (!getPermissionModel(sender).mayMigrateRegionStore()) {
            throw new CommandPermissionsException();
        }

        String from = args.getString(0).toLowerCase().trim();
        String to = args.getString(1).toLowerCase().trim();

        if (from.equals(to)) {
            throw new CommandException("Will not migrate with common source and target.");
        }

        Map<MigratorKey, Class<? extends AbstractDatabaseMigrator>> migrators = AbstractDatabaseMigrator
                .getMigrators();
        MigratorKey key = new MigratorKey(from, to);

        if (!migrators.containsKey(key)) {
            throw new CommandException("No migrator found for that combination and direction.");
        }

        long lastRequest = 10000000;
        if (this.migrateDBRequestDate != null) {
            lastRequest = new Date().getTime() - this.migrateDBRequestDate.getTime();
        }
        if (this.migrateDBRequest == null || lastRequest > 60000) {
            this.migrateDBRequest = key;
            this.migrateDBRequestDate = new Date();

            throw new CommandException("This command is potentially dangerous.\n"
                    + "Please ensure you have made a backup of your data, and then re-enter the command exactly to procede.");
        }

        Class<? extends AbstractDatabaseMigrator> cls = migrators.get(key);

        try {
            AbstractDatabaseMigrator migrator = cls.getConstructor(WorldGuardPlugin.class).newInstance(plugin);

            migrator.migrate();
        } catch (IllegalArgumentException ignore) {
        } catch (SecurityException ignore) {
        } catch (InstantiationException ignore) {
        } catch (IllegalAccessException ignore) {
        } catch (InvocationTargetException ignore) {
        } catch (NoSuchMethodException ignore) {
        } catch (MigrationException e) {
            throw new CommandException("Error migrating database: " + e.getMessage());
        }

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

    /**
     * Teleport to a region
     * 
     * @param args the arguments
     * @param sender the sender
     * @throws CommandException any error
     */
    @Command(aliases = { "teleport",
            "tp" }, usage = "<id>", flags = "s", desc = "Teleports you to the location associated with the region.", min = 1, max = 1)
    public void teleport(CommandContext args, CommandSender sender) throws CommandException {
        Player player = plugin.checkPlayer(sender);
        Location teleportLocation;

        // Lookup the existing region
        RegionManager regionManager = plugin.getGlobalRegionManager().get(player.getWorld());
        ProtectedRegion existing = findExistingRegion(regionManager, args.getString(0), false);

        // Check permissions
        if (!getPermissionModel(sender).mayTeleportTo(existing)) {
            throw new CommandPermissionsException();
        }

        // -s for spawn location
        if (args.hasFlag('s')) {
            teleportLocation = existing.getFlag(DefaultFlag.SPAWN_LOC);

            if (teleportLocation == null) {
                throw new CommandException("The region has no spawn point associated.");
            }
        } else {
            teleportLocation = existing.getFlag(DefaultFlag.TELE_LOC);

            if (teleportLocation == null) {
                throw new CommandException("The region has no teleport point associated.");
            }
        }

        player.teleport(BukkitUtil.toLocation(teleportLocation));
        sender.sendMessage("Teleported you to the region '" + existing.getId() + "'.");
    }
}