<|start_filename|>src/main/java/com/griefcraft/listeners/LWC114Listener.java<|end_filename|>
package com.griefcraft.listeners;

import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Permission;
import com.griefcraft.model.Protection;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTakeLecternBookEvent;

public class LWC114Listener implements Listener {

    @EventHandler(ignoreCancelled = true)
    public void onPlayerTakeLecternBook(PlayerTakeLecternBookEvent event) {
        LWC lwc = LWC.getInstance();
        Protection protection = lwc.findProtection(event.getLectern());
        if (protection == null || protection.isOwner(event.getPlayer()) || protection.getType() == Protection.Type.PUBLIC) {
            return;
        }
        if (protection.getAccess(event.getPlayer().getUniqueId().toString(), Permission.Type.PLAYER) == Permission.Access.NONE) {
            event.setCancelled(true);
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/ProtectionFinder.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util;

import com.griefcraft.cache.ProtectionCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Protection;
import com.griefcraft.util.matchers.DoorMatcher;
import com.griefcraft.util.matchers.DoubleChestMatcher;
import com.griefcraft.util.matchers.GravityMatcher;
import com.griefcraft.util.matchers.WallMatcher;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * Searches for blocks that can potentially be a protection
 */
public class ProtectionFinder {

    /**
     * The LWC object to work with
     */
    private LWC lwc;

    /**
     * The base block to match off of
     */
    private BlockState baseBlock = null;

    /**
     * The matched protection if found
     */
    private Protection matchedProtection = null;

    /**
     * If we already checked the database
     */
    private boolean searched = false;

    /**
     * All of the matched blocks
     */
    private final List<BlockState> blocks = new LinkedList<>();

    /**
     * All of the blocks that are protectables
     */
    private final List<BlockState> protectables = new LinkedList<>();

    public ProtectionFinder(LWC lwc) {
        this.lwc = lwc;
    }

    /**
     * Try and match blocks using the given base block
     *
     * @param baseBlock
     * @return TRUE if a set of blocks was found
     */
    public boolean matchBlocks(Block baseBlock) {
        return matchBlocks(baseBlock.getState());
    }

    /**
     * Try and match blocks using the given base block
     *
     * @param baseBlock
     * @return TRUE if a set of blocks was found
     */
    public boolean matchBlocks(BlockState baseBlock) {
        // Did we already find a protection?
        if (matchedProtection != null) {
            return true;
        }

        // First, reset
        reset();
        this.baseBlock = baseBlock;

        // Add the base block
        addBlock(baseBlock);

        // Check the base-block
        Result result;
        if ((result = tryLoadProtection(baseBlock, false)) != Result.E_NOT_FOUND) {
            return result == Result.E_FOUND;
        }

        // Now attempt to use the matchers
        for (Matcher matcher : getProtectionMatchers()) {
            boolean matches = matcher.matches(this);

            if (matches) {
                // we matched a protection somewhere!
                return true;
            }
        }

        // No matches
        searched = true;
        return false;
    }

    /**
     * Do a full sweeping match of all the blocks for a given protection
     */
    public void fullMatchBlocks() {

        // Reset the blocks
        blocks.clear();

        // Add the base block
        blocks.add(baseBlock);

        // Go through each matcher and execute it
        for (Matcher matcher : getProtectionMatchers()) {
            if (matcher.matches(this)) {
                break;
            }
        }

    }

    /**
     * Get the possible protection matchers that can match the protection
     *
     * @return
     */
    public Matcher[] getProtectionMatchers() {
        Material material = baseBlock.getType();

        if (material == Material.HOPPER) {
            return new Matcher[0];
        }

        // Double chests
        if (DoubleChestMatcher.PROTECTABLES_CHESTS.contains(material)) {
            return new Matcher[]{
                    new DoubleChestMatcher()
            };
        }

        // Gravity
        else if (GravityMatcher.PROTECTABLES_POSTS.contains(material)) {
            return new Matcher[]{
                    new GravityMatcher()
            };
        }

        // Doors
        else if (DoorMatcher.PROTECTABLES_DOORS.contains(material)) {
            return new Matcher[]{
                    new DoorMatcher()
            };
        }

        // Anything else
        else {
            return new Matcher[]{
                    new DoorMatcher(),
                    new GravityMatcher(),
                    new WallMatcher()
            };
        }

    }

    /**
     * Add a block to the matched blocks
     *
     * @param block
     */
    public void addBlock(Block block) {
        addBlock(block.getState());
    }

    /**
     * Add a block to the matched blocks
     *
     * @param block
     */
    public void addBlock(BlockState block) {
        if (!blocks.contains(block)) {
            blocks.add(block);
        }
    }

    /**
     * Load the protection for the calculated protectables.
     * Returns NULL if no protection was found.
     *
     * @return
     */
    public Protection loadProtection() {
        return loadProtection(false);
    }

    /**
     * Load the protection for the calculated protectables.
     * Returns NULL if no protection was found.
     *
     * @param noAutoCache if a match is found, don't cache it to be the protection we use
     * @return
     */
    public Protection loadProtection(boolean noAutoCache) {
        // Do we have a result already cached?
        if (searched) {
            return matchedProtection;
        }

        // Calculate the protectables
        calculateProtectables();
        searched = true;

        for (BlockState block : protectables) {
            if (tryLoadProtection(block, noAutoCache) == Result.E_FOUND) {
                return matchedProtection;
            }
        }

        return null;
    }

    /**
     * Try and load a protection for a given block. If succeded, cache it locally
     *
     * @param block
     * @param noAutoCache if a match is found, don't cache it to be the protection we use
     * @return
     */
    protected Result tryLoadProtection(BlockState block, boolean noAutoCache) {
        if (matchedProtection != null) {
            return Result.E_FOUND;
        }

        LWC lwc = LWC.getInstance();
        ProtectionCache cache = lwc.getProtectionCache();

        // Check the cache
        if ((matchedProtection = cache.getProtection(block)) != null) {
            searched = true;
            if (matchedProtection.getProtectionFinder() == null) {
                fullMatchBlocks();
                matchedProtection.setProtectionFinder(this);
                cache.addProtection(matchedProtection);
            }
            return Result.E_FOUND;
        }

        // Manual intervention is required
        if (block.getType() == Material.REDSTONE_WIRE || block.getType() == Material.REDSTONE_WALL_TORCH ||
                block.getType() == Material.REDSTONE_TORCH) {
            return Result.E_ABORT;
        }

        // don't bother trying to load it if it is not protectable
        if (!lwc.isProtectable(block)) {
            return Result.E_NOT_FOUND;
        }

        // Null-check
        if (block.getWorld() == null) {
            return Result.E_NOT_FOUND;
        }

        Protection protection = lwc.getPhysicalDatabase().loadProtection(block.getWorld().getName(), block.getX(), block.getY(), block.getZ());

        if (protection != null) {
            if (protection.getProtectionFinder() == null) {
                protection.setProtectionFinder(this);
                fullMatchBlocks();
                cache.addProtection(matchedProtection);
            }

            // ensure it's the right block
            if (protection.getBlockId() > 0) {
                if (protection.isBlockInWorld()) {
                    if (noAutoCache) {
                        return Result.E_FOUND;
                    }

                    this.matchedProtection = protection;
                    searched = true;
                } else {
                    // Removing orrupted protection
                    protection.remove();
                }
            }
        }

        return this.matchedProtection != null ? Result.E_FOUND : Result.E_NOT_FOUND;
    }

    /**
     * Get the finder's base block
     *
     * @return
     */
    public BlockState getBaseBlock() {
        return baseBlock;
    }

    /**
     * Get an immutable set of the matched blocks
     *
     * @return
     */
    public List<BlockState> getBlocks() {
        return Collections.unmodifiableList(blocks);
    }

    /**
     * Remove a block from the finder
     *
     * @param block
     */
    public void removeBlock(BlockState block) {
        Iterator<BlockState> iter = blocks.iterator();

        while (iter.hasNext()) {
            if (lwc.blockEquals(block, iter.next())) {
                iter.remove();
            }
        }
    }

    /**
     * Reset the matcher state
     */
    private void reset() {
        blocks.clear();
        protectables.clear();
        baseBlock = null;
        searched = false;
    }

    /**
     * From the matched blocks calculate and store the blocks that are protectable
     */
    private void calculateProtectables() {
        // reset the matched protectables
        protectables.clear();
        searched = false;

        // if there's only 1 block it was already checked (the base block)
        int size = blocks.size();
        if (size == 1) {
            return;
        }

        // go through the blocks
        for (int index = 1; index < size; index++) {
            BlockState state = blocks.get(index);

            if (lwc.isProtectable(state)) {
                protectables.add(state);
            }
        }
    }

    /**
     * Matches protections
     */
    public interface Matcher {

        /**
         * Check if the block matches any VALID protections.
         *
         * @return
         */
        public boolean matches(ProtectionFinder finder);

    }

    private enum Result {E_FOUND, E_ABORT, E_NOT_FOUND}

}


<|start_filename|>src/main/java/com/griefcraft/util/matchers/DoorMatcher.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util.matchers;

import com.griefcraft.lwc.LWC;
import com.griefcraft.util.ProtectionFinder;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;

import java.util.EnumSet;
import java.util.Set;

/**
 * Matches doors (both Iron & Wooden)
 */
public class DoorMatcher implements ProtectionFinder.Matcher {

    public static final Set<Material> PROTECTABLES_DOORS = EnumSet.of(Material.OAK_DOOR, Material.SPRUCE_DOOR,
            Material.BIRCH_DOOR, Material.ACACIA_DOOR, Material.JUNGLE_DOOR, Material.DARK_OAK_DOOR,
            Material.IRON_DOOR);

    // doors that open when clicked
    public static final Set<Material> WOODEN_DOORS = EnumSet.of(Material.OAK_DOOR, Material.SPRUCE_DOOR,
            Material.BIRCH_DOOR, Material.ACACIA_DOOR, Material.JUNGLE_DOOR, Material.DARK_OAK_DOOR);

    public static final Set<Material> FENCE_GATES = EnumSet.of(Material.OAK_FENCE_GATE, Material.SPRUCE_FENCE_GATE,
            Material.BIRCH_FENCE_GATE, Material.JUNGLE_FENCE_GATE, Material.ACACIA_FENCE_GATE,
            Material.DARK_OAK_FENCE_GATE);

    // fence gates that open when clicked
    public static final Set<Material> WOODEN_FENCE_GATES = EnumSet.of(Material.OAK_FENCE_GATE, Material.SPRUCE_FENCE_GATE,
            Material.BIRCH_FENCE_GATE, Material.JUNGLE_FENCE_GATE, Material.ACACIA_FENCE_GATE,
            Material.DARK_OAK_FENCE_GATE);

    public static final Set<Material> TRAP_DOORS = EnumSet.of(Material.OAK_TRAPDOOR, Material.SPRUCE_TRAPDOOR,
            Material.BIRCH_TRAPDOOR, Material.JUNGLE_TRAPDOOR, Material.ACACIA_TRAPDOOR, Material.DARK_OAK_TRAPDOOR,
            Material.IRON_TRAPDOOR);

    // trapdoors that open when clicked
    public static final Set<Material> WOODEN_TRAP_DOORS = EnumSet.of(Material.OAK_TRAPDOOR, Material.SPRUCE_TRAPDOOR,
            Material.BIRCH_TRAPDOOR, Material.JUNGLE_TRAPDOOR, Material.ACACIA_TRAPDOOR, Material.DARK_OAK_TRAPDOOR);

    public static final Set<Material> PRESSURE_PLATES = EnumSet.of(Material.OAK_PRESSURE_PLATE,
            Material.SPRUCE_PRESSURE_PLATE, Material.BIRCH_PRESSURE_PLATE, Material.JUNGLE_PRESSURE_PLATE,
            Material.ACACIA_PRESSURE_PLATE, Material.DARK_OAK_PRESSURE_PLATE, Material.STONE_PRESSURE_PLATE,
            Material.LIGHT_WEIGHTED_PRESSURE_PLATE, Material.HEAVY_WEIGHTED_PRESSURE_PLATE);

    private static final BlockFace[] faces = new BlockFace[]{BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH,
            BlockFace.SOUTH};

    public boolean matches(ProtectionFinder finder) {
        BlockState baseBlockState = finder.getBaseBlock();
        Block block = baseBlockState.getBlock();
        // Get the block above the base block
        Block aboveBaseBlock = block.getRelative(BlockFace.UP);

        // Get the block above the block above the base block
        Block aboveAboveBaseBlock = aboveBaseBlock.getRelative(BlockFace.UP);

        // look for door if they're clicking a pressure plate
        if (PRESSURE_PLATES.contains(baseBlockState.getType()) || PRESSURE_PLATES.contains(aboveBaseBlock.getType())) {
            Block pressurePlate = PRESSURE_PLATES.contains(baseBlockState.getType()) ? block : aboveBaseBlock;

            for (BlockFace face : faces) {
                Block relative = pressurePlate.getRelative(face);

                // only check if it's a door
                if (!PROTECTABLES_DOORS.contains(relative.getType())) {
                    continue;
                }

                // create a protection finder
                ProtectionFinder doorFinder = new ProtectionFinder(LWC.getInstance());

                // attempt to match the door
                if (doorFinder.matchBlocks(relative)) {
                    // add the blocks it matched
                    for (BlockState found : doorFinder.getBlocks()) {
                        finder.addBlock(found);
                    }

                    // add the pressure plate
                    finder.addBlock(pressurePlate);
                    return true;
                }
            }
        }

        // Match the block UNDER the door
        if (PROTECTABLES_DOORS.contains(aboveAboveBaseBlock.getType())
                && PROTECTABLES_DOORS.contains(aboveBaseBlock.getType())) {
            finder.addBlock(aboveAboveBaseBlock);
            finder.addBlock(aboveBaseBlock);
            findPressurePlate(finder, aboveBaseBlock);
            return true;
        }

        // Match the bottom half of the door
        else if (PROTECTABLES_DOORS.contains(aboveBaseBlock.getType())) {
            finder.addBlock(aboveBaseBlock);
            finder.addBlock(block.getRelative(BlockFace.DOWN));
            findPressurePlate(finder, block);
            return true;
        }

        // Match the top half of the door
        else if (PROTECTABLES_DOORS.contains(baseBlockState.getType())) {
            Block bottomHalf = block.getRelative(BlockFace.DOWN);

            finder.addBlock(bottomHalf);
            finder.addBlock(bottomHalf.getRelative(BlockFace.DOWN));
            findPressurePlate(finder, bottomHalf);
            return true;
        }

        return false;
    }

    /**
     * Found a pressure plate that is around a specific block
     *
     * @param finder
     * @param block
     */
    private void findPressurePlate(ProtectionFinder finder, Block block) {
        for (BlockFace face : faces) {
            Block relative = block.getRelative(face);

            if (PRESSURE_PLATES.contains(relative.getType())) {
                finder.addBlock(relative);
            }
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/Statistics.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util;

import com.griefcraft.cache.MethodCounter;
import com.griefcraft.cache.ProtectionCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.lwc.LWCInfo;
import com.griefcraft.scripting.MetaData;
import com.griefcraft.sql.Database;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.plugin.Plugin;

import java.text.NumberFormat;
import java.util.List;
import java.util.Map;

public class Statistics {

    /**
     * Number of queries executed on the database
     */
    private static int queries = 0;

    /**
     * Time when LWC was started
     */
    private static long startTime = 0L;

    /**
     * Add a query
     */
    public static void addQuery() {
        queries++;
    }

    /**
     * @return the average of a value
     */
    public static double getAverage(long value) {
        return (double) value / getTimeRunningSeconds();
    }

    /**
     * Obtain an entity count of a Entity class.
     *
     * @param clazz If null, all entities are counted
     * @return
     */
    public static int getEntityCount(Class<? extends Entity> clazz) {
        int count = 0;

        for (World world : Bukkit.getServer().getWorlds()) {
            if (clazz == null) {
                count += world.getEntities().size();
            } else {
                for (Entity entity : world.getEntities()) {
                    if (entity != null && clazz.isInstance(entity)) {
                        count++;
                    }
                }
            }
        }

        return count;
    }

    /**
     * Send a performance report to a Console Sender
     *
     * @param sender
     */
    public static void sendReport(CommandSender sender) {
        LWC lwc = LWC.getInstance();

        sender.sendMessage(" ");
        sender.sendMessage(Colors.Dark_Red + "LWC Report");
        sender.sendMessage("  Version: " + Colors.Dark_Green + LWCInfo.FULL_VERSION);
        sender.sendMessage("  Running time: " + Colors.Dark_Green + TimeUtil.timeToString(getTimeRunningSeconds()));
        sender.sendMessage("  Players: " + Colors.Dark_Green + Bukkit.getServer().getOnlinePlayers().size() + "/" + Bukkit.getServer().getMaxPlayers());
        sender.sendMessage("  Item entities: " + Colors.Dark_Green + getEntityCount(Item.class) + "/" + getEntityCount(null));
        sender.sendMessage("  Permissions API: " + Colors.Dark_Green + lwc.getPermissions().getClass().getSimpleName());
        sender.sendMessage("  Currency API: " + Colors.Dark_Green + lwc.getCurrency().getClass().getSimpleName());
        sender.sendMessage(" ");
        sender.sendMessage(Colors.Dark_Red + " ==== Modules ====");

        for (Map.Entry<Plugin, List<MetaData>> entry : lwc.getModuleLoader().getRegisteredModules().entrySet()) {
            Plugin plugin = entry.getKey();
            List<MetaData> modules = entry.getValue();

            // Why?
            if (plugin == null) {
                continue;
            }

            sender.sendMessage("  " + Colors.Dark_Green + plugin.getDescription().getName() + " v" + plugin.getDescription().getVersion() + Colors.Yellow + " -> " + Colors.Dark_Green + modules.size() + Colors.Yellow + " registered modules");
        }
        sender.sendMessage(" ");

        sender.sendMessage(Colors.Dark_Red + " ==== Database ====");
        sender.sendMessage("  Engine: " + Colors.Dark_Green + Database.DefaultType);
        sender.sendMessage("  Protections: " + Colors.Dark_Green + formatNumber(lwc.getPhysicalDatabase().getProtectionCount()));
        sender.sendMessage("  Queries: " + Colors.Dark_Green + formatNumber(queries) + " | " + String.format("%.2f", getAverage(queries)) + " / second");
        sender.sendMessage(" ");

        sender.sendMessage(Colors.Dark_Red + " ==== Cache ==== ");

        ProtectionCache cache = lwc.getProtectionCache();

        double cachePercentFilled = ((double) cache.size() / cache.totalCapacity()) * 100;

        String cacheColour = Colors.Dark_Green;
        if (cachePercentFilled > 75 && cachePercentFilled < 85) {
            cacheColour = Colors.Yellow;
        } else if (cachePercentFilled > 85 && cachePercentFilled < 95) {
            cacheColour = Colors.Red;
        } else if (cachePercentFilled > 95) {
            cacheColour = Colors.Dark_Red;
        }

        sender.sendMessage("  Usage: " + cacheColour + String.format("%.2f", cachePercentFilled) + "% " + Colors.White + " ( " + cache.size() + "/" + cache.totalCapacity() + " [" + cache.capacity() + "+" + cache.adaptiveCapacity() + "] )");
        sender.sendMessage("  Profile: ");
        sendMethodCounter(sender, cache.getMethodCounter());
        // sender.sendMessage("  Reads: " + formatNumber(cache.getReads()) + " | " + String.format("%.2f", getAverage(cache.getReads())) + " / second");
        // sender.sendMessage("  Writes: " + formatNumber(cache.getWrites()) + " | " + String.format("%.2f", getAverage(cache.getWrites())) + " / second");
    }

    private static void sendMethodCounter(CommandSender sender, MethodCounter counter) {
        Map<String, Integer> sorted = counter.sortByValue();

        for (Map.Entry<String, Integer> entry : sorted.entrySet()) {
            String method = entry.getKey();
            int count = entry.getValue();

            sender.sendMessage("    " + method + ": " + formatNumber(count) + " (" + String.format("%.2f", getAverage(count)) + " / second)");
        }

    }


    /**
     * Format a number
     *
     * @param number
     * @return
     */
    public static String formatNumber(long number) {
        return NumberFormat.getInstance().format(number);
    }

    /**
     * @return time in seconds it was being ran
     */
    public static int getTimeRunningSeconds() {
        return (int) ((System.currentTimeMillis() - startTime) / 1000);
    }

    /**
     * Initialize
     */
    public static void init() {
        startTime = System.currentTimeMillis();
    }

}


<|start_filename|>src/main/java/com/griefcraft/modules/doors/DoorsModule.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.modules.doors;

import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Flag;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.util.config.Configuration;
import com.griefcraft.util.matchers.DoorMatcher;
import org.bukkit.Effect;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.Openable;
import org.bukkit.entity.Player;

public class DoorsModule extends JavaModule {

    /**
     * The amount of server ticks there usually are per second
     */
    private final static int TICKS_PER_SECOND = 20;

    /**
     * The course of action for opening doors
     */
    private enum Action {

        /**
         * The door should automatically open and then close
         */
        OPEN_AND_CLOSE,

        /**
         * The doors should just be opened, not closed after a set amount of time
         */
        TOGGLE

    }

    /**
     * The configuration file
     */
    private final Configuration configuration = Configuration.load("doors.yml");

    /**
     * The LWC object, set by load ()
     */
    private LWC lwc;

    /**
     * The current action to use, default to toggling the door open and closed
     */
    private Action action = Action.TOGGLE;

    @Override
    public void load(LWC lwc) {
        this.lwc = lwc;
        loadAction();
    }

    @Override
    public void onProtectionInteract(LWCProtectionInteractEvent event) {
        if (event.getResult() == Result.CANCEL || !isEnabled()) {
            return;
        }

        // The more important check
        if (!event.canAccess()) {
            return;
        }

        Protection protection = event.getProtection();
        Block block = event.getEvent().getClickedBlock(); // The block they actually clicked :)
        Player player = event.getPlayer();

        // Check if the block is even something that should be opened
        if (!isValid(block.getType())) {
            return;
        }

        // Should we look for double doors?
        boolean doubleDoors = usingDoubleDoors();

        // The BOTTOM half of the other side of the double door
        Block doubleDoorBlock = null;

        // Only waste CPU if we need the double door block
        if (doubleDoors) {
            doubleDoorBlock = getDoubleDoor(block);

            if (doubleDoorBlock != null) {
                Protection other = lwc.findProtection(doubleDoorBlock.getLocation());
                if (!lwc.canAccessProtection(player, other)) {
                    doubleDoorBlock = null; // don't open the other door :-)
                }
            }
        }

        // toggle the other side of the door open
        boolean opensWhenClicked = (DoorMatcher.WOODEN_DOORS.contains(block.getType())
                || DoorMatcher.WOODEN_FENCE_GATES.contains(block.getType())
                || DoorMatcher.WOODEN_TRAP_DOORS.contains(block.getType()));
        changeDoorStates(true, (opensWhenClicked ? null : block), doubleDoorBlock);

        if (action == Action.OPEN_AND_CLOSE || protection.hasFlag(Flag.Type.AUTOCLOSE)) {
            // Abuse the fact that we still use final variables inside the task
            // The double door block object is initially only assigned if we
            // need
            // it, so we just create a second variable ^^
            final Block finalBlock = block;
            final Block finalDoubleDoorBlock = doubleDoorBlock;

            // Calculate the wait time
            // This is basically Interval * TICKS_PER_SECOND
            int wait = getAutoCloseInterval() * TICKS_PER_SECOND;

            // Create the task
            // If we are set to close the door after a set period, let's create
            // a sync task for it
            lwc.getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(lwc.getPlugin(), new Runnable() {
                public void run() {

                    // Essentially all we need to do is reset the door
                    // states
                    // But DO NOT open the door if it's closed !
                    changeDoorStates(false, finalBlock, finalDoubleDoorBlock);

                }
            }, wait);
        }

    }

    /**
     * Change all of the given door states to be inverse; that is, if a door is
     * open, it will be closed afterwards. If the door is closed, it will become
     * open.
     * <p/>
     * Note that the blocks given must be the bottom block of the door.
     *
     * @param allowDoorToOpen If FALSE, and the door is currently CLOSED, it will NOT be opened!
     * @param doors           Blocks given must be the bottom block of the door
     */
    private void changeDoorStates(boolean allowDoorToOpen, Block... doors) {
        for (Block door : doors) {
            if (door == null) {
                continue;
            }

            // ensure this is an openable door
            Openable doorBlockData = null;
            try {
                doorBlockData = (Openable) door.getBlockData();
            } catch (ClassCastException e) {
                continue;
            }

            boolean doorIsOpen = doorBlockData.isOpen();

            // If we aren't allowing the door to open, check if it's already closed
            if (!allowDoorToOpen && !doorIsOpen) {
                // The door is already closed and we don't want to open it
                continue;
            }

            // toggle the door!
            doorBlockData.setOpen(!doorIsOpen);
            door.setBlockData(doorBlockData);

            // make the correct door sound
            switch (door.getType()) {
                case OAK_DOOR:
                case SPRUCE_DOOR:
                case BIRCH_DOOR:
                case JUNGLE_DOOR:
                case ACACIA_DOOR:
                case DARK_OAK_DOOR:
                    door.getWorld().playEffect(door.getLocation(),
                            doorIsOpen ? Effect.DOOR_CLOSE : Effect.DOOR_TOGGLE, 0);
                    break;
                case IRON_DOOR:
                    door.getWorld().playEffect(door.getLocation(),
                            doorIsOpen ? Effect.IRON_DOOR_CLOSE : Effect.IRON_DOOR_TOGGLE, 0);
                    break;
                case OAK_TRAPDOOR:
                case SPRUCE_TRAPDOOR:
                case BIRCH_TRAPDOOR:
                case JUNGLE_TRAPDOOR:
                case ACACIA_TRAPDOOR:
                case DARK_OAK_TRAPDOOR:
                    door.getWorld().playEffect(door.getLocation(),
                            doorIsOpen ? Effect.TRAPDOOR_CLOSE : Effect.TRAPDOOR_TOGGLE, 0);
                    break;
                case IRON_TRAPDOOR:
                    door.getWorld().playEffect(door.getLocation(),
                            doorIsOpen ? Effect.IRON_TRAPDOOR_CLOSE : Effect.IRON_TRAPDOOR_TOGGLE, 0);
                    break;
                case OAK_FENCE_GATE:
                case SPRUCE_FENCE_GATE:
                case BIRCH_FENCE_GATE:
                case JUNGLE_FENCE_GATE:
                case ACACIA_FENCE_GATE:
                case DARK_OAK_FENCE_GATE:
                    door.getWorld().playEffect(door.getLocation(),
                            doorIsOpen ? Effect.FENCE_GATE_CLOSE : Effect.FENCE_GATE_TOGGLE, 0);
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * Get the double door for the given block
     *
     * @param block
     * @return
     */
    private Block getDoubleDoor(Block block) {
        if (!isValid(block.getType())) {
            return null;
        }

        Block found;

        for (Material material : DoorMatcher.PROTECTABLES_DOORS) {
            if ((found = lwc.findAdjacentBlock(block, material)) != null) {
                return found;
            }
        }

        return null;
    }

    /**
     * Check if automatic door opening is enabled
     *
     * @return
     */
    public boolean isEnabled() {
        return configuration.getBoolean("doors.enabled", true);
    }

    /**
     * Check if the material is auto openable/closable
     *
     * @param material
     * @return
     */
    private boolean isValid(Material material) {
        return DoorMatcher.PROTECTABLES_DOORS.contains(material) || DoorMatcher.FENCE_GATES.contains(material) ||
                DoorMatcher.TRAP_DOORS.contains(material);
    }

    /**
     * Get the amount of seconds after opening a door it should be closed
     *
     * @return
     */
    private int getAutoCloseInterval() {
        return configuration.getInt("doors.interval", 3);
    }

    /**
     * Get if we are allowing double doors to be used
     *
     * @return
     */
    private boolean usingDoubleDoors() {
        return configuration.getBoolean("doors.doubleDoors", true);
    }

    /**
     * Load the action from the configuration
     */
    private void loadAction() {
        String strAction = configuration.getString("doors.action");

        if (strAction.equalsIgnoreCase("openAndClose")) {
            this.action = Action.OPEN_AND_CLOSE;
        } else {
            this.action = Action.TOGGLE;
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/scripting/JavaModule.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.scripting;

import com.griefcraft.lwc.LWC;
import com.griefcraft.scripting.event.LWCAccessEvent;
import com.griefcraft.scripting.event.LWCBlockInteractEvent;
import com.griefcraft.scripting.event.LWCCommandEvent;
import com.griefcraft.scripting.event.LWCDropItemEvent;
import com.griefcraft.scripting.event.LWCEntityInteractEvent;
import com.griefcraft.scripting.event.LWCMagnetPullEvent;
import com.griefcraft.scripting.event.LWCProtectionDestroyEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEntityEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEntityEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.scripting.event.LWCProtectionRegistrationPostEvent;
import com.griefcraft.scripting.event.LWCProtectionRemovePostEvent;
import com.griefcraft.scripting.event.LWCRedstoneEvent;
import com.griefcraft.scripting.event.LWCReloadEvent;
import com.griefcraft.scripting.event.LWCSendLocaleEvent;

public class JavaModule implements Module {

    /**
     * Allow the event to occur (e.g allow the redstone, allow a protection
     * destruction, and so on)
     */
    public final static Result ALLOW = Result.ALLOW;

    /**
     * Cancel the event from happening (e.g disallow protection interaction,
     * disallow protection registration)
     */
    public final static Result CANCEL = Result.CANCEL;

    /**
     * The default result returned by events
     */
    public final static Result DEFAULT = Result.DEFAULT;

    public void load(LWC lwc) {
    }

    public void onReload(LWCReloadEvent event) {

    }

    public void onAccessRequest(LWCAccessEvent event) {

    }

    public void onDropItem(LWCDropItemEvent event) {

    }

    public void onCommand(LWCCommandEvent event) {

    }

    public void onRedstone(LWCRedstoneEvent event) {

    }

    public void onDestroyProtection(LWCProtectionDestroyEvent event) {

    }

    public void onProtectionInteract(LWCProtectionInteractEvent event) {

    }

    public void onBlockInteract(LWCBlockInteractEvent event) {

    }

    public void onRegisterProtection(LWCProtectionRegisterEvent event) {

    }

    public void onPostRegistration(LWCProtectionRegistrationPostEvent event) {

    }

    public void onPostRemoval(LWCProtectionRemovePostEvent event) {

    }

    public void onSendLocale(LWCSendLocaleEvent event) {

    }

    public void onEntityInteract(LWCEntityInteractEvent event) {

    }

    public void onMagnetPull(LWCMagnetPullEvent event) {

    }

    public void onEntityInteractProtection(LWCProtectionInteractEntityEvent event) {

    }

    public void onRegisterEntity(LWCProtectionRegisterEntityEvent event) {

    }

}


<|start_filename|>src/main/java/com/griefcraft/modules/create/CreateModule.java<|end_filename|>
/*
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.modules.create;

import com.griefcraft.bukkit.EntityBlock;
import com.griefcraft.cache.BlockCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Action;
import com.griefcraft.model.LWCPlayer;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCBlockInteractEvent;
import com.griefcraft.scripting.event.LWCCommandEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.scripting.event.LWCProtectionRegistrationPostEvent;
import com.griefcraft.sql.PhysDB;
import com.griefcraft.util.StringUtil;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;

public class CreateModule extends JavaModule {

    @Override
    public void onProtectionInteract(LWCProtectionInteractEvent event) {
        if (event.getResult() != Result.DEFAULT) {
            return;
        }

        if (!event.hasAction("create")) {
            return;
        }

        LWC lwc = event.getLWC();
        Protection protection = event.getProtection();
        Player player = event.getPlayer();

        BlockCache blockCache = BlockCache.getInstance();
        if (protection.isOwner(player)) {
            lwc.sendLocale(player, "protection.interact.error.alreadyregistered", "block",
                    LWC.materialToString(blockCache.getBlockType(protection.getBlockId())));
        } else {
            lwc.sendLocale(player, "protection.interact.error.notowner", "block",
                    LWC.materialToString(blockCache.getBlockType(protection.getBlockId())));
        }

        lwc.removeModes(player);
        event.setResult(Result.CANCEL);
    }

    @Override
    public void onBlockInteract(LWCBlockInteractEvent event) {
        if (event.getResult() != Result.DEFAULT) {
            return;
        }

        if (!event.hasAction("create")) {
            return;
        }

        LWC lwc = event.getLWC();
        Block block = event.getBlock();
        LWCPlayer player = lwc.wrapPlayer(event.getPlayer());

        if (!lwc.isProtectable(block)) {
            return;
        }

        PhysDB physDb = lwc.getPhysicalDatabase();

        Action action = player.getAction("create");
        String actionData = action.getData();
        String[] split = actionData.split(" ");
        String protectionType = split[0].toLowerCase();
        String protectionData = StringUtil.join(split, 1);

        // check permissions again (DID THE LITTLE SHIT MOVE WORLDS??!?!?!?!?!?)
        if (!lwc.hasPermission(event.getPlayer(), "lwc.create." + protectionType, "lwc.create", "lwc.protect")) {
            lwc.sendLocale(player, "protection.accessdenied");
            lwc.removeModes(player);
            event.setResult(Result.CANCEL);
            return;
        }

        String worldName = block.getWorld().getName();
        int blockX;
        int blockY;
        int blockZ;

        if (block instanceof EntityBlock) {
            Entity entity = EntityBlock.getEntity();
            blockX = EntityBlock.POSITION_OFFSET + entity.getUniqueId().hashCode();
            blockY = EntityBlock.POSITION_OFFSET + entity.getUniqueId().hashCode();
            blockZ = EntityBlock.POSITION_OFFSET + entity.getUniqueId().hashCode();
        } else {
            blockX = block.getX();
            blockY = block.getY();
            blockZ = block.getZ();
        }

        lwc.removeModes(player);
        LWCProtectionRegisterEvent evt = new LWCProtectionRegisterEvent(player.getBukkitPlayer(), block);
        lwc.getModuleLoader().dispatchEvent(evt);

        // another plugin cancelled the registration
        if (evt.isCancelled()) {
            return;
        }

        // The created protection
        Protection protection = null;

        BlockCache blockCache = BlockCache.getInstance();
        int blockId = blockCache.getBlockId(block);
        if (blockId < 0) {
            return;
        }
        if (protectionType.equals("public")) {
            if (block instanceof EntityBlock) {
                protection = physDb.registerProtection(EntityBlock.ENTITY_BLOCK_ID, Protection.Type.PUBLIC, worldName,
                        player.getUniqueId().toString(), "", blockX, blockY, blockZ);
            } else {
                protection = physDb.registerProtection(blockId, Protection.Type.PUBLIC, worldName,
                        player.getUniqueId().toString(), "", blockX, blockY, blockZ);
            }
            lwc.sendLocale(player, "protection.interact.create.finalize");
        } else if (protectionType.equals("password")) {
            String password = lwc.encrypt(protectionData);

            if (block instanceof EntityBlock) {
                protection = physDb.registerProtection(EntityBlock.ENTITY_BLOCK_ID, Protection.Type.PASSWORD,
                        worldName, player.getUniqueId().toString(), password, blockX, blockY, blockZ);
            } else {
                protection = physDb.registerProtection(blockId, Protection.Type.PASSWORD, worldName,
                        player.getUniqueId().toString(), password, blockX, blockY, blockZ);
            }
            player.addAccessibleProtection(protection);

            lwc.sendLocale(player, "protection.interact.create.finalize");
            lwc.sendLocale(player, "protection.interact.create.password");
        } else if (protectionType.equals("private") || protectionType.equals("donation")) {
            String[] rights = protectionData.split(" ");
            if (block instanceof EntityBlock) {
                protection = physDb.registerProtection(EntityBlock.ENTITY_BLOCK_ID,
                        Protection.Type.matchType(protectionType), worldName, player.getUniqueId().toString(), "",
                        blockX, blockY, blockZ);
            } else {
                protection = physDb.registerProtection(blockId,
                        Protection.Type.matchType(protectionType), worldName, player.getUniqueId().toString(), "",
                        blockX, blockY, blockZ);
            }

            lwc.sendLocale(player, "protection.interact.create.finalize");
            lwc.processRightsModifications(player, protection, rights);
        }

        // tell the modules that a protection was registered
        if (protection != null) {
            // Fix the blocks that match it
            protection.removeCache();
            LWC.getInstance().getProtectionCache().addProtection(protection);
            lwc.getModuleLoader().dispatchEvent(new LWCProtectionRegistrationPostEvent(protection));
        }

        event.setResult(Result.CANCEL);
    }

    @Override
    public void onCommand(LWCCommandEvent event) {
        if (event.isCancelled()) {
            return;
        }

        if (!event.hasFlag("c", "create")) {
            return;
        }

        LWC lwc = event.getLWC();
        CommandSender sender = event.getSender();
        String[] args = event.getArgs();

        if (!(sender instanceof Player)) {
            return;
        }

        if (args.length == 0) {
            lwc.sendLocale(sender, "help.creation");
            return;
        }

        LWCPlayer player = lwc.wrapPlayer(sender);

        String full = StringUtil.join(args, 0).trim();
        String type = args[0].toLowerCase();
        String data = StringUtil.join(args, 1);
        event.setCancelled(true);

        /**
         * Allow individual enforcements with e.g lwc.create.private, or just the
         * umbrella lwc.create for all
         */
        if (!lwc.hasPermission(sender, "lwc.create." + type, "lwc.create", "lwc.protect")) {
            lwc.sendLocale(sender, "protection.accessdenied");
            return;
        }

        try {
            switch (Protection.Type.matchType(type)) {
                case PASSWORD:
                    if (args.length < 2) {
                        lwc.sendSimpleUsage(player, "/lwc -c password <Password>");
                        return;
                    }

                    String hiddenPass = StringUtil.transform(data, '*');
                    lwc.sendLocale(player, "protection.create.password", "password", hiddenPass);
                    break;
                default:
                    break;
            }
        } catch (IllegalArgumentException e) {
            // Invalid protection type!
            lwc.sendLocale(player, "help.creation");
            return;
        }

        Action action = new Action();
        action.setName("create");
        action.setPlayer(player);
        action.setData(full);

        player.removeAllActions();
        player.addAction(action);

        lwc.sendLocale(player, "protection.create.finalize", "type",
                lwc.getPlugin().getMessageParser().parseMessage(type));
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/StopWatch.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */
/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Note: this code is originally part of the package org.springframework.util,
 * which is a part of the Spring Framework application framework.
 */

package com.griefcraft.util;

import org.bukkit.command.CommandSender;

import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;

/**
 * Simple stop watch, allowing for timing of a number of tasks,
 * exposing total running time and running time for each named task.
 * <p/>
 * <p>Conceals use of <code>System.currentTimeMillis()</code>, improving the
 * readability of application code and reducing the likelihood of calculation errors.
 * <p/>
 * <p>Note that this object is not designed to be thread-safe and does not
 * use synchronization.
 * <p/>
 * <p>This class is normally used to verify performance during proof-of-concepts
 * and in development, rather than as part of production applications.
 *
 * @author <NAME>
 * @author <NAME>
 * @since May 2, 2001
 */
public class StopWatch {

    /**
     * Identifier of this stop watch.
     * Handy when we have output from multiple stop watches
     * and need to distinguish between them in log or console output.
     */
    private final String id;

    private boolean keepTaskList = true;

    /**
     * List of TaskInfo objects
     */
    private final List<TaskInfo> taskList = new LinkedList<>();

    /**
     * Start time of the current task
     */
    private long startTimeMillis;

    /**
     * Is the stop watch currently running?
     */
    private boolean running;

    /**
     * Name of the current task
     */
    private String currentTaskName;

    private TaskInfo lastTaskInfo;

    private int taskCount;

    /**
     * Total running time
     */
    private long totalTimeMillis;


    /**
     * Construct a new stop watch. Does not start any task.
     */
    public StopWatch() {
        this.id = "";
    }

    /**
     * Construct a new stop watch with the given id.
     * Does not start any task.
     *
     * @param id identifier for this stop watch.
     *           Handy when we have output from multiple stop watches
     *           and need to distinguish between them.
     */
    public StopWatch(String id) {
        this.id = id;
    }

    /**
     * Determine whether the TaskInfo array is built over time. Set this to
     * "false" when using a StopWatch for millions of intervals, or the task
     * info structure will consume excessive memory. Default is "true".
     */
    public void setKeepTaskList(boolean keepTaskList) {
        this.keepTaskList = keepTaskList;
    }


    /**
     * Start an unnamed task. The results are undefined if {@link #stop()}
     * or timing methods are called without invoking this method.
     *
     * @see #stop()
     */
    public void start() throws IllegalStateException {
        start("");
    }

    /**
     * Start a named task. The results are undefined if {@link #stop()}
     * or timing methods are called without invoking this method.
     *
     * @param taskName the name of the task to start
     * @see #stop()
     */
    public void start(String taskName) throws IllegalStateException {
        if (this.running) {
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        }
        this.startTimeMillis = System.currentTimeMillis();
        this.running = true;
        this.currentTaskName = taskName;
    }

    /**
     * Stop the current task. The results are undefined if timing
     * methods are called without invoking at least one pair
     * {@link #start()} / {@link #stop()} methods.
     *
     * @see #start()
     */
    public void stop() throws IllegalStateException {
        if (!this.running) {
            throw new IllegalStateException("Can't stop StopWatch: it's not running");
        }
        long lastTime = System.currentTimeMillis() - this.startTimeMillis;
        this.totalTimeMillis += lastTime;
        this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
        if (this.keepTaskList) {
            this.taskList.add(lastTaskInfo);
        }
        ++this.taskCount;
        this.running = false;
        this.currentTaskName = null;
    }

    /**
     * Return whether the stop watch is currently running.
     */
    public boolean isRunning() {
        return this.running;
    }


    /**
     * Return the time taken by the last task.
     */
    public long getLastTaskTimeMillis() throws IllegalStateException {
        if (this.lastTaskInfo == null) {
            throw new IllegalStateException("No tests run: can't get last interval");
        }
        return this.lastTaskInfo.getTimeMillis();
    }

    /**
     * Return the total time in milliseconds for all tasks.
     */
    public long getTotalTimeMillis() {
        return totalTimeMillis;
    }

    /**
     * Return the total time in seconds for all tasks.
     */
    public double getTotalTimeSeconds() {
        return totalTimeMillis / 1000.0;
    }

    /**
     * Return the number of tasks timed.
     */
    public int getTaskCount() {
        return taskCount;
    }

    /**
     * Return an array of the data for tasks performed.
     */
    public TaskInfo[] getTaskInfo() {
        if (!this.keepTaskList) {
            throw new UnsupportedOperationException("Task info is not being kept!");
        }
        return this.taskList.toArray(new TaskInfo[this.taskList.size()]);
    }


    /**
     * Return a short description of the total running time.
     */
    public String shortSummary() {
        return "" + this.id + ": " + Colors.Yellow + getTotalTimeMillis() + " ms";
    }

    /**
     * Return a string with a table describing all tasks performed.
     * For custom reporting, call getTaskInfo() and use the task info directly.
     */
    public String prettyPrint() {
        StringBuilder sb = new StringBuilder(shortSummary());
        sb.append('\n');
        if (!this.keepTaskList) {
            sb.append("No task info kept");
        } else {
            TaskInfo[] tasks = getTaskInfo();
            sb.append("-----------------------------------------\n");
            sb.append("ms     %     Task name\n");
            sb.append("-----------------------------------------\n");
            NumberFormat nf = NumberFormat.getNumberInstance();
            nf.setMinimumIntegerDigits(5);
            nf.setGroupingUsed(false);
            NumberFormat pf = NumberFormat.getPercentInstance();
            pf.setMinimumIntegerDigits(3);
            pf.setGroupingUsed(false);
            for (TaskInfo task : tasks) {
                sb.append(nf.format(task.getTimeMillis())).append("  ");
                sb.append(pf.format(task.getTimeSeconds() / getTotalTimeSeconds())).append("  ");
                sb.append(task.getTaskName()).append("\n");
            }
        }
        return sb.toString();
    }

    /**
     * Send the pretty print table to a command sender
     */
    public void prettyPrint(CommandSender sender) {
        sender.sendMessage(shortSummary());
        if (!this.keepTaskList) {
            sender.sendMessage("No task info kept");
        } else {
            TaskInfo[] tasks = getTaskInfo();
            sender.sendMessage("-----------------------------------------");
            sender.sendMessage("ms     %     Task name");
            sender.sendMessage("-----------------------------------------");
            NumberFormat nf = NumberFormat.getNumberInstance();
            nf.setMinimumIntegerDigits(5);
            nf.setGroupingUsed(false);
            NumberFormat pf = NumberFormat.getPercentInstance();
            pf.setMinimumIntegerDigits(3);
            pf.setGroupingUsed(false);

            for (TaskInfo task : tasks) {
                String line = "";
                line += nf.format(task.getTimeMillis()) + "  ";
                line += pf.format(task.getTimeSeconds() / getTotalTimeSeconds()) + "  ";
                line += task.getTaskName();

                sender.sendMessage(line);
            }
        }
    }

    /**
     * Return an informative string describing all tasks performed
     * For custom reporting, call <code>getTaskInfo()</code> and use the task info directly.
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(shortSummary());
        if (this.keepTaskList) {
            TaskInfo[] tasks = getTaskInfo();
            for (TaskInfo task : tasks) {
                sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis());
                long percent = Math.round((100.0 * task.getTimeSeconds()) / getTotalTimeSeconds());
                sb.append(" = ").append(percent).append("%");
            }
        } else {
            sb.append("; no task info kept");
        }
        return sb.toString();
    }


    /**
     * Inner class to hold data about one task executed within the stop watch.
     */
    public static class TaskInfo {

        private final String taskName;

        private final long timeMillis;

        private TaskInfo(String taskName, long timeMillis) {
            this.taskName = taskName;
            this.timeMillis = timeMillis;
        }

        /**
         * Return the name of this task.
         */
        public String getTaskName() {
            return taskName;
        }

        /**
         * Return the time in milliseconds this task took.
         */
        public long getTimeMillis() {
            return timeMillis;
        }

        /**
         * Return the time in seconds this task took.
         */
        public double getTimeSeconds() {
            return timeMillis / 1000.0;
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/VersionUtil.java<|end_filename|>
package com.griefcraft.util;

import org.bukkit.Bukkit;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class VersionUtil {

    public static int getMinorVersion() {
        Matcher versionCheck = Pattern.compile("\\d[.]\\d+").matcher(Bukkit.getVersion());
        return versionCheck.find() ? Integer.parseInt(versionCheck.group().substring(2)) : -1;
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/DatabaseThread.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util;

import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Protection;
import com.griefcraft.sql.Database;

import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class DatabaseThread implements Runnable {

    /**
     * The LWC object
     */
    private final LWC lwc;

    /**
     * The protections waiting to be updated in the database
     */
    private final Queue<Protection> updateQueue = new ConcurrentLinkedQueue<>();

    /**
     * The thread we are running in
     */
    private final Thread thread = new Thread(this);

    /**
     * If the database thread is active and running
     */
    private boolean running = false;

    /**
     * The last time the queue was flushed to the database
     */
    private long lastFlush = -1L;

    /**
     * The time the next keepalive packet will be sent at
     */
    private long nextKeepalivePacket = 0;

    /**
     * Interval between pinging the database
     */
    private int pingInterval = 0;

    public DatabaseThread(LWC lwc) {
        this.lwc = lwc;
        this.running = true;
        this.lastFlush = System.currentTimeMillis();
        this.thread.start();
        pingInterval = lwc.getConfiguration().getInt("database.ping_interval", 300);
    }

    /**
     * Adds a protection to the update queue so that it is flushed to the database
     * asap
     *
     * @param protection
     */
    public void addProtection(Protection protection) {
        updateQueue.offer(protection);
    }

    /**
     * Removes a protection from the update queue
     *
     * @param protection
     */
    public void removeProtection(Protection protection) {
        updateQueue.remove(protection);
    }

    /**
     * Gets the current amount of protections queued to be updated
     *
     * @return the amount of protections queued to be updated
     */
    public int size() {
        return updateQueue.size();
    }

    /**
     * Stop the database thread
     */
    public void stop() {
        // stop running and interrupt the thread
        running = false;
        if (!thread.isInterrupted()) {
            thread.interrupt();
        }

        // Flush the rest of the entries
        flushDatabase();
    }

    /**
     * Recommend a flush as soon as possible. This does not guarantee the database
     * will be flushed immediately.
     */
    public void flush() {
        lastFlush = System.currentTimeMillis() - 9999999L;
    }

    /**
     * Flush the protections to the database
     */
    private void flushDatabase() {
        if (!updateQueue.isEmpty()) {
            Database database = lwc.getPhysicalDatabase();
            database.setAutoCommit(false);
            database.setUseStatementCache(false);

            // Begin iterating through the queue
            Iterator<Protection> iter = updateQueue.iterator();
            while (iter.hasNext()) {
                Protection protection = iter.next();
                iter.remove();
                protection.saveNow();
            }

            // Commit the changes to the database
            database.setUseStatementCache(true);
            database.setAutoCommit(true);
        }

        // update the time we last flushed at
        lastFlush = System.currentTimeMillis();

        if (System.currentTimeMillis() > nextKeepalivePacket && lwc.getPhysicalDatabase().isConnected()) {
            nextKeepalivePacket = System.currentTimeMillis() + (pingInterval * 1000);
            lwc.getPhysicalDatabase().pingDatabase();
        }
    }

    public void run() {
        while (running) {
            // how many seconds between each flush
            int interval = lwc.getConfiguration().getInt("core.flushInterval", 5);

            if (interval > 120) {
                interval = 120;
            }

            long currentTime = System.currentTimeMillis();
            long intervalMilliseconds = interval * 1000L;

            // compare the current time to the last flush
            if (currentTime - lastFlush > intervalMilliseconds) {
                flushDatabase();
            }

            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                running = false;
            }
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/cache/MethodCounter.java<|end_filename|>
package com.griefcraft.cache;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class MethodCounter {

    /**
     * A map of the counts
     */
    private final Map<String, Integer> counts = new HashMap<>();

    /**
     * Increment a method in the counts
     *
     * @param method
     */
    public void increment(String method) {
        deltaMethod(method, 1);
    }

    /**
     * Decrement a method in the cache
     *
     * @param method
     */
    public void decrement(String method) {
        deltaMethod(method, -1);
    }

    /**
     * Get the counts for a method
     *
     * @param method
     * @return
     */
    public int get(String method) {
        return counts.containsKey(method) ? counts.get(method) : 0;
    }

    /**
     * Sorts the method counts by the value and returns an unmodifiable map for it
     *
     * @return
     */
    public Map<String, Integer> sortByValue() {
        return Collections.unmodifiableMap(sortByComparator(counts, false));
    }

    /**
     * Handle increment / decrement
     *
     * @param method
     * @param delta
     */
    private void deltaMethod(String method, int delta) {
        if (!counts.containsKey(method)) {
            counts.put(method, delta);
            return;
        }

        counts.put(method, counts.get(method) + delta);
    }

    /**
     * Sort a hashmap by the values
     * <p/>
     * credits: http://stackoverflow.com/questions/8119366/sorting-hashmap-by-values/13913206#13913206
     *
     * @param unsortMap
     * @param order
     * @return
     */
    private static Map<String, Integer> sortByComparator(Map<String, Integer> unsortMap, final boolean order) {
        List<Map.Entry<String, Integer>> list = new LinkedList<>(unsortMap.entrySet());

        // Sorting the list based on values
        Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
            public int compare(Map.Entry<String, Integer> o1,
                               Map.Entry<String, Integer> o2) {
                if (order) {
                    return o1.getValue().compareTo(o2.getValue());
                } else {
                    return o2.getValue().compareTo(o1.getValue());

                }
            }
        });

        // Maintaining insertion order with the help of LinkedList
        Map<String, Integer> sortedMap = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> entry : list) {
            sortedMap.put(entry.getKey(), entry.getValue());
        }

        return sortedMap;
    }

}


<|start_filename|>src/main/java/com/griefcraft/modules/limits/LimitsModule.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.modules.limits;

import com.griefcraft.cache.BlockCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.util.config.Configuration;
import org.apache.commons.lang.StringUtils;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachmentInfo;

public class LimitsModule extends JavaModule {

    /**
     * The permission node for type: default protections
     */
    public static final String PERMISSION_NODE_GLOBAL = "lwc.limit.";

    /**
     * NODE.BLOCK.limit
     */
    public static final String PERMISSION_NODE_BLOCK = "lwc.limit.block.";

    /**
     * Limits type
     */
    public enum Type {
        /**
         * Undefined type
         */
        NULL,

        /**
         * Combines all blocks together into one
         * This type ignores chest:, furnace:, etc
         */
        DEFAULT,

        /**
         * Limits are defined per-block
         */
        CUSTOM;

        public static Type resolve(String name) {
            for (Type type : values()) {
                if (type.toString().equalsIgnoreCase(name)) {
                    return type;
                }
            }

            return NULL;
        }
    }

    private Configuration configuration = Configuration.load("limits.yml", false);

    /**
     * Integer value that represents unlimited protections
     */
    private int UNLIMITED = Integer.MAX_VALUE;

    /**
     * If protection limits are enabled
     */
    private boolean enabled = true;

    public LimitsModule() {
        enabled = LWC.getInstance().getConfiguration().getBoolean("optional.useProtectionLimits", true);
    }

    /**
     * Set a config value in the configuration
     *
     * @param path
     * @param value
     */
    public void set(String path, Object value) {
        configuration.setProperty(path, value);
    }

    /**
     * Save the configuration
     */
    public boolean save() {
        return configuration.save();
    }

    /**
     * Check if a player has reached their protection limit for a specific block type
     *
     * @param player
     * @param block
     * @return true if the player reached their limit
     */
    public boolean hasReachedLimit(Player player, Block block) {
        if (configuration == null) {
            return false;
        }

        LWC lwc = LWC.getInstance();
        BlockCache blockCache = BlockCache.getInstance();
        int limit = mapProtectionLimit(player, blockCache.getBlockId(block));

        // if they're limit is unlimited, how could they get above it? :)
        if (limit == UNLIMITED) {
            return false;
        }

        Type type = Type.resolve(resolveString(player, "type"));
        int protections; // 0 = *

        switch (type) {
            case DEFAULT:
                protections = lwc.getPhysicalDatabase().getProtectionCount(player.getName());
                break;
            case CUSTOM:
                protections = lwc.getPhysicalDatabase().getProtectionCount(player.getName(), blockCache.getBlockId(block));
                break;
            default:
                throw new UnsupportedOperationException("Limit type " + type.toString() + " is undefined in LimitsModule::hasReachedLimit");
        }

        return protections >= limit;
    }

    /**
     * Search the player's permissions for a permission and return it
     * Depending on this is used, this can become O(scary)
     *
     * @param player
     * @param prefix
     * @return
     */
    public PermissionAttachmentInfo searchPermissions(Player player, String prefix) {
        for (PermissionAttachmentInfo attachment : player.getEffectivePermissions()) {
            String permission = attachment.getPermission();

            // check for the perm node
            if (attachment.getValue() && permission.startsWith(prefix)) {
                // Bingo!
                return attachment;
            }
        }

        return null;
    }

    /**
     * Search permissions for an integer and if found, return it
     *
     * @param player
     * @param prefix
     * @return
     */
    public int searchPermissionsForInteger(Player player, String prefix) {
        PermissionAttachmentInfo attachment = searchPermissions(player, prefix);

        // Not found
        if (attachment == null) {
            return -1;
        }

        // Found
        return Integer.parseInt(attachment.getPermission().substring(prefix.length()));
    }

    /**
     * Get the protection limits for a player
     *
     * @param player
     * @param blockId
     * @return
     */
    public int mapProtectionLimit(Player player, int blockId) {
        if (configuration == null) {
            return 0;
        }

        int limit = -1;
        Type type = Type.resolve(resolveString(player, "type"));

        // Try permissions
        int globalLimit = searchPermissionsForInteger(player, PERMISSION_NODE_GLOBAL);

        // Was it found?
        if (globalLimit >= 0) {
            return globalLimit;
        }

        // Try the block id now
        int blockLimit = searchPermissionsForInteger(player, PERMISSION_NODE_BLOCK + blockId + ".");

        if (blockLimit != -1) {
            return blockLimit;
        }

        switch (type) {

            case DEFAULT:
                limit = resolveInteger(player, "limit");
                break;

            case CUSTOM:
                // first try the block id
                limit = resolveInteger(player, blockId + "");

                // and now try the name
                if (limit == -1 && blockId > 0) {
                    BlockCache blockCache = BlockCache.getInstance();
                    String name = StringUtils.replace(blockCache.getBlockType(blockId).toString().toLowerCase(), "block", "");

                    if (name.endsWith("_")) {
                        name = name.substring(0, name.length() - 1);
                    }

                    limit = resolveInteger(player, name);
                }

                // if it's STILL null, fall back
                if (limit == -1) {
                    limit = resolveInteger(player, "limit");
                }
                break;
            default:
                break;

        }

        return limit == -1 ? UNLIMITED : limit;
    }

    /**
     * Resolve a configuration node for a player. Tries nodes in this order:
     * <pre>
     * players.PLAYERNAME.node
     * groups.GROUPNAME.node
     * master.node
     * </pre>
     *
     * @param player
     * @param node
     * @return
     */
    private String resolveString(Player player, String node) {
        LWC lwc = LWC.getInstance();

        // resolve the limits type
        String value;

        // try the player
        value = configuration.getString("players." + player.getName() + "." + node);

        // try the player's groups
        if (value == null) {
            for (String groupName : lwc.getPermissions().getGroups(player)) {
                if (groupName != null && !groupName.isEmpty() && value == null) {
                    value = map("groups." + groupName + "." + node);
                }
            }
        }

        // if all else fails, use master
        if (value == null) {
            value = map("master." + node);
        }

        return value != null && !value.isEmpty() ? value : null;
    }

    /**
     * Resolve an integer for a player
     *
     * @param player
     * @param node
     * @return
     */
    private int resolveInteger(Player player, String node) {
        LWC lwc = LWC.getInstance();

        // resolve the limits type
        int value = -1;

        // try the player
        String temp = configuration.getString("players." + player.getName() + "." + node);

        if (temp != null && !temp.isEmpty()) {
            value = parseInt(temp);
        }

        // try the player's groups
        if (value == -1) {
            for (String groupName : lwc.getPermissions().getGroups(player)) {
                if (groupName != null && !groupName.isEmpty()) {
                    temp = map("groups." + groupName + "." + node);

                    if (temp != null && !temp.isEmpty()) {
                        int resolved = parseInt(temp);

                        // Is it higher than what we already have?
                        if (resolved > value) {
                            value = resolved;
                        }
                    }
                }
            }
        }

        // if all else fails, use master
        if (value == -1) {
            temp = map("master." + node);

            if (temp != null && !temp.isEmpty()) {
                value = parseInt(temp);
            }
        }

        // Default to 0, not -1 if it is still -1
        return value;
    }

    /**
     * Parse an int
     *
     * @param input
     * @return
     */
    private int parseInt(String input) {
        if (input.equalsIgnoreCase("unlimited")) {
            return UNLIMITED;
        }

        return Integer.parseInt(input);
    }

    /**
     * Get the value from either the path or the master value if it's null
     *
     * @param path
     * @return
     */
    private String map(String path) {
        String value = configuration.getString(path);

        if (value == null) {
            int lastIndex = path.lastIndexOf(".");
            String node = path.substring(lastIndex + 1);

            value = configuration.getString("master." + node);
        }

        if (value == null) {
            value = "";
        }

        return value;
    }

    @Override
    public void onRegisterProtection(LWCProtectionRegisterEvent event) {
        if (!enabled || event.isCancelled()) {
            return;
        }

        LWC lwc = event.getLWC();
        Player player = event.getPlayer();
        Block block = event.getBlock();

        if (hasReachedLimit(player, block)) {
            lwc.sendLocale(player, "protection.exceeded");
            event.setCancelled(true);
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/UUIDRegistry.java<|end_filename|>
package com.griefcraft.util;

import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class UUIDRegistry {

    /**
     * Temporal caches
     */
    private static final Map<String, PlayerInfo> nameToUUIDCache = new HashMap<>();
    private static final Map<UUID, PlayerInfo> UUIDToNameCache = new HashMap<>();

    static class PlayerInfo {

        private UUID uuid;
        private String name;

        public PlayerInfo(UUID uuid, String name) {
            this.uuid = uuid;
            this.name = name;
        }

        public UUID getUUID() {
            return uuid;
        }

        public String getName() {
            return name;
        }
    }

    /**
     * Update the cache with a new UUID/name pair
     *
     * @param uuid
     * @param name
     */
    public static void updateCache(UUID uuid, String name) {
        PlayerInfo playerInfo = new PlayerInfo(uuid, name);
        nameToUUIDCache.put(name.toLowerCase(), playerInfo);
        UUIDToNameCache.put(uuid, playerInfo);
    }

    /**
     * Check if a uuid in string form is a valid Java UUID
     *
     * @param uuid
     * @return true if the string is a valid UUID
     */
    public static boolean isValidUUID(String uuid) {
        return uuid.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}");
    }

    /**
     * Get the name for the given UUID. If it is not already known, it will be retrieved from the account servers.
     *
     * @param uuid
     * @return
     */
    public static String getName(UUID uuid) {
        if (uuid == null) {
            return null;
        }

        if (UUIDToNameCache.containsKey(uuid)) {
            return UUIDToNameCache.get(uuid).getName();
        }

        // First way: if they're on the server already
        Player player = Bukkit.getPlayer(uuid);

        if (player != null) {
            updateCache(uuid, player.getName());
            return player.getName();
        }

        // Second way: if they have been on the server before
        OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);

        if (offlinePlayer != null && offlinePlayer.getName() != null) {
            updateCache(uuid, offlinePlayer.getName());
            return offlinePlayer.getName();
        }

        // Third way: use the web API
        try {
            Map<UUID, String> results = new NameFetcher(Arrays.asList(uuid)).call();

            if (results.containsKey(uuid)) {
                updateCache(uuid, results.get(uuid));
                return results.get(uuid);
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Get the UUID for the given name. If it is not already known, it will be retrieved from the account servers.
     *
     * @param name
     * @return
     * @throws Exception
     */
    @SuppressWarnings("deprecation")
    public static UUID getUUID(String name) {
        String nameLower = name.toLowerCase();

        try {
            if (nameToUUIDCache.containsKey(nameLower)) {
                return nameToUUIDCache.get(nameLower).getUUID();
            }

            if (isValidUUID(name)) {
                return UUID.fromString(name);
            }

            if (Bukkit.getOnlineMode()) {
                Map<String, UUID> results = new UUIDFetcher(Arrays.asList(nameLower)).call();

                // The returned name is the exact casing; so we need to look for it
                // in the case-insensitive version
                for (String key : results.keySet()) {
                    if (key.equalsIgnoreCase(name)) {
                        UUID uuid = results.get(key);
                        updateCache(uuid, key);
                        return uuid;
                    }
                }
            } else {
                OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(name);

                if (offlinePlayer != null && offlinePlayer.getUniqueId() != null) {
                    if (offlinePlayer.getName() != null) {
                        name = offlinePlayer.getName();
                    }

                    updateCache(offlinePlayer.getUniqueId(), name);
                    return offlinePlayer.getUniqueId();
                }
            }

            nameToUUIDCache.put(nameLower, null);
            return null;
        } catch (Exception e) {
            nameToUUIDCache.put(nameLower, null);
            return null;
        }
    }

    /**
     * Attempts to format a player's name, which can be a name or a UUID. If the owner is a UUID and then
     * UUID is unknown, then "Unknown (uuid)" will be returned.
     *
     * @param name
     * @param showUUID
     * @return
     */
    public static String formatPlayerName(String name, boolean showUUID) {
        if (isValidUUID(name)) {
            String formattedName = getName(UUID.fromString(name));
            return (formattedName == null ? "Unknown" : formattedName) + (showUUID ? " (" + name + ")" : "");
        } else {
            return name;
        }
    }

    public static String formatPlayerName(String name) {
        return formatPlayerName(name, true);
    }

}


<|start_filename|>src/main/java/com/griefcraft/integration/currency/VaultCurrency.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.integration.currency;

import com.griefcraft.integration.ICurrency;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;

@SuppressWarnings("deprecation")
public class VaultCurrency implements ICurrency {

    /**
     * The economy services handler
     */
    private Economy economy;

    public VaultCurrency() {
        checkEconomy();
    }

    /**
     * Check for an economy providor and set it if it was found
     */
    private void checkEconomy() {
        RegisteredServiceProvider<Economy> serviceProvider = Bukkit.getServer().getServicesManager().getRegistration(Economy.class);

        if (serviceProvider != null) {
            economy = serviceProvider.getProvider();
        }
    }

    public boolean isActive() {
        // If the economy provider is still null it is possible it hasn't been hooked in yet
        // So we just check for it again, since isActive() is called before any econ
        // calls should be used :D
        if (economy == null) {
            checkEconomy();
        }

        return economy != null;
    }

    public boolean usingCentralBank() {
        return false;
    }

    public String format(double money) {
        return economy.format(money);
    }

    public String getMoneyName() {
        return economy.currencyNameSingular();
    }

    public double getBalance(Player player) {
        return economy.getBalance(player.getName());
    }

    public boolean canAfford(Player player, double money) {
        return economy.has(player.getName(), money);
    }

    public boolean canCentralBankAfford(double money) {
        return false;
    }

    public double addMoney(Player player, double money) {
        economy.depositPlayer(player.getName(), money);
        return getBalance(player);
    }

    public double removeMoney(Player player, double money) {
        economy.withdrawPlayer(player.getName(), money);
        return getBalance(player);
    }
}


<|start_filename|>src/main/java/com/griefcraft/modules/pluginsupport/Factions.java<|end_filename|>
package com.griefcraft.modules.pluginsupport;

import com.griefcraft.lwc.LWC;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.massivecraft.factions.Conf;
import com.massivecraft.factions.Board;
import com.massivecraft.factions.FLocation;
import com.massivecraft.factions.FPlayer;
import com.massivecraft.factions.FPlayers;
import com.massivecraft.factions.Faction;
import com.massivecraft.factions.zcore.fperms.Access;
import com.massivecraft.factions.zcore.fperms.PermissableAction;
import org.bukkit.plugin.Plugin;

public class Factions extends JavaModule {

    private boolean factionCheck;

    private Plugin factions = null;

    @Override
    public void load(LWC lwc) {
        this.factionCheck = lwc.getConfiguration().getBoolean("core.factionCheck", false);
        factions = lwc.getPlugin().getServer().getPluginManager().getPlugin("Factions");
    }

    @Override
    public void onRegisterProtection(LWCProtectionRegisterEvent event) {
        if (factions == null || !factionCheck) {
            return;
        }

        boolean canProtect = false;
        FLocation fLocation = new FLocation(event.getBlock().getLocation());
        FPlayer fPlayer = FPlayers.getInstance().getByPlayer(event.getPlayer());
        Faction faction = Board.getInstance().getFactionAt(fLocation);
        Faction myFaction = fPlayer.getFaction();
        boolean fBypass = Conf.playersWhoBypassAllProtection.contains(fPlayer.getName()) || fPlayer.isAdminBypassing();
        boolean fWilderness = faction.isWilderness() && !Conf.wildernessDenyBuild;
        boolean fWarzone = faction.isWarZone() && !Conf.warZoneDenyBuild;
        boolean fSafezone = faction.isSafeZone() && !Conf.safeZoneDenyBuild;

        if (fBypass || fWilderness || fWarzone || fSafezone) {
            canProtect = true;
        } else if (!myFaction.getRelationTo(faction).confDenyBuild(faction.hasPlayersOnline())) {
            canProtect = true;
        } else {
            Access fAccess = faction.getAccess(fPlayer, PermissableAction.BUILD);
            if (fAccess != null) { canProtect = (fAccess == Access.ALLOW); }
        }

        if (!canProtect) {
            event.getLWC().sendLocale(event.getPlayer(), "lwc.factions.blocked");
            event.setCancelled(true);
        }
    }
}


<|start_filename|>src/main/java/com/griefcraft/modules/modify/ModifyModule.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.modules.modify;

import com.griefcraft.cache.BlockCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Action;
import com.griefcraft.model.LWCPlayer;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCBlockInteractEvent;
import com.griefcraft.scripting.event.LWCCommandEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.griefcraft.util.StringUtil.join;

public class ModifyModule extends JavaModule {

    /**
     * Used to match an id parameter in /cmodify id:##
     */
    private final Pattern ID_MATCHER = Pattern.compile(".*id:(\\d+).*");

    @Override
    public void onProtectionInteract(LWCProtectionInteractEvent event) {
        if (event.getResult() != Result.DEFAULT) {
            return;
        }

        if (!event.hasAction("modify")) {
            return;
        }

        LWC lwc = event.getLWC();
        Protection protection = event.getProtection();
        LWCPlayer player = lwc.wrapPlayer(event.getPlayer());
        Player bPlayer = event.getPlayer();
        event.setResult(Result.CANCEL);

        if (!lwc.isAdmin(bPlayer) && Boolean.parseBoolean(lwc.resolveProtectionConfiguration(protection.getBlock(), "readonly-modify"))) {
            lwc.sendLocale(player, "protection.accessdenied");
            return;
        }

        if (lwc.canAdminProtection(player.getBukkitPlayer(), protection)) {
            Action action = player.getAction("modify");

            String data = action.getData();
            String[] rights = new String[0];

            if (data.length() > 0) {
                rights = data.split(" ");
            }

            lwc.removeModes(player);
            lwc.processRightsModifications(player, protection, rights);
        } else {
            lwc.sendLocale(player, "protection.interact.error.notowner", "block",
                    LWC.materialToString(BlockCache.getInstance().getBlockType(protection.getBlockId())));
            lwc.removeModes(player);
        }

    }

    @Override
    public void onBlockInteract(LWCBlockInteractEvent event) {
        if (event.getResult() != Result.DEFAULT) {
            return;
        }

        if (!event.hasAction("modify")) {
            return;
        }

        LWC lwc = event.getLWC();
        Block block = event.getBlock();
        Player player = event.getPlayer();
        event.setResult(Result.CANCEL);

        lwc.sendLocale(player, "protection.interact.error.notregistered", "block", LWC.materialToString(block));
        lwc.removeModes(player);
    }

    @Override
    public void onCommand(LWCCommandEvent event) {
        if (event.isCancelled()) {
            return;
        }

        if (!event.hasFlag("m", "modify")) {
            return;
        }

        LWC lwc = event.getLWC();
        CommandSender sender = event.getSender();
        String[] args = event.getArgs();
        String full = join(args, 0).trim();
        event.setCancelled(true);

        if (!lwc.hasPlayerPermission(sender, "lwc.modify")) {
            lwc.sendLocale(sender, "protection.accessdenied");
            return;
        }

        if (args.length < 1) {
            lwc.sendLocale(sender, "help.modify");
            return;
        }

        // Check for ID parameter
        Matcher matcher = ID_MATCHER.matcher(full);
        if (matcher.matches()) {
            int protectionId = Integer.parseInt(matcher.group(1));

            // load the protection
            Protection protection = lwc.getPhysicalDatabase().loadProtection(protectionId);

            // Does it even exist?
            if (protection == null) {
                lwc.sendLocale(sender, "lwc.protectionnotfound");
                return;
            }

            // Can they admin it? (if they're console, they can!)
            if (sender instanceof Player) {
                if (!lwc.canAdminProtection((Player) sender, protection)) {
                    lwc.sendLocale(sender, "protection.accessdenied");
                    return;
                }
            }

            // process it
            lwc.processRightsModifications(sender, protection, args);
            return;
        }

        if (!(sender instanceof Player)) {
            lwc.sendLocale(sender, "lwc.onlyrealplayers");
            return;
        }

        LWCPlayer player = lwc.wrapPlayer(sender);

        Action action = new Action();
        action.setName("modify");
        action.setPlayer(player);
        action.setData(full);

        player.removeAllActions();
        player.addAction(action);

        lwc.sendLocale(sender, "protection.modify.finalize");
    }

}


<|start_filename|>src/main/java/com/griefcraft/sql/PhysDB.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.sql;

import com.griefcraft.bukkit.EntityBlock;
import com.griefcraft.cache.BlockCache;
import com.griefcraft.cache.LRUCache;
import com.griefcraft.cache.ProtectionCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Flag;
import com.griefcraft.model.History;
import com.griefcraft.model.Permission;
import com.griefcraft.model.Protection;
import com.griefcraft.modules.limits.LimitsModule;
import com.griefcraft.scripting.Module;
import com.griefcraft.util.MaterialUtil;
import com.griefcraft.util.UUIDRegistry;
import com.griefcraft.util.config.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PhysDB extends Database {

    /**
     * The JSON Parser object
     */
    private final JSONParser jsonParser = new JSONParser();

    /**
     * The database version
     */
    private int databaseVersion = 0;

    /**
     * The number of protections that should exist
     */
    private int protectionCount = 0;

    public PhysDB() {
        super();
    }

    public PhysDB(Type currentType) {
        super(currentType);
    }

    /**
     * Decrement the known protection counter
     */
    public void decrementProtectionCount() {
        protectionCount--;
    }

    /**
     * Check if the protection cache has all of the known protections cached
     *
     * @return
     */
    public boolean hasAllProtectionsCached() {
        ProtectionCache cache = LWC.getInstance().getProtectionCache();

        return cache.size() >= protectionCount;
    }

    PreparedStatement statement;

    /**
     * Fetch an object from the sql database
     *
     * @param sql
     * @param column
     * @return
     */
    private Object fetch(String sql, String column, Object... toBind) {
        try {
            int index = 1;
            PreparedStatement statement = prepare(sql);

            for (Object bind : toBind) {
                statement.setObject(index, bind);
                index++;
            }

            ResultSet set = statement.executeQuery();

            if (set.next()) {
                Object object = set.getObject(column);
                set.close();
                return object;
            }
        } catch (Exception e) {
            printException(e);
        }

        return null;
    }

    /**
     * Get the total amount of protections
     *
     * @return the number of protections
     */
    public int getProtectionCount() {
        return Integer.decode(fetch("SELECT COUNT(*) AS count FROM " + prefix + "protections", "count").toString());
    }

    /**
     * Get the amount of protections for the given protection type
     *
     * @param type
     * @return the number of protected chests
     */
    public int getProtectionCount(Protection.Type type) {
        return Integer.decode(
                fetch("SELECT COUNT(*) AS count FROM " + prefix + "protections WHERE type = " + type.ordinal(), "count")
                        .toString());
    }

    /**
     * @return the number of history items stored
     */
    public int getHistoryCount() {
        return Integer.decode(fetch("SELECT COUNT(*) AS count FROM " + prefix + "history", "count").toString());
    }

    /**
     * Get the amount of protections a player has
     *
     * @param player
     * @return the amount of protections they have
     */
    public int getProtectionCount(String player) {
        int count = 0;

        try {
            PreparedStatement statement = prepare(
                    "SELECT COUNT(*) as count FROM " + prefix + "protections WHERE owner = ?");
            UUID uuid = UUIDRegistry.getUUID(player);
            statement.setString(1, uuid != null ? uuid.toString() : player);

            ResultSet set = statement.executeQuery();

            if (set.next()) {
                count = set.getInt("count");
            }
        } catch (SQLException e) {
            printException(e);
        }

        return count;
    }

    /**
     * Get the amount of protections a player has
     *
     * @param player
     * @return the amount of protections they have
     */
    public int getHistoryCount(String player) {
        int count = 0;

        try {
            PreparedStatement statement = prepare(
                    "SELECT COUNT(*) AS count FROM " + prefix + "history WHERE LOWER(player) = LOWER(?)");
            UUID uuid = UUIDRegistry.getUUID(player);
            statement.setString(1, uuid != null ? uuid.toString() : player);
            ResultSet set = statement.executeQuery();

            if (set.next()) {
                count = set.getInt("count");
            }

        } catch (SQLException e) {
            printException(e);
        }

        return count;
    }

    /**
     * Get the amount of chests a player has of a specific block id
     *
     * @param player
     * @return the amount of protections they have of blockId
     */
    public int getProtectionCount(String player, int blockId) {
        int count = 0;

        try {
            PreparedStatement statement = prepare(
                    "SELECT COUNT(*) AS count FROM " + prefix + "protections WHERE owner = ? AND blockId = ?");
            UUID uuid = UUIDRegistry.getUUID(player);
            statement.setString(1, uuid != null ? uuid.toString() : player);
            statement.setInt(2, blockId);

            ResultSet set = statement.executeQuery();

            if (set.next()) {
                count = set.getInt("count");
            }

        } catch (SQLException e) {
            printException(e);
        }

        return count;
    }

    /**
     * Get the menu style for a player
     *
     * @param player
     * @return
     * @deprecated
     */
    public String getMenuStyle(String player) {
        return "basic";
    }

    /**
     * Load the database and do any updating required or create the tables
     */
    @Override
    public void load() {
        if (loaded) {
            return;
        }

        Column column;

        Table protections = new Table(this, "protections");
        {
            column = new Column("id");
            column.setType("INTEGER");
            column.setPrimary(true);
            protections.add(column);

            column = new Column("owner");
            column.setType("VARCHAR(36)");
            protections.add(column);

            column = new Column("type");
            column.setType("INTEGER");
            protections.add(column);

            column = new Column("x");
            column.setType("INTEGER");
            protections.add(column);

            column = new Column("y");
            column.setType("INTEGER");
            protections.add(column);

            column = new Column("z");
            column.setType("INTEGER");
            protections.add(column);

            column = new Column("flags");
            column.setType("INTEGER");
            protections.add(column);

            column = new Column("data");
            column.setType("TEXT");
            protections.add(column);

            column = new Column("blockId");
            column.setType("INTEGER");
            protections.add(column);

            column = new Column("world");
            column.setType("VARCHAR(32)");
            protections.add(column);

            column = new Column("password");
            column.setType("VARCHAR(40)");
            protections.add(column);

            column = new Column("date");
            column.setType("VARCHAR(32)");
            protections.add(column);

            column = new Column("last_accessed");
            column.setType("INTEGER");
            protections.add(column);
        }

        Table history = new Table(this, "history");
        {
            column = new Column("id");
            column.setType("INTEGER");
            column.setPrimary(true);
            history.add(column);

            column = new Column("protectionId");
            column.setType("INTEGER");
            history.add(column);

            column = new Column("player");
            column.setType("VARCHAR(36)");
            history.add(column);

            column = new Column("x");
            column.setType("INTEGER");
            history.add(column);

            column = new Column("y");
            column.setType("INTEGER");
            history.add(column);

            column = new Column("z");
            column.setType("INTEGER");
            history.add(column);

            column = new Column("type");
            column.setType("INTEGER");
            history.add(column);

            column = new Column("status");
            column.setType("INTEGER");
            history.add(column);

            column = new Column("metadata");
            column.setType("VARCHAR(255)");
            history.add(column);

            column = new Column("timestamp");
            column.setType("long");
            history.add(column);
        }

        Table internal = new Table(this, "internal");
        {
            column = new Column("name");
            column.setType("VARCHAR(40)");
            column.setPrimary(true);
            column.setAutoIncrement(false);
            internal.add(column);

            column = new Column("value");
            column.setType("VARCHAR(40)");
            internal.add(column);
        }

        Table blocks = new Table(this, "blocks");
        {
            column = new Column("id");
            column.setType("INTEGER");
            column.setPrimary(true);
            column.setAutoIncrement(false);
            blocks.add(column);

            column = new Column("name");
            column.setType("VARCHAR(40)");
            blocks.add(column);
        }

        protections.execute();
        history.execute();
        internal.execute();
        blocks.execute();

        /**
         * Updates that alter or rename a table go here
         */
        doUpdate301();
        doUpdate302();
        doUpdate330();
        doUpdate400_1();
        doUpdate400_4();
        doUpdate400_4();
        doUpdate400_5();
        doUpdate400_6();
        doUpdateModernLWC();
        doUpdateAquatic();
        doUpdateVillageAndPillage();

        // Initialize the block cache
        BlockCache.getInstance().loadBlocks();

        // Load the database version
        loadDatabaseVersion();

        // perform database upgrades
        performDatabaseUpdates();

        // get the amount of protections
        protectionCount = getProtectionCount();

        loaded = true;
    }

    /**
     * Perform any database updates
     */
    public void performDatabaseUpdates() {
        LWC lwc = LWC.getInstance();

        // Indexes
        if (databaseVersion == 0) {
            // Drop old, old indexes
            log("Dropping old indexes (One time, may take a while!)");
            dropIndex("protections", "in1");
            dropIndex("protections", "in6");
            dropIndex("protections", "in7");
            dropIndex("history", "in8");
            dropIndex("history", "in9");
            dropIndex("protections", "in10");
            dropIndex("history", "in12");
            dropIndex("history", "in13");
            dropIndex("history", "in14");

            // Create our updated (good) indexes
            log("Creating new indexes (One time, may take a while!)");
            createIndex("protections", "protections_main", "x, y, z, world");
            createIndex("protections", "protections_utility", "owner");
            createIndex("history", "history_main", "protectionId");
            createIndex("history", "history_utility", "player");
            createIndex("history", "history_utility2", "x, y, z");

            // increment the database version
            incrementDatabaseVersion();
        }

        if (databaseVersion == 1) {
            log("Creating index on internal");
            createIndex("internal", "internal_main", "name");
            incrementDatabaseVersion();
        }

        if (databaseVersion == 2) {
            doUpdate400_2();
            incrementDatabaseVersion();
        }

        if (databaseVersion == 3) {
            createIndex("protections", "protections_type", "type");
            incrementDatabaseVersion();
        }

        if (databaseVersion == 4) {
            List<String> blacklistedBlocks = lwc.getConfiguration().getStringList("optional.blacklistedBlocks",
                    new ArrayList<String>());

            if (!blacklistedBlocks.contains("hopper")) {
                blacklistedBlocks.add(Material.HOPPER.name().toLowerCase());
                lwc.getConfiguration().setProperty("optional.blacklistedBlocks", blacklistedBlocks);
                lwc.getConfiguration().save();
                Configuration.reload();

                lwc.log("Added Hoppers to Blacklisted Blocks in core.yml (optional.blacklistedBlocks)");
                lwc.log("This means that Hoppers CANNOT be placed around protections a player does not have access to");
                lwc.log("If you DO NOT want this feature, simply remove '" + Material.HOPPER.name().toLowerCase()
                        + "' from blacklistedBlocks :-)");
            }

            incrementDatabaseVersion();
        }

        if (databaseVersion == 5) {
            boolean foundTrappedChest = false;

            for (String key : lwc.getConfiguration().getNode("protections.blocks").getKeys(null)) {
                if (key.equalsIgnoreCase("trapped_chest")) {
                    foundTrappedChest = true;
                    break;
                }
            }

            if (!foundTrappedChest) {
                lwc.getConfiguration().setProperty("protections.blocks.trapped_chest.enabled", true);
                lwc.getConfiguration().setProperty("protections.blocks.trapped_chest.autoRegister", "private");
                lwc.getConfiguration().save();
                Configuration.reload();

                lwc.log("Added Trapped Chests to core.yml as default protectable (ENABLED & AUTO REGISTERED)");
                lwc.log("Trapped chests are nearly the same as reg chests but can light up! They can also be double chests.");
                lwc.log("If you DO NOT want this as protected, simply remove it from core.yml! (search/look for trapped_chests under protections -> blocks");
            }

            incrementDatabaseVersion();
        }
    }

    /**
     * Increment the database version
     */
    public void incrementDatabaseVersion() {
        setDatabaseVersion(++databaseVersion);
    }

    /**
     * Set the database version and sync it to the database
     *
     * @param databaseVersion
     */
    public void setDatabaseVersion(int databaseVersion) {
        // set it locally
        this.databaseVersion = databaseVersion;

        // ship it to the database
        try {
            PreparedStatement statement = prepare("UPDATE " + prefix + "internal SET value = ? WHERE name = ?");
            statement.setInt(1, databaseVersion);
            statement.setString(2, "version");

            // ok
            statement.executeUpdate();
            statement.close();
        } catch (SQLException e) {
        }
    }

    /**
     * Get a value in the internal table
     *
     * @param key
     * @return the value found, otherwise NULL if none exists
     */
    public String getInternal(String key) {
        try {
            PreparedStatement statement = prepare("SELECT value FROM " + prefix + "internal WHERE name = ?");
            statement.setString(1, key);

            ResultSet set = statement.executeQuery();
            if (set.next()) {
                String value = set.getString("value");
                set.close();
                return value;
            }
            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return null;
    }

    /**
     * Set a value in the internal table
     *
     * @param key
     * @param value
     */
    public void setInternal(String key, String value) {
        try {
            PreparedStatement statement = prepare("INSERT INTO " + prefix + "internal (name, value) VALUES (?, ?)");
            statement.setString(1, key);
            statement.setString(2, value);

            statement.executeUpdate();
        } catch (SQLException e) {
            // Already exists
            try {
                PreparedStatement statement = prepare("UPDATE " + prefix + "internal SET value = ? WHERE name = ?");
                statement.setString(1, value);
                statement.setString(2, key);

                statement.executeUpdate();
                statement.close();
            } catch (SQLException ex) {
                // Something bad went wrong
                printException(ex);
            }
        }
    }

    /**
     * Load the database internal version
     *
     * @return
     */
    public int loadDatabaseVersion() {
        try {
            PreparedStatement statement = prepare("SELECT value FROM " + prefix + "internal WHERE name = ?");
            statement.setString(1, "version");

            // Execute it
            ResultSet set = statement.executeQuery();

            // load the version
            if (set.next()) {
                databaseVersion = Integer.parseInt(set.getString("value"));
            } else {
                throw new IllegalStateException("Internal is empty");
            }

            // close everything
            set.close();
        } catch (Exception e) {
            // Doesn't exist, create it
            try {
                PreparedStatement statement = prepare("INSERT INTO " + prefix + "internal (name, value) VALUES(?, ?)");
                statement.setString(1, "version");
                statement.setInt(2, databaseVersion);

                // ok
                statement.executeUpdate();
                statement.close();
            } catch (SQLException ex) {
            }
        }

        return databaseVersion;
    }

    /**
     * Load a protection with the given id
     *
     * @param id
     * @return the Chest object
     */
    public Protection loadProtection(int id) {
        // the protection cache
        ProtectionCache cache = LWC.getInstance().getProtectionCache();

        // check if the protection is already cached
        Protection cached = cache.getProtectionById(id);
        if (cached != null) {
            return cached;
        }

        try {
            PreparedStatement statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections WHERE id = ?");
            statement.setInt(1, id);

            Protection protection = resolveProtection(statement);

            if (protection != null) {
                cache.addProtection(protection);
                return protection;
            }
        } catch (SQLException e) {
            printException(e);
        }

        return null;
    }

    /**
     * Load protections using a specific type
     *
     * @param type
     * @return the Protection object
     */
    public List<Protection> loadProtectionsUsingType(Protection.Type type) {
        PreparedStatement statement;
        try {
            statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections WHERE type = ?");
            statement.setInt(1, type.ordinal());

            return resolveProtections(statement);
        } catch (SQLException e) {
            printException(e);
        }

        return new ArrayList<Protection>();
    }

    /**
     * Resolve one protection from a ResultSet. The ResultSet is not closed.
     *
     * @param set
     * @return
     */
    @SuppressWarnings("unchecked")
    public Protection resolveProtection(ResultSet set) {
        try {
            Protection protection = new Protection();

            int protectionId = set.getInt("id");
            int x = set.getInt("x");
            int y = set.getInt("y");
            int z = set.getInt("z");
            int blockId = set.getInt("blockId");
            int type = set.getInt("type");
            String world = set.getString("world");
            String owner = set.getString("owner");
            String password = set.getString("password");
            String date = set.getString("date");
            long lastAccessed = set.getLong("last_accessed");

            protection.setId(protectionId);
            protection.setX(x);
            protection.setY(y);
            protection.setZ(z);
            protection.setBlockId(blockId);
            protection.setType(Protection.Type.values()[type]);
            protection.setWorld(world);
            protection.setOwner(owner);
            protection.setPassword(password);
            protection.setCreation(date);
            protection.setLastAccessed(lastAccessed);

            // check for oh so beautiful data!
            String data = set.getString("data");

            if (data == null || data.trim().isEmpty()) {
                return protection;
            }

            // rev up them JSON parsers!
            Object object = null;

            try {
                object = jsonParser.parse(data);
            } catch (Exception e) {
                return protection;
            } catch (Error e) {
                return protection;
            }

            if (!(object instanceof JSONObject)) {
                return protection;
            }

            // obtain the root
            JSONObject root = (JSONObject) object;
            protection.getData().putAll(root);

            // Attempt to parse rights
            Object rights = root.get("rights");

            if (rights != null && (rights instanceof JSONArray)) {
                JSONArray array = (JSONArray) rights;

                for (Object node : array) {
                    // we only want to use the maps
                    if (!(node instanceof JSONObject)) {
                        continue;
                    }

                    JSONObject map = (JSONObject) node;

                    // decode the map
                    Permission permission = Permission.decodeJSON(map);

                    // bingo!
                    if (permission != null) {
                        protection.addPermission(permission);
                    }
                }
            }

            // Attempt to parse flags
            Object flags = root.get("flags");
            if (flags != null && (rights instanceof JSONArray)) {
                JSONArray array = (JSONArray) flags;

                for (Object node : array) {
                    if (!(node instanceof JSONObject)) {
                        continue;
                    }

                    JSONObject map = (JSONObject) node;

                    Flag flag = Flag.decodeJSON(map);

                    if (flag != null) {
                        protection.addFlag(flag);
                    }
                }
            }

            return protection;
        } catch (SQLException e) {
            printException(e);
            return null;
        }
    }

    /**
     * Resolve every protection from a result set
     *
     * @param set
     * @return
     */
    private List<Protection> resolveProtections(ResultSet set) {
        List<Protection> protections = new ArrayList<Protection>();

        try {
            while (set.next()) {
                Protection protection = resolveProtection(set);

                if (protection != null) {
                    protections.add(protection);
                }
            }
        } catch (SQLException e) {
            printException(e);
        }

        return protections;
    }

    /**
     * Resolve a list of protections from a statement
     *
     * @param statement
     * @return
     */
    private List<Protection> resolveProtections(PreparedStatement statement) {
        List<Protection> protections = new ArrayList<Protection>();
        ResultSet set = null;

        try {
            set = statement.executeQuery();
            protections = resolveProtections(set);
        } catch (SQLException e) {
            printException(e);
        } finally {
            if (set != null) {
                try {
                    set.close();
                } catch (SQLException e) {
                }
            }
        }

        return protections;
    }

    /**
     * Resolve the first protection from a statement
     *
     * @param statement
     * @return
     */
    private Protection resolveProtection(PreparedStatement statement) {
        List<Protection> protections = resolveProtections(statement);

        if (protections.size() == 0) {
            return null;
        }
        return protections.get(0);
    }

    /**
     * Fill the protection cache as much as possible with protections Caches the
     * most recent protections
     */
    public void precache() {
        LWC lwc = LWC.getInstance();
        ProtectionCache cache = lwc.getProtectionCache();

        // clear the cache incase we're working on a dirty cache
        cache.clear();

        int precacheSize = lwc.getConfiguration().getInt("core.precache", -1);

        if (precacheSize == -1) {
            precacheSize = lwc.getConfiguration().getInt("core.cacheSize", 10000);
        }
        try {
            statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections ORDER BY id DESC LIMIT ?");
            statement.setInt(1, precacheSize);
            statement.setFetchSize(10);

            // scrape the protections from the result set now
            List<Protection> protections = resolveProtections(statement);

            // throw all of the protections in
            for (Protection protection : protections) {
                cache.addProtection(protection);
            }

        } catch (SQLException e) {
            printException(e);
        }
    }

    /**
     * Load a protection at the given coordinates
     *
     * @param x
     * @param y
     * @param z
     * @return the Protection object
     */
    public Protection loadProtection(String worldName, int x, int y, int z) {
        return loadProtection(worldName, x, y, z, false);
    }

    /**
     * Load a protection at the given coordinates
     *
     * @param x
     * @param y
     * @param z
     * @param ignoreProtectionCount
     * @return the Protection object
     */
    private Protection loadProtection(String worldName, int x, int y, int z, boolean ignoreProtectionCount) {
        // the unique key to use in the cache
        String cacheKey = worldName + ":" + x + ":" + y + ":" + z;

        // the protection cache
        ProtectionCache cache = LWC.getInstance().getProtectionCache();

        // check if the protection is already cached
        Protection cached = cache.getProtection(cacheKey);
        if (cached != null) {
            return cached;
        }

        // Is it possible that there are protections in the cache?
        if (!ignoreProtectionCount && hasAllProtectionsCached()) {
            return null; // nothing was in the cache, nothing assumed to be in the database
        }
        try {
            statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections WHERE x = ? AND y = ? AND z = ? AND world = ?");
            statement.setInt(1, x);
            statement.setInt(2, y);
            statement.setInt(3, z);
            statement.setString(4, worldName);

            Protection protection = resolveProtection(statement);

            if (protection != null) {
                // cache the protection
                cache.addProtection(protection);
            }
            return protection;
        } catch (SQLException e) {
            printException(e);
        }

        return null;
    }

    /**
     * Load all protections (use sparingly !!)
     *
     * @return
     */
    public List<Protection> loadProtections() {
        try {
            PreparedStatement statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections");
            return resolveProtections(statement);
        } catch (Exception e) {
            printException(e);
        }

        return new ArrayList<Protection>();
    }

    /**
     * Load the first protection within a block's radius
     *
     * @param world
     * @param baseX
     * @param baseY
     * @param baseZ
     * @param radius
     * @return list of Protection objects found
     */
    public List<Protection> loadProtections(String world, int baseX, int baseY, int baseZ, int radius) {
        if (hasAllProtectionsCached()) {
            ProtectionCache cache = LWC.getInstance().getProtectionCache();
            List<Protection> protections = new ArrayList<Protection>();

            if (cache.size() < 1000) {
                for (Protection protection : cache.getReferences().keySet()) {
                    int x = protection.getX();
                    int y = protection.getY();
                    int z = protection.getZ();

                    if (x >= baseX - radius && x <= baseX + radius && y >= baseY - radius && y <= baseY + radius
                            && z >= baseZ - radius && z <= baseZ + radius) {
                        protections.add(protection);
                    }
                }
            } else {
                for (int x = baseX - radius; x < baseX + radius; x++) {
                    for (int y = baseY - radius; y < baseY + radius; y++) {
                        for (int z = baseZ - radius; z < baseZ + radius; z++) {
                            Protection protection = cache.getProtection(world + ":" + x + ":" + y + ":" + z);

                            if (protection != null) {
                                protections.add(protection);
                            }
                        }
                    }
                }
            }

            return protections;
        }
        try {
            statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix
                            + "protections WHERE world = ? AND x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ?");
            statement.setString(1, world);
            statement.setInt(2, baseX - radius);
            statement.setInt(3, baseX + radius);
            statement.setInt(4, baseY - radius);
            statement.setInt(5, baseY + radius);
            statement.setInt(6, baseZ - radius);
            statement.setInt(7, baseZ + radius);

            return resolveProtections(statement);
        } catch (Exception e) {
            printException(e);
        }

        return new ArrayList<Protection>();
    }

    /**
     * Remove all protections for a given player
     *
     * @param player
     * @return the amount of protections removed
     */
    public int removeProtectionsByPlayer(String player) {
        int removed = 0;

        for (Protection protection : loadProtectionsByPlayer(player)) {
            protection.remove();
            removed++;
        }

        return removed;
    }

    /**
     * Load all protections in the coordinate ranges
     *
     * @param world
     * @param x1
     * @param x2
     * @param y1
     * @param y2
     * @param z1
     * @param z2
     * @return list of Protection objects found
     */
    public List<Protection> loadProtections(String world, int x1, int x2, int y1, int y2, int z1, int z2) {
        try {
            PreparedStatement statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix
                            + "protections WHERE world = ? AND x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ?");
            statement.setString(1, world);
            statement.setInt(2, x1);
            statement.setInt(3, x2);
            statement.setInt(4, y1);
            statement.setInt(5, y2);
            statement.setInt(6, z1);
            statement.setInt(7, z2);

            return resolveProtections(statement);
        } catch (Exception e) {
            printException(e);
        }

        return new ArrayList<Protection>();
    }

    /**
     * Load protections by a player
     *
     * @param player
     * @return
     */
    public List<Protection> loadProtectionsByPlayer(String player) {
        List<Protection> protections = new ArrayList<Protection>();

        try {
            PreparedStatement statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections WHERE owner = ?");
            UUID uuid = UUIDRegistry.getUUID(player);
            statement.setString(1, uuid != null ? uuid.toString() : player);

            return resolveProtections(statement);
        } catch (Exception e) {
            printException(e);
        }

        return protections;
    }

    /**
     * Load protections by a player
     *
     * @param player
     * @param start
     * @param count
     * @return
     */
    public List<Protection> loadProtectionsByPlayer(String player, int start, int count) {
        List<Protection> protections = new ArrayList<Protection>();

        UUID uuid = UUIDRegistry.getUUID(player);

        try {
            PreparedStatement statement = prepare(
                    "SELECT id, owner, type, x, y, z, data, blockId, world, password, date, last_accessed FROM "
                            + prefix + "protections WHERE owner = ? ORDER BY id DESC limit ?,?");
            statement.setString(1, uuid != null ? uuid.toString() : player);
            statement.setInt(2, start);
            statement.setInt(3, count);

            return resolveProtections(statement);
        } catch (Exception e) {
            printException(e);
        }

        return protections;
    }

    /**
     * Register a protection
     *
     * @param blockId
     * @param type
     * @param world
     * @param player
     * @param data
     * @param x
     * @param y
     * @param z
     * @return
     */
    public Protection registerProtection(int blockId, int type, String world, String player, String data, int x, int y,
                                         int z) {
        return registerProtection(blockId, Protection.Type.values()[type], world, player, data, x, y, z);
    }

    /**
     * Register a protection
     *
     * @param blockId
     * @param type
     * @param world
     * @param player
     * @param data
     * @param x
     * @param y
     * @param z
     * @return
     */
    public Protection registerProtection(int blockId, Protection.Type type, String world, String player,
                                         String data, int x, int y, int z) {
        ProtectionCache cache = LWC.getInstance().getProtectionCache();
        try {
            statement = prepare("INSERT INTO " + prefix
                    + "protections (blockId, type, world, owner, password, x, y, z, date, last_accessed) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
            statement.setInt(1, blockId);
            statement.setInt(2, type.ordinal());
            statement.setString(3, world);
            statement.setString(4, player);
            statement.setString(5, data);
            statement.setInt(6, x);
            statement.setInt(7, y);
            statement.setInt(8, z);
            statement.setString(9, new Timestamp(new Date().getTime()).toString());
            statement.setLong(10, System.currentTimeMillis() / 1000L);

            statement.executeUpdate();
            statement = null;

            // We need to create the initial transaction for this protection
            // this transaction is viewable and modifiable during
            // POST_REGISTRATION
            Protection protection = loadProtection(world, x, y, z, true);
            protection.removeCache();

            // if history logging is enabled, create it
            if (LWC.getInstance().isHistoryEnabled() && protection != null) {
                History transaction = protection.createHistoryObject();

                transaction.setPlayer(player);
                transaction.setType(History.Type.TRANSACTION);
                transaction.setStatus(History.Status.ACTIVE);

                // store the player that created the protection
                transaction.addMetaData("creator=" + player);

                // now sync the history object to the database
                transaction.saveNow();
            }

            // Cache it
            if (protection != null) {
                cache.addProtection(protection);
                protectionCount++;
            }

            // return the newly created protection
            return protection;
        } catch (SQLException e) {
            printException(e);
        }
        return null;
    }

    /**
     * Sync a History object to the database or save a newly created one
     *
     * @param history
     */
    public void saveHistory(History history) {
        try {
            if (history.doesExist()) {
                statement = prepare("UPDATE " + prefix
                        + "history SET protectionId = ?, player = ?, x = ?, y = ?, z = ?, type = ?, status = ?, metadata = ?, timestamp = ? WHERE id = ?");
            } else {
                statement = prepare("INSERT INTO " + prefix
                                + "history (protectionId, player, x, y, z, type, status, metadata, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
                        true);
                history.setTimestamp(System.currentTimeMillis() / 1000L);
            }
            statement.setInt(1, history.getProtectionId());
            statement.setString(2, history.getPlayer());
            statement.setInt(3, history.getX());
            statement.setInt(4, history.getY());
            statement.setInt(5, history.getZ());
            statement.setInt(6, history.getType().ordinal());
            statement.setInt(7, history.getStatus().ordinal());
            statement.setString(8, history.getSafeMetaData());
            statement.setLong(9, history.getTimestamp());

            if (history.doesExist()) {
                statement.setInt(10, history.getId());
            }

            int affectedRows = statement.executeUpdate();

            // set the history id if inserting
            if (!history.doesExist()) {
                if (affectedRows > 0) {
                    ResultSet generatedKeys = statement.getGeneratedKeys();

                    // get the key inserted
                    if (generatedKeys.next()) {
                        history.setId(generatedKeys.getInt(1));
                    }

                    generatedKeys.close();
                }
            }
        } catch (SQLException e) {
            printException(e);
        }
    }

    /**
     * Invalid all history objects for a player
     *
     * @param player
     */
    public void invalidateHistory(String player) {
        UUID uuid = UUIDRegistry.getUUID(player);
        try {
            PreparedStatement statement = prepare(
                    "UPDATE " + prefix + "history SET status = ? WHERE Lower(player) = Lower(?)");
            statement.setInt(1, History.Status.INACTIVE.ordinal());
            statement.setObject(2, uuid);

            statement.executeUpdate();
        } catch (SQLException e) {
            printException(e);
        }
    }

    /**
     * Resolve 1 history object from the result set but do not close it
     *
     * @return
     */
    private History resolveHistory(History history, ResultSet set) throws SQLException {
        if (history == null) {
            return null;
        }

        int historyId = set.getInt("id");
        int protectionId = set.getInt("protectionId");
        int x = set.getInt("x");
        int y = set.getInt("y");
        int z = set.getInt("z");
        String player = set.getString("player");
        int type_ord = set.getInt("type");
        int status_ord = set.getInt("status");
        String[] metadata = set.getString("metadata").split(",");
        long timestamp = set.getLong("timestamp");

        History.Type type = History.Type.values()[type_ord];
        History.Status status = History.Status.values()[status_ord];

        history.setId(historyId);
        history.setProtectionId(protectionId);
        history.setType(type);
        history.setPlayer(player);
        history.setX(x);
        history.setY(y);
        history.setZ(z);
        history.setStatus(status);
        history.setMetaData(metadata);
        history.setTimestamp(timestamp);

        return history;
    }

    /**
     * Load all of the History objects for a given protection
     *
     * @param protection
     * @return
     */
    public List<History> loadHistory(Protection protection) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }

        try {
            statement = prepare("SELECT * FROM " + prefix + "history WHERE protectionId = ? ORDER BY id DESC");
            statement.setInt(1, protection.getId());

            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(protection.createHistoryObject(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all protection history that the given player created
     *
     * @param player
     * @return
     */
    public List<History> loadHistory(Player player) {
        return loadHistory(player.getName());
    }

    /**
     * Load all protection history that the given player created
     *
     * @param player
     * @return
     */
    public List<History> loadHistory(String player) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }
        UUID uuid = UUIDRegistry.getUUID(player);
        if (uuid == null) {
            uuid = UUID.fromString(player);
        }

        if (uuid != null) {
            player = uuid.toString();
        }
        try {
            statement = prepare("SELECT * FROM " + prefix + "history WHERE LOWER(player) = LOWER(?) ORDER BY id DESC");
            statement.setString(1, player);

            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all protection history that has the given history id
     *
     * @param historyId
     * @return
     */
    public History loadHistory(int historyId) {
        if (!LWC.getInstance().isHistoryEnabled()) {
            return null;
        }

        try {
            statement = prepare("SELECT * FROM " + prefix + "history WHERE id = ?");
            statement.setInt(1, historyId);

            ResultSet set = statement.executeQuery();

            if (set.next()) {
                History history = resolveHistory(new History(), set);

                set.close();
                return history;
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return null;
    }

    /**
     * Load all protection history that the given player created for a given page,
     * getting count history items.
     *
     * @param player
     * @param start
     * @param count
     * @return
     */
    public List<History> loadHistory(Player player, int start, int count) {
        return loadHistory(player.getName(), start, count);
    }

    /**
     * Load all protection history that the given player created for a given page,
     * getting count history items.
     *
     * @param player
     * @param start
     * @param count
     * @return
     */
    public List<History> loadHistory(String player, int start, int count) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }
        UUID uuid = UUIDRegistry.getUUID(player);
        if (uuid != null) {
            player = uuid.toString();
        }
        try {
            PreparedStatement statement = prepare(
                    "SELECT * FROM " + prefix + "history WHERE LOWER(player) = LOWER(?) ORDER BY id DESC LIMIT ?,?");
            statement.setString(1, player);
            statement.setInt(2, start);
            statement.setInt(3, count);

            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all protection history
     *
     * @return
     */
    public List<History> loadHistory() {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }

        try {
            PreparedStatement statement = prepare("SELECT * FROM " + prefix + "history ORDER BY id DESC");
            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all protection history for the given status
     *
     * @return
     */
    public List<History> loadHistory(History.Status status) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }

        try {
            PreparedStatement statement = prepare(
                    "SELECT * FROM " + prefix + "history WHERE status = ? ORDER BY id DESC");
            statement.setInt(1, status.ordinal());
            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all of the history at the given location
     *
     * @param x
     * @param y
     * @param z
     * @return
     */
    public List<History> loadHistory(int x, int y, int z) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }

        try {
            PreparedStatement statement = prepare(
                    "SELECT * FROM " + prefix + "history WHERE x = ? AND y = ? AND z = ?");
            statement.setInt(1, x);
            statement.setInt(2, y);
            statement.setInt(3, z);

            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all of the history at the given location
     *
     * @param player
     * @param x
     * @param y
     * @param z
     * @return
     */
    public List<History> loadHistory(String player, int x, int y, int z) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }
        UUID uuid = UUIDRegistry.getUUID(player);
        if (uuid != null) {
            player = uuid.toString();
        }

        try {
            PreparedStatement statement = prepare(
                    "SELECT * FROM " + prefix + "history WHERE LOWER(player) = LOWER(?) AND x = ? AND y = ? AND z = ?");
            statement.setString(1, player);
            statement.setInt(2, x);
            statement.setInt(3, y);
            statement.setInt(4, z);

            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Load all protection history
     *
     * @return
     */
    public List<History> loadHistory(int start, int count) {
        List<History> temp = new ArrayList<History>();

        if (!LWC.getInstance().isHistoryEnabled()) {
            return temp;
        }

        try {
            PreparedStatement statement = prepare("SELECT * FROM " + prefix + "history ORDER BY id DESC LIMIT ?,?");
            statement.setInt(1, start);
            statement.setInt(2, count);

            ResultSet set = statement.executeQuery();

            while (set.next()) {
                History history = resolveHistory(new History(), set);

                if (history != null) {
                    // seems ok
                    temp.add(history);
                }
            }

            set.close();
        } catch (SQLException e) {
            printException(e);
        }

        return temp;
    }

    /**
     * Save a protection to the database
     *
     * @param protection
     */
    public void saveProtection(Protection protection) {
        try {
            PreparedStatement statement = prepare("REPLACE INTO " + prefix
                    + "protections (id, type, blockId, world, data, owner, password, x, y, z, date, last_accessed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");

            statement.setInt(1, protection.getId());
            statement.setInt(2, protection.getType().ordinal());
            statement.setInt(3, protection.getBlockId());
            statement.setString(4, protection.getWorld());
            statement.setString(5, protection.getData().toJSONString());
            statement.setString(6, protection.getOwner());
            statement.setString(7, protection.getPassword());
            statement.setInt(8, protection.getX());
            statement.setInt(9, protection.getY());
            statement.setInt(10, protection.getZ());
            statement.setString(11, protection.getCreation());
            statement.setLong(12, protection.getLastAccessed());

            statement.executeUpdate();
        } catch (SQLException e) {
            printException(e);
        }
    }

    /**
     * Free a chest from protection
     *
     * @param protectionId the protection Id
     */
    public void removeProtection(int protectionId) {
        try {
            PreparedStatement statement = prepare("DELETE FROM " + prefix + "protections WHERE id = ?");
            statement.setInt(1, protectionId);

            int affected = statement.executeUpdate();

            if (affected >= 1) {
                protectionCount -= affected;
            }
        } catch (SQLException e) {
            printException(e);
        }

        // removeProtectionHistory(protectionId);
    }

    public void removeProtectionHistory(int protectionId) {
        try {
            PreparedStatement statement = prepare("DELETE FROM " + prefix + "history WHERE protectionId = ?");
            statement.setInt(1, protectionId);

            statement.executeUpdate();
        } catch (SQLException e) {
            printException(e);
        }
    }

    public void removeHistory(int historyId) {
        try {
            PreparedStatement statement = prepare("DELETE FROM " + prefix + "history WHERE id = ?");
            statement.setInt(1, historyId);

            statement.executeUpdate();
        } catch (SQLException e) {
            printException(e);
        }
    }

    /**
     * Remove **<b>ALL</b>** all of the protections registered by LWC
     */
    public void removeAllProtections() {
        try {
            Statement statement = connection.createStatement();
            statement.executeUpdate("DELETE FROM " + prefix + "protections");
            protectionCount = 0;
        } catch (SQLException e) {
            printException(e);
        }
    }

    /**
     * Attempt to create an index on the table
     *
     * @param table
     * @param indexName
     * @param columns
     */
    private void createIndex(String table, String indexName, String columns) {
        Statement statement = null;

        try {
            statement = connection.createStatement();
            statement.executeUpdate("CREATE INDEX" + (currentType == Type.SQLite ? " IF NOT EXISTS" : "") + " "
                    + indexName + " ON " + prefix + table + " (" + columns + ")");
        } catch (Exception e) {
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * Attempt to create an index on the table
     *
     * @param indexName
     */
    private void dropIndex(String table, String indexName) {
        Statement statement = null;

        try {
            statement = connection.createStatement();

            if (currentType == Type.SQLite) {
                statement.executeUpdate("DROP INDEX IF EXISTS " + indexName);
            } else {
                statement.executeUpdate("DROP INDEX " + indexName + " ON " + prefix + table);
            }
        } catch (Exception e) {
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 3.01
     */
    private void doUpdate301() {
        // check limits table
        try {
            Statement statement = connection.createStatement();
            statement.executeQuery("SELECT * FROM limits LIMIT 1");
            statement.close();
        } catch (Exception e) {
            return;
        }

        // Convert limits
        LWC lwc = LWC.getInstance();
        Module rawModule = lwc.getModuleLoader().getModule(LimitsModule.class);

        if (rawModule == null) {
            log("Failed to load the Limits module. Something is wrong!");
            return;
        }

        LimitsModule limits = (LimitsModule) rawModule;

        // start going through the database
        PreparedStatement statement = prepare("SELECT * FROM limits");
        try {
            ResultSet result = statement.executeQuery();

            while (result.next()) {
                int type = result.getInt("type");
                int amount = result.getInt("amount");
                String entity = result.getString("entity");

                switch (type) {
                    // Global
                    case 2:
                        limits.set("master.type", "default");
                        limits.set("master.limit", amount);
                        break;

                    // Group
                    case 0:
                        limits.set("groups." + entity + ".type", "default");
                        limits.set("groups." + entity + ".limit", amount);
                        break;

                    // Player
                    case 1:
                        limits.set("players." + entity + ".type", "default");
                        limits.set("players." + entity + ".limit", amount);
                        break;
                }
            }
            statement.close();
        } catch (SQLException e) {
            printException(e);
            return;
        }
        limits.save();
        dropTable("limits");
    }

    /**
     * 3.02
     */
    private void doUpdate302() {
        if (prefix == null || prefix.length() == 0) {
            return;
        }

        // check for the table
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute("SELECT id FROM " + prefix + "protections limit 1");
        } catch (SQLException e) {
            // The table does not exist, let's go ahead and rename all of the
            // tables
            renameTable("protections", prefix + "protections");
            renameTable("rights", prefix + "rights");
            renameTable("menu_styles", prefix + "menu_styles");
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 3.30
     */
    private void doUpdate330() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute("SELECT last_accessed FROM " + prefix + "protections LIMIT 1");
        } catch (SQLException e) {
            addColumn(prefix + "protections", "last_accessed", "INTEGER");
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 4.0.0, update 1
     */
    private void doUpdate400_1() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute("SELECT rights FROM " + prefix + "protections LIMIT 1");
        } catch (SQLException e) {
            addColumn(prefix + "protections", "rights", "TEXT");
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 4.0.0, update 2
     */
    private void doUpdate400_2() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute("SELECT id FROM " + prefix + "rights LIMIT 1");

            log("Migrating LWC3 rights to LWC4 format");

            // it exists ..!
            Statement stmt = connection.createStatement();
            ResultSet set = stmt.executeQuery("SELECT * FROM " + prefix + "rights");

            // keep a mini-cache of protections, max size of 100k should be OK!
            LRUCache<Integer, Protection> cache = new LRUCache<Integer, Protection>(1000 * 100);

            while (set.next()) {
                // load the data we will be using
                int protectionId = set.getInt("chest");
                String entity = set.getString("entity");
                int access = set.getInt("rights");
                int type = set.getInt("type");

                // begin loading the protection
                Protection protection = null;

                // check cache
                if (cache.containsKey(protectionId)) {
                    protection = cache.get(protectionId);
                } else {
                    // else, load it...
                    protection = loadProtection(protectionId);

                    if (protection == null) {
                        continue;
                    }

                    cache.put(protectionId, protection);
                }

                if (protection == null) {
                    continue;
                }

                // create the permission
                Permission permission = new Permission(entity, Permission.Type.values()[type],
                        Permission.Access.values()[access]);

                // add it to the protection and queue it for saving!
                protection.addPermission(permission);
            }

            // Save all of the protections
            for (Protection protection : cache.values()) {
                protection.saveNow();
            }

            // Good!
            set.close();

            // drop the rights table
            dropTable(prefix + "rights");
            precache();
        } catch (SQLException e) {
            // no need to convert!
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 4.0.0, update 4
     */
    private void doUpdate400_4() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.execute("SELECT data FROM " + prefix + "protections LIMIT 1");
        } catch (SQLException e) {
            dropColumn(prefix + "protections", "rights");
            addColumn(prefix + "protections", "data", "TEXT");
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 4.0.0, update 5
     */
    private void doUpdate400_5() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.executeQuery("SELECT flags FROM " + prefix + "protections LIMIT 1");

            // The flags column is still there ..!
            // instead of looping through every protection, let's do this a
            // better way
            PreparedStatement pStatement = prepare("SELECT * FROM " + prefix + "protections WHERE flags = 8"); // exempt

            for (Protection protection : resolveProtections(pStatement)) {
                Flag flag = new Flag(Flag.Type.EXEMPTION);
                protection.addFlag(flag);
                protection.save();
            }

            pStatement = prepare("SELECT * FROM " + prefix + "protections WHERE flags = 3"); // redstone

            for (Protection protection : resolveProtections(pStatement)) {
                Flag flag = new Flag(Flag.Type.MAGNET);
                protection.addFlag(flag);
                protection.save();
            }

            pStatement = prepare("SELECT * FROM " + prefix + "protections WHERE flags = 2"); // redstone

            for (Protection protection : resolveProtections(pStatement)) {
                Flag flag = new Flag(Flag.Type.REDSTONE);
                protection.addFlag(flag);
                protection.save();
            }

            dropColumn(prefix + "protections", "flags");
        } catch (SQLException e) {

        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * 4.0.0, update 6 (alpha7)
     */
    private void doUpdate400_6() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            statement.executeQuery("SELECT x FROM " + prefix + "history LIMIT 1");
        } catch (SQLException e) {
            // add x, y, z
            addColumn(prefix + "history", "x", "INTEGER");
            addColumn(prefix + "history", "y", "INTEGER");
            addColumn(prefix + "history", "z", "INTEGER");
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * Update the database for the "Update Aquatic", otherwise known as MineCraft 1.13! (from ModernLWC 2.0.0 - 2.0.9)
     */
    private void doUpdateModernLWC() {
        Statement statement = null;
        BlockCache blockCache = BlockCache.getInstance();
        try {
            statement = connection.createStatement();
            ResultSet test = statement.executeQuery("SELECT id FROM " + prefix + "blocks WHERE id = 0");
            try {
                test.first(); // needed for MySQL
            } catch (SQLException e) {
            }
            test.getInt("id"); // this should throw an exception if there are no entries in blocks table
            test.close();
        } catch (SQLException e) {
            try {
                LWC.getInstance().log("Performing database optimization for 1.13...");
                // fix any inconsistent entries, such as where blockName is NULL
                try {
                    ResultSet fs = statement.executeQuery("SELECT DISTINCT blockId FROM " + prefix + "protections where blockName is null");
                    PreparedStatement fixSmt = prepare("UPDATE " + prefix + "protections SET blockName = ? WHERE blockId = ?");
                    while (fs.next()) {
                        int blockId = fs.getInt(1);
                        Material blockMaterial = MaterialUtil.getMaterialById(blockId);
                        fixSmt.setString(1, blockMaterial == null ? Material.AIR.name() : blockMaterial.name());
                        fixSmt.setInt(2, blockId);
                        fixSmt.executeUpdate();
                    }
                    fs.close();
                    fixSmt.close();
                } catch (SQLException e2) {
                    return; // error while trying to fix inconsistencies (or no blockName column)
                }
                // do the rest of the update, assuming no inconsistencies
                ResultSet rs = statement.executeQuery("SELECT DISTINCT blockName FROM " + prefix + "protections");
                PreparedStatement updateSmt = prepare("UPDATE " + prefix + "protections SET blockId = ? WHERE blockName = ?");
                HashSet<String> entityNames = new HashSet<>();
                entityNames.add("Entity");
                for (EntityType entity : EntityType.values()) {
                    entityNames.add(entity.name());
                }
                while (rs.next()) {
                    String blockName = rs.getString(1);
                    int blockId = blockCache.addBlock(blockName);
                    if (blockId > 0) {
                        updateSmt.setInt(1, entityNames.contains(blockName)
                                ? EntityBlock.ENTITY_BLOCK_ID : blockId);
                        updateSmt.setString(2, blockName);
                        updateSmt.executeUpdate();
                    }
                }
                rs.close();
                updateSmt = prepare("UPDATE " + prefix + "protections SET blockName = NULL");
                updateSmt.executeUpdate();
                updateSmt = prepare("INSERT INTO " + prefix + "blocks (`id`,`name`) VALUES (?, ?)");
                updateSmt.setInt(1, 0);
                updateSmt.setString(2, Material.AIR.name());
                updateSmt.executeUpdate();
                updateSmt.close();
            } catch (SQLException e2) {
                return; // exit, probably no blockName column
            }
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * Update the database for the "Update Aquatic", otherwise known as MineCraft 1.13! (from ModernLWC 1.9.4 & prior)
     */
    private void doUpdateAquatic() {
        Statement statement = null;
        try {
            statement = connection.createStatement();
            try {
                ResultSet rs = statement.executeQuery("SELECT blockId FROM " + prefix + "protections LIMIT 1");
                rs.close();
            } catch (SQLException e) {
                return; // no protections table, no update
            }
            ResultSet rs = statement.executeQuery("SELECT id FROM " + prefix + "blocks WHERE id = 0");
            try {
                rs.first(); // needed for MySQL
            } catch (SQLException e) {
            }
            rs.getInt("id"); // this should throw an exception if there are no entries in blocks table
            rs.close();
        } catch (SQLException e) {
            try {
                LWC.getInstance().log("Performing database migration to 1.13 format...");
                statement.executeUpdate("UPDATE " + prefix + "protections SET blockId = -1 WHERE blockId IS NULL");
                ResultSet rs = statement.executeQuery("SELECT DISTINCT blockId FROM " + prefix + "protections");
                PreparedStatement insertSmt = prepare("INSERT INTO " + prefix + "blocks (`id`,`name`) VALUES (?, ?)");
                while (rs.next()) {
                    int id = rs.getInt("blockId");
                    if (id > 0 && id != EntityBlock.ENTITY_BLOCK_ID) {
                        Material material = MaterialUtil.getMaterialById(id);
                        if (material != null) {
                            insertSmt.setInt(1, id);
                            insertSmt.setString(2, material.name());
                            insertSmt.executeUpdate();
                        }
                    }
                }
                insertSmt.setInt(1, 0);
                insertSmt.setString(2, Material.AIR.name());
                insertSmt.executeUpdate();
                insertSmt.close();
            } catch (SQLException e2) {
                printException(e2);
            }
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                }
            }
        }
    }

    /**
     * Update the database for the "Village and Pillage" update, otherwise known as MineCraft 1.14.
     */
    private void doUpdateVillageAndPillage() {
        Matcher versionCheck = Pattern.compile("\\d[.]\\d+").matcher(Bukkit.getVersion());
        if (!versionCheck.find()) {
            return;
        }
        int minorVersion = Integer.parseInt(versionCheck.group().substring(2));
        if (minorVersion >= 14) {
            Statement statement = null;
            try {
                statement = connection.createStatement();
                statement.executeUpdate("UPDATE " + prefix + "blocks SET name = 'OAK_SIGN' WHERE name = 'SIGN'");
                statement.executeUpdate("UPDATE " + prefix + "blocks SET name = 'OAK_WALL_SIGN' WHERE name = 'WALL_SIGN'");
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/Colors.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util;

import java.util.HashMap;
import java.util.Map;

public class Colors {

    // Color codes
    public static final String Black           = "\u00A70";
    public static final String Dark_Blue       = "\u00A71";
    public static final String Dark_Green      = "\u00A72";
    public static final String Dark_Aqua       = "\u00A73";
    public static final String Dark_Red        = "\u00A74";
    public static final String Dark_Purple     = "\u00A75";
    public static final String Gold            = "\u00A76";
    public static final String Gray            = "\u00A77";
    public static final String Dark_Gray       = "\u00A78";
    public static final String Blue            = "\u00A79";
    public static final String Green           = "\u00A7a";
    public static final String Aqua            = "\u00A7b";
    public static final String Red             = "\u00A7c";
    public static final String Light_Purple    = "\u00A7d";
    public static final String Yellow          = "\u00A7e";
    public static final String White           = "\u00A7f";

    // Special codes
    public static final String Obfuscated       = "\u00A7k";
    public static final String Bold             = "\u00A7l";
    public static final String Strikethrough    = "\u00A7m";
    public static final String Underline        = "\u00A7n";
    public static final String Italic           = "\u00A7o";
    public static final String Reset            = "\u00A7r";

    // contains colors for locales
    public static final Map<String, String> localeColors = new HashMap<>();

    static {
        // Color code in percentage block
        localeColors.put("%black%", Black);
        localeColors.put("%dark_blue%", Dark_Blue);
        localeColors.put("%dark_green%", Dark_Green);
        localeColors.put("%dark_aqua%", Dark_Aqua);
        localeColors.put("%dark_red%", Dark_Red);
        localeColors.put("%dark_purple%", Dark_Purple);
        localeColors.put("%gold%", Gold);
        localeColors.put("%gray%", Gray);
        localeColors.put("%dark_gray%", Dark_Gray);
        localeColors.put("%blue%", Blue);
        localeColors.put("%green%", Green);
        localeColors.put("%aqua%", Aqua);
        localeColors.put("%red%", Red);
        localeColors.put("%light_purple%",Light_Purple);
        localeColors.put("%yellow%", Yellow);
        localeColors.put("%white%", White);

        localeColors.put("%obfuscated%", Obfuscated);
        localeColors.put("%bold%", Bold);
        localeColors.put("%strikethrough%", Strikethrough);
        localeColors.put("%underline%", Underline);
        localeColors.put("%italic%", Italic);
        localeColors.put("%reset%", Reset);
    }

}


<|start_filename|>src/main/java/com/griefcraft/modules/economy/EconomyModule.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.modules.economy;

import com.griefcraft.integration.ICurrency;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.History;
import com.griefcraft.model.LWCPlayer;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.*;
import com.griefcraft.util.Colors;
import com.griefcraft.util.config.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.block.Action;
import org.bukkit.inventory.InventoryHolder;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

public class EconomyModule extends JavaModule {

    /**
     * Our cache
     */
    private final Map<String, String> cache = new HashMap<>();
    private Logger logger = Logger.getLogger("LWC");

    /**
     * The iConomy module configuration
     */
    private Configuration configuration = Configuration.load("economy.yml");

    /**
     * A cache of prices. When a value is inputted, it stays in memory for milliseconds at best.
     * The best way to do this? ha, probably not
     */
    private Map<Location, String> priceCache = Collections.synchronizedMap(new HashMap<>());

    @Override
    public void onProtectionInteract(LWCProtectionInteractEvent event) {
        if (event.getResult() != Result.DEFAULT || !event.canAccess())
            return;

        if (!configuration.getBoolean("Economy.enabled", true))
            return;

        LWC lwc = event.getLWC();
        Protection protection = event.getProtection();
        Player player = event.getPlayer();

        // first, do we still have a currency processor?
        if (!lwc.getCurrency().isActive())
            return;

        // is it actually a container? :p
        if (!(protection.getBlock().getState() instanceof InventoryHolder))
            return;

        // Are they right clicking the chest (aka open) ?
        if (event.getEvent().getAction() != Action.RIGHT_CLICK_BLOCK)
            return;

        // Usage fee
        double usageFee = resolveDouble(player, "usageFee", false);

        // No fee! :D
        if (usageFee <= 0)
            return;

        // Can they afford it?
        if (!lwc.getCurrency().canAfford(player, usageFee)) {
            // Nope!
            // TODO: Extract string to string.yml
            player.sendMessage(Colors.Dark_Red + "You need " + lwc.getCurrency().format(usageFee) + " to open your protection!");
            event.setResult(Result.CANCEL);
            return;
        }

        // Charge them!
        lwc.getCurrency().removeMoney(player, usageFee);
        // TODO: Extract string to string.yml
        player.sendMessage(Colors.Dark_Green + "You have been charged " + lwc.getCurrency().format(usageFee) + " to open your protection.");
    }

    @Override
    public void onDestroyProtection(LWCProtectionDestroyEvent event) {
        if (event.isCancelled())
            return;

        if (!configuration.getBoolean("Economy.enabled", true))
            return;

        // is refunding enabled?
        if (!configuration.getBoolean("Economy.refunds", true))
            return;

        if (!LWC.getInstance().isHistoryEnabled())
            return;

        LWC lwc = event.getLWC();
        Protection protection = event.getProtection();
        Player player = event.getPlayer();

        // first, do we still have a currency processor?
        if (!lwc.getCurrency().isActive())
            return;

        // Does it support the server bank feature
        if (!lwc.getCurrency().usingCentralBank())
            return;

        // load the transactions so we can check the server bank
        List<History> transactions = protection.getRelatedHistory(History.Type.TRANSACTION);

        for (History history : transactions) {
            if (history.getStatus() == History.Status.INACTIVE)
                continue;

            // obtain the charge
            double charge = history.getDouble("charge");

            // No need to refund if it's negative or 0
            if (charge <= 0)
                continue;

            // check the server bank
            if (!lwc.getCurrency().canCentralBankAfford(charge)) {
                // TODO: Extract string to string.yml
                player.sendMessage(Colors.Dark_Red + "The Server's Bank does not contain enough funds to remove that protection!");
                event.setCancelled(true);
                return;
            }
        }
    }

    @Override
    public void onPostRegistration(LWCProtectionRegistrationPostEvent event) {
        if (!configuration.getBoolean("Economy.enabled", true))
            return;

        if (!LWC.getInstance().isHistoryEnabled())
            return;

        Protection protection = event.getProtection();

        // we need to inject the iconomy price into the transaction!
        Block block = protection.getBlock();

        // Uh-oh! This REALLY should never happen ... !
        if (block == null || !priceCache.containsKey(block.getLocation()))
            return;

        Location location = block.getLocation();

        // okey, get how much they were charged
        String cachedPrice = priceCache.get(location);

        boolean usedDiscount = cachedPrice.startsWith("d");
        double charge = Double.parseDouble(usedDiscount ? cachedPrice.substring(1) : cachedPrice);

        // get related transactions..
        List<History> transactions = protection.getRelatedHistory(History.Type.TRANSACTION);

        // this really should not happen either (never!)
        if (transactions.size() == 0)
            logger.severe("LWC-Economy POST_REGISTRATION encountered a severe problem!: transactions.size() == 0");

        // get the last entry
        History history = transactions.get(transactions.size() - 1);

        // add the price
        history.addMetaData("charge=" + charge);

        // was it a discount?
        if (usedDiscount) {
            history.addMetaData("discount=true");

            // Was the discount's id non-null?
            String discountId = resolveValue(protection.getBukkitOwner(), "discount.id");

            if (!discountId.isEmpty())
                history.addMetaData("discountId=" + discountId);
        }

        // save it immediately
        history.saveNow();

        // we no longer need the value in the price cache :)
        priceCache.remove(location);
    }

    @Override
    public void onPostRemoval(LWCProtectionRemovePostEvent event) {
        if (!configuration.getBoolean("Economy.enabled", true))
            return;

        // is refunding enabled?
        if (!configuration.getBoolean("Economy.refunds", true))
            return;

        if (!LWC.getInstance().isHistoryEnabled())
            return;

        LWC lwc = event.getLWC();
        Protection protection = event.getProtection();

        // first, do we still have a currency processor?
        if (!lwc.getCurrency().isActive())
            return;

        // we need to refund them, load up transactions
        List<History> transactions = protection.getRelatedHistory(History.Type.TRANSACTION);

        for (History history : transactions) {
            if (history.getStatus() == History.Status.INACTIVE)
                continue;

            // obtain the charge
            double charge = history.getDouble("charge");

            // No need to refund if it's negative or 0
            if (charge <= 0)
                continue;

            // refund them :)
            Player owner = Bukkit.getServer().getPlayer(history.getPlayer());

            // we can't pay them ..
            if (owner == null)
                continue;

            // the currency to use
            ICurrency currency = lwc.getCurrency();

            currency.addMoney(owner, charge);
            // TODO: Extract string to string.yml
            owner.sendMessage(Colors.Dark_Green + "You have been refunded " + currency.format(charge) + " because an LWC protection of yours was removed!");
        }
    }

    @Override
    public void onRegisterProtection(LWCProtectionRegisterEvent event) {
        if (event.isCancelled())
            return;

        if (!configuration.getBoolean("Economy.enabled", true))
            return;

        LWC lwc = event.getLWC();
        Block block = event.getBlock();
        Player player = event.getPlayer();

        // currency handler to use
        ICurrency currency = lwc.getCurrency();

        if (!currency.isActive())
            return;

        // if a discount was used
        boolean usedDiscount = false;

        // how much to charge the player
        double charge = 0D;

        String value;
        // Check for a block override charge
        // TODO: Implement description to main LWC configuration -- "core.yml"
        if ((value = lwc.resolveProtectionConfiguration(block, "charge")) != null) {
            try {
                charge = Double.parseDouble(value);
            } catch (NumberFormatException e) {
                //TODO: Throw an error message for being weird on the price, then set value to zero.
            }
        } else charge = resolveDouble(player, "charge", false);

        // check if they have a discount available
        try {
            boolean isDiscountActive = Boolean.parseBoolean(resolveValue(player, "discount.active"));

            if (isDiscountActive) {
                int discountedProtections = resolveInt(player, "discount.amount", true);
                double discountPrice = resolveDouble(player, "discount.newCharge", false);

                if (discountedProtections > 0) {
                    int currentProtections = 0;

                    // Match the discount type
                    DiscountType discountType = DiscountType.valueOf(resolveValue(player, "discount.type").toUpperCase());

                    // The unique id of the discount, by default they are shared between discounts if not set
                    String discountId = resolveValue(player, "discount.id");

                    // Count the protections
                    switch (discountType) {
                        case EXACT:
                            currentProtections = countDiscountedProtections(lwc, player, discountPrice, discountId.isEmpty() ? null : discountId, false);
                            break;

                        case TOTAL:
                            currentProtections = lwc.getPhysicalDatabase().getProtectionCount(player.getName());
                            break;

                        case IN_USE:
                            currentProtections = countDiscountedProtections(lwc, player, discountPrice, discountId.isEmpty() ? null : discountId, true);
                            break;
                    }

                    if (discountedProtections > currentProtections) {
                        charge = discountPrice;
                        usedDiscount = true;
                    }
                }
            }
        } catch (NumberFormatException e) {
            //TODO: Throw an error message for being weird on the price, then set value to zero.
        }

        // used for price cache
        Location location = block.getLocation();

        // cache the charge momentarily
        if (lwc.isHistoryEnabled())
            priceCache.put(location, (usedDiscount ? "d" : "") + charge);

        // Check if the charge is free
        if (charge == 0) {
            // TODO: Extract string to string.yml
            player.sendMessage(Colors.Dark_Green + "This one's on us!");
        } else {
            if (!currency.canAfford(player, charge)) {
                // TODO: Extract string to string.yml
                player.sendMessage(Colors.Dark_Red + "You do not have enough " + currency.getMoneyName() + " to buy an LWC protection.");
                // TODO: Extract string to string.yml
                player.sendMessage(Colors.Dark_Red + "The balance required for an LWC protection is: " + currency.format(charge));

                // remove from cache
                priceCache.remove(location);
                event.setCancelled(true);
                return;
            }

            // remove the money from their account
            currency.removeMoney(player, charge);
            // TODO: Extract string to string.yml
            player.sendMessage(Colors.Dark_Green + "Charged " + currency.format(charge) + (usedDiscount ? (Colors.Dark_Red + " (Discount)" + Colors.Dark_Green) : "") + " for an LWC protection. Thank you.");
        }

    }

    /**
     * Resolve a configuration node for a player. Tries nodes in this order:
     * <pre>
     * players.PLAYERNAME.node
     * groups.GROUPNAME.node
     * Economy.node
     * </pre>
     *
     * @param player
     * @param node
     * @param sortHighest
     * @return
     */
    private int resolveInt(Player player, String node, boolean sortHighest) {
        return (int) resolveDouble(player, node, sortHighest);
    }

    /**
     * Resolve a configuration node for a player. Tries nodes in this order:
     * <pre>
     * players.PLAYERNAME.node
     * groups.GROUPNAME.node
     * Economy.node
     * </pre>
     *
     * @param player
     * @param node
     * @param sortHighest sort values by highest value
     * @return
     */
    private double resolveDouble(Player player, String node, boolean sortHighest) {
        LWC lwc = LWC.getInstance();
        double value = -1;

        String cacheKey = "resolve-" + player.getName() + node;
        if (cache.containsKey(cacheKey))
            return Double.parseDouble(cache.get(cacheKey));

        // try the player
        try {
            value = Double.parseDouble(configuration.getString("players." + player.getName() + "." + node, "-1"));
        } catch (NumberFormatException e) {
            //TODO: Throw an error message for being weird on the price, then set value to zero.
            //TODO: Save Hidendra from hell.
        } // May I be forgiven in hell for that --Hidendra

        // try their groups
        if (value == -1) {
            for (String groupName : lwc.getPermissions().getGroups(player)) {
                if (groupName != null && !groupName.isEmpty()) {
                    try {
                        double v = Double.parseDouble(map("groups." + groupName + "." + node, "-1"));

                        if (v == -1)
                            continue;

                        // check the value
                        if ((sortHighest && (v > value || value == -1)) || (!sortHighest && (v < value || value == -1)))
                            value = v;
                    } catch (NumberFormatException e) {
                        //TODO: Throw an error message for being weird on the price, then set value to zero.
                    }
                }
            }
        }

        // if all else fails, use master
        if (value == -1) {
            try {
                value = Double.parseDouble(map("Economy." + node, "-1"));
            } catch (NumberFormatException e) {
                //TODO: Throw an error message for being weird on the price, then set value to zero.
            }
        }

        cache.put(cacheKey, Double.toString(value));
        return value;
    }

    /**
     * Resolve a configuration node for a player. Tries nodes in this order:
     * 1. players.PLAYERNAME.node
     * 2. groups.GROUPNAME.node
     * 3. Economy.node
     *
     * @param player
     * @param node
     * @return
     */
    private String resolveValue(Player player, String node) {
        LWC lwc = LWC.getInstance();

        // resolve the limits type
        String value;

        // try the player
        value = configuration.getString("players." + player.getName() + "." + node, null);

        // try permissions
        if (value == null)
            for (String groupName : lwc.getPermissions().getGroups(player))
                if (groupName != null && !groupName.isEmpty() && value == null)
                    value = map("groups." + groupName + "." + node, null);


        // if all else fails, use master
        if (value == null)
            value = map("Economy." + node, null);

        return value != null && !value.isEmpty() ? value : "";
    }

    /**
     * Get the value from either the path or the default value if it's null
     *
     * @param path
     * @return
     */
    private String map(String path, String defaultValue) {
        String value = configuration.getString(path, defaultValue);

        if (value == null) {
            int lastIndex = path.lastIndexOf(".");
            String node = path.substring(lastIndex + 1);

            value = configuration.getString("Economy." + node);
        }

        return value;
    }

    /**
     * Get the amount of protections the player purchased for  the given discount price
     *
     * @param lwc
     * @param player
     * @param discountPrice
     * @param discountId
     * @param onlyCountActiveTransactions
     * @return
     */
    private int countDiscountedProtections(LWC lwc, Player player, double discountPrice, String discountId, boolean onlyCountActiveTransactions) {
        LWCPlayer lwcPlayer = lwc.wrapPlayer(player);
        List<History> related = lwcPlayer.getRelatedHistory(History.Type.TRANSACTION);

        int amount = 0;

        for (History history : related) {
            if (!history.getBoolean("discount"))
                continue;

            // Check the other discount id
            if (discountId != null)
                if (!history.hasKey("discountId") || !history.getString("discountId").equals(discountId))
                    continue;

            // Are we only looking for valid transactions?
            if (onlyCountActiveTransactions && history.getStatus() == History.Status.INACTIVE)
                continue;

            // obtain the charge
            double charge = history.getDouble("charge");

            if (charge == discountPrice)
                amount++;
        }

        return amount;
    }

    public enum DiscountType {

        /**
         * The discount will apply while the player has under X protections total.
         */
        TOTAL,

        /**
         * The discount will only give the player X discounted protections - no more.
         */
        EXACT,

        /**
         * The discount will apply while the player has under X DISCOUNTED protections total.
         * This is different from TOTAL because if they have 5 free protections, they get 5
         * for free, and if they remove one of the free ones, they will get 1 protection
         * for free.
         */
        IN_USE

    }

}

<|start_filename|>src/main/java/com/griefcraft/cache/BlockCache.java<|end_filename|>
package com.griefcraft.cache;

import com.griefcraft.lwc.LWC;
import com.griefcraft.sql.PhysDB;
import com.griefcraft.util.MaterialUtil;
import org.bukkit.Material;
import org.bukkit.block.Block;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * Loads and stores block mappings from the database, as well as caching them during use for performance.
 */
public class BlockCache {

    /**
     * The BlockCache singleton.
     */
    private static BlockCache blockCache = new BlockCache();

    /**
     * The LWC instance.
     */
    private LWC lwc;

    /**
     * Storing block mappings internally using two HashMaps, to allow bidirectional lookups.
     */
    private Map<Integer, Material> intBlockCache;
    private Map<Material, Integer> materialBlockCache;

    /**
     * The lowest available block ID for new additions.
     */
    private int nextId = 1;

    /**
     * Values that LWC will expect to be constant across databases.
     */
    public static enum Constants {
        AIR(0),
        ENTITY(1);

        private int id;

        Constants(int id) {
            this.id = id;
        }

        public int getValue() {
            return id;
        }
    }

    /**
     * Initialization for the block cache.
     */
    private BlockCache() {
        lwc = LWC.getInstance();
        intBlockCache = new HashMap<>();
        materialBlockCache = new HashMap<>();
    }

    /**
     * Gets the block cache.
     *
     * @return
     */
    public static BlockCache getInstance() {
        return blockCache;
    }

    /**
     * Clean up the singleton instance when disabling.
     */
    public static void destruct() {
        blockCache = null;
    }

    /**
     * Add a mapping to both maps. Only to be used internally.
     *
     * @param integer
     * @param material
     */
    private void addMapping(Integer integer, Material material) {
        intBlockCache.put(integer, material);
        materialBlockCache.put(material, integer);
    }

    /**
     * Remove a mapping from both maps. Only to be used internally.
     *
     * @param integer
     * @param material
     */
    private void removeMapping(Integer integer, Material material) {
        intBlockCache.remove(integer);
        materialBlockCache.remove(material);
    }

    /**
     * Loads all block mappings from the database.
     */
    public void loadBlocks() {
        PhysDB database = lwc.getPhysicalDatabase();
        String prefix = database.getPrefix();
        try {
            PreparedStatement statement = database.prepare("SELECT id, name FROM " + prefix + "blocks");
            ResultSet set = statement.executeQuery();
            while (set.next()) {
                Integer materialId = set.getInt("id");
                String materialName = set.getString("name");
                Material material = Material.matchMaterial(materialName);
                if (material != null) {
                    addMapping(materialId, material);
                    if (materialId >= nextId) {
                        nextId = materialId + 1;
                    }
                } else {
                    lwc.log("Unable to load " + materialName + "from " + prefix + "blocks!");
                }
            }
        } catch (SQLException e) {
            lwc.log("Unable to load " + prefix + "blocks!");
            e.printStackTrace();
        }
    }

    /**
     * Adds a block the block cache by its Material type, and tries to add it if it doesn't exist.
     *
     * @param blockMaterial
     */
    public int addBlock(Material blockMaterial) {
        if (materialBlockCache.containsKey(blockMaterial)) {
            return materialBlockCache.get(blockMaterial);
        }
        PhysDB database = lwc.getPhysicalDatabase();
        String prefix = database.getPrefix();
        try {
            PreparedStatement statement = database.prepare("INSERT INTO " + prefix
                    + "blocks (id, name) VALUES(?, ?)");
            statement.setInt(1, nextId);
            statement.setString(2, blockMaterial.name());
            statement.executeUpdate();
            int newId = nextId;
            addMapping(newId, blockMaterial);
            ++nextId;
            return newId;
        } catch (SQLException e) {
            lwc.log("Unable to add new block to " + prefix + "blocks!");
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * Adds a block to the block cache by its Block name, and tries to add it if it doesn't exist.
     *
     * @param block
     */
    public int addBlock(String block) {
        Material material = Material.matchMaterial(block);
        if (material != null) {
            return addBlock(material);
        }
        return -1;
    }

    /**
     * Adds a block to the block cache by its Block type, and tries to add it if it doesn't exist.
     *
     * @param block
     */
    public int addBlock(Block block) {
        return addBlock(block.getType());
    }

    /**
     * Adds a block to the block cache by its pre-1.13 ID, and tries to add it if it doesn't exist.
     *
     * @param blockId
     */
    public int addBlock(int blockId) {
        return addBlock(MaterialUtil.getMaterialById(blockId));
    }

    /**
     * Removes a block the block cache by its Material type, if it exists.
     *
     * @param blockMaterial
     */
    public void removeBlock(Material blockMaterial) {
        if (materialBlockCache.containsKey(blockMaterial)) {
            removeBlock(materialBlockCache.get(blockMaterial));
        }
    }

    public void removeBlock(String block) {
        Material material = Material.matchMaterial(block);
        if (material != null) {
            removeBlock(material);
        }
    }

    /**
     * Removes a block to the block cache by its Block type, if it exists.
     *
     * @param block
     */
    public void removeBlock(Block block) {
        removeBlock(block.getType());
    }

    /**
     * Removes a block by its ID in the database, if it exists.
     *
     * @param blockId
     */
    public void removeBlock(int blockId) {
        if (!intBlockCache.containsKey(blockId)) {
            return;
        }
        PhysDB database = lwc.getPhysicalDatabase();
        String prefix = database.getPrefix();
        try {
            PreparedStatement statement = database.prepare("DELETE FROM " + prefix + "blocks WHERE id = ?");
            statement.setInt(1, blockId);
            statement.executeUpdate();
            removeMapping(blockId, intBlockCache.get(blockId));
        } catch (SQLException e) {
            lwc.log("Unable to remove block from " + prefix + "blocks!");
            e.printStackTrace();
        }
    }

    /**
     * Get a block's id, or try to add it if it doesn't exist.
     *
     * @param blockMaterial
     * @return
     */
    public int getBlockId(Material blockMaterial) {
        Integer id = materialBlockCache.get(blockMaterial);
        if (id != null) {
            return id;
        }
        return addBlock(blockMaterial);
    }

    /**
     * Get a block's id, or try to add it if it doesn't exist.
     *
     * @param blockName
     * @return
     */
    public int getBlockId(String blockName) {
        Material material = Material.matchMaterial(blockName);
        if (material != null) {
            return getBlockId(material);
        }
        return addBlock(blockName);
    }

    /**
     * Get a block's id, or try to add it if it doesn't exist.
     *
     * @param block
     * @return
     */
    public int getBlockId(Block block) {
        return getBlockId(block.getType());
    }

    /**
     * Get a block's id, or try to add it if it doesn't exist.
     *
     * @param blockId
     * @return
     */
    public int getBlockId(int blockId) {
        if (intBlockCache.containsKey(blockId)) {
            return blockId;
        }
        return addBlock(blockId);
    }

    /**
     * Get a block's type, or try to add it if it doesn't exist.
     *
     * @param blockMaterial
     * @return
     */
    public Material getBlockType(Material blockMaterial) {
        if (materialBlockCache.containsKey(blockMaterial)) {
            return blockMaterial;
        }
        int id = addBlock(blockMaterial);
        if (intBlockCache.containsKey(id)) {
            return intBlockCache.get(id);
        }
        return null;
    }

    /**
     * Get a block's type, or try to add it if it doesn't exist.
     *
     * @param blockName
     * @return
     */
    public Material getBlockType(String blockName) {
        Material material = Material.matchMaterial(blockName);
        if (material != null) {
            return getBlockType(material);
        }
        return null;
    }

    /**
     * Get a block's type, or try to add it if it doesn't exist.
     *
     * @param block
     * @return
     */
    public Material getBlockType(Block block) {
        return getBlockType(block.getType());
    }

    /**
     * Get a block's type, or try to add it if it doesn't exist.
     *
     * @param blockId
     * @return
     */
    public Material getBlockType(int blockId) {
        if (intBlockCache.containsKey(blockId)) {
            return intBlockCache.get(blockId);
        }
        Material material = MaterialUtil.getMaterialById(blockId);
        if (material != null) {
            int id = addBlock(material);
            if (intBlockCache.containsKey(id)) {
                return intBlockCache.get(id);
            }
        }
        return null;
    }

}


<|start_filename|>src/main/java/com/griefcraft/listeners/LWCEntityListener.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.listeners;

import com.griefcraft.bukkit.EntityBlock;
import com.griefcraft.cache.ProtectionCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.lwc.LWCPlugin;
import com.griefcraft.model.Flag;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.event.LWCProtectionDestroyEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.scripting.event.LWCProtectionRegistrationPostEvent;
import com.griefcraft.util.Colors;
import org.bukkit.GameMode;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.event.entity.EntityBreakDoorEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityInteractEvent;
import org.bukkit.event.hanging.HangingPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;

import java.util.UUID;

public class LWCEntityListener implements Listener {

    /**
     * The plugin instance
     */
    private LWCPlugin plugin;

    private UUID placedArmorStandPlayer;

    public LWCEntityListener(LWCPlugin plugin) {
        this.plugin = plugin;
    }


    @EventHandler(ignoreCancelled = true)
    public void onHangingPlace(HangingPlaceEvent event) {
        Player player = event.getPlayer();
        Entity block = event.getEntity();
        entityCreatedByPlayer(block, player);
    }

    @EventHandler(ignoreCancelled = true)
    public void onPlayerInteract(PlayerInteractEvent e) {
        ItemStack inHand = e.getItem();
        if (inHand != null) {
            placedArmorStandPlayer = e.getPlayer().getUniqueId();
        }
    }

    @EventHandler(ignoreCancelled = true)
    public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
        if (!LWC.ENABLED || event.isCancelled()) {
            return;
        }

        LWC lwc = plugin.getLWC();

        if (event.getDamager() instanceof Player) {
            Player player = (Player) event.getDamager();
            Entity entity = event.getEntity();
            EntityBlock entityBlock = new EntityBlock(entity);

            boolean ignoreBlockDestruction = Boolean
                    .parseBoolean(lwc.resolveProtectionConfiguration(entityBlock, "ignoreBlockDestruction"));

            if (ignoreBlockDestruction) {
                return;
            }

            if (event.getEntityType().equals(EntityType.ARMOR_STAND)) {
                if (event.getDamage() < 1.0 ||
                        ((Player) event.getDamager()).getGameMode().equals(GameMode.CREATIVE)) { // Armor Stand Broke
                    ProtectionCache cache = lwc.getProtectionCache();
                    String cacheKey = cache.cacheKey(entityBlock.getLocation());

                    // In the event they place a block, remove any known nulls there
                    if (cache.isKnownNull(cacheKey)) {
                        cache.remove(cacheKey);
                    }

                    Protection protection = lwc.findProtection(entityBlock);

                    if (protection == null) {
                        return;
                    }

                    boolean canAccess = lwc.canAccessProtection(player, protection);
                    boolean canAdmin = lwc.canAdminProtection(player, protection);

                    try {
                        // Removing protection
                        LWCProtectionDestroyEvent evt = new LWCProtectionDestroyEvent(player, protection,
                                LWCProtectionDestroyEvent.Method.ENTITY_DESTRUCTION, canAccess, canAdmin);
                        lwc.getModuleLoader().dispatchEvent(evt);

                        protection.remove();
                        protection.removeAllPermissions();
                        protection.removeCache();

                        if (evt.isCancelled() || !canAccess) {
                            event.setCancelled(true);
                        }
                    } catch (Exception e) {
                        event.setCancelled(true);
                        lwc.sendLocale(player, "protection.internalerror", "id", "BLOCK_BREAK");
                        e.printStackTrace();
                    }
                }
                /*else { // Armor Stand Punched
                    LWC.getInstance().log("Armor Stand Punched");
                    if(plugin.getLWC().isProtectable(entity.getType())){
                        int A = 50000 + entity.getUniqueId().hashCode();
                        Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
                        boolean canAccess = lwc.canAccessProtection(player, protection);
                        boolean canAdmin = lwc.canAdminProtection(player, protection);
                        Set<String> actions = lwc.wrapPlayer(player).getActionNames();
                        Module.Result result = Module.Result.CANCEL;

                        // TODO: Finish this implementation
                        if (protection != null) {
                            LWCEntityDamageByEntityEvent evt =
                                    new LWCEntityDamageByEntityEvent(event, protection, actions, canAccess, canAdmin);
                            lwc.getModuleLoader().dispatchEvent(evt);

                            result = evt.getResult();
                        } else {

                        }
                        if (result == Module.Result.ALLOW) {
                            return;
                        }
                        if (player.hasPermission("lwc.lockentity." + entity.getType()) || player.hasPermission("lwc.lockentity.all")) {
                            if (onPlayerEntityInteract(p, entity, e.isCancelled())) {
                                chunkUnload(entity.getWorld().getName(), A);
                                e.setCancelled(true);
                            }
                        }
                        if (protection != null) {
                            if (canAccess)
                                return;
                            e.setCancelled(true);
                        }
                    }
                }*/
            }
        }


    }

    @EventHandler(ignoreCancelled = true)
    public void onCreateSpawn(CreatureSpawnEvent e) {
        if (placedArmorStandPlayer != null) {
            Player player = plugin.getServer().getPlayer(placedArmorStandPlayer);
            Entity block = e.getEntity();
            placedArmorStandPlayer = null;
            if (player != null) {
                if (player.getWorld().equals(block.getWorld())
                        && player.getLocation().distance(block.getLocation()) <= 5) {
                    entityCreatedByPlayer(block, player);
                }
            }
        }
    }

    private void entityCreatedByPlayer(Entity entity, Player player) {
        if (!LWC.ENABLED) {
            return;
        }

        LWC lwc = plugin.getLWC();

        int A = 50000 + entity.getUniqueId().hashCode();

        // Update the cache if a protection is matched here
        try {
            Protection current = lwc.findProtection(EntityBlock.getEntityBlock(entity));
            if (current != null) {
                if (!current.isBlockInWorld()) {
                    // Removing corrupted protection
                    current.remove();
                } else {
                    if (current.getProtectionFinder() != null) {
                        current.getProtectionFinder().fullMatchBlocks();
                        lwc.getProtectionCache().addProtection(current);
                    }

                    return;
                }
            }
        } catch (java.lang.ClassCastException ex) {
            ex.printStackTrace();
        }


        if (!lwc.isProtectable(entity.getType())) {
            return;
        }

        String autoRegisterType = lwc.resolveProtectionConfiguration(entity.getType(), "autoRegister");

        // is it auto protectable?
        if (!autoRegisterType.equalsIgnoreCase("private") && !autoRegisterType.equalsIgnoreCase("public") && !autoRegisterType.equalsIgnoreCase("donation")) {
            return;
        }

        if (!lwc.hasPermission(player, "lwc.create." + autoRegisterType, "lwc.create", "lwc.protect")) {
            return;
        }

        // Parse the type
        Protection.Type type;

        try {
            type = Protection.Type.valueOf(autoRegisterType.toUpperCase());
        } catch (IllegalArgumentException e) {
            // No auto protect type found
            return;
        }

        // Is it okay?
        if (type == null) {
            player.sendMessage(Colors.Dark_Red + "LWC_INVALID_CONFIG_autoRegister");
            return;
        }

        try {
            LWCProtectionRegisterEvent evt = new LWCProtectionRegisterEvent(player, EntityBlock.getEntityBlock(entity));
            lwc.getModuleLoader().dispatchEvent(evt);

            // something cancelled registration
            if (evt.isCancelled()) {
                return;
            }

            // All good!
            Protection protection = lwc.getPhysicalDatabase().registerProtection(EntityBlock.ENTITY_BLOCK_ID, type,
                    entity.getWorld().getName(), player.getUniqueId().toString(), "", A, A, A);

            if (!Boolean.parseBoolean(lwc.resolveProtectionConfiguration(EntityBlock.getEntityBlock(entity), "quiet"))) {
                lwc.sendLocaleToActionBar(player, "protection.onplace.create.finalize", "type",
                        lwc.getPlugin().getMessageParser().parseMessage(autoRegisterType.toLowerCase()), "block",
                        LWC.materialToString(EntityBlock.getEntityBlock(entity)));
            }

            if (protection != null) {
                lwc.getModuleLoader().dispatchEvent(new LWCProtectionRegistrationPostEvent(protection));
            }
        } catch (Exception e) {
            lwc.sendLocale(player, "protection.internalerror", "id", "PLAYER_INTERACT");
            e.printStackTrace();
        }
    }

    @EventHandler
    public void entityInteract(EntityInteractEvent event) {
        Block block = event.getBlock();

        Protection protection = plugin.getLWC().findProtection(block.getLocation());

        if (protection != null) {
            boolean allowEntityInteract = Boolean
                    .parseBoolean(plugin.getLWC().resolveProtectionConfiguration(block, "allowEntityInteract"));

            if (!allowEntityInteract) {
                event.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void entityBreakDoor(EntityBreakDoorEvent event) {
        Block block = event.getBlock();

        // See if there is a protection there
        Protection protection = plugin.getLWC().findProtection(block.getLocation());

        if (protection != null) {
            // protections.allowEntityBreakDoor
            boolean allowEntityBreakDoor = Boolean
                    .parseBoolean(plugin.getLWC().resolveProtectionConfiguration(block, "allowEntityBreakDoor"));

            if (!allowEntityBreakDoor) {
                event.setCancelled(true);
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGH)
    public void onEntityExplode(EntityExplodeEvent event) {
        if (!LWC.ENABLED || event.isCancelled()) {
            return;
        }

        LWC lwc = LWC.getInstance();

        for (Block block : event.blockList()) {
            Protection protection = plugin.getLWC().findProtection(block.getLocation());

            if (protection != null) {
                boolean ignoreExplosions = Boolean
                        .parseBoolean(lwc.resolveProtectionConfiguration(protection.getBlock(), "ignoreExplosions"));

                if (!(ignoreExplosions || protection.hasFlag(Flag.Type.ALLOWEXPLOSIONS))) {
                    event.setCancelled(true);
                }
            }
        }
    }
}


<|start_filename|>src/main/java/com/griefcraft/modules/history/HistoryModule.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY TYLER BLAIR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TYLER BLAIR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Tyler Blair.
 */

package com.griefcraft.modules.history;

import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Action;
import com.griefcraft.model.History;
import com.griefcraft.model.LWCPlayer;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCBlockInteractEvent;
import com.griefcraft.scripting.event.LWCCommandEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.util.Colors;
import com.griefcraft.util.TimeUtil;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.util.Date;
import java.util.List;

public class HistoryModule extends JavaModule {

    /**
     * Amount of history items to show per page
     */
    public static final int ITEMS_PER_PAGE = 15;

    /**
     * History tool
     */
    @Override
    public void onProtectionInteract(LWCProtectionInteractEvent event) {
        if (event.getResult() != DEFAULT) {
            return;
        }

        if (!event.hasAction("history")) {
            return;
        }

        LWC lwc = event.getLWC();
        Player player = event.getPlayer();
        Protection protection = event.getProtection();

        historyTool(lwc, player, protection.getX(), protection.getY(), protection.getZ());
        event.setResult(Result.CANCEL);
        lwc.removeModes(player);
    }

    @Override
    public void onBlockInteract(LWCBlockInteractEvent event) {
        if (event.getResult() != DEFAULT) {
            return;
        }

        if (!event.hasAction("history")) {
            return;
        }

        LWC lwc = event.getLWC();
        Block block = event.getBlock();
        Player player = event.getPlayer();

        historyTool(lwc, player, block.getX(), block.getY(), block.getZ());
        event.setResult(Result.CANCEL);
        lwc.removeModes(player);
    }

    /**
     * History tool logic
     *
     * @param player
     * @param x
     * @param y
     * @param z
     */
    private void historyTool(LWC lwc, Player player, int x, int y, int z) {
        LWCPlayer lwcPlayer = lwc.wrapPlayer(player);
        Action action = lwcPlayer.getAction("history");

        if (action == null) {
            player.sendMessage(Colors.Dark_Red + "History action is null!");
            return;
        }

        // the current "page"
        int page = Integer.parseInt(action.getData());

        // load the history objects for the protection
        List<History> relatedHistory = null;

        if (lwc.isAdmin(player)) {
            relatedHistory = lwc.getPhysicalDatabase().loadHistory(x, y, z);
        } else {
            relatedHistory = lwc.getPhysicalDatabase().loadHistory(player.getName(), x, y, z);
        }

        if (page < 0 || page >= relatedHistory.size()) {
            page = 0;
        }

        // No results
        if (relatedHistory.size() == 0) {
            lwc.sendLocale(player, "lwc.noresults");
            return;
        }

        // The relevent history object
        History history = relatedHistory.get(page);

        // Uh-oh! This normally will not happen (?)
        if (history == null) {
            lwcPlayer.removeAllActions();
            lwcPlayer.sendMessage("Refusing to send null history object");
            return;
        }

        // Make sure they can access it
        if (!lwc.isAdmin(player) && !history.getPlayer().equals(player.getName())) {
            return;
        }

        // Send the header
        lwcPlayer.sendMessage("History tool: Page " + (page + 1) + "/" + relatedHistory.size());

        // Send the details
        sendDetails(player, history);

        // increment the page
        if (page >= relatedHistory.size()) {
            // rewind
            action.setData("0");
        } else {
            action.setData(Integer.toString(page + 1));
        }
    }

    /**
     * Send the history list to a player
     *
     * @param sender
     * @param relatedHistory
     * @param page
     * @param maxPages
     * @param historyCount
     */
    public void sendHistoryList(CommandSender sender, List<History> relatedHistory, int page, int maxPages, int historyCount) {
        String format = "%4s%12s%12s%12s";

        // Header
        LWC.getInstance().sendLocale(sender, "lwc.history.list", "header", String.format(format, "Id", "Player", "Type", "Status"), "size", relatedHistory.size(),
                "page", page, "totalpages", maxPages, "totalhistory", historyCount);

        // Send all that is found to them
        for (History history : relatedHistory) {
            sender.sendMessage(String.format(format, ("" + history.getId()), history.getPlayer(), history.getType(), history.getStatus()));
        }
    }

    /**
     * Send history details to a player
     *
     * @param sender
     * @param history
     */
    private void sendDetails(CommandSender sender, History history) {
        LWC lwc = LWC.getInstance();
        Protection protection = history.getProtection(); // If the protection still exists, that is

        // if it was removed, hide the protection (if they're using SQLite, primary keys are reused)
        if (history.hasKey("destroyer")) {
            protection = null;
        }

        lwc.sendLocale(sender, "lwc.history.details.header", "id", history.getId(), "player", history.getPlayer(),
                "location", String.format("[%d %d %d]", history.getX(), history.getY(), history.getZ()),
                "status", history.getStatus(), "type", history.getType(),
                "protection", (protection == null ? "n/a" : protection),
                "creator", history.getString("creator"),
                "currencyname", lwc.getCurrency().getMoneyName());
        if (history.hasKey("destroyer")) {
            lwc.sendLocale(sender, "lwc.history.details.destroyer", "player", history.getString("destroyer"));
        }

        // New line!
        sender.sendMessage(" ");

        // If it had an economy charge, show it
        if (history.hasKey("charge")) {
            lwc.sendLocale(sender, "lwc.history.details.econcharge", "charge", history.getDouble("charge"), "moneyname", lwc.getCurrency().getMoneyName(),
                    "discount",
                    // Rest your eyes and avoid the line two lines below
                    // Its format: Yes|No (ID)  -- (ID) is only shown if Yes is shown.
                    ((history.hasKey("discount") ? (Colors.Dark_Red + "Yes") : (Colors.Yellow + "No")) + Colors.Yellow + " " + /* Discount id */ (history.hasKey("discountId") ? ("(" + history.getString("discountId") + ")") : "")));
        }

        // Show the creation date
        String creation = null;

        if (history.getTimestamp() > 0) {
            creation = new Date(history.getTimestamp() * 1000L).toString();
        }

        lwc.sendLocale(sender, "lwc.history.details.dates", "date", (creation == null ? "Unknown" : creation));

        // Only show how long ago it was if we know when it was created
        if (creation != null) {
            lwc.sendLocale(sender, "lwc.history.details.timeago", "time", TimeUtil.timeToString((System.currentTimeMillis() / 1000L) - history.getTimestamp()));
        }

        // if its been removed, it most likely will have this key
        if (history.hasKey("destroyer")) {
            // When it was removed
            String destroyerTime = history.getString("destroyerTime");
            long destroyed = 0L;

            // parse it if it's found
            if (!destroyerTime.isEmpty()) {
                destroyed = Long.parseLong(destroyerTime);
            }

            // Parse a date to show
            String absoluteDestroyDate = destroyed > 0 ? new Date(destroyed * 1000L).toString() : "Unknown";
            String relativeDestroyDate = destroyed > 0 ? TimeUtil.timeToString((System.currentTimeMillis() / 1000L) - destroyed) : "Unknown";

            // Send the exact time if known
            lwc.sendLocale(sender, "lwc.history.details.destroyed", "date", absoluteDestroyDate);

            // If there's a relative date available, display it :D
            if (destroyed > 0) {
                lwc.sendLocale(sender, "lwc.history.details.timeago", "time", relativeDestroyDate);
            }
        }
    }

    /**
     * Called when /lwc details or /lwc d is used.
     *
     * @param event
     */
    private void doDetailsCommand(LWCCommandEvent event) {
        LWC lwc = event.getLWC();
        CommandSender sender = event.getSender();
        String[] args = event.getArgs();
        event.setCancelled(true);

        // We MUST have an argument ..!
        if (args.length < 1) {
            lwc.sendSimpleUsage(sender, "/lwc details <HistoryId>");
            return;
        }

        // Load it ..
        int historyId;

        try {
            historyId = Integer.parseInt(args[0]);
        } catch (NumberFormatException e) {
            lwc.sendLocale(sender, "lwc.noresults");
            return;
        }

        // Try and load the history object
        History history = lwc.getPhysicalDatabase().loadHistory(historyId);

        if (history == null) {
            lwc.sendLocale(sender, "lwc.noresults");
            return;
        }

        // Can they access it?
        if (!lwc.isAdmin(sender)) {
            if (sender instanceof Player) {
                // verify they actually OWN the history object
                if (!history.getPlayer().equalsIgnoreCase(((Player) sender).getName())) {
                    // Make them think no results were found
                    lwc.sendLocale(sender, "lwc.noresults");
                    return;
                }
            }
        }

        // Tell them about it!
        sendDetails(sender, history);
    }

    /**
     * Called when /lwc history or /lwc h is used.
     *
     * @param event
     */
    private void doHistoryCommand(LWCCommandEvent event) {
        LWC lwc = event.getLWC();
        CommandSender sender = event.getSender();
        String[] args = event.getArgs();
        event.setCancelled(true);

        // Some vars we'll use more later on
        boolean isWildcard = false;
        int page = 1;
        int historyCount = 0;
        int pageCount = 0;

        // If it's the console without arguments, lookup for every player
        if (!(sender instanceof Player) && args.length == 0) {
            isWildcard = true;
        }

        // The player name to try to parse
        String playerName = null;

        // check for page number
        if (args.length > 0) {
            try {
                page = Integer.parseInt(args[0]);

                if (args.length > 1) {
                    playerName = args[1];
                }
            } catch (NumberFormatException e) {
                playerName = args[0];

                // look for page number
                if (args.length > 1) {
                    try {
                        page = Integer.parseInt(args[1]);
                    } catch (NumberFormatException ex) {
                    }
                }
            }
        }

        // check for player string
        if (playerName != null) {
            // Are they an admin, to be able to do that?
            if (!lwc.isAdmin(sender)) {
                // Nope, go away!
                lwc.sendLocale(sender, "protection.accessdenied");
                return;
            } else {
                // Using a wildcard?
                if (playerName.equals("*")) {
                    isWildcard = true;
                }
            }
        } else {
            // if they're a player, look themselves up
            if (sender instanceof Player) {
                playerName = ((Player) sender).getName();
            }
        }

        // Check if a player was STILL not found and just apply defaults if so
        if (playerName == null) {
            if (!(sender instanceof Player)) {
                isWildcard = true;
            } else {
                playerName = ((Player) sender).getName();
            }
        }

        // Get the first page
        List<History> relatedHistory;

        if (isWildcard) {
            relatedHistory = lwc.getPhysicalDatabase().loadHistory((page - 1) * ITEMS_PER_PAGE, ITEMS_PER_PAGE);
            historyCount = lwc.getPhysicalDatabase().getHistoryCount();
        } else {
            relatedHistory = lwc.getPhysicalDatabase().loadHistory(playerName, (page - 1) * ITEMS_PER_PAGE, ITEMS_PER_PAGE);
            historyCount = lwc.getPhysicalDatabase().getHistoryCount(playerName);
        }

        // Calculate page count
        if (historyCount > 0) {
            pageCount = (int) Math.floor(historyCount / (page * ITEMS_PER_PAGE));

            // compensate for what's left
            while ((pageCount * ITEMS_PER_PAGE) < historyCount) {
                pageCount++;
            }
        }

        // Were there any usable results?
        if (relatedHistory.size() == 0) {
            lwc.sendLocale(sender, "lwc.noresults");
            return;
        }

        // Is it a valid page? (this normally will NOT happen, with the previous statement in place!)
        if (page > pageCount) {
            lwc.sendLocale(sender, "lwc.noresults");
            return;
        }

        // Send it to them
        sendHistoryList(sender, relatedHistory, page, pageCount, historyCount);
    }

    @Override
    public void onCommand(LWCCommandEvent event) {
        if (event.isCancelled()) {
            return;
        }

        if (event.hasFlag("h", "history")) {
            doHistoryCommand(event);
        } else if (event.hasFlag("d", "details")) {
            doDetailsCommand(event);
        }
    }

}


<|start_filename|>src/main/java/com/griefcraft/listeners/LWCPlayerListener.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.listeners;

import com.griefcraft.bukkit.EntityBlock;
import com.griefcraft.cache.BlockCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.lwc.LWCPlugin;
import com.griefcraft.model.Flag;
import com.griefcraft.model.LWCPlayer;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.Module;
import com.griefcraft.scripting.event.LWCBlockInteractEvent;
import com.griefcraft.scripting.event.LWCDropItemEvent;
import com.griefcraft.scripting.event.LWCEntityInteractEvent;
import com.griefcraft.scripting.event.LWCProtectionDestroyEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEntityEvent;
import com.griefcraft.scripting.event.LWCProtectionInteractEvent;
import com.griefcraft.util.UUIDRegistry;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Container;
import org.bukkit.block.DoubleChest;
import org.bukkit.block.Hopper;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.minecart.HopperMinecart;
import org.bukkit.entity.minecart.StorageMinecart;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.hanging.HangingBreakByEntityEvent;
import org.bukkit.event.hanging.HangingBreakEvent;
import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.vehicle.VehicleDestroyEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;

import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LWCPlayerListener implements Listener {

    /**
     * The plugin instance
     */
    private static LWCPlugin plugin;

    public LWCPlayerListener(LWCPlugin plugin) {
        LWCPlayerListener.plugin = plugin;
    }

    @EventHandler
    public void hangingBreakByEvent(HangingBreakByEntityEvent event) {
        Entity entity = event.getEntity();
        if (plugin.getLWC().isProtectable(event.getEntity().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();
            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            if (event.getRemover() instanceof Projectile && protection != null) {
                event.setCancelled(true);
            }
            if (event.getRemover() instanceof Player) {
                Player p = (Player) event.getRemover();
                if (p.hasPermission("lwc.lockentity." + entity.getType()) || p.hasPermission("lwc.lockentity.all")) {
                    if (onPlayerEntityInteract(p, entity, event.isCancelled())) {
                        event.setCancelled(true);
                    }
                }
                if (!event.isCancelled() && protection != null) {
                    boolean canAccess = lwc.canAccessProtection(p, protection);
                    if (canAccess) {
                        protection.remove();
                        protection.removeAllPermissions();
                        protection.removeCache();
                        return;
                    }
                    event.setCancelled(true);
                }
            }
        }
    }

    @EventHandler
    public void minecartBreak(VehicleDestroyEvent e) {
        Entity entity = e.getVehicle();
        if (plugin.getLWC().isProtectable(e.getVehicle().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();
            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            if ((((entity instanceof StorageMinecart)) || ((entity instanceof HopperMinecart)))
                    && (protection != null)) {
                if (e.getAttacker() instanceof Projectile) {
                    e.setCancelled(true);
                }
                Player p = (Player) e.getAttacker();
                boolean canAccess = lwc.canAccessProtection(p, protection);
                if (canAccess) {
                    protection.remove();
                    protection.removeAllPermissions();
                    protection.removeCache();
                    return;
                }
                e.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void hangingBreak(HangingBreakEvent event) {
        Entity entity = event.getEntity();
        if (plugin.getLWC().isProtectable(event.getEntity().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();

            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            if (protection != null) {
                if (event.getCause() == RemoveCause.PHYSICS || event.getCause() == RemoveCause.EXPLOSION
                        || event.getCause() == RemoveCause.OBSTRUCTION) {
                    event.setCancelled(true);
                }
            }
        }
    }

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onPlayerArmorStandManipulate(PlayerArmorStandManipulateEvent e) {
        Entity entity = e.getRightClicked();
        if (plugin.getLWC().isProtectable(e.getRightClicked().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();

            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            Player p = e.getPlayer();
            boolean canAccess = lwc.canAccessProtection(p, protection);
            if (onPlayerEntityInteract(p, entity, e.isCancelled())) {
                e.setCancelled(true);
            }
            if (protection != null) {
                if (canAccess)
                    return;
                e.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void onEntityDamage(EntityDamageEvent e) {
        if (e instanceof EntityDamageByEntityEvent
                && !(e.getCause() == DamageCause.BLOCK_EXPLOSION || e.getCause() == DamageCause.ENTITY_EXPLOSION))
            return;
        Entity entity = e.getEntity();
        if (plugin.getLWC().isProtectable(e.getEntity().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();
            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            if (protection != null) {
                if (e.getCause() != DamageCause.CONTACT)
                    e.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void itemFrameItemRemoval(EntityDamageByEntityEvent e) {
        if (e.isCancelled())
            return;
        Entity entity = e.getEntity();
        if (plugin.getLWC().isProtectable(e.getEntity().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();
            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            if (!(entity instanceof Player)) {
                if (e.getDamager() instanceof Projectile) {
                    if (protection != null) {
                        e.setCancelled(true);
                    }
                    if ((((entity instanceof StorageMinecart)) || ((entity instanceof HopperMinecart)))
                            && (protection != null)) {
                        e.setCancelled(true);
                    }
                }
                if (e.getDamager() instanceof Player) {
                    Player p = (Player) e.getDamager();
                    if (protection != null && !lwc.canAccessProtection(p, protection)) {
                        e.setCancelled(true);
                    }
                    if (p.hasPermission("lwc.lockentity." + e.getEntityType())
                            || p.hasPermission("lwc.lockentity.all")) {
                        if (onPlayerEntityInteract(p, entity, e.isCancelled())) {
                            e.setCancelled(true);
                        }
                    }
                    if ((((entity instanceof StorageMinecart)) || ((entity instanceof HopperMinecart)))
                            && (protection != null)) {
                        e.setCancelled(true);
                    }
                }
            }
        }
    }

    @EventHandler
    public void onDeath(EntityDeathEvent e) {
        Entity entity = e.getEntity();
        if (plugin.getLWC().isProtectable(e.getEntity().getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();
            Player player = e.getEntity().getKiller();
            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            if (protection != null) {
                boolean canAccess = lwc.canAccessProtection(player, protection);
                boolean canAdmin = lwc.canAdminProtection(player, protection);
                try {
                    if (player != null) {
                        LWCProtectionDestroyEvent evt = new LWCProtectionDestroyEvent(player, protection,
                                LWCProtectionDestroyEvent.Method.ENTITY_DESTRUCTION, canAccess, canAdmin);
                        lwc.getModuleLoader().dispatchEvent(evt);
                    } else {
                        protection.remove();
                        protection.removeAllPermissions();
                        protection.removeCache();
                    }
                } catch (Exception ex) {
                    if (player != null) {
                        lwc.sendLocale(player, "protection.internalerror", "id", "ENTITY_DEATH");
                    }
                    lwc.sendLocale(Bukkit.getServer().getConsoleSender(), "protection.internalerror", "id",
                            "ENTITY_DEATH");
                    ex.printStackTrace();
                }
            }
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void onEntityInteract(PlayerInteractEntityEvent e) {
        Entity entity = e.getRightClicked();
        if (plugin.getLWC().isProtectable(entity.getType())) {
            int A = 50000 + entity.getUniqueId().hashCode();
            LWC lwc = LWC.getInstance();
            Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
            Player p = e.getPlayer();
            boolean canAccess = lwc.canAccessProtection(p, protection);
            if (entity instanceof Player) {
                return;
            }
            boolean canAdmin = lwc.canAdminProtection(p, protection);
            Set<String> actions = lwc.wrapPlayer(p).getActionNames();
            Module.Result result;
            if (protection != null) {
                LWCProtectionInteractEntityEvent evt = new LWCProtectionInteractEntityEvent(e, protection, actions,
                        canAccess, canAdmin);
                lwc.getModuleLoader().dispatchEvent(evt);

                result = evt.getResult();
            } else {
                LWCEntityInteractEvent evt = new LWCEntityInteractEvent(e, entity, actions);
                lwc.getModuleLoader().dispatchEvent(evt);

                result = evt.getResult();
            }
            if (result == Module.Result.ALLOW) {
                return;
            }
            if (p.hasPermission("lwc.lockentity." + entity.getType()) || p.hasPermission("lwc.lockentity.all")) {
                if (onPlayerEntityInteract(p, entity, e.isCancelled())) {
                    e.setCancelled(true);
                }
            }
            if (protection != null) {
                if (canAccess)
                    return;
                e.setCancelled(true);
            }
        }
    }

    @EventHandler
    public void storageMinecraftInventoryOpen(InventoryOpenEvent event) {
        InventoryHolder holder = event.getInventory().getHolder();
        Player player = (Player) event.getPlayer();
        if ((!(holder instanceof StorageMinecart)) && (!(holder instanceof HopperMinecart))) {
            return;
        }
        Entity entity = (Entity) holder;
        if (plugin.getLWC().isProtectable(entity.getType())) {
            if (!plugin.getLWC().hasPermission(player, "lwc.protect")
                    && plugin.getLWC().hasPermission(player, "lwc.deny") && !plugin.getLWC().isAdmin(player)
                    && !plugin.getLWC().isMod(player)) {
                plugin.getLWC().sendLocale(player, "protection.interact.error.blocked");
                event.setCancelled(true);
                return;
            }
            if (onPlayerEntityInteract((Player) event.getPlayer(), entity, event.isCancelled())) {
                event.setCancelled(true);
            }
        }
    }

    private boolean onPlayerEntityInteract(Player player, Entity entity, boolean cancelled) {
        int A = EntityBlock.POSITION_OFFSET + entity.getUniqueId().hashCode();

        // attempt to load the protection for this cart
        LWC lwc = LWC.getInstance();
        Protection protection = lwc.getPhysicalDatabase().loadProtection(entity.getWorld().getName(), A, A, A);
        LWCPlayer lwcPlayer = lwc.wrapPlayer(player);

        try {
            Set<String> actions = lwcPlayer.getActionNames();
            Module.Result result;
            boolean canAccess = lwc.canAccessProtection(player, protection);

            // Calculate if the player has a pending action (i.e any action
            // besides 'interacted')
            int actionCount = actions.size();
            boolean hasInteracted = actions.contains("interacted");
            boolean hasPendingAction = (hasInteracted && actionCount > 1) || (!hasInteracted && actionCount > 0);

            // If the event was cancelled and they have an action, warn them
            if (cancelled) {
                // only send it if a non-"interacted" action is set which is
                // always set on the player
                if (hasPendingAction) {
                    lwc.sendLocale(player, "lwc.pendingaction");
                }

                // it's cancelled, do not continue !
                return false;
            }

            // register in an action what protection they interacted with (if
            // applicable.)
            if (protection != null) {
                com.griefcraft.model.Action action = new com.griefcraft.model.Action();
                action.setName("interacted");
                action.setPlayer(lwcPlayer);
                action.setProtection(protection);

                lwcPlayer.addAction(action);
            }

            // events are only used when they already have an action pending
            boolean canAdmin = lwc.canAdminProtection(player, protection);
            Block fakeBlock = EntityBlock.getEntityBlock(entity);
            PlayerInteractEvent fakeEvent = new PlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, null, fakeBlock,
                    null);

            if (protection != null) {
                LWCProtectionInteractEvent evt = new LWCProtectionInteractEvent(fakeEvent, protection, actions,
                        canAccess, canAdmin);
                lwc.getModuleLoader().dispatchEvent(evt);

                result = evt.getResult();
            } else {
                LWCBlockInteractEvent evt = new LWCBlockInteractEvent(fakeEvent, fakeBlock, actions);
                lwc.getModuleLoader().dispatchEvent(evt);

                result = evt.getResult();
            }

            if (result == Module.Result.ALLOW) {
                return false;
            }

            // optional.onlyProtectIfOwnerIsOnline
            if (protection != null && !canAccess
                    && lwc.getConfiguration().getBoolean("optional.onlyProtectWhenOwnerIsOnline", false)) {
                Player owner = protection.getBukkitOwner();

                // If they aren't online, allow them in :P
                if (owner == null || !owner.isOnline()) {
                    return false;
                }
            }

            // optional.onlyProtectIfOwnerIsOffline
            if (protection != null && !canAccess
                    && lwc.getConfiguration().getBoolean("optional.onlyProtectWhenOwnerIsOffline", false)) {
                Player owner = protection.getBukkitOwner();

                // If they aren't online, allow them in :P
                if (owner != null && owner.isOnline()) {
                    return false;
                }
            }

            if (result == Module.Result.DEFAULT) {
                canAccess = lwc.enforceAccess(player, protection, entity, canAccess);
            }

            if (!canAccess || result == Module.Result.CANCEL) {
                return true;
            }
        } catch (Exception e) {
            lwc.sendLocale(player, "protection.internalerror", "id", "PLAYER_INTERACT");
            e.printStackTrace();
            return true;
        }
        return false;
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void onPlayerJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        UUIDRegistry.updateCache(player.getUniqueId(), player.getName());
    }

    @EventHandler(ignoreCancelled = true)
    public void onMoveItem(InventoryMoveItemEvent event) {
        boolean result;

        // if the initiator is the same as the source it is a dropper i.e.
        // depositing items
        if (event.getInitiator() == event.getSource()) {
            result = handleMoveItemEvent(event.getInitiator(), event.getDestination());
        } else {
            result = handleMoveItemEvent(event.getInitiator(), event.getSource());
        }

        if (result) {
            event.setCancelled(true);
        }
    }

    /**
     * Handle the item move event
     *
     * @param inventory
     */
    private boolean handleMoveItemEvent(Inventory initiator, Inventory inventory) {
        LWC lwc = LWC.getInstance();

        if (inventory == null) {
            return false;
        }

        Location location;
        InventoryHolder holder;
        Location hopperLocation = null;
        InventoryHolder hopperHolder;

        try {
            holder = inventory.getHolder();
            hopperHolder = initiator.getHolder();
        } catch (AbstractMethodError e) {
            return false;
        }

        try {
            if (holder instanceof BlockState) {
                location = ((BlockState) holder).getLocation();
            } else if (holder instanceof DoubleChest) {
                location = ((DoubleChest) holder).getLocation();
            } else {
                return false;
            }

            if (hopperHolder instanceof Hopper) {
                hopperLocation = ((Hopper) hopperHolder).getLocation();
            } else if (hopperHolder instanceof HopperMinecart) {
                hopperLocation = ((HopperMinecart) hopperHolder).getLocation();
            }
        } catch (Exception e) {
            return false;
        }

        // High-intensity zone: increase protection cache if it's full,
        // otherwise
        // the database will be getting rammed
        lwc.getProtectionCache().increaseIfNecessary();

        // Attempt to load the protection at that location
        Protection protection = lwc.findProtection(location);

        // If no protection was found we can safely ignore it
        if (protection == null) {
            return false;
        }

        if (hopperLocation != null
                && Boolean.parseBoolean(lwc.resolveProtectionConfiguration(Material.HOPPER, "enabled"))) {
            Protection hopperProtection = lwc.findProtection(hopperLocation);

            if (hopperProtection != null) {
                // if they're owned by the same person then we can allow the
                // move
                if (protection.getOwner().equals(hopperProtection.getOwner())) {
                    return false;
                }
            }
        }

        BlockCache blockCache = BlockCache.getInstance();
        boolean denyHoppers = Boolean.parseBoolean(
                lwc.resolveProtectionConfiguration(blockCache.getBlockType(protection.getBlockId()), "denyHoppers"));

        // xor = (a && !b) || (!a && b)
        if (denyHoppers ^ protection.hasFlag(Flag.Type.HOPPER)) {
            return true;
        }

        return false;
    }

    @EventHandler(priority = EventPriority.HIGH)
    public void onPlayerDropItem(PlayerDropItemEvent event) {
        if (event.isCancelled() || !LWC.ENABLED) {
            return;
        }

        Player player = event.getPlayer();

        LWCDropItemEvent evt = new LWCDropItemEvent(player, event);
        plugin.getLWC().getModuleLoader().dispatchEvent(evt);

        if (evt.isCancelled()) {
            event.setCancelled(true);
        }
    }

    /*
     * @EventHandler public void onPlayerChat(PlayerChatEvent event) { if
     * (event.isCancelled() || !LWC.ENABLED) { return; }
     *
     * LWC lwc = plugin.getLWC(); if
     * (!lwc.getConfiguration().getBoolean("core.filterunlock", true)) { return; }
     *
     * // We want to block messages starting with cunlock incase someone screws up
     * /cunlock password. String message = event.getMessage();
     *
     * if (message.startsWith("cunlock") || message.startsWith("lcunlock") ||
     * message.startsWith(".cunlock")) { event.setCancelled(true);
     * lwc.sendLocale(event.getPlayer(), "lwc.blockedmessage"); } }
     */

    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
        LWC lwc = plugin.getLWC();
        Player player = event.getPlayer();
        LWCPlayer lwcPlayer = lwc.wrapPlayer(player);

        if (event.getAction() != Action.LEFT_CLICK_BLOCK && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
            return;
        }

        // Only accept interaction from offhand in exceptional circumstances (when fired somehow for a container)
        if (event.getHand() == EquipmentSlot.OFF_HAND && !(event.getClickedBlock() instanceof Container)) {
            return;
        }

        Block block = event.getClickedBlock();
        BlockState state;
        try {
            state = block.getState();
        } catch (NullPointerException e) {
            return;
        }

        // Prevent players with lwc.deny from interacting with blocks that have
        // an inventory
        if (state instanceof InventoryHolder && lwc.isProtectable(block)) {
            if (!lwc.hasPermission(player, "lwc.protect") && lwc.hasPermission(player, "lwc.deny")
                    && !lwc.isAdmin(player) && !lwc.isMod(player)) {
                lwc.sendLocale(player, "protection.interact.error.blocked");
                event.setCancelled(true);
                return;
            }
        }

        try {
            Set<String> actions = lwcPlayer.getActionNames();
            Protection protection = lwc.findProtection(block.getLocation());
            Module.Result result;
            boolean canAccess = lwc.canAccessProtection(player, protection);

            // Calculate if the player has a pending action (i.e any action
            // besides 'interacted')
            int actionCount = actions.size();
            boolean hasInteracted = actions.contains("interacted");
            boolean hasPendingAction = (hasInteracted && actionCount > 1) || (!hasInteracted && actionCount > 0);

            if (event.getAction() == Action.LEFT_CLICK_BLOCK) {
                boolean ignoreLeftClick = Boolean
                        .parseBoolean(lwc.resolveProtectionConfiguration(block, "ignoreLeftClick"));

                if (ignoreLeftClick) {
                    return;
                }
            } else if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
                boolean ignoreRightClick = Boolean
                        .parseBoolean(lwc.resolveProtectionConfiguration(block, "ignoreRightClick"));

                if (ignoreRightClick) {
                    return;
                }
            }

            // If the event was cancelled and they have an action, warn them
            if (event.isCancelled()) {
                // only send it if a non-"interacted" action is set which is
                // always set on the player
                if (hasPendingAction) {
                    lwc.sendLocale(player, "lwc.pendingaction");
                }

                // it's cancelled, do not continue !
                return;
            }

            // register in an action what protection they interacted with (if
            // applicable.)
            if (protection != null) {
                com.griefcraft.model.Action action = new com.griefcraft.model.Action();
                action.setName("interacted");
                action.setPlayer(lwcPlayer);
                action.setProtection(protection);

                lwcPlayer.addAction(action);
            }

            // events are only used when they already have an action pending
            boolean canAdmin = lwc.canAdminProtection(player, protection);

            if (protection != null) {
                LWCProtectionInteractEvent evt = new LWCProtectionInteractEvent(event, protection, actions, canAccess,
                        canAdmin);
                lwc.getModuleLoader().dispatchEvent(evt);

                result = evt.getResult();
            } else {
                LWCBlockInteractEvent evt = new LWCBlockInteractEvent(event, block, actions);
                lwc.getModuleLoader().dispatchEvent(evt);

                result = evt.getResult();
            }

            if (result == Module.Result.ALLOW) {
                return;
            }

            // optional.onlyProtectIfOwnerIsOnline
            if (protection != null && !canAccess
                    && lwc.getConfiguration().getBoolean("optional.onlyProtectWhenOwnerIsOnline", false)) {
                Player owner = protection.getBukkitOwner();

                // If they aren't online, allow them in :P
                if (owner == null || !owner.isOnline()) {
                    return;
                }
            }

            // optional.onlyProtectIfOwnerIsOffline
            if (protection != null && !canAccess
                    && lwc.getConfiguration().getBoolean("optional.onlyProtectWhenOwnerIsOffline", false)) {
                Player owner = protection.getBukkitOwner();

                // If they aren't online, allow them in :P
                if (owner != null && owner.isOnline()) {
                    return;
                }
            }

            if (result == Module.Result.DEFAULT) {
                canAccess = lwc.enforceAccess(player, protection, block, canAccess);
            }

            if (!canAccess || result == Module.Result.CANCEL) {
                event.setCancelled(true);
                event.setUseInteractedBlock(org.bukkit.event.Event.Result.DENY);
            }
        } catch (Exception e) {
            event.setCancelled(true);
            event.setUseInteractedBlock(org.bukkit.event.Event.Result.DENY);
            lwc.sendLocale(player, "protection.internalerror", "id", "PLAYER_INTERACT");
            e.printStackTrace();
        }
    }

    @EventHandler(priority = EventPriority.MONITOR)
    public void onPlayerQuit(PlayerQuitEvent event) {
        if (!LWC.ENABLED) {
            return;
        }

        // remove the place from the player cache and reset anything they can
        // access
        LWCPlayer.removePlayer(event.getPlayer());
    }

    @EventHandler(ignoreCancelled = true)
    public void onInventoryClick(InventoryClickEvent event) {
        LWC lwc = LWC.getInstance();

        if (!(event.getWhoClicked() instanceof Player)) {
            return;
        }

        // Player interacting with the inventory
        Player player = (Player) event.getWhoClicked();

        // The inventory they are using
        Inventory inventory = event.getInventory();

        if (inventory == null || event.getSlot() < 0) {
            return;
        }

        // Location of the container
        Location location;
        InventoryHolder holder = null;

        try {
            holder = event.getInventory().getHolder();
        } catch (AbstractMethodError e) {
            e.printStackTrace();
            return;
        }

        try {
            if (holder instanceof BlockState) {
                location = ((BlockState) holder).getLocation();
            } else if (holder instanceof DoubleChest) {
                location = ((DoubleChest) holder).getLocation();
            } else {
                return;
            }
        } catch (Exception e) {
            Location ploc = player.getLocation();
            String holderName = holder.getClass().getSimpleName();
            e.printStackTrace();
            return;
        }

        if (event.getAction() != InventoryAction.COLLECT_TO_CURSOR) {
            // If it's not a container, we don't want it
            if (event.getSlotType() != InventoryType.SlotType.CONTAINER) {
                return;
            }

            // Nifty trick: these will different IFF they are interacting with
            // the player's inventory or hotbar instead of the block's inventory
            if (event.getSlot() != event.getRawSlot()) {
                return;
            }

            // The item they are taking/swapping with
            ItemStack item;

            try {
                item = event.getCurrentItem();
            } catch (ArrayIndexOutOfBoundsException e) {
                return;
            }

            // Item their cursor has
            ItemStack cursor = event.getCursor();

            if (item == null || item.getType() == null || item.getType() == Material.AIR) {
                return;
            }

            // if it's not a right click or a shift click it should be a left
            // click (no shift)
            // this is for when players are INSERTing items (i.e. item in hand
            // and left clicking)
            if (player.getInventory().getItemInMainHand() == null && (!event.isRightClick() && !event.isShiftClick())) {
                return;
            }
        }

        // Attempt to load the protection at that location
        Protection protection = lwc.findProtection(location);

        // If no protection was found we can safely ignore it
        if (protection == null) {
            return;
        }

        // If it's not a donation chest, ignore if
        if (protection.getType() != Protection.Type.DONATION) {
            return;
        }

        // Can they admin it? (remove items/etc)
        boolean canAdmin = lwc.canAdminProtection(player, protection);

        // nope.avi
        if (!canAdmin) {
            event.setCancelled(true);
        }
    }

    /**
     * Compares the enchantments on two item stacks and checks that they are equal
     * (identical)
     *
     * @param stack1
     * @param stack2
     * @return
     */
    private boolean areEnchantmentsEqual(ItemStack stack1, ItemStack stack2) {
        if (stack1 == null || stack2 == null) {
            return false;
        }

        Map<Enchantment, Integer> enchantments1 = stack1.getEnchantments();
        Map<Enchantment, Integer> enchantments2 = stack2.getEnchantments();

        if (enchantments1.size() != enchantments2.size()) {
            return false;
        }

        // Enchanted Books use ItemMeta
        if (stack1.getItemMeta() != null && stack2.getItemMeta() != null) {
            if (!stack1.getItemMeta().equals(stack2.getItemMeta())) {
                return false;
            }
        }

        for (Enchantment enchantment : enchantments1.keySet()) {
            if (!enchantments2.containsKey(enchantment)) {
                return false;
            }

            int level1 = enchantments1.get(enchantment);
            int level2 = enchantments2.get(enchantment);

            if (level1 != level2) {
                return false;
            }
        }

        return true;
    }

    public static String getMinecraftVersion() {
        Matcher matcher = Pattern.compile("(\\(MC: )([\\d\\.]+)(\\))").matcher(Bukkit.getVersion());
        if (matcher.find()) {
            return matcher.group(2);
        }
        return null;
    }

}


<|start_filename|>src/main/java/com/griefcraft/modules/limits/LimitsV2.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.modules.limits;

import com.griefcraft.cache.BlockCache;
import com.griefcraft.lwc.LWC;
import com.griefcraft.model.Protection;
import com.griefcraft.scripting.JavaModule;
import com.griefcraft.scripting.event.LWCCommandEvent;
import com.griefcraft.scripting.event.LWCProtectionRegisterEvent;
import com.griefcraft.scripting.event.LWCReloadEvent;
import com.griefcraft.util.Colors;
import com.griefcraft.util.VersionUtil;
import com.griefcraft.util.config.Configuration;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachmentInfo;

import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class LimitsV2 extends JavaModule {

    /**
     * The limit represented by unlimited
     */
    public final static int UNLIMITED = Integer.MAX_VALUE;

    /**
     * If the limits module is enabled
     */
    private boolean enabled = true;

    /**
     * The limits configuration
     */
    private final Configuration configuration = Configuration.load("limitsv2.yml");

    /**
     * A map of the default limits
     */
    private final List<Limit> defaultLimits = new LinkedList<>();

    /**
     * A map of all of the player limits
     */
    private final Map<String, List<Limit>> playerLimits = new HashMap<>();

    /**
     * A map of all of the group limits - downcasted to lowercase to simplify
     * comparisons
     */
    private final Map<String, List<Limit>> groupLimits = new HashMap<>();

    /**
     * A map mapping string representations of materials to their Material
     * counterpart
     */
    private final Map<String, Material> materialCache = new HashMap<>();

    /**
     * Set of materials for the various sign blocks used in the sign limit
     */
    private static final Set<Material> SIGNS;

    static {
        if (VersionUtil.getMinorVersion() > 13) {
            SIGNS = EnumSet.of(Material.OAK_WALL_SIGN, Material.BIRCH_WALL_SIGN,
                    Material.SPRUCE_WALL_SIGN, Material.JUNGLE_WALL_SIGN, Material.ACACIA_WALL_SIGN,
                    Material.DARK_OAK_WALL_SIGN, Material.OAK_SIGN, Material.BIRCH_SIGN, Material.SPRUCE_SIGN,
                    Material.JUNGLE_SIGN, Material.ACACIA_SIGN, Material.DARK_OAK_SIGN);
        } else {
            SIGNS = EnumSet.of(Objects.requireNonNull(Material.getMaterial("SIGN")), Objects.requireNonNull(Material.getMaterial("WALL_SIGN")));
        }
    }

    {
        for (Material material : Material.values()) {
            String materialName = LWC.normalizeMaterialName(material);

            // add the name & the block id
            materialCache.put(materialName, material);
//			materialCache.put(material.getId() + "", material);

            if (!materialName.equals(material.toString().toLowerCase())) {
                materialCache.put(material.toString().toLowerCase(), material);
            }
        }
    }

    public abstract class Limit {

        /**
         * The limit
         */
        private final int limit;

        public Limit(int limit) {
            this.limit = limit;
        }

        /**
         * Get the player's protection count that should be used with this limit
         *
         * @param player
         * @param material
         * @return
         */
        public abstract int getProtectionCount(Player player, Material material);

        /**
         * @return
         */
        public int getLimit() {
            return limit;
        }
    }

    public final class DefaultLimit extends Limit {

        public DefaultLimit(int limit) {
            super(limit);
        }

        @Override
        public int getProtectionCount(Player player, Material material) {
            return LWC.getInstance().getPhysicalDatabase().getProtectionCount(player.getName());
        }

    }

    public final class BlockLimit extends Limit {

        /**
         * The block material to limit
         */
        private final Material material;

        public BlockLimit(Material material, int limit) {
            super(limit);
            this.material = material;
        }

        @Override
        public int getProtectionCount(Player player, Material material) {
            BlockCache blockCache = BlockCache.getInstance();
            return LWC.getInstance().getPhysicalDatabase().getProtectionCount(player.getName(), blockCache.getBlockId(material));
        }

        /**
         * @return
         */
        public Material getMaterial() {
            return material;
        }

    }

    public final class EntityLimit extends Limit {

        /**
         * The block material to limit
         */

        public EntityLimit(EntityType material, int limit) {
            super(limit);
        }

        @Override
        public int getProtectionCount(Player player, Material material) {
            int i = 0;
            for (World world : Bukkit.getWorlds()) {
                for (Entity ent : world.getEntities()) {
                    int A = 50000 + ent.getUniqueId().hashCode();
                    Protection prot = LWC.getInstance().getPhysicalDatabase().loadProtection(ent.getWorld().getName(),
                            A, A, A);
                    if (prot.getBukkitOwner() == player) {
                        i++;
                    }
                }
            }
            return i;
        }
    }

    public final class SignLimit extends Limit {

        public SignLimit(int limit) {
            super(limit);
        }

        @Override
        public int getProtectionCount(Player player, Material material) {
            LWC lwc = LWC.getInstance();
            BlockCache blockCache = BlockCache.getInstance();
            int signCount = 0;
            for (Material sign : SIGNS) {
                signCount += lwc.getPhysicalDatabase().getProtectionCount(player.getName(), blockCache.getBlockId(sign));
            }
            return signCount;
        }

    }

    public LimitsV2() {
        enabled = LWC.getInstance().getConfiguration().getBoolean("optional.useProtectionLimits", true);

        if (enabled) {
            loadLimits();
        }
    }

    @Override
    public void onReload(LWCReloadEvent event) {
        if (enabled) {
            reload();
        }
    }

    @Override
    public void onRegisterProtection(LWCProtectionRegisterEvent event) {
        if (!enabled || event.isCancelled()) {
            return;
        }

        LWC lwc = event.getLWC();
        Player player = event.getPlayer();
        Block block = event.getBlock();

        if (hasReachedLimit(player, block.getType())) {
            lwc.sendLocale(player, "protection.exceeded");
            event.setCancelled(true);
        }
    }

    @Override
    public void onCommand(LWCCommandEvent event) {
        if (!enabled || event.isCancelled()) {
            return;
        }

        if (!event.hasFlag("limits")) {
            return;
        }

        LWC lwc = event.getLWC();
        CommandSender sender = event.getSender();
        String[] args = event.getArgs();
        event.setCancelled(true);

        String playerName;

        if (args.length == 0) {
            if (args.length == 0 && !(sender instanceof Player)) {
                sender.sendMessage(Colors.Dark_Red + "You are not a player!");
                return;
            }

            playerName = sender.getName();
        } else {
            if (lwc.isAdmin(sender)) {
                playerName = args[0];
            } else {
                lwc.sendLocale(sender, "protection.accessdenied");
                return;
            }
        }

        @SuppressWarnings("deprecation")
        Player player = lwc.getPlugin().getServer().getPlayer(playerName);

        if (player == null) {
            sender.sendMessage(Colors.Dark_Red + "That player is not online!");
            return;
        }

        // send their limits to them
        sendLimits(sender, player, getPlayerLimits(player));
    }

    /**
     * Gets the raw limits configuration
     *
     * @return
     */
    public Configuration getConfiguration() {
        return configuration;
    }

    /**
     * Reload the limits
     */
    public void reload() {
        loadLimits();
    }

    /**
     * Sends the list of limits to the player
     *
     * @param sender the commandsender to send the limits to
     * @param target the player limits are being shown for, can be null
     * @param limits
     */
    public void sendLimits(CommandSender sender, Player target, List<Limit> limits) {
        for (Limit limit : limits) {
            if (limit == null) {
                continue;
            }

            String stringLimit = limit.getLimit() == UNLIMITED ? "Unlimited" : Integer.toString(limit.getLimit());
            String colour = Colors.Yellow;

            if (target != null) {
                Material material = null;

                if (limit instanceof BlockLimit) {
                    material = ((BlockLimit) limit).getMaterial();
                } else if (limit instanceof SignLimit) {
                    material = Material.OAK_SIGN;
                } else if (limit instanceof EntityLimit) {
                    material = Material.AIR;
                }

                boolean reachedLimit = hasReachedLimit(target, material);

                colour = reachedLimit ? Colors.Dark_Red : Colors.Dark_Green;
            }

            if (limit instanceof DefaultLimit) {
                String currentProtected = target != null
                        ? (Integer.toString(limit.getProtectionCount(target, null)) + "/")
                        : "";
                sender.sendMessage("Default: " + colour + currentProtected + stringLimit);
            } else if (limit instanceof BlockLimit) {
                BlockLimit blockLimit = (BlockLimit) limit;
                String currentProtected = target != null
                        ? (Integer.toString(limit.getProtectionCount(target, blockLimit.getMaterial())) + "/")
                        : "";
                sender.sendMessage(LWC.materialToString(blockLimit.getMaterial()) + ": " + colour + currentProtected
                        + stringLimit);
            } else if (limit instanceof SignLimit) {
                String currentProtected = target != null
                        ? (Integer.toString(limit.getProtectionCount(target, null)) + "/")
                        : "";
                sender.sendMessage("Sign: " + colour + currentProtected + stringLimit);
            } else if (limit instanceof EntityLimit) {
                String currentProtected = target != null
                        ? (Integer.toString(limit.getProtectionCount(target, Material.AIR)) + "/")
                        : "";
                sender.sendMessage(((EntityLimit) limit).getClass().getSimpleName() + ": " + colour + currentProtected
                        + stringLimit);
            } else {
                sender.sendMessage(limit.getClass().getSimpleName() + ": " + Colors.Yellow + stringLimit);
            }

        }
    }

    /**
     * Checks if a player has reached their protection limit (both for the block limit, and default global limit)
     *
     * @param player
     * @param material the material type the player has interacted with
     * @return
     */
    public boolean hasReachedLimit(Player player, Material material) {
        Limit limit = getEffectiveLimit(player, material);
        Limit defaultLimit = getEffectiveLimit(player, null);

        // if they don't have a limit it's not possible to reach it ^^
        // ... but if it's null, what the hell did the server owner do?
        if (limit == null || defaultLimit == null) {
            return false;
        }

        // Get the effective limit placed upon them
        int maxProtections = limit.getLimit();
        int maxProtectionsDefault = defaultLimit.getLimit();

        // get the amount of protections the player has
        int protections = limit.getProtectionCount(player, material);
        int protectionsDefault = defaultLimit.getProtectionCount(player, material);

        return protections >= maxProtections || protectionsDefault >= maxProtectionsDefault;
    }

    /**
     * Find limits that are attached to the player via permissions (e.g
     * lwc.protect.*.10 = 10 of any protection)
     *
     * @param player
     * @return
     */
    private List<Limit> findLimitsViaPermissions(Player player) {
        List<Limit> limits = new LinkedList<>();

        for (PermissionAttachmentInfo pai : player.getEffectivePermissions()) {
            String permission = pai.getPermission();
            boolean value = pai.getValue();

            if (!value || !permission.startsWith("lwc.protect.")) {
                continue;
            }

            String[] split = permission.substring("lwc.protect.".length()).split(".");

            if (split.length != 2) {
                continue;
            }

            String matchName = split[0];
            String strCount = split[1];

            int count;
            try {
                count = Integer.parseInt(strCount);
            } catch (NumberFormatException e) {
                continue;
            }

            if (matchName.equals("*")) {
                limits.add(new DefaultLimit(count));
            } else if (matchName.equals("sign")) {
                limits.add(new SignLimit(count));
            } else if (!matchName.equals("*") && !matchName.equals("sign")) {
                Material material = materialCache.get(matchName);

                if (material == null) {
                    continue;
                }

                limits.add(new BlockLimit(material, count));
            } else if (EntityType.valueOf(matchName.toUpperCase()) != null) {
                limits.add(new EntityLimit(EntityType.valueOf(matchName.toUpperCase()), count));
            }
        }

        return limits;
    }

    /**
     * Gets the list of limits that may apply to the player. For group limits, it
     * uses the highest one found.
     *
     * @param player
     * @return
     */
    public List<Limit> getPlayerLimits(Player player) {
        LWC lwc = LWC.getInstance();
        List<Limit> limits = new LinkedList<>();

        // get all of their own limits
        String playerName = player.getName().toLowerCase();
        if (playerLimits.containsKey(playerName)) {
            limits.addAll(playerLimits.get(playerName));
        }

        for (Limit limit : findLimitsViaPermissions(player)) {
            Limit matched = findLimit(limits, limit);

            if (matched != null) {
                // Is our limit better?
                if (limit.getLimit() > matched.getLimit()) {
                    limits.remove(matched);
                    limits.add(limit);
                }
            } else {
                limits.add(limit);
            }
        }

        // Look over the group limits
        for (String group : lwc.getPermissions().getGroups(player)) {
            if (groupLimits.containsKey(group.toLowerCase())) {
                for (Limit limit : groupLimits.get(group.toLowerCase())) {
                    // try to match one already inside what we found
                    Limit matched = findLimit(limits, limit);

                    if (matched != null) {
                        // Is our limit better?
                        if (limit.getLimit() > matched.getLimit()) {
                            limits.remove(matched);
                            limits.add(limit);
                        }
                    } else {
                        limits.add(limit);
                    }
                }
            }
        }

        // Look at the default limits
        for (Limit limit : defaultLimits) {
            // try to match one already inside what we found
            Limit matched = findLimit(limits, limit);

            if (matched == null) {
                limits.add(limit);
            }
        }

        return limits;
    }

    /**
     * Get the player's effective limit that should take precedence
     *
     * @param player
     * @param material
     * @return
     */
    public Limit getEffectiveLimit(Player player, Material material) {
        return getEffectiveLimit(getPlayerLimits(player), material);
    }

    /**
     * Gets an immutable list of the default limits
     *
     * @return
     */
    public List<Limit> getDefaultLimits() {
        return Collections.unmodifiableList(defaultLimits);
    }

    /**
     * Gets an unmodiable map of the player limits
     *
     * @return
     */
    public Map<String, List<Limit>> getPlayerLimits() {
        return Collections.unmodifiableMap(playerLimits);
    }

    /**
     * Gets an unmodiable map of the group limits
     *
     * @return
     */
    public Map<String, List<Limit>> getGroupLimits() {
        return Collections.unmodifiableMap(groupLimits);
    }

    /**
     * Orders the limits, putting the default limit at the top of the list
     *
     * @param limits
     * @return
     */
    private List<Limit> orderLimits(List<Limit> limits) {
        Limit defaultLimit = null;

        // Locate the default limit
        for (Limit limit : limits) {
            if (limit instanceof DefaultLimit) {
                defaultLimit = limit;
                break;
            }
        }

        // remove it
        limits.remove(defaultLimit);

        // readd it at the head
        limits.add(0, defaultLimit);

        return limits;
    }

    /**
     * Gets the material's effective limit that should take precedence
     *
     * @param limits
     * @param material
     * @return Limit object if one is found otherwise NULL
     */
    private Limit getEffectiveLimit(List<Limit> limits, Material material) {
        if (limits == null) {
            return null;
        }

        // Temporary storage to use if the default is found so we save time if no
        // override was found
        Limit defaultLimit = null;

        for (Limit limit : limits) {
            // Record the default limit if found
            if (limit instanceof DefaultLimit) {
                if (material == null) {
                    return limit;
                }
                defaultLimit = limit;
            } else if (limit instanceof SignLimit) {
                if (SIGNS.contains(material)) {
                    return limit;
                }
            } else if (limit instanceof BlockLimit) {
                BlockLimit blockLimit = (BlockLimit) limit;

                // If it's the appropriate material we can return immediately
                if (blockLimit.getMaterial() == material) {
                    return blockLimit;
                }
            } else if (limit instanceof EntityLimit) {
                if (material == Material.AIR) {
                    return limit;
                }
            }
        }

        return defaultLimit;
    }

    /**
     * Load all of the limits
     */
    private void loadLimits() {
        // make sure we're working on a clean slate
        defaultLimits.clear();
        playerLimits.clear();
        groupLimits.clear();

        // add the default limits
        defaultLimits.addAll(findLimits("defaults"));

        // add all of the player limits
        try {
            for (String player : configuration.getKeys("players")) {
                playerLimits.put(player.toLowerCase(), findLimits("players." + player));
            }
        } catch (NullPointerException e) {
        }

        // add all of the group limits
        try {
            for (String group : configuration.getKeys("groups")) {
                groupLimits.put(group.toLowerCase(), findLimits("groups." + group));
            }
        } catch (NullPointerException e) {
        }
    }

    /**
     * Find and match all of the limits in a given list of nodes for the config
     *
     * @param node
     * @return
     */
    private List<Limit> findLimits(String node) {
        List<Limit> limits = new LinkedList<>();
        List<String> keys = configuration.getKeys(node);

        for (String key : keys) {
            String value = configuration.getString(node + "." + key);

            int limit;

            if (value.equalsIgnoreCase("unlimited")) {
                limit = UNLIMITED;
            } else {
                limit = Integer.parseInt(value);
            }

            // Match default
            if (key.equalsIgnoreCase("default")) {
                limits.add(new DefaultLimit(limit));
            } else if (key.equalsIgnoreCase("sign")) {
                limits.add(new SignLimit(limit));
            } else if (!key.equalsIgnoreCase("default") && !key.equalsIgnoreCase("sign")) {
                // attempt to resolve it as a block id
                int blockId = -1;
                try {
                    blockId = Integer.parseInt(key);
                } catch (NumberFormatException e) {
                }

                // resolve the material
                Material material;

                BlockCache blockCache = BlockCache.getInstance();
                if (blockId >= 0) {
                    material = blockCache.getBlockType(blockId);
                } else {
                    material = Material.getMaterial(key.toUpperCase());
                }

                if (material != null) {
                    limits.add(new BlockLimit(material, limit));
                }
            } else if (EntityType.valueOf(key.toUpperCase()) != null) {
                limits.add(new EntityLimit(EntityType.valueOf(key.toUpperCase()), limit));
            }
        }

        // Order it
        orderLimits(limits);

        return limits;
    }

    /**
     * Find a limit in the list of limits that equals the given limit in class; The
     * LIMIT itself does not need to be equal; only the class & if it's a
     * BlockLimit, the material must equal
     *
     * @param limits
     * @param compare
     * @return
     */
    private Limit findLimit(List<Limit> limits, Limit compare) {
        for (Limit limit : limits) {
            if (limit != null && limit.getClass().isInstance(compare)) {
                if (limit instanceof BlockLimit) {
                    BlockLimit cmp1 = (BlockLimit) limit;
                    BlockLimit cmp2 = (BlockLimit) compare;

                    if (cmp1.getMaterial() == cmp2.getMaterial()) {
                        return limit;
                    }
                } else {
                    return limit;
                }
            }
        }

        return null;
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/matchers/WallMatcher.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util.matchers;

import com.griefcraft.util.ProtectionFinder;
import com.griefcraft.util.VersionUtil;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;

import java.util.EnumSet;
import java.util.Set;

/**
 * Matches wall entities TODO fix buttons and levers
 */
public class WallMatcher implements ProtectionFinder.Matcher {

    /**
     * Blocks that can be attached to the wall and be protected. This assumes that
     * the block is DESTROYED if the wall they are attached to is broken.
     */
    public static final Set<Material> PROTECTABLES_WALL;

    static {
        PROTECTABLES_WALL = EnumSet.of(Material.WHITE_WALL_BANNER, Material.ORANGE_WALL_BANNER,
                Material.MAGENTA_WALL_BANNER, Material.LIGHT_BLUE_WALL_BANNER, Material.YELLOW_WALL_BANNER,
                Material.LIME_WALL_BANNER, Material.PINK_WALL_BANNER, Material.GRAY_WALL_BANNER,
                Material.LIGHT_GRAY_WALL_BANNER, Material.CYAN_WALL_BANNER, Material.PURPLE_WALL_BANNER,
                Material.BLUE_WALL_BANNER, Material.BROWN_WALL_BANNER, Material.GREEN_WALL_BANNER,
                Material.RED_WALL_BANNER, Material.BLACK_WALL_BANNER);
        if (VersionUtil.getMinorVersion() > 13) {
            PROTECTABLES_WALL.addAll(EnumSet.of(Material.OAK_WALL_SIGN, Material.BIRCH_WALL_SIGN,
                    Material.SPRUCE_WALL_SIGN, Material.JUNGLE_WALL_SIGN, Material.ACACIA_WALL_SIGN,
                    Material.DARK_OAK_WALL_SIGN));
        } else {
            PROTECTABLES_WALL.add(Material.getMaterial("WALL_SIGN"));
        }
    }

    /**
     * Those evil levers and buttons have all different bits for directions. Gah!
     */
    public static final Set<Material> PROTECTABLES_LEVERS_ET_AL = EnumSet.of(Material.LEVER, Material.OAK_BUTTON,
            Material.BIRCH_BUTTON, Material.SPRUCE_BUTTON, Material.JUNGLE_BUTTON, Material.ACACIA_BUTTON,
            Material.DARK_OAK_BUTTON, Material.STONE_BUTTON);

    /**
     * Possible faces around the base block that protections could be at
     */
    public static final BlockFace[] POSSIBLE_FACES = new BlockFace[]{BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST,
            BlockFace.WEST};

    public boolean matches(ProtectionFinder finder) {
        // The block we are working on
        Block block = finder.getBaseBlock().getBlock();

        // Match wall signs to the wall it's attached to
        for (BlockFace blockFace : POSSIBLE_FACES) {
            Block face; // the relative block

            if ((face = block.getRelative(blockFace)) != null) {
                // Try and match it
                Block matched = tryMatchBlock(face, blockFace);

                // We found something ..! Try and load the protection
                if (matched != null) {
                    finder.addBlock(matched);
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Try and match a wall block
     *
     * @param block
     * @param matchingFace
     * @return
     */
    private Block tryMatchBlock(Block block, BlockFace matchingFace) {
        byte direction = block.getData();
        BlockData blockData = block.getBlockData();

        // Blocks such as wall signs or banners
        if (PROTECTABLES_WALL.contains(block.getType()) && blockData instanceof Directional) {
            if (((Directional) block.getState().getBlockData()).getFacing() == matchingFace) {
                return block;
            }
        }

        // Levers, buttons
        else if (PROTECTABLES_LEVERS_ET_AL.contains(block.getType())) {
            byte EAST = 0x4;
            byte WEST = 0x3;
            byte SOUTH = 0x1;
            byte NORTH = 0x2;

            if (matchingFace == BlockFace.EAST && (direction & EAST) == EAST) {
                return block;
            } else if (matchingFace == BlockFace.WEST && (direction & WEST) == WEST) {
                return block;
            } else if (matchingFace == BlockFace.SOUTH && (direction & SOUTH) == SOUTH) {
                return block;
            } else if (matchingFace == BlockFace.NORTH && (direction & NORTH) == NORTH) {
                return block;
            }
        }

        return null;
    }

}


<|start_filename|>src/main/java/com/griefcraft/integration/permissions/SuperPermsPermissions.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.integration.permissions;

import com.griefcraft.integration.IPermissions;
import com.griefcraft.lwc.LWC;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachmentInfo;

import java.util.ArrayList;
import java.util.List;

public class SuperPermsPermissions implements IPermissions {

    /**
     * The group prefix to use to lookup in Permissions - can be overrided in core.yml with groupPrefix: 'new.prefix.'
     * Must include leading period (.)
     * <p/>
     * Default: lwc.group.
     */
    private String groupPrefix;

    public SuperPermsPermissions() {
        groupPrefix = LWC.getInstance().getConfiguration().getString("core.groupPrefix", "group.");
    }

    // modified implementation by ZerothAngel ( https://github.com/Hidendra/LWC/issues/88#issuecomment-2017807 )
    @Override
    public List<String> getGroups(Player player) {
        LWC.getInstance();
        List<String> groups = new ArrayList<>();

        for (PermissionAttachmentInfo pai : player.getEffectivePermissions()) {
            if (pai.getPermission().startsWith(groupPrefix)) {
                groups.add(pai.getPermission().substring(groupPrefix.length()));
            }
        }

        return groups;
    }

}


<|start_filename|>src/main/java/com/griefcraft/model/Permission.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.model;

import com.griefcraft.util.Colors;
import com.griefcraft.util.StringUtil;
import com.griefcraft.util.UUIDRegistry;
import org.json.simple.JSONObject;

public class Permission {

    /**
     * The access level
     * The ordering of this enum <b>MUST NOT</b> change as ordinal values are used internally.
     */
    public enum Access {

        /**
         * The player has no access
         */
        NONE,

        /**
         * The player has rights that of a regular player
         */
        PLAYER,

        /**
         * The player has admin rights
         */
        ADMIN;

        @Override
        public String toString() {
            return StringUtil.capitalizeFirstLetter(super.toString());
        }
    }

    /**
     * The type this permission applies to.
     * The ordering of this enum <b>MUST NOT</b> change as ordinal values are used internally.
     */
    public enum Type {
        /**
         * Applies to a specific group of players
         */
        GROUP,

        /**
         * Applies to a specific player
         */
        PLAYER,

        /**
         * Unused / reserved, has been used before
         */
        RESERVED,

        /**
         * Applies to citizens of a Towny town
         */
        TOWN,

        /**
         * Allows a specific item (such as a key) to open the protection when interacted with in hand
         */
        ITEM,

        /**
         * Applies to members of a WorldGuard region
         */
        REGION;

        @Override
        public String toString() {
            return StringUtil.capitalizeFirstLetter(super.toString());
        }
    }

    /**
     * The entity this applies to
     */
    private String name;

    /**
     * The type of access used for the permission
     */
    private Type type;

    /**
     * The access the permission has to the protection
     */
    private Access access = Access.PLAYER;

    /**
     * If the permission is not synchronized to the database
     */
    private boolean isVolatile = false;

    public Permission() {
    }

    public Permission(String name) {
        this.name = name;
    }

    public Permission(String name, Type type) {
        this(name);
        this.type = type;
    }

    public Permission(String name, Type type, Access access) {
        this(name, type);
        this.access = access;
    }

    /**
     * Encode the Permission object to a JSONObject
     *
     * @return
     */
    @SuppressWarnings("unchecked")
    public JSONObject encodeToJSON() {
        JSONObject object = new JSONObject();

        object.put("name", name);
        object.put("type", getType().ordinal());
        object.put("rights", getAccess().ordinal());

        return object;
    }

    /**
     * Decode a JSONObject into a Permission object
     *
     * @param node
     * @return
     */
    public static Permission decodeJSON(JSONObject node) {
        Permission permission = new Permission();

        Access access = Access.values()[((Long) node.get("rights")).intValue()];
        if (access.ordinal() == 0) {
            access = Access.PLAYER;
        }

        // The values are stored as longs internally, despite us passing an int
        permission.setName((String) node.get("name"));
        permission.setType(Type.values()[((Long) node.get("type")).intValue()]);
        permission.setAccess(access);

        return permission;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(Colors.Yellow);
        if (type == Type.PLAYER) {
            builder.append(UUIDRegistry.formatPlayerName(getName()));
        } else {
            builder.append(getName());
        }
        builder.append(Colors.White);
        builder.append(" (");
        builder.append(Colors.Dark_Green);
        builder.append(getType());
        builder.append(Colors.White);
        builder.append(") ");

        if (getAccess() == Access.ADMIN) {
            builder.append(Colors.White);
            builder.append("(");
            builder.append(Colors.Dark_Red);
            builder.append("ADMIN");
            builder.append(Colors.White);
            builder.append(")");
        }
        return builder.toString();
        // return String.format("Permission = { protection=%d name=%s rights=%d type=%s }", protectionId, name, rights, typeToString(rights));
    }

    public String getName() {
        return name;
    }

    public Access getAccess() {
        return access;
    }

    public Type getType() {
        return type;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAccess(Access access) {
        this.access = access;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public void setVolatile(boolean isVolatile) {
        this.isVolatile = isVolatile;
    }

    public boolean isVolatile() {
        return isVolatile;
    }

}


<|start_filename|>src/main/java/com/griefcraft/util/config/ConfigUpdater.java<|end_filename|>
/*
 * Copyright 2011 <NAME>. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and contributors and should not be interpreted as representing official policies,
 * either expressed or implied, of anybody else.
 */

package com.griefcraft.util.config;

import com.griefcraft.lwc.LWC;

import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Updates config files with config vars that aren't already stored in the local
 * config file (so the config file appears to always be up to date.)
 */
public class ConfigUpdater {

    /**
     * Config nodes NOT to update - this includes child nodes
     */
    private static List<String> BLACKLIST = Arrays.asList(
            "protections.blocks",
            "master",
            "groups",
            "players",
            "defaults"
    );

    /**
     * The cache for the reference config files
     */
    private final Map<String, Configuration> referenceConfigFileCache = new HashMap<>();

    /**
     * Load the reference config files in the local jar file. The key in the map is the
     * file name
     *
     * @return
     */
    @SuppressWarnings("resource")
    private Map<String, Configuration> loadReferenceConfigFiles() throws IOException {
        if (referenceConfigFileCache.size() > 0) {
            return referenceConfigFileCache;
        }

        // Load our jar file
        ZipFile jarFile = new ZipFile(URLDecoder.decode(LWC.getInstance().getPlugin().getFile().getPath(), "UTF-8"));

        // Begin loading the files
        Enumeration<? extends ZipEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry file = (ZipEntry) entries.nextElement();
            String name = file.getName();

            // We only want config dir
            if (!name.startsWith("config/") || !name.endsWith(".yml")) {
                continue;
            }

            // Get just the name
            String realName = name.substring(name.indexOf('/') + 1);

            // Insert it
            Configuration configuration = new Configuration(null);
            configuration.load(jarFile.getInputStream(file));
            referenceConfigFileCache.put(realName, configuration);
        }

        return referenceConfigFileCache;
    }

    /**
     * Run the updater against a configuration file
     */
    public void update(Configuration configuration) {
        try {
            Map<String, Configuration> referenceFiles = loadReferenceConfigFiles();

            // Check for the file in the reference
            Configuration reference = referenceFiles.get(configuration.getFile().getName());

            // Was it found?
            if (reference == null) {
                return;
            }

            // The found keys
            List<String> currentKeys = getKeysDepth2(configuration);
            List<String> referenceKeys = getKeysDepth2(reference);
            boolean modified = false;

            // Now go through and add any new keys
            for (String key : referenceKeys) {
                boolean contains = configuration.getProperty(key) != null;

                if (!contains) {
                    configuration.setProperty(key, reference.getProperty(key));
                    modified = true;
                }
            }

            // Look for any config values to remove
            for (String key : currentKeys) {
                boolean contains = reference.getProperty(key) != null;

                if (!contains) {
                    configuration.removeProperty(key);
                    modified = true;
                }
            }

            // Save the file if it was modified
            if (modified) {
                configuration.save();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Get all of the nodes in a configuration file up to 1 node down from the root
     * <pre>
     * root
     *    -> node
     *    -> node
     *        -> subnode
     *        -> subnode
     * </pre>
     * In this case, the subnodes are not returned (the root and nodes however, are)
     *
     * @param configuration
     * @return
     */
    private List<String> getKeysDepth2(Configuration configuration) {
        List<String> keys = new ArrayList<>();

        // go through the root
        for (String key : configuration.getKeys(null)) {
            // Is it blacklisted?
            if (BLACKLIST.contains(key)) {
                continue;
            }

            keys.add(key);

            // Add the subnodes
            for (String subkey : configuration.getKeys(key)) {
                String fullKey = key + "." + subkey;

                // Is it blacklisted?
                if (BLACKLIST.contains(fullKey)) {
                    continue;
                }

                // good good!
                keys.add(fullKey);
            }
        }

        return keys;
    }

}
