diff --git a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java
index ee6bc082..d800380e 100644
--- a/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java
+++ b/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java
@@ -19,6 +19,7 @@
package com.sk89q.worldguard.bukkit.listener;
+import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent;
import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent;
@@ -30,13 +31,19 @@
import com.sk89q.worldguard.bukkit.util.Materials;
import com.sk89q.worldguard.bukkit.util.RegionQuery;
import com.sk89q.worldguard.protection.flags.DefaultFlag;
+import com.sk89q.worldguard.protection.managers.RegionManager;
import org.bukkit.ChatColor;
+import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
+import org.bukkit.event.world.ChunkLoadEvent;
+import org.bukkit.event.world.ChunkUnloadEvent;
+import org.bukkit.event.world.WorldLoadEvent;
+import org.bukkit.event.world.WorldUnloadEvent;
/**
* Handle events that need to be processed by region protection.
@@ -56,6 +63,34 @@ private void tellErrorMessage(CommandSender sender, Object subject) {
sender.sendMessage(ChatColor.DARK_RED + "You don't have permission for this area.");
}
+ @EventHandler
+ public void onWorldLoad(WorldLoadEvent event) {
+ getPlugin().getGlobalRegionManager().load(event.getWorld());
+ }
+
+ @EventHandler
+ public void onWorldUnload(WorldUnloadEvent event) {
+ getPlugin().getGlobalRegionManager().unload(event.getWorld());
+ }
+
+ @EventHandler
+ public void onChunkLoad(ChunkLoadEvent event) {
+ RegionManager manager = getPlugin().getGlobalRegionManager().get(event.getWorld());
+ if (manager != null) {
+ Chunk chunk = event.getChunk();
+ manager.loadChunk(new Vector2D(chunk.getX(), chunk.getZ()));
+ }
+ }
+
+ @EventHandler
+ public void onChunkUnload(ChunkUnloadEvent event) {
+ RegionManager manager = getPlugin().getGlobalRegionManager().get(event.getWorld());
+ if (manager != null) {
+ Chunk chunk = event.getChunk();
+ manager.unloadChunk(new Vector2D(chunk.getX(), chunk.getZ()));
+ }
+ }
+
@EventHandler(ignoreCancelled = true)
public void onPlaceBlock(PlaceBlockEvent event) {
Player player = event.getCause().getPlayerRootCause();
diff --git a/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java b/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java
index b5260092..2810df23 100644
--- a/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java
+++ b/src/main/java/com/sk89q/worldguard/protection/GlobalRegionManager.java
@@ -19,6 +19,7 @@
package com.sk89q.worldguard.protection;
+import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.bukkit.BukkitUtil;
import com.sk89q.worldguard.bukkit.ConfigurationManager;
@@ -26,14 +27,17 @@
import com.sk89q.worldguard.bukkit.WorldGuardPlugin;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.managers.RegionManager;
+import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import javax.annotation.Nullable;
+import java.util.ArrayList;
import java.util.List;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.worldguard.bukkit.BukkitUtil.toVector;
public class GlobalRegionManager {
@@ -50,7 +54,16 @@ public GlobalRegionManager(WorldGuardPlugin plugin) {
@Nullable
public RegionManager load(World world) {
- return container.load(world.getName());
+ checkNotNull(world);
+ RegionManager manager = container.load(world.getName());
+ if (manager != null) {
+ List positions = new ArrayList();
+ for (Chunk chunk : world.getLoadedChunks()) {
+ positions.add(new Vector2D(chunk.getX(), chunk.getZ()));
+ }
+ manager.loadChunks(positions);
+ }
+ return manager;
}
public void preload() {
@@ -59,6 +72,10 @@ public void preload() {
}
}
+ public void unload(World world) {
+ unload(world.getName());
+ }
+
public void unload(String name) {
container.unload(name);
}
diff --git a/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java b/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java
index 521f0fc1..2ce82afb 100644
--- a/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java
+++ b/src/main/java/com/sk89q/worldguard/protection/ManagerContainer.java
@@ -22,6 +22,7 @@
import com.google.common.base.Supplier;
import com.sk89q.worldguard.bukkit.ConfigurationManager;
import com.sk89q.worldguard.protection.managers.RegionManager;
+import com.sk89q.worldguard.protection.managers.index.ChunkHashTable;
import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex;
import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex;
import com.sk89q.worldguard.protection.managers.storage.RegionStore;
@@ -57,7 +58,7 @@ class ManagerContainer {
private final Object lock = new Object();
private final EnumMap drivers = new EnumMap(DriverType.class);
private final RegionStoreDriver defaultDriver;
- private final Supplier extends ConcurrentRegionIndex> indexFactory = new PriorityRTreeIndex.Factory();
+ private final Supplier extends ConcurrentRegionIndex> indexFactory = new ChunkHashTable.Factory(new PriorityRTreeIndex.Factory());
private final Timer timer = new Timer();
ManagerContainer(ConfigurationManager config) {
diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java b/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java
index 3ccfe844..122c4e35 100644
--- a/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java
+++ b/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java
@@ -22,6 +22,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.LocalPlayer;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex;
@@ -29,6 +30,7 @@
import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException;
import com.sk89q.worldguard.protection.managers.storage.RegionStore;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
+import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
import com.sk89q.worldguard.util.Normal;
import javax.annotation.Nullable;
@@ -113,6 +115,33 @@ public void saveChanges() throws IOException {
}
}
+ /**
+ * Load the regions for a chunk.
+ *
+ * @param position the position
+ */
+ public void loadChunk(Vector2D position) {
+ index.bias(position);
+ }
+
+ /**
+ * Load the regions for a chunk.
+ *
+ * @param positions a collection of positions
+ */
+ public void loadChunks(Collection positions) {
+ index.biasAll(positions);
+ }
+
+ /**
+ * Unload the regions for a chunk.
+ *
+ * @param position the position
+ */
+ public void unloadChunk(Vector2D position) {
+ index.forget(position);
+ }
+
/**
* Get an unmodifiable map of regions containing the state of the
* index at the time of call.
diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java
new file mode 100644
index 00000000..07edebc1
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java
@@ -0,0 +1,335 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.protection.managers.index;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.sk89q.odeum.concurrent.EvenMoreExecutors;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.Vector2D;
+import com.sk89q.worldguard.protection.managers.RegionDifference;
+import com.sk89q.worldguard.protection.managers.RemovalStrategy;
+import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
+import com.sk89q.worldguard.protection.regions.ProtectedRegion;
+import com.sk89q.worldguard.protection.util.RegionCollectionConsumer;
+import com.sk89q.worldguard.util.collect.LongHashTable;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Maintains a hash table for each chunk containing a list of regions that
+ * are contained within that chunk, allowing for fast spatial lookup.
+ */
+public class ChunkHashTable implements ConcurrentRegionIndex {
+
+ private ListeningExecutorService executor = createExecutor();
+ private LongHashTable states = new LongHashTable();
+ private final RegionIndex index;
+ private final Object lock = new Object();
+
+ /**
+ * Create a new instance.
+ *
+ * @param index the index
+ */
+ public ChunkHashTable(RegionIndex index) {
+ checkNotNull(index);
+ this.index = index;
+ }
+
+ /**
+ * Create an executor.
+ *
+ * @return an executor service
+ */
+ private ListeningExecutorService createExecutor() {
+ return MoreExecutors.listeningDecorator(
+ EvenMoreExecutors.newBoundedCachedThreadPool(0, 4, Integer.MAX_VALUE));
+ }
+
+ /**
+ * Get a state object at the given position.
+ *
+ * @param position the position
+ * @param create true to create an entry if one does not exist
+ * @return a chunk state object, or {@code null} (only if {@code create} is false)
+ */
+ @Nullable
+ private ChunkState get(Vector2D position, boolean create) {
+ ChunkState state;
+ synchronized (lock) {
+ state = states.get(position.getBlockX(), position.getBlockZ());
+ if (state == null && create) {
+ state = new ChunkState(position);
+ states.put(position.getBlockX(), position.getBlockZ(), state);
+ executor.submit(new EnumerateRegions(position));
+ }
+ }
+ return state;
+ }
+
+ /**
+ * Get a state at the given position or create a new entry if one does
+ * not exist.
+ *
+ * @param position the position
+ * @return a state
+ */
+ private ChunkState getOrCreate(Vector2D position) {
+ return get(position, true);
+ }
+
+ /**
+ * Clear the current hash table and rebuild it in the background.
+ */
+ private void rebuild() {
+ synchronized (lock) {
+ ListeningExecutorService previousExecutor = executor;
+ LongHashTable previousStates = states;
+
+ previousExecutor.shutdownNow();
+ states = new LongHashTable();
+ executor = createExecutor();
+
+ List positions = new ArrayList();
+ for (ChunkState state : previousStates.values()) {
+ Vector2D position = state.getPosition();
+ positions.add(position);
+ states.put(position.getBlockX(), position.getBlockZ(), new ChunkState(position));
+ }
+
+ if (!positions.isEmpty()) {
+ executor.submit(new EnumerateRegions(positions));
+ }
+ }
+ }
+
+ @Override
+ public void bias(Vector2D chunkPosition) {
+ checkNotNull(chunkPosition);
+ getOrCreate(chunkPosition);
+ }
+
+ @Override
+ public void biasAll(Collection chunkPositions) {
+ synchronized (lock) {
+ for (Vector2D position : chunkPositions) {
+ bias(position);
+ }
+ }
+ }
+
+ @Override
+ public void forget(Vector2D chunkPosition) {
+ checkNotNull(chunkPosition);
+ synchronized (lock) {
+ states.remove(chunkPosition.getBlockX(), chunkPosition.getBlockZ());
+ }
+ }
+
+ @Override
+ public void forgetAll() {
+ synchronized (lock) {
+ executor.shutdownNow();
+ states = new LongHashTable();
+ executor = createExecutor();
+ }
+ }
+
+ @Override
+ public void add(ProtectedRegion region) {
+ index.add(region);
+ rebuild();
+ }
+
+ @Override
+ public void addAll(Collection regions) {
+ index.addAll(regions);
+ rebuild();
+ }
+
+ @Override
+ public Set remove(String id, RemovalStrategy strategy) {
+ Set removed = index.remove(id, strategy);
+ rebuild();
+ return removed;
+ }
+
+ @Override
+ public boolean contains(String id) {
+ return index.contains(id);
+ }
+
+ @Nullable
+ @Override
+ public ProtectedRegion get(String id) {
+ return index.get(id);
+ }
+
+ @Override
+ public void apply(Predicate consumer) {
+ index.apply(consumer);
+ }
+
+ @Override
+ public void applyContaining(Vector position, Predicate consumer) {
+ checkNotNull(position);
+ checkNotNull(consumer);
+
+ ChunkState state = get(new Vector2D(position.getBlockX() >> 4, position.getBlockZ() >> 4), false);
+ if (state != null && state.isLoaded()) {
+ for (ProtectedRegion region : state.getRegions()) {
+ if (region.contains(position)) {
+ consumer.apply(region);
+ }
+ }
+ } else {
+ index.applyContaining(position, consumer);
+ }
+ }
+
+ @Override
+ public void applyIntersecting(ProtectedRegion region, Predicate consumer) {
+ index.applyIntersecting(region, consumer);
+ }
+
+ @Override
+ public int size() {
+ return index.size();
+ }
+
+ @Override
+ public RegionDifference getAndClearDifference() {
+ return index.getAndClearDifference();
+ }
+
+ @Override
+ public Collection values() {
+ return index.values();
+ }
+
+ @Override
+ public boolean isDirty() {
+ return index.isDirty();
+ }
+
+ @Override
+ public void setDirty(boolean dirty) {
+ index.setDirty(dirty);
+ }
+
+ /**
+ * A task to enumerate the regions for a list of provided chunks.
+ */
+ private class EnumerateRegions implements Runnable {
+ private final List positions;
+
+ private EnumerateRegions(Vector2D position) {
+ this(Arrays.asList(checkNotNull(position)));
+ }
+
+ private EnumerateRegions(List positions) {
+ checkNotNull(positions);
+ checkArgument(!positions.isEmpty(), "List of positions can't be empty");
+ this.positions = positions;
+ }
+
+ @Override
+ public void run() {
+ for (Vector2D position : positions) {
+ ChunkState state = get(position, false);
+
+ if (state != null) {
+ List regions = new ArrayList();
+ ProtectedRegion chunkRegion = new ProtectedCuboidRegion(
+ "_",
+ position.toVector(0).toBlockVector(),
+ position.add(16, 16).toVector(Integer.MAX_VALUE).toBlockVector());
+ index.applyIntersecting(chunkRegion, new RegionCollectionConsumer(regions, false));
+
+ state.setRegions(Collections.unmodifiableList(regions));
+
+ if (Thread.currentThread().isInterrupted()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Stores a cache of region data for a chunk.
+ */
+ private class ChunkState {
+ private final Vector2D position;
+ private boolean loaded = false;
+ private List regions = Collections.emptyList();
+
+ private ChunkState(Vector2D position) {
+ this.position = position;
+ }
+
+ public Vector2D getPosition() {
+ return position;
+ }
+
+ public List getRegions() {
+ return regions;
+ }
+
+ public void setRegions(List regions) {
+ this.regions = regions;
+ this.loaded = true;
+ }
+
+ public boolean isLoaded() {
+ return loaded;
+ }
+ }
+
+ /**
+ * A factory for instances of {@code ChunkHashCache}.
+ */
+ public static class Factory implements Supplier {
+ private final Supplier extends ConcurrentRegionIndex> supplier;
+
+ public Factory(Supplier extends ConcurrentRegionIndex> supplier) {
+ checkNotNull(supplier);
+ this.supplier = supplier;
+ }
+
+ @Override
+ public ChunkHashTable get() {
+ return new ChunkHashTable(supplier.get());
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java
index aaa86a93..6d9f43ba 100644
--- a/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java
+++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java
@@ -22,6 +22,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.protection.managers.RegionDifference;
import com.sk89q.worldguard.protection.managers.RemovalStrategy;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@@ -102,6 +103,26 @@ public void addAll(Collection regions) {
}
}
+ @Override
+ public void bias(Vector2D chunkPosition) {
+ // Nothing to do
+ }
+
+ @Override
+ public void biasAll(Collection chunkPositions) {
+ // Nothing to do
+ }
+
+ @Override
+ public void forget(Vector2D chunkPosition) {
+ // Nothing to do
+ }
+
+ @Override
+ public void forgetAll() {
+ // Nothing to do
+ }
+
@Override
public void add(ProtectedRegion region) {
synchronized (lock) {
diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java b/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java
index 76e43812..264dceae 100644
--- a/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java
+++ b/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java
@@ -21,6 +21,7 @@
import com.google.common.base.Predicate;
import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.Vector2D;
import com.sk89q.worldguard.protection.managers.RegionDifference;
import com.sk89q.worldguard.protection.managers.RemovalStrategy;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@@ -39,6 +40,37 @@
*/
public interface RegionIndex extends ChangeTracked {
+ /**
+ * Bias the given chunk for faster lookups (put it in a hash table, etc.).
+ *
+ * Implementations may choose to do nothing.
+ *
+ * @param chunkPosition the chunk position
+ */
+ void bias(Vector2D chunkPosition);
+
+ /**
+ * Bias the given chunk for faster lookups (put it in a hash table, etc.).
+ *
+ * Implementations may choose to do nothing.
+ *
+ * @param chunkPosition the chunk position
+ */
+ void biasAll(Collection chunkPosition);
+
+ /**
+ * No longer bias the given chunk for faster lookup.
+ *
+ * @param chunkPosition the chunk position
+ */
+ void forget(Vector2D chunkPosition);
+
+ /**
+ * Clearly all extra cache data created by any calls to
+ * {@link #bias(Vector2D)}.
+ */
+ void forgetAll();
+
/**
* Add a region to this index, replacing any existing one with the same
* name (equality determined using {@link Normal}).
diff --git a/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java b/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java
similarity index 90%
rename from src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java
rename to src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java
index 0ab9d6fd..a36554ff 100644
--- a/src/main/java/com/sk89q/worldguard/protection/managers/RegionCollectionConsumer.java
+++ b/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java
@@ -17,7 +17,7 @@
* along with this program. If not, see .
*/
-package com.sk89q.worldguard.protection.managers;
+package com.sk89q.worldguard.protection.util;
import com.google.common.base.Predicate;
import com.sk89q.worldguard.protection.regions.ProtectedRegion;
@@ -33,7 +33,7 @@
* to the collection, although it may result in duplicates in the collection
* if the collection is not a set.
*/
-class RegionCollectionConsumer implements Predicate {
+public class RegionCollectionConsumer implements Predicate {
private final Collection collection;
private final boolean addParents;
@@ -44,7 +44,7 @@ class RegionCollectionConsumer implements Predicate {
* @param collection the collection to add regions to
* @param addParents true to also add the parents to the collection
*/
- RegionCollectionConsumer(Collection collection, boolean addParents) {
+ public RegionCollectionConsumer(Collection collection, boolean addParents) {
checkNotNull(collection);
this.collection = collection;
diff --git a/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java b/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java
new file mode 100644
index 00000000..41a58ec5
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java
@@ -0,0 +1,30 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.util.collect;
+
+public class EntryBase {
+
+ protected long key;
+
+ public EntryBase(long key) {
+ this.key = key;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java b/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java
new file mode 100644
index 00000000..0ae50759
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java
@@ -0,0 +1,130 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.util.collect;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class LongBaseHashTable extends LongHash {
+
+ EntryBase[][][] values = new EntryBase[256][][];
+ EntryBase cache = null;
+
+ public void put(int msw, int lsw, EntryBase entry) {
+ put(entry);
+ }
+
+ public EntryBase getEntry(int msw, int lsw) {
+ return getEntry(toLong(msw, lsw));
+ }
+
+ public synchronized void put(EntryBase entry) {
+ int mainIdx = (int) (entry.key & 255);
+ EntryBase[][] outer = this.values[mainIdx];
+ if (outer == null) this.values[mainIdx] = outer = new EntryBase[256][];
+
+ int outerIdx = (int) ((entry.key >> 32) & 255);
+ EntryBase[] inner = outer[outerIdx];
+
+ if (inner == null) {
+ outer[outerIdx] = inner = new EntryBase[5];
+ inner[0] = this.cache = entry;
+ } else {
+ int i;
+ for (i = 0; i < inner.length; i++) {
+ if (inner[i] == null || inner[i].key == entry.key) {
+ inner[i] = this.cache = entry;
+ return;
+ }
+ }
+
+ outer[outerIdx] = inner = Arrays.copyOf(inner, i + i);
+ inner[i] = entry;
+ }
+ }
+
+ public synchronized EntryBase getEntry(long key) {
+ return containsKey(key) ? cache : null;
+ }
+
+ public synchronized boolean containsKey(long key) {
+ if (this.cache != null && cache.key == key) return true;
+
+ int outerIdx = (int) ((key >> 32) & 255);
+ EntryBase[][] outer = this.values[(int) (key & 255)];
+ if (outer == null) return false;
+
+ EntryBase[] inner = outer[outerIdx];
+ if (inner == null) return false;
+
+ for (int i = 0; i < inner.length; i++) {
+ EntryBase e = inner[i];
+ if (e == null) {
+ return false;
+ } else if (e.key == key) {
+ this.cache = e;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public synchronized void remove(long key) {
+ EntryBase[][] outer = this.values[(int) (key & 255)];
+ if (outer == null) return;
+
+ EntryBase[] inner = outer[(int) ((key >> 32) & 255)];
+ if (inner == null) return;
+
+ for (int i = 0; i < inner.length; i++) {
+ if (inner[i] == null) continue;
+
+ if (inner[i].key == key) {
+ for (i++; i < inner.length; i++) {
+ if (inner[i] == null) break;
+ inner[i - 1] = inner[i];
+ }
+
+ inner[i-1] = null;
+ this.cache = null;
+ return;
+ }
+ }
+ }
+
+ public synchronized ArrayList entries() {
+ ArrayList ret = new ArrayList();
+
+ for (EntryBase[][] outer : this.values) {
+ if (outer == null) continue;
+
+ for (EntryBase[] inner : outer) {
+ if (inner == null) continue;
+
+ for (EntryBase entry : inner) {
+ if (entry == null) break;
+
+ ret.add(entry);
+ }
+ }
+ }
+ return ret;
+ }
+}
diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java b/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java
new file mode 100644
index 00000000..89554007
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java
@@ -0,0 +1,48 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.util.collect;
+
+public abstract class LongHash {
+
+ public static long toLong(int msw, int lsw) {
+ return ((long) msw << 32) + lsw - Integer.MIN_VALUE;
+ }
+
+ public static int msw(long l) {
+ return (int) (l >> 32);
+ }
+
+ public static int lsw(long l) {
+ return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE;
+ }
+
+ public boolean containsKey(int msw, int lsw) {
+ return containsKey(toLong(msw, lsw));
+ }
+
+ public void remove(int msw, int lsw) {
+ remove(toLong(msw, lsw));
+ }
+
+ public abstract boolean containsKey(long key);
+
+ public abstract void remove(long key);
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java b/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java
new file mode 100644
index 00000000..a23789d1
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java
@@ -0,0 +1,199 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.util.collect;
+
+import java.util.Arrays;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
+public class LongHashSet extends LongHash {
+
+ protected long[][][] values = new long[256][][];
+ protected int count = 0;
+ protected ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
+ protected ReadLock rl = rwl.readLock();
+ protected WriteLock wl = rwl.writeLock();
+
+ public boolean isEmpty() {
+ rl.lock();
+ try {
+ return this.count == 0;
+ } finally {
+ rl.unlock();
+ }
+ }
+
+ public int size() {
+ return count;
+ }
+
+ public void add(int msw, int lsw) {
+ add(toLong(msw, lsw));
+ }
+
+ public void add(long key) {
+ wl.lock();
+ try {
+ int mainIdx = (int) (key & 255);
+ long outer[][] = this.values[mainIdx];
+ if (outer == null) this.values[mainIdx] = outer = new long[256][];
+
+ int outerIdx = (int) ((key >> 32) & 255);
+ long inner[] = outer[outerIdx];
+
+ if (inner == null) {
+ synchronized (this) {
+ outer[outerIdx] = inner = new long[1];
+ inner[0] = key;
+ this.count++;
+ }
+ } else {
+ int i;
+ for (i = 0; i < inner.length; i++) {
+ if (inner[i] == key) {
+ return;
+ }
+ }
+ inner = Arrays.copyOf(inner, i + 1);
+ outer[outerIdx] = inner;
+ inner[i] = key;
+ this.count++;
+ }
+ } finally {
+ wl.unlock();
+ }
+ }
+
+ public boolean containsKey(long key) {
+ rl.lock();
+ try {
+ long[][] outer = this.values[(int) (key & 255)];
+ if (outer == null) return false;
+
+ long[] inner = outer[(int) ((key >> 32) & 255)];
+ if (inner == null) return false;
+
+ for (long entry : inner) {
+ if (entry == key) return true;
+ }
+ return false;
+ } finally {
+ rl.unlock();
+ }
+ }
+
+ public void remove(long key) {
+ wl.lock();
+ try {
+ long[][] outer = this.values[(int) (key & 255)];
+ if (outer == null) return;
+
+ long[] inner = outer[(int) ((key >> 32) & 255)];
+ if (inner == null) return;
+
+ int max = inner.length - 1;
+ for (int i = 0; i <= max; i++) {
+ if (inner[i] == key) {
+ this.count--;
+ if (i != max) {
+ inner[i] = inner[max];
+ }
+
+ outer[(int) ((key >> 32) & 255)] = (max == 0 ? null : Arrays.copyOf(inner, max));
+ return;
+ }
+ }
+ } finally {
+ wl.unlock();
+ }
+ }
+
+ public long popFirst() {
+ wl.lock();
+ try {
+ for (long[][] outer: this.values) {
+ if (outer == null) continue;
+
+ for (int i = 0; i < outer.length; i++) {
+ long[] inner = outer[i];
+ if (inner == null || inner.length == 0) continue;
+
+ this.count--;
+ long ret = inner[inner.length - 1];
+ outer[i] = Arrays.copyOf(inner, inner.length - 1);
+
+ return ret;
+ }
+ }
+ } finally {
+ wl.unlock();
+ }
+ return 0;
+ }
+
+ public long[] popAll() {
+ int index = 0;
+ wl.lock();
+ try {
+ long[] ret = new long[this.count];
+ for (long[][] outer : this.values) {
+ if (outer == null) continue;
+
+ for (int oIdx = outer.length - 1; oIdx >= 0; oIdx--) {
+ long[] inner = outer[oIdx];
+ if (inner == null) continue;
+
+ for (long entry: inner) {
+ ret[index++] = entry;
+ }
+ outer[oIdx] = null;
+ }
+ }
+ count = 0;
+ return ret;
+ } finally {
+ wl.unlock();
+ }
+ }
+
+ public long[] keys() {
+ int index = 0;
+ rl.lock();
+ try {
+ long[] ret = new long[this.count];
+ for (long[][] outer : this.values) {
+ if (outer == null) continue;
+
+ for (long[] inner : outer) {
+ if (inner == null) continue;
+
+ for (long entry : inner) {
+ ret[index++] = entry;
+ }
+ }
+ }
+ return ret;
+ } finally {
+ rl.unlock();
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java b/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java
new file mode 100644
index 00000000..46462562
--- /dev/null
+++ b/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java
@@ -0,0 +1,65 @@
+/*
+ * WorldGuard, a suite of tools for Minecraft
+ * Copyright (C) sk89q
+ * Copyright (C) WorldGuard team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldguard.util.collect;
+
+import java.util.ArrayList;
+
+public class LongHashTable extends LongBaseHashTable {
+
+ public void put(int msw, int lsw, V value) {
+ put(toLong(msw, lsw), value);
+ }
+
+ public V get(int msw, int lsw) {
+ return get(toLong(msw, lsw));
+ }
+
+ public synchronized void put(long key, V value) {
+ put(new Entry(key, value));
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized V get(long key) {
+ Entry entry = ((Entry) getEntry(key));
+ return entry != null ? entry.value : null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public synchronized ArrayList values() {
+ ArrayList ret = new ArrayList();
+
+ ArrayList entries = entries();
+
+ for (EntryBase entry : entries) {
+ ret.add(((Entry) entry).value);
+ }
+ return ret;
+ }
+
+ private class Entry extends EntryBase {
+ V value;
+
+ Entry(long k, V v) {
+ super(k);
+ this.value = v;
+ }
+ }
+
+}