Rewrite session code and add support for persistent sessions.

This commit is contained in:
sk89q 2014-07-27 20:44:05 -07:00
parent e95eeefa2b
commit aaeaf19fc8
30 changed files with 1418 additions and 382 deletions

View File

@ -19,19 +19,27 @@
package com.sk89q.worldedit.bukkit;
import com.sk89q.worldedit.WorldEditPermissionException;
import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.auth.AuthorizationException;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.annotation.Nullable;
import java.io.File;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class BukkitCommandSender implements Actor {
/**
* One time generated ID.
*/
private static final UUID DEFAULT_ID = UUID.fromString("a233eb4b-4cab-42cd-9fd9-7e7b9a3f74be");
private CommandSender sender;
private WorldEditPlugin plugin;
@ -44,6 +52,11 @@ public class BukkitCommandSender implements Actor {
this.sender = sender;
}
@Override
public UUID getUniqueId() {
return DEFAULT_ID;
}
@Override
public String getName() {
return sender.getName();
@ -93,7 +106,7 @@ public class BukkitCommandSender implements Actor {
}
@Override
public void checkPermission(String permission) throws WorldEditPermissionException {
public void checkPermission(String permission) throws AuthorizationException {
}
@Override
@ -115,4 +128,29 @@ public class BukkitCommandSender implements Actor {
public void dispatchCUIEvent(CUIEvent event) {
}
@Override
public SessionKey getSessionKey() {
return new SessionKey() {
@Nullable
@Override
public String getName() {
return null;
}
@Override
public boolean isActive() {
return false;
}
@Override
public boolean isPersistent() {
return false;
}
@Override
public UUID getUniqueId() {
return DEFAULT_ID;
}
};
}
}

View File

@ -30,14 +30,18 @@ import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.session.SessionKey;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import javax.annotation.Nullable;
import java.util.UUID;
public class BukkitPlayer extends LocalPlayer {
private Player player;
private WorldEditPlugin plugin;
@ -46,6 +50,11 @@ public class BukkitPlayer extends LocalPlayer {
this.player = player;
}
@Override
public UUID getUniqueId() {
return player.getUniqueId();
}
@Override
public int getItemInHand() {
ItemStack itemStack = player.getItemInHand();
@ -192,4 +201,48 @@ public class BukkitPlayer extends LocalPlayer {
public <T> T getFacet(Class<? extends T> cls) {
return null;
}
@Override
public SessionKey getSessionKey() {
return new SessionKeyImpl(this.player.getUniqueId(), player.getName());
}
private static class SessionKeyImpl implements SessionKey {
// If not static, this will leak a reference
private final UUID uuid;
private final String name;
private SessionKeyImpl(UUID uuid, String name) {
this.uuid = uuid;
this.name = name;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Nullable
@Override
public String getName() {
return name;
}
@Override
public boolean isActive() {
// This is a thread safe call on CraftBukkit because it uses a
// CopyOnWrite list for the list of players, but the Bukkit
// specification doesn't require thread safety (though the
// spec is extremely incomplete)
return Bukkit.getServer().getPlayerExact(name) != null;
}
@Override
public boolean isPersistent() {
return true;
}
}
}

View File

@ -37,7 +37,6 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerGameModeChangeEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* Handles all events thrown in relation to a Player
@ -62,20 +61,6 @@ public class WorldEditListener implements Listener {
this.plugin = plugin;
}
/**
* Called when a player leaves a server
*
* @param event Relevant event details
*/
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
if (!plugin.getInternalPlatform().isHookingEvents()) {
return;
}
plugin.getWorldEdit().markExpire(plugin.wrapPlayer(event.getPlayer()));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onGamemode(PlayerGameModeChangeEvent event) {
if (!plugin.getInternalPlatform().isHookingEvents()) {

View File

@ -110,9 +110,6 @@ public class WorldEditPlugin extends JavaPlugin implements TabCompleter {
// Now we can register events
getServer().getPluginManager().registerEvents(new WorldEditListener(this), this);
// Register session timer
getServer().getScheduler().runTaskTimerAsynchronously(this, new SessionTimer(worldEdit, getServer()), 120, 120);
// If we are on MCPC+/Cauldron, then Forge will have already loaded
// Forge WorldEdit and there's (probably) not going to be any other
// platforms to be worried about... at the current time of writing

View File

@ -27,6 +27,7 @@ import com.sk89q.worldedit.extension.platform.AbstractPlayerActor;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.LocalWorldAdapter;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.Location;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
@ -34,6 +35,7 @@ import net.minecraft.network.packet.Packet250CustomPayload;
import net.minecraft.util.ChatMessageComponent;
import javax.annotation.Nullable;
import java.util.UUID;
public class ForgePlayer extends AbstractPlayerActor {
@ -41,13 +43,21 @@ public class ForgePlayer extends AbstractPlayerActor {
protected ForgePlayer(EntityPlayerMP player) {
this.player = player;
ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId());
}
@Override
public UUID getUniqueId() {
return player.getUniqueID();
}
@Override
public int getItemInHand() {
ItemStack is = this.player.getCurrentEquippedItem();
return is == null ? 0 : is.itemID;
}
@Override
public String getName() {
return this.player.username;
}
@ -67,26 +77,32 @@ public class ForgePlayer extends AbstractPlayerActor {
this.player.cameraPitch);
}
@Override
public WorldVector getPosition() {
return new WorldVector(LocalWorldAdapter.adapt(ForgeWorldEdit.inst.getWorld(this.player.worldObj)), this.player.posX, this.player.posY, this.player.posZ);
}
@Override
public com.sk89q.worldedit.world.World getWorld() {
return ForgeWorldEdit.inst.getWorld(this.player.worldObj);
}
@Override
public double getPitch() {
return this.player.rotationPitch;
}
@Override
public double getYaw() {
return this.player.rotationYaw;
}
@Override
public void giveItem(int type, int amt) {
this.player.inventory.addItemStackToInventory(new ItemStack(type, amt, 0));
}
@Override
public void dispatchCUIEvent(CUIEvent event) {
String[] params = event.getParameters();
String send = event.getTypeId();
@ -97,42 +113,50 @@ public class ForgePlayer extends AbstractPlayerActor {
this.player.playerNetServerHandler.sendPacketToPlayer(packet);
}
@Override
public void printRaw(String msg) {
for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText(part));
}
}
@Override
public void printDebug(String msg) {
for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a77" + part));
}
}
@Override
public void print(String msg) {
for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7d" + part));
}
}
@Override
public void printError(String msg) {
for (String part : msg.split("\n")) {
this.player.sendChatToPlayer(ChatMessageComponent.createFromText("\u00a7c" + part));
}
}
@Override
public void setPosition(Vector pos, float pitch, float yaw) {
this.player.playerNetServerHandler.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), pitch, yaw);
}
@Override
public String[] getGroups() {
return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username);
}
@Override
public BlockBag getInventoryBlockBag() {
return null;
}
@Override
public boolean hasPermission(String perm) {
return ForgeUtil.hasPermission(this.player, perm);
}
@ -143,4 +167,45 @@ public class ForgePlayer extends AbstractPlayerActor {
return null;
}
@Override
public SessionKey getSessionKey() {
return new SessionKeyImpl(player.getUniqueID(), player.username);
}
private static class SessionKeyImpl implements SessionKey {
// If not static, this will leak a reference
private final UUID uuid;
private final String name;
private SessionKeyImpl(UUID uuid, String name) {
this.uuid = uuid;
this.name = name;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Nullable
@Override
public String getName() {
return name;
}
@Override
public boolean isActive() {
// We can't directly check if the player is online because
// the list of players is not thread safe
return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid);
}
@Override
public boolean isPersistent() {
return true;
}
}
}

View File

@ -32,8 +32,15 @@ import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.event.*;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.event.FMLServerAboutToStartEvent;
import cpw.mods.fml.common.event.FMLServerStartedEvent;
import cpw.mods.fml.common.event.FMLServerStoppingEvent;
import cpw.mods.fml.common.network.NetworkMod;
import cpw.mods.fml.common.registry.TickRegistry;
import cpw.mods.fml.relauncher.Side;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
@ -84,6 +91,8 @@ public class ForgeWorldEdit {
config = new ForgeConfiguration(this);
config.load();
TickRegistry.registerTickHandler(ThreadSafeCache.getInstance(), Side.SERVER);
}
@EventHandler

View File

@ -0,0 +1,91 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.forge;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ITickHandler;
import cpw.mods.fml.common.TickType;
import net.minecraft.entity.player.EntityPlayerMP;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Caches data that cannot be accessed from another thread safely.
*/
class ThreadSafeCache implements ITickHandler {
private static final long REFRESH_DELAY = 1000 * 30;
private static final ThreadSafeCache INSTANCE = new ThreadSafeCache();
private Set<UUID> onlineIds = Collections.emptySet();
private long lastRefresh = 0;
/**
* Get an concurrent-safe set of UUIDs of online players.
*
* @return a set of UUIDs
*/
public Set<UUID> getOnlineIds() {
return onlineIds;
}
@Override
public void tickStart(EnumSet<TickType> type, Object... tickData) {
long now = System.currentTimeMillis();
if (now - lastRefresh > REFRESH_DELAY) {
Set<UUID> onlineIds = new HashSet<UUID>();
for (Object object : FMLCommonHandler.instance().getMinecraftServerInstance().getConfigurationManager().playerEntityList) {
if (object != null) {
EntityPlayerMP player = (EntityPlayerMP) object;
onlineIds.add(player.getUniqueID());
}
}
this.onlineIds = new CopyOnWriteArraySet<UUID>(onlineIds);
lastRefresh = now;
}
}
@Override
public void tickEnd(EnumSet<TickType> type, Object... tickData) {
}
@Override
public EnumSet<TickType> ticks() {
return EnumSet.of(TickType.SERVER);
}
@Override
public String getLabel() {
return "WorldEdit Cache";
}
public static ThreadSafeCache getInstance() {
return INSTANCE;
}
}

View File

@ -40,9 +40,6 @@ import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
import com.sk89q.worldedit.util.formatting.Style;
import com.sk89q.worldedit.util.formatting.StyledFragment;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.snapshot.Snapshot;
@ -52,57 +49,102 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* An instance of this represents the WorldEdit session of a user. A session
* stores history and settings. Sessions are not tied particularly to any
* player and can be shuffled between players, saved, and loaded.
*
* @author sk89q
* Stores session information.
*/
public class LocalSession {
private static final boolean SHOW_HELP_MESSAGE = false;
public transient static int MAX_HISTORY_SIZE = 15;
public static int MAX_HISTORY_SIZE = 15;
public static int EXPIRATION_GRACE = 600000;
// Non-session related fields
private transient LocalConfiguration config;
private transient final AtomicBoolean dirty = new AtomicBoolean();
private LocalConfiguration config;
// Session related
private transient RegionSelector selector = new CuboidRegionSelector();
private transient boolean placeAtPos1 = false;
private transient LinkedList<EditSession> history = new LinkedList<EditSession>();
private transient int historyPointer = 0;
private transient ClipboardHolder clipboard;
private transient boolean toolControl = true;
private transient boolean superPickaxe = false;
private transient BlockTool pickaxeMode = new SinglePickaxe();
private transient Map<Integer, Tool> tools = new HashMap<Integer, Tool>();
private transient int maxBlocksChanged = -1;
private transient boolean useInventory;
private transient Snapshot snapshot;
private transient boolean hasCUISupport = false;
private transient int cuiVersion = -1;
private transient boolean fastMode = false;
private transient Mask mask;
private transient TimeZone timezone = TimeZone.getDefault();
private long expirationTime = System.currentTimeMillis() + EXPIRATION_GRACE;
private RegionSelector selector = new CuboidRegionSelector();
private boolean placeAtPos1 = false;
private LinkedList<EditSession> history = new LinkedList<EditSession>();
private int historyPointer = 0;
private ClipboardHolder clipboard;
private boolean toolControl = true;
private boolean superPickaxe = false;
private BlockTool pickaxeMode = new SinglePickaxe();
private Map<Integer, Tool> tools = new HashMap<Integer, Tool>();
private int maxBlocksChanged = -1;
private boolean useInventory;
private Snapshot snapshot;
// Saved properties
private String lastScript;
private boolean beenToldVersion = false;
private boolean hasCUISupport = false;
private int cuiVersion = -1;
private boolean fastMode = false;
private Mask mask;
private TimeZone timezone = TimeZone.getDefault();
/**
* Construct the object.
*
* @param config
* <p>{@link #setConfiguration(LocalConfiguration)} should be called
* later with configuration.</p>
*/
public LocalSession(LocalConfiguration config) {
public LocalSession() {
}
/**
* Construct the object.
*
* @param config the configuration
*/
public LocalSession(@Nullable LocalConfiguration config) {
this.config = config;
}
/**
* Set the configuration.
*
* @param config the configuration
*/
public void setConfiguration(LocalConfiguration config) {
checkNotNull(config);
this.config = config;
}
/**
* Get whether this session is "dirty" and has changes that needs to
* be committed.
*
* @return true if dirty
*/
public boolean isDirty() {
return dirty.get();
}
/**
* Set this session as dirty.
*/
private void setDirty() {
dirty.set(true);
}
/**
* Get whether this session is "dirty" and has changes that needs to
* be committed, and reset it to {@code false}.
*
* @return true if the dirty value was {@code true}
*/
public boolean compareAndResetDirty() {
return dirty.compareAndSet(true, false);
}
/**
* Get the session's timezone.
*
* @return
* @return the timezone
*/
public TimeZone getTimeZone() {
return timezone;
@ -111,9 +153,10 @@ public class LocalSession {
/**
* Set the session's timezone.
*
* @param timezone
* @param timezone the user's timezone
*/
public void setTimezone(TimeZone timezone) {
checkNotNull(timezone);
this.timezone = timezone;
}
@ -129,9 +172,11 @@ public class LocalSession {
* Remember an edit session for the undo history. If the history maximum
* size is reached, old edit sessions will be discarded.
*
* @param editSession
* @param editSession the edit session
*/
public void remember(EditSession editSession) {
checkNotNull(editSession);
// Don't store anything if no changes were made
if (editSession.size() == 0) return;
@ -149,8 +194,8 @@ public class LocalSession {
/**
* Performs an undo.
*
* @param newBlockBag
* @param player
* @param newBlockBag a new block bag
* @param player the player
* @return whether anything was undone
*/
public EditSession undo(BlockBag newBlockBag, LocalPlayer player) {
@ -160,11 +205,13 @@ public class LocalSession {
/**
* Performs an undo.
*
* @param newBlockBag
* @param player
* @param newBlockBag a new block bag
* @param player the player
* @return whether anything was undone
*/
public EditSession undo(BlockBag newBlockBag, Player player) {
checkNotNull(newBlockBag);
checkNotNull(player);
--historyPointer;
if (historyPointer >= 0) {
EditSession editSession = history.get(historyPointer);
@ -183,8 +230,8 @@ public class LocalSession {
/**
* Performs a redo
*
* @param newBlockBag
* @param player
* @param newBlockBag a new block bag
* @param player the player
* @return whether anything was redone
*/
public EditSession redo(BlockBag newBlockBag, LocalPlayer player) {
@ -194,11 +241,13 @@ public class LocalSession {
/**
* Performs a redo
*
* @param newBlockBag
* @param player
* @param newBlockBag a new block bag
* @param player the player
* @return whether anything was redone
*/
public EditSession redo(BlockBag newBlockBag, Player player) {
checkNotNull(newBlockBag);
checkNotNull(player);
if (historyPointer < history.size()) {
EditSession editSession = history.get(historyPointer);
EditSession newEditSession = WorldEdit.getInstance().getEditSessionFactory()
@ -213,6 +262,9 @@ public class LocalSession {
return null;
}
/**
* @deprecated Use {@link #getRegionSelector(World)}
*/
@Deprecated
public RegionSelector getRegionSelector(LocalWorld world) {
return getRegionSelector((World) world);
@ -222,10 +274,11 @@ public class LocalSession {
* Get the region selector for defining the selection. If the selection
* was defined for a different world, the old selection will be discarded.
*
* @param world
* @return position
* @param world the world
* @return position the position
*/
public RegionSelector getRegionSelector(World world) {
checkNotNull(world);
if (selector.getIncompleteRegion().getWorld() == null) {
selector = new CuboidRegionSelector(world);
} else if (!selector.getIncompleteRegion().getWorld().equals(world)) {
@ -236,16 +289,16 @@ public class LocalSession {
}
/**
* Get the region selector. This won't check worlds so make sure that
* this region selector isn't used blindly.
*
* @return position
* @deprecated use {@link #getRegionSelector(World)}
*/
@Deprecated
public RegionSelector getRegionSelector() {
return selector;
}
/**
* @deprecated use {@link #setRegionSelector(World, RegionSelector)}
*/
@Deprecated
public void setRegionSelector(LocalWorld world, RegionSelector selector) {
setRegionSelector((World) world, selector);
@ -254,10 +307,12 @@ public class LocalSession {
/**
* Set the region selector.
*
* @param world
* @param selector
* @param world the world
* @param selector the selector
*/
public void setRegionSelector(World world, RegionSelector selector) {
checkNotNull(world);
checkNotNull(selector);
selector.getIncompleteRegion().setWorld(world);
this.selector = selector;
}
@ -265,13 +320,16 @@ public class LocalSession {
/**
* Returns true if the region is fully defined.
*
* @return
* @return true if a region selection is defined
*/
@Deprecated
public boolean isRegionDefined() {
return selector.isDefined();
}
/**
* @deprecated use {@link #isSelectionDefined(World)}
*/
@Deprecated
public boolean isSelectionDefined(LocalWorld world) {
return isSelectionDefined((World) world);
@ -280,10 +338,11 @@ public class LocalSession {
/**
* Returns true if the region is fully defined for the specified world.
*
* @param world
* @return
* @param world the world
* @return true if a region selection is defined
*/
public boolean isSelectionDefined(World world) {
checkNotNull(world);
if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) {
return false;
}
@ -291,16 +350,16 @@ public class LocalSession {
}
/**
* Use <code>getSelection()</code>.
*
* @return region
* @throws IncompleteRegionException
* @deprecated use {@link #getSelection(World)}
*/
@Deprecated
public Region getRegion() throws IncompleteRegionException {
return selector.getRegion();
}
/**
* @deprecated use {@link #getSelection(World)}
*/
@Deprecated
public Region getSelection(LocalWorld world) throws IncompleteRegionException {
return getSelection((World) world);
@ -309,14 +368,15 @@ public class LocalSession {
/**
* Get the selection region. If you change the region, you should
* call learnRegionChanges(). If the selection is defined in
* a different world, the <code>IncompleteRegionException</code>
* a different world, the {@code IncompleteRegionException}
* exception will be thrown.
*
* @param world
* @return region
* @throws IncompleteRegionException
* @param world the world
* @return a region
* @throws IncompleteRegionException if no region is selected
*/
public Region getSelection(World world) throws IncompleteRegionException {
checkNotNull(world);
if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) {
throw new IncompleteRegionException();
}
@ -326,7 +386,7 @@ public class LocalSession {
/**
* Get the selection world.
*
* @return
* @return the the world of the selection
*/
public World getSelectionWorld() {
return selector.getIncompleteRegion().getWorld();
@ -335,7 +395,7 @@ public class LocalSession {
/**
* Gets the clipboard.
*
* @return clipboard, may be null
* @return clipboard
* @throws EmptyClipboardException thrown if no clipboard is set
*/
public ClipboardHolder getClipboard() throws EmptyClipboardException {
@ -368,7 +428,7 @@ public class LocalSession {
/**
* Change tool control setting.
*
* @param toolControl
* @param toolControl true to enable tool control
*/
public void setToolControl(boolean toolControl) {
this.toolControl = toolControl;
@ -386,7 +446,7 @@ public class LocalSession {
/**
* Set the maximum number of blocks that can be changed.
*
* @param maxBlocksChanged
* @param maxBlocksChanged the maximum number of blocks changed
*/
public void setBlockChangeLimit(int maxBlocksChanged) {
this.maxBlocksChanged = maxBlocksChanged;
@ -418,7 +478,7 @@ public class LocalSession {
/**
* Toggle the super pick axe.
*
* @return status
* @return whether the super pick axe is now enabled
*/
public boolean toggleSuperPickAxe() {
superPickaxe = !superPickaxe;
@ -426,13 +486,15 @@ public class LocalSession {
}
/**
* Get the placement position.
* Get the position use for commands that take a center point
* (i.e. //forestgen, etc.).
*
* @param player
* @return position
* @throws IncompleteRegionException
* @param player the player
* @return the position to use
* @throws IncompleteRegionException thrown if a region is not fully selected
*/
public Vector getPlacementPosition(Player player) throws IncompleteRegionException {
checkNotNull(player);
if (!placeAtPos1) {
return player.getBlockIn();
}
@ -443,7 +505,7 @@ public class LocalSession {
/**
* Toggle placement position.
*
* @return
* @return whether "place at position 1" is now enabled
*/
public boolean togglePlacementPosition() {
placeAtPos1 = !placeAtPos1;
@ -453,10 +515,12 @@ public class LocalSession {
/**
* Get a block bag for a player.
*
* @param player
* @return
* @param player the player to get the block bag for
* @return a block bag
*/
@Nullable
public BlockBag getBlockBag(Player player) {
checkNotNull(player);
if (!useInventory) {
return null;
}
@ -468,6 +532,7 @@ public class LocalSession {
*
* @return the snapshot
*/
@Nullable
public Snapshot getSnapshot() {
return snapshot;
}
@ -475,34 +540,38 @@ public class LocalSession {
/**
* Select a snapshot.
*
* @param snapshot
* @param snapshot a snapshot
*/
public void setSnapshot(Snapshot snapshot) {
public void setSnapshot(@Nullable Snapshot snapshot) {
this.snapshot = snapshot;
}
/**
* @return the superPickaxeMode
* Get the assigned block tool.
*
* @return the super pickaxe tool mode
*/
public BlockTool getSuperPickaxe() {
return pickaxeMode;
}
/**
* Set the super pickaxe tool.
* Set the super pick axe tool.
*
* @param tool
* @param tool the tool to set
*/
public void setSuperPickaxe(BlockTool tool) {
checkNotNull(tool);
this.pickaxeMode = tool;
}
/**
* Get the tool assigned to the item.
*
* @param item
* @return the tool
* @param item the item type ID
* @return the tool, which may be {@link null}
*/
@Nullable
public Tool getTool(int item) {
return tools.get(item);
}
@ -512,10 +581,11 @@ public class LocalSession {
* or the tool is not assigned, the slot will be replaced with the
* brush tool.
*
* @param item
* @return the tool
* @throws InvalidToolBindException
* @param item the item type ID
* @return the tool, or {@code null}
* @throws InvalidToolBindException if the item can't be bound to that item
*/
@Nullable
public BrushTool getBrushTool(int item) throws InvalidToolBindException {
Tool tool = getTool(item);
@ -530,11 +600,11 @@ public class LocalSession {
/**
* Set the tool.
*
* @param item
* @param tool the tool to set
* @throws InvalidToolBindException
* @param item the item type ID
* @param tool the tool to set, which can be {@code null}
* @throws InvalidToolBindException if the item can't be bound to that item
*/
public void setTool(int item, Tool tool) throws InvalidToolBindException {
public void setTool(int item, @Nullable Tool tool) throws InvalidToolBindException {
if (item > 0 && item < 255) {
throw new InvalidToolBindException(item, "Blocks can't be used");
} else if (item == config.wandItem) {
@ -549,7 +619,7 @@ public class LocalSession {
/**
* Returns whether inventory usage is enabled for this session.
*
* @return the useInventory
* @return if inventory is being used
*/
public boolean isUsingInventory() {
return useInventory;
@ -558,7 +628,7 @@ public class LocalSession {
/**
* Set the state of inventory usage.
*
* @param useInventory the useInventory to set
* @param useInventory if inventory is to be used
*/
public void setUseInventory(boolean useInventory) {
this.useInventory = useInventory;
@ -567,8 +637,9 @@ public class LocalSession {
/**
* Get the last script used.
*
* @return the lastScript
* @return the last script's name
*/
@Nullable
public String getLastScript() {
return lastScript;
}
@ -576,58 +647,55 @@ public class LocalSession {
/**
* Set the last script used.
*
* @param lastScript the lastScript to set
* @param lastScript the last script's name
*/
public void setLastScript(String lastScript) {
public void setLastScript(@Nullable String lastScript) {
this.lastScript = lastScript;
setDirty();
}
/**
* Tell the player the WorldEdit version.
*
* @param player
* @param player the player
*/
@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"})
public void tellVersion(Actor player) {
if (config.showHelpInfo && SHOW_HELP_MESSAGE) {
if (!beenToldVersion) {
StyledFragment fragment = new StyledFragment(Style.GRAY_DARK);
fragment.append("Need help with WorldEdit? Ask us on IRC (irc.esper.net #sk89q) or on our forums @ http://forum.enginehub.org");
player.printRaw(ColorCodeBuilder.asColorCodes(fragment));
beenToldVersion = true;
}
}
}
/**
* Dispatch a CUI event but only if the player has CUI support.
* Dispatch a CUI event but only if the actor has CUI support.
*
* @param player
* @param event
* @param actor the actor
* @param event the event
*/
public void dispatchCUIEvent(Actor player, CUIEvent event) {
public void dispatchCUIEvent(Actor actor, CUIEvent event) {
checkNotNull(actor);
checkNotNull(event);
if (hasCUISupport) {
player.dispatchCUIEvent(event);
actor.dispatchCUIEvent(event);
}
}
/**
* Dispatch the initial setup CUI messages.
*
* @param player
* @param actor the actor
*/
public void dispatchCUISetup(Player player) {
public void dispatchCUISetup(Actor actor) {
if (selector != null) {
dispatchCUISelection(player);
dispatchCUISelection(actor);
}
}
/**
* Send the selection information.
*
* @param player
* @param actor the actor
*/
public void dispatchCUISelection(Player player) {
public void dispatchCUISelection(Actor actor) {
checkNotNull(actor);
if (!hasCUISupport) {
return;
}
@ -636,17 +704,24 @@ public class LocalSession {
CUIRegion tempSel = (CUIRegion) selector;
if (tempSel.getProtocolVersion() > cuiVersion) {
player.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID()));
tempSel.describeLegacyCUI(this, player);
actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID()));
tempSel.describeLegacyCUI(this, actor);
} else {
player.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID()));
tempSel.describeCUI(this, player);
actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID()));
tempSel.describeCUI(this, actor);
}
}
}
public void describeCUI(Actor player) {
/**
* Describe the selection to the CUI actor.
*
* @param actor the actor
*/
public void describeCUI(Actor actor) {
checkNotNull(actor);
if (!hasCUISupport) {
return;
}
@ -655,15 +730,22 @@ public class LocalSession {
CUIRegion tempSel = (CUIRegion) selector;
if (tempSel.getProtocolVersion() > cuiVersion) {
tempSel.describeLegacyCUI(this, player);
tempSel.describeLegacyCUI(this, actor);
} else {
tempSel.describeCUI(this, player);
tempSel.describeCUI(this, actor);
}
}
}
/**
* Handle a CUI initialization message.
*
* @param text the message
*/
public void handleCUIInitializationMessage(String text) {
checkNotNull(text);
String[] split = text.split("\\|");
if (split.length > 1 && split[0].equalsIgnoreCase("v")) { // enough fields and right message
setCUISupport(true);
@ -678,7 +760,7 @@ public class LocalSession {
/**
* Gets the status of CUI support.
*
* @return
* @return true if CUI is enabled
*/
public boolean hasCUISupport() {
return hasCUISupport;
@ -687,7 +769,7 @@ public class LocalSession {
/**
* Sets the status of CUI support.
*
* @param support
* @param support true if CUI is enabled
*/
public void setCUISupport(boolean support) {
hasCUISupport = support;
@ -696,7 +778,7 @@ public class LocalSession {
/**
* Gets the client's CUI protocol version
*
* @return
* @return the CUI version
*/
public int getCUIVersion() {
return cuiVersion;
@ -705,7 +787,7 @@ public class LocalSession {
/**
* Sets the client's CUI protocol version
*
* @param cuiVersion
* @param cuiVersion the CUI version
*/
public void setCUIVersion(int cuiVersion) {
this.cuiVersion = cuiVersion;
@ -714,10 +796,13 @@ public class LocalSession {
/**
* Detect date from a user's input.
*
* @param input
* @return
* @param input the input to parse
* @return a date
*/
@Nullable
public Calendar detectDate(String input) {
checkNotNull(input);
Time.setTimeZone(getTimeZone());
Options opt = new com.sk89q.jchronic.Options();
opt.setNow(Calendar.getInstance(getTimeZone()));
@ -730,27 +815,9 @@ public class LocalSession {
}
/**
* Update the last update time for calculating expiration.
*/
public void update() {
expirationTime = System.currentTimeMillis();
}
/**
* Returns whether this session has expired.
*
* @return
*/
public boolean hasExpired() {
return System.currentTimeMillis() - expirationTime > EXPIRATION_GRACE;
}
/**
* Construct a new edit session.
*
* @param player
* @return
* @deprecated use {@link #createEditSession(Player)}
*/
@Deprecated
public EditSession createEditSession(LocalPlayer player) {
return createEditSession((Player) player);
}
@ -758,11 +825,13 @@ public class LocalSession {
/**
* Construct a new edit session.
*
* @param player
* @return
* @param player the player
* @return an edit session
*/
@SuppressWarnings("deprecation")
public EditSession createEditSession(Player player) {
checkNotNull(player);
BlockBag blockBag = getBlockBag(player);
// Create an edit session
@ -779,7 +848,7 @@ public class LocalSession {
/**
* Checks if the session has fast mode enabled.
*
* @return
* @return true if fast mode is enabled
*/
public boolean hasFastMode() {
return fastMode;
@ -788,7 +857,7 @@ public class LocalSession {
/**
* Set fast mode.
*
* @param fastMode
* @param fastMode true if fast mode is enabled
*/
public void setFastMode(boolean fastMode) {
this.fastMode = fastMode;

View File

@ -660,41 +660,6 @@ public class WorldEdit {
}
}
/**
* Handle a disconnection.
*
* @param player the player
*/
@Deprecated
public void handleDisconnect(Player player) {
forgetPlayer(player);
}
/**
* Mark for expiration of the session.
*
* @param player the player
*/
public void markExpire(Player player) {
sessions.markforExpiration(player);
}
/**
* Forget a player.
*
* @param player the player
*/
public void forgetPlayer(Player player) {
sessions.remove(player);
}
/*
* Flush expired sessions.
*/
public void flushExpiredSessions(SessionCheck checker) {
sessions.removeExpired(checker);
}
/**
* Called on arm swing.
*

View File

@ -24,6 +24,7 @@ import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.extension.platform.Platform;
@ -78,6 +79,7 @@ public class WorldEditCommands {
@CommandPermissions("worldedit.reload")
public void reload(Actor actor) throws WorldEditException {
we.getServer().reload();
we.getEventBus().post(new ConfigurationLoadEvent(we.getPlatformManager().queryCapability(Capability.CONFIGURATION).getConfiguration()));
actor.print("Configuration reloaded!");
}

View File

@ -0,0 +1,53 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.event.platform;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.event.Event;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Raised when the configuration has been loaded or re-loaded.
*/
public class ConfigurationLoadEvent extends Event {
private final LocalConfiguration configuration;
/**
* Create a new instance.
*
* @param configuration the new configuration
*/
public ConfigurationLoadEvent(LocalConfiguration configuration) {
checkNotNull(configuration);
this.configuration = configuration;
}
/**
* Get the configuration.
*
* @return the configuration
*/
public LocalConfiguration getConfiguration() {
return configuration;
}
}

View File

@ -17,15 +17,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit;
package com.sk89q.worldedit.event.platform;
import com.sk89q.worldedit.event.Event;
/**
* @author zml2008
* Fired when configuration has been loaded and the platform is in the
* intialization stage.
*
* <p>This event is fired once.</p>
*/
public class WorldEditPermissionException extends WorldEditException {
private static final long serialVersionUID = 1L;
public WorldEditPermissionException() {
super("You don't have permission to do this.");
}
public class PlatformInitializeEvent extends Event {
}

View File

@ -19,13 +19,13 @@
package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.util.auth.AuthorizationException;
import com.sk89q.worldedit.BlockWorldVector;
import com.sk89q.worldedit.LocalPlayer;
import com.sk89q.worldedit.NotABlockException;
import com.sk89q.worldedit.PlayerDirection;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.WorldEditPermissionException;
import com.sk89q.worldedit.WorldVector;
import com.sk89q.worldedit.WorldVectorFace;
import com.sk89q.worldedit.blocks.BaseBlock;
@ -464,9 +464,9 @@ public abstract class AbstractPlayerActor implements Actor, Player, Cloneable {
}
@Override
public void checkPermission(String permission) throws WorldEditPermissionException {
public void checkPermission(String permission) throws AuthorizationException {
if (!hasPermission(permission)) {
throw new WorldEditPermissionException();
throw new AuthorizationException();
}
}

View File

@ -19,16 +19,17 @@
package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.WorldEditPermissionException;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.session.SessionOwner;
import com.sk89q.worldedit.util.Identifiable;
import com.sk89q.worldedit.util.auth.Subject;
import java.io.File;
/**
* An object that can perform actions in WorldEdit.
*/
public interface Actor {
public interface Actor extends Identifiable, SessionOwner, Subject {
/**
* Get the name of the actor.
@ -72,30 +73,6 @@ public interface Actor {
*/
boolean canDestroyBedrock();
/**
* Get a actor's list of groups.
*
* @return an array containing a group name per entry
*/
String[] getGroups();
/**
* Checks if a player has permission.
*
* @param perm The permission to check
* @return true if the player has that permission
*/
boolean hasPermission(String perm);
/**
* Check whether this actor has the given permission, and throw an
* exception if not.
*
* @param permission the permission
* @throws WorldEditPermissionException thrown if permission is not availabe
*/
void checkPermission(String permission) throws WorldEditPermissionException;
/**
* Return whether this actor is a player.
*

View File

@ -19,12 +19,22 @@
package com.sk89q.worldedit.extension.platform;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.command.tool.*;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldVector;
import com.sk89q.worldedit.command.tool.BlockTool;
import com.sk89q.worldedit.command.tool.DoubleActionBlockTool;
import com.sk89q.worldedit.command.tool.DoubleActionTraceTool;
import com.sk89q.worldedit.command.tool.Tool;
import com.sk89q.worldedit.command.tool.TraceTool;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.BlockInteractEvent;
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
import com.sk89q.worldedit.event.platform.Interaction;
import com.sk89q.worldedit.event.platform.PlatformInitializeEvent;
import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
import com.sk89q.worldedit.event.platform.PlayerInputEvent;
import com.sk89q.worldedit.internal.ServerInterfaceAdapter;
@ -34,8 +44,13 @@ import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -56,6 +71,8 @@ public class PlatformManager {
private final List<Platform> platforms = new ArrayList<Platform>();
private final Map<Capability, Platform> preferences = new EnumMap<Capability, Platform>(Capability.class);
private @Nullable String firstSeenVersion;
private final AtomicBoolean initialized = new AtomicBoolean();
private final AtomicBoolean configured = new AtomicBoolean();
/**
* Create a new platform manager.
@ -163,6 +180,11 @@ public class PlatformManager {
capability.initialize(this, preferred);
}
}
// Fire configuration event
if (preferences.containsKey(Capability.CONFIGURATION) && configured.compareAndSet(false, true)) {
worldEdit.getEventBus().post(new ConfigurationLoadEvent(queryCapability(Capability.CONFIGURATION).getConfiguration()));
}
}
/**
@ -276,6 +298,9 @@ public class PlatformManager {
@Subscribe
public void handlePlatformReady(PlatformReadyEvent event) {
choosePreferred();
if (initialized.compareAndSet(false, true)) {
worldEdit.getEventBus().post(new PlatformInitializeEvent());
}
}
@SuppressWarnings("deprecation")

View File

@ -25,11 +25,14 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extent.inventory.BlockBag;
import com.sk89q.worldedit.internal.cui.CUIEvent;
import com.sk89q.worldedit.session.SessionKey;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.World;
import javax.annotation.Nullable;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkNotNull;
class PlayerProxy extends AbstractPlayerActor {
@ -50,6 +53,11 @@ class PlayerProxy extends AbstractPlayerActor {
this.world = world;
}
@Override
public UUID getUniqueId() {
return basePlayer.getUniqueId();
}
@Override
public int getItemInHand() {
return basePlayer.getItemInHand();
@ -145,4 +153,9 @@ class PlayerProxy extends AbstractPlayerActor {
public <T> T getFacet(Class<? extends T> cls) {
return basePlayer.getFacet(cls);
}
@Override
public SessionKey getSessionKey() {
return basePlayer.getSessionKey();
}
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session;
/**
* Raised when the session is missing.
*/
public class MissingSessionException extends Exception {
public MissingSessionException() {
}
public MissingSessionException(String message) {
super(message);
}
public MissingSessionException(String message, Throwable cause) {
super(message, cause);
}
public MissingSessionException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,62 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session;
import com.sk89q.worldedit.util.Identifiable;
import javax.annotation.Nullable;
/**
* Provides information about a session.
*
* <p>A reference for this object may be kept around for a long time.</p>
*/
public interface SessionKey extends Identifiable {
/**
* Get the name for this session, if one is available, so that it can
* be referred to by others.
*
* @return a name or {@code null}
*/
@Nullable
String getName();
/**
* Return whether the session is still active. Sessions that are inactive
* for a prolonged amount of time may be removed. If this method
* always returns {@code false}, the the related session may never
* be stored.
*
* <p>This method may be called from any thread, so this call
* must be thread safe.</p>
*
* @return true if active
*/
boolean isActive();
/**
* Return whether this session should be persisted.
*
* @return true if persistent
*/
boolean isPersistent();
}

View File

@ -19,28 +19,53 @@
package com.sk89q.worldedit.session;
import com.sk89q.worldedit.*;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
import com.sk89q.worldedit.session.storage.JsonFileSessionStore;
import com.sk89q.worldedit.session.storage.SessionStore;
import com.sk89q.worldedit.session.storage.VoidStore;
import com.sk89q.worldedit.util.concurrency.EvenMoreExecutors;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Session manager for WorldEdit.
* </p>
* Get a reference to one from {@link WorldEdit}.
* </p>
* While this class is thread-safe, the returned session may not be.
*
* <p>Get a reference to one from {@link WorldEdit}.</p>
*
* <p>While this class is thread-safe, the returned session may not be.</p>
*/
public class SessionManager {
public static int EXPIRATION_GRACE = 600000;
private static final int FLUSH_PERIOD = 1000 * 30;
private static final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 5));
private static final Logger log = Logger.getLogger(SessionManager.class.getCanonicalName());
private final Timer timer = new Timer();
private final WorldEdit worldEdit;
private final HashMap<String, LocalSession> sessions = new HashMap<String, LocalSession>();
private final Map<UUID, SessionHolder> sessions = new HashMap<UUID, SessionHolder>();
private SessionStore store = new VoidStore();
/**
* Create a new session manager.
@ -48,73 +73,98 @@ public class SessionManager {
* @param worldEdit a WorldEdit instance
*/
public SessionManager(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = worldEdit;
worldEdit.getEventBus().register(this);
timer.schedule(new SessionTracker(), FLUSH_PERIOD, FLUSH_PERIOD);
}
/**
* Get whether a session exists for the given actor.
* Get whether a session exists for the given owner.
*
* @param actor the actor
* @param owner the owner
* @return true if a session exists
*/
public synchronized boolean contains(Actor actor) {
checkNotNull(actor);
return sessions.containsKey(getKey(actor));
public synchronized boolean contains(SessionOwner owner) {
checkNotNull(owner);
return sessions.containsKey(getKey(owner));
}
/**
* Gets the session for an actor and return it if it exists, otherwise
* return <code>null</code>.
* Find a session by its name specified by {@link SessionKey#getName()}.
*
* @param actor the actor
* @return the session for the actor, if it exists
* @param name the name
* @return the session, if found, otherwise {@code null}
*/
public synchronized @Nullable LocalSession find(Actor actor) {
checkNotNull(actor);
return sessions.get(getKey(actor));
}
/**
* Gets the session for someone named by the given name and return it if
* it exists, otherwise return <code>null</code>.
*
* @param name the actor's name
* @return the session for the actor, if it exists
*/
public synchronized @Nullable LocalSession findByName(String name) {
@Nullable
public synchronized LocalSession findByName(String name) {
checkNotNull(name);
return sessions.get(name);
for (SessionHolder holder : sessions.values()) {
String test = holder.key.getName();
if (test != null && name.equals(test)) {
return holder.session;
}
}
return null;
}
/**
* Get the session for an actor and create one if one doesn't exist.
* Gets the session for an owner and return it if it exists, otherwise
* return {@code null}.
*
* @param actor the actor
* @param owner the owner
* @return the session for the owner, if it exists
*/
@Nullable
public synchronized LocalSession getIfPresent(SessionOwner owner) {
checkNotNull(owner);
SessionHolder stored = sessions.get(getKey(owner));
if (stored != null) {
return stored.session;
} else {
return null;
}
}
/**
* Get the session for an owner and create one if one doesn't exist.
*
* @param owner the owner
* @return a session
*/
public synchronized LocalSession get(Actor actor) {
checkNotNull(actor);
public synchronized LocalSession get(SessionOwner owner) {
checkNotNull(owner);
LocalSession session;
LocalSession session = getIfPresent(owner);
LocalConfiguration config = worldEdit.getConfiguration();
SessionKey sessionKey = owner.getSessionKey();
if (sessions.containsKey(actor.getName())) {
session = sessions.get(actor.getName());
} else {
session = new LocalSession(config);
// No session exists yet -- create one
if (session == null) {
try {
session = store.load(getKey(sessionKey));
} catch (IOException e) {
log.log(Level.WARNING, "Failed to load saved session", e);
session = new LocalSession();
}
session.setConfiguration(config);
session.setBlockChangeLimit(config.defaultChangeLimit);
// Remember the session
sessions.put(actor.getName(), session);
// Remember the session if the session is still active
if (sessionKey.isActive()) {
sessions.put(getKey(owner), new SessionHolder(sessionKey, session));
}
}
// Set the limit on the number of blocks that an operation can
// change at once, or don't if the actor has an override or there
// change at once, or don't if the owner has an override or there
// is no limit. There is also a default limit
int currentChangeLimit = session.getBlockChangeLimit();
if (!actor.hasPermission("worldedit.limit.unrestricted")
&& config.maxChangeLimit > -1) {
if (!owner.hasPermission("worldedit.limit.unrestricted") && config.maxChangeLimit > -1) {
// If the default limit is infinite but there is a maximum
// limit, make sure to not have it be overridden
if (config.defaultChangeLimit < 0) {
@ -130,47 +180,90 @@ public class SessionManager {
}
}
// Have the session use inventory if it's enabled and the actor
// Have the session use inventory if it's enabled and the owner
// doesn't have an override
session.setUseInventory(config.useInventory
&& !(config.useInventoryOverride
&& (actor.hasPermission("worldedit.inventory.unrestricted")
|| (config.useInventoryCreativeOverride && (!(actor instanceof Player) || ((Player) actor).hasCreativeMode())))));
&& (owner.hasPermission("worldedit.inventory.unrestricted")
|| (config.useInventoryCreativeOverride && (!(owner instanceof Player) || ((Player) owner).hasCreativeMode())))));
return session;
}
/**
* Get the key to use in the map for an actor.
* Save a map of sessions to disk.
*
* @param actor the actor
* @return the key object
* @param sessions a map of sessions to save
* @return a future that completes on save or error
*/
protected String getKey(Actor actor) {
return actor.getName();
private ListenableFuture<?> commit(final Map<SessionKey, LocalSession> sessions) {
checkNotNull(sessions);
if (sessions.isEmpty()) {
return Futures.immediateFuture(sessions);
}
return executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
Exception exception = null;
for (Map.Entry<SessionKey, LocalSession> entry : sessions.entrySet()) {
SessionKey key = entry.getKey();
if (key.isPersistent()) {
try {
store.save(getKey(key), entry.getValue());
} catch (IOException e) {
log.log(Level.WARNING, "Failed to write session for UUID " + getKey(key), e);
exception = e;
}
}
}
if (exception != null) {
throw exception;
}
return sessions;
}
});
}
/**
* Mark for expiration.
* Get the key to use in the map for an owner.
*
* @param actor the actor
* @param owner the owner
* @return the key object
*/
public synchronized void markforExpiration(Actor actor) {
checkNotNull(actor);
LocalSession session = find(actor);
if (session != null) {
session.update();
protected UUID getKey(SessionOwner owner) {
return getKey(owner.getSessionKey());
}
/**
* Get the key to use in the map for a {@code SessionKey}.
*
* @param key the session key object
* @return the key object
*/
protected UUID getKey(SessionKey key) {
String forcedKey = System.getProperty("worldedit.session.uuidOverride");
if (forcedKey != null) {
return UUID.fromString(forcedKey);
} else {
return key.getUniqueId();
}
}
/**
* Remove the session for the given actor if one exists.
* Remove the session for the given owner if one exists.
*
* @param actor the actor
* @param owner the owner
*/
public synchronized void remove(Actor actor) {
checkNotNull(actor);
sessions.remove(actor.getName());
public synchronized void remove(SessionOwner owner) {
checkNotNull(owner);
sessions.remove(getKey(owner));
}
/**
@ -180,18 +273,61 @@ public class SessionManager {
sessions.clear();
}
/**
* Remove expired sessions with the given session checker.
*
* @param checker the session checker
*/
public synchronized void removeExpired(SessionCheck checker) {
Iterator<Map.Entry<String, LocalSession>> it = sessions.entrySet().iterator();
@Subscribe
public void onConfigurationLoad(ConfigurationLoadEvent event) {
LocalConfiguration config = event.getConfiguration();
File dir = new File(config.getWorkingDirectory(), "sessions");
store = new JsonFileSessionStore(dir);
}
while (it.hasNext()) {
Map.Entry<String, LocalSession> entry = it.next();
if (entry.getValue().hasExpired() && !checker.isOnlinePlayer(entry.getKey())) {
it.remove();
/**
* Stores the owner of a session, the session, and the last active time.
*/
private static class SessionHolder {
private final SessionKey key;
private final LocalSession session;
private long lastActive = System.currentTimeMillis();
private SessionHolder(SessionKey key, LocalSession session) {
this.key = key;
this.session = session;
}
}
/**
* Removes inactive sessions after they have been inactive for a period
* of time. Commits them as well.
*/
private class SessionTracker extends TimerTask {
@Override
public void run() {
synchronized (SessionManager.this) {
long now = System.currentTimeMillis();
Iterator<SessionHolder> it = sessions.values().iterator();
Map<SessionKey, LocalSession> saveQueue = new HashMap<SessionKey, LocalSession>();
while (it.hasNext()) {
SessionHolder stored = it.next();
if (stored.key.isActive()) {
stored.lastActive = now;
if (stored.session.compareAndResetDirty()) {
saveQueue.put(stored.key, stored.session);
}
} else {
if (now - stored.lastActive > EXPIRATION_GRACE) {
if (stored.session.compareAndResetDirty()) {
saveQueue.put(stored.key, stored.session);
}
it.remove();
}
}
}
if (!saveQueue.isEmpty()) {
commit(saveQueue);
}
}
}
}

View File

@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session;
import com.sk89q.worldedit.util.Identifiable;
import com.sk89q.worldedit.util.auth.Subject;
/**
* An object that owns a session.
*/
public interface SessionOwner extends Subject {
/**
* Get an object describing this session.
*
* @return the status object
*/
SessionKey getSessionKey();
}

View File

@ -0,0 +1,42 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session;
/**
* Thrown if the session cannot be persisted
* (because {@link SessionKey#isPersistent()} returns false).
*/
public class TransientSessionException extends Exception {
public TransientSessionException() {
}
public TransientSessionException(String message) {
super(message);
}
public TransientSessionException(String message, Throwable cause) {
super(message, cause);
}
public TransientSessionException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,135 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session.storage;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.google.gson.JsonParseException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.util.gson.GsonUtil;
import com.sk89q.worldedit.util.io.Closer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Stores sessions as JSON files in a directory.
*
* <p>Currently, this implementation doesn't handle thread safety very well.</p>
*/
public class JsonFileSessionStore implements SessionStore {
private static final Logger log = Logger.getLogger(JsonFileSessionStore.class.getCanonicalName());
private final Gson gson;
private final File dir;
/**
* Create a new session store.
*
* @param dir the directory
*/
public JsonFileSessionStore(File dir) {
checkNotNull(dir);
if (!dir.isDirectory()) {
if (!dir.mkdirs()) {
log.log(Level.WARNING, "Failed to create directory '" + dir.getPath() + "' for sessions");
}
}
this.dir = dir;
GsonBuilder builder = GsonUtil.createBuilder();
gson = builder.create();
}
/**
* Get the path for the given UUID.
*
* @param id the ID
* @return the file
*/
private File getPath(UUID id) {
checkNotNull(id);
return new File(dir, id + ".json");
}
@Override
public LocalSession load(UUID id) throws IOException {
File file = getPath(id);
Closer closer = Closer.create();
try {
FileReader fr = closer.register(new FileReader(file));
BufferedReader br = closer.register(new BufferedReader(fr));
return gson.fromJson(br, LocalSession.class);
} catch (JsonParseException e) {
throw new IOException(e);
} catch (FileNotFoundException e) {
return new LocalSession();
} finally {
try {
closer.close();
} catch (IOException ignored) {
}
}
}
@Override
public void save(UUID id, LocalSession session) throws IOException {
File finalFile = getPath(id);
File tempFile = new File(finalFile.getParentFile(), finalFile.getName() + ".tmp");
Closer closer = Closer.create();
try {
FileWriter fr = closer.register(new FileWriter(tempFile));
BufferedWriter bw = closer.register(new BufferedWriter(fr));
gson.toJson(session, bw);
} catch (JsonIOException e) {
throw new IOException(e);
} finally {
try {
closer.close();
} catch (IOException ignored) {
}
}
if (finalFile.exists()) {
if (!finalFile.delete()) {
log.log(Level.WARNING, "Failed to delete " + finalFile.getPath() + " so the .tmp file can replace it");
}
}
if (!tempFile.renameTo(finalFile)) {
log.log(Level.WARNING, "Failed to rename temporary session file to " + finalFile.getPath());
}
}
}

View File

@ -0,0 +1,57 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.session.storage;
import com.sk89q.worldedit.LocalSession;
import java.io.IOException;
import java.util.UUID;
/**
* Commits sessions to disk.
*
* <p>Both {@link #load(UUID)} and {@link #save(UUID, LocalSession)} may be
* called at the same in different threads, so implementations should
* be aware of this issue.</p>
*/
public interface SessionStore {
/**
* Load a session identified by the given UUID.
*
* <p>If the session does not exist (has never been saved), then
* a new {@link LocalSession} must be returned.</p>
*
* @param id the UUID
* @return a session
* @throws IOException thrown on read error
*/
LocalSession load(UUID id) throws IOException;
/**
* Save the given session identified by the given UUID.
*
* @param id the UUID
* @param session a session
* @throws IOException thrown on read error
*/
void save(UUID id, LocalSession session) throws IOException;
}

View File

@ -17,34 +17,25 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.bukkit;
package com.sk89q.worldedit.session.storage;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.sk89q.worldedit.SessionCheck;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.LocalSession;
import java.io.IOException;
import java.util.UUID;
/**
* Used to remove expired sessions in Bukkit.
* A session store that doesn't know how to store sessions.
*/
class SessionTimer implements Runnable {
public class VoidStore implements SessionStore {
private WorldEdit worldEdit;
private SessionCheck checker;
SessionTimer(WorldEdit worldEdit, final Server server) {
this.worldEdit = worldEdit;
this.checker = new SessionCheck() {
public boolean isOnlinePlayer(String name) {
Player player = server.getPlayer(name);
return player != null && player.isOnline();
}
};
@Override
public LocalSession load(UUID id) throws IOException {
return new LocalSession();
}
@Override
public void run() {
worldEdit.flushExpiredSessions(checker);
public void save(UUID id, LocalSession session) throws IOException {
}
}

View File

@ -1,37 +1,36 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit;
/**
* Used to discard old sessions.
*
* @author sk89q
*/
public interface SessionCheck {
/**
* Checks if a player is online.
*
* @param name
* @return
*/
public boolean isOnlinePlayer(String name);
}
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util;
import java.util.UUID;
/**
* Represents an object that can be identified by a UUID.
*/
public interface Identifiable {
/**
* Get the UUID for this object.
*
* @return the UUID
*/
UUID getUniqueId();
}

View File

@ -22,6 +22,7 @@ package com.sk89q.worldedit.util;
import com.sk89q.util.yaml.YAMLProcessor;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.session.SessionManager;
import com.sk89q.worldedit.world.snapshot.SnapshotRepository;
import java.io.IOException;
@ -106,7 +107,7 @@ public class YAMLConfiguration extends LocalConfiguration {
allowSymlinks = config.getBoolean("files.allow-symbolic-links", false);
LocalSession.MAX_HISTORY_SIZE = Math.max(0, config.getInt("history.size", 15));
LocalSession.EXPIRATION_GRACE = config.getInt("history.expiration", 10) * 60 * 1000;
SessionManager.EXPIRATION_GRACE = config.getInt("history.expiration", 10) * 60 * 1000;
showHelpInfo = config.getBoolean("show-help-on-first-use", true);

View File

@ -0,0 +1,43 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.auth;
import com.sk89q.worldedit.WorldEditException;
/**
* Raised when authorization is not granted.
*/
public class AuthorizationException extends WorldEditException {
public AuthorizationException() {
}
public AuthorizationException(String message) {
super(message);
}
public AuthorizationException(String message, Throwable cause) {
super(message, cause);
}
public AuthorizationException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,51 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.auth;
/**
* A subject has authorization attached to it.
*/
public interface Subject {
/**
* Get a list of groups that this subject is a part of.
*
* @return an array containing a group name per entry
*/
String[] getGroups();
/**
* Check whether this subject has been granted the given permission
* and throw an exception on error.
*
* @param permission the permission
* @throws AuthorizationException thrown if not permitted
*/
void checkPermission(String permission) throws AuthorizationException;
/**
* Return whether this subject has the given permission.
*
* @param permission the permission
* @return true if permission is granted
*/
boolean hasPermission(String permission);
}

View File

@ -0,0 +1,54 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.concurrency;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Even more {@code ExecutorService} factory methods.
*/
public final class EvenMoreExecutors {
private EvenMoreExecutors() {
}
/**
* Creates a thread pool that creates new threads as needed up to
* a maximum number of threads, but will reuse previously constructed
* threads when they are available.
*
* @param minThreads the minimum number of threads to have at a given time
* @param maxThreads the maximum number of threads to have at a given time
* @param queueSize the size of the queue before new submissions are rejected
* @return the newly created thread pool
*/
public static ExecutorService newBoundedCachedThreadPool(int minThreads, int maxThreads, int queueSize) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
minThreads, maxThreads,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize));
threadPoolExecutor.allowCoreThreadTimeOut(true);
return threadPoolExecutor;
}
}

View File

@ -0,0 +1,44 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.util.gson;
import com.google.gson.GsonBuilder;
import com.sk89q.worldedit.Vector;
/**
* Utility methods for Google's GSON library.
*/
public final class GsonUtil {
private GsonUtil() {
}
/**
* Create a standard {@link GsonBuilder} for WorldEdit.
*
* @return a builder
*/
public static GsonBuilder createBuilder() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Vector.class, new VectorAdapter());
return gsonBuilder;
}
}