From aaeaf19fc8e56d94a03f1c0511944d2e3e228f4d Mon Sep 17 00:00:00 2001
From: sk89q
Date: Sun, 27 Jul 2014 20:44:05 -0700
Subject: [PATCH] Rewrite session code and add support for persistent sessions.
---
.../worldedit/bukkit/BukkitCommandSender.java | 42 +-
.../sk89q/worldedit/bukkit/BukkitPlayer.java | 53 +++
.../worldedit/bukkit/WorldEditListener.java | 15 -
.../worldedit/bukkit/WorldEditPlugin.java | 3 -
.../sk89q/worldedit/forge/ForgePlayer.java | 65 +++
.../sk89q/worldedit/forge/ForgeWorldEdit.java | 11 +-
.../worldedit/forge/ThreadSafeCache.java | 91 +++++
.../com/sk89q/worldedit/LocalSession.java | 371 +++++++++++-------
.../java/com/sk89q/worldedit/WorldEdit.java | 35 --
.../worldedit/command/WorldEditCommands.java | 2 +
.../platform/ConfigurationLoadEvent.java | 53 +++
.../platform/PlatformInitializeEvent.java} | 16 +-
.../platform/AbstractPlayerActor.java | 6 +-
.../worldedit/extension/platform/Actor.java | 31 +-
.../extension/platform/PlatformManager.java | 31 +-
.../extension/platform/PlayerProxy.java | 13 +
.../session/MissingSessionException.java | 42 ++
.../sk89q/worldedit/session/SessionKey.java | 62 +++
.../worldedit/session/SessionManager.java | 288 ++++++++++----
.../sk89q/worldedit/session/SessionOwner.java | 37 ++
.../session/TransientSessionException.java | 42 ++
.../session/storage/JsonFileSessionStore.java | 135 +++++++
.../session/storage/SessionStore.java | 57 +++
.../worldedit/session/storage/VoidStore.java} | 31 +-
.../Identifiable.java} | 73 ++--
.../worldedit/util/YAMLConfiguration.java | 3 +-
.../util/auth/AuthorizationException.java | 43 ++
.../sk89q/worldedit/util/auth/Subject.java | 51 +++
.../util/concurrency/EvenMoreExecutors.java | 54 +++
.../sk89q/worldedit/util/gson/GsonUtil.java | 44 +++
30 files changed, 1418 insertions(+), 382 deletions(-)
create mode 100644 src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java
create mode 100644 src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java
rename src/main/java/com/sk89q/worldedit/{WorldEditPermissionException.java => event/platform/PlatformInitializeEvent.java} (75%)
create mode 100644 src/main/java/com/sk89q/worldedit/session/MissingSessionException.java
create mode 100644 src/main/java/com/sk89q/worldedit/session/SessionKey.java
create mode 100644 src/main/java/com/sk89q/worldedit/session/SessionOwner.java
create mode 100644 src/main/java/com/sk89q/worldedit/session/TransientSessionException.java
create mode 100644 src/main/java/com/sk89q/worldedit/session/storage/JsonFileSessionStore.java
create mode 100644 src/main/java/com/sk89q/worldedit/session/storage/SessionStore.java
rename src/{bukkit/java/com/sk89q/worldedit/bukkit/SessionTimer.java => main/java/com/sk89q/worldedit/session/storage/VoidStore.java} (53%)
rename src/main/java/com/sk89q/worldedit/{SessionCheck.java => util/Identifiable.java} (75%)
create mode 100644 src/main/java/com/sk89q/worldedit/util/auth/AuthorizationException.java
create mode 100644 src/main/java/com/sk89q/worldedit/util/auth/Subject.java
create mode 100644 src/main/java/com/sk89q/worldedit/util/concurrency/EvenMoreExecutors.java
create mode 100644 src/main/java/com/sk89q/worldedit/util/gson/GsonUtil.java
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
index 74dfc2e41..df7fdd520 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitCommandSender.java
@@ -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;
+ }
+ };
+ }
}
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
index 519aee885..25f02915b 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java
@@ -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 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;
+ }
+
+ }
+
}
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java
index 91a7adcda..4b212f7f0 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditListener.java
@@ -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()) {
diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
index 0a804208d..26647fcc8 100644
--- a/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
+++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java
@@ -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
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java b/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java
index 327b03602..da592ba8f 100644
--- a/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java
+++ b/src/forge/java/com/sk89q/worldedit/forge/ForgePlayer.java
@@ -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;
+ }
+
+ }
+
}
\ No newline at end of file
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java
index 6b706a08f..d0d6f0104 100644
--- a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java
+++ b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java
@@ -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
diff --git a/src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java b/src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java
new file mode 100644
index 000000000..c10ef5f1b
--- /dev/null
+++ b/src/forge/java/com/sk89q/worldedit/forge/ThreadSafeCache.java
@@ -0,0 +1,91 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * 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 .
+ */
+
+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 onlineIds = Collections.emptySet();
+ private long lastRefresh = 0;
+
+ /**
+ * Get an concurrent-safe set of UUIDs of online players.
+ *
+ * @return a set of UUIDs
+ */
+ public Set getOnlineIds() {
+ return onlineIds;
+ }
+
+ @Override
+ public void tickStart(EnumSet type, Object... tickData) {
+ long now = System.currentTimeMillis();
+
+ if (now - lastRefresh > REFRESH_DELAY) {
+ Set onlineIds = new HashSet();
+
+ for (Object object : FMLCommonHandler.instance().getMinecraftServerInstance().getConfigurationManager().playerEntityList) {
+ if (object != null) {
+ EntityPlayerMP player = (EntityPlayerMP) object;
+ onlineIds.add(player.getUniqueID());
+ }
+ }
+
+ this.onlineIds = new CopyOnWriteArraySet(onlineIds);
+
+ lastRefresh = now;
+ }
+ }
+
+ @Override
+ public void tickEnd(EnumSet type, Object... tickData) {
+ }
+
+ @Override
+ public EnumSet ticks() {
+ return EnumSet.of(TickType.SERVER);
+ }
+
+ @Override
+ public String getLabel() {
+ return "WorldEdit Cache";
+ }
+
+ public static ThreadSafeCache getInstance() {
+ return INSTANCE;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/LocalSession.java b/src/main/java/com/sk89q/worldedit/LocalSession.java
index 03647eb1c..85b02b091 100644
--- a/src/main/java/com/sk89q/worldedit/LocalSession.java
+++ b/src/main/java/com/sk89q/worldedit/LocalSession.java
@@ -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 history = new LinkedList();
+ 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 tools = new HashMap();
+ 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 history = new LinkedList();
- private int historyPointer = 0;
- private ClipboardHolder clipboard;
- private boolean toolControl = true;
- private boolean superPickaxe = false;
- private BlockTool pickaxeMode = new SinglePickaxe();
- private Map tools = new HashMap();
- 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
+ *
{@link #setConfiguration(LocalConfiguration)} should be called
+ * later with configuration.
*/
- 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 getSelection().
- *
- * @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 IncompleteRegionException
+ * 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;
diff --git a/src/main/java/com/sk89q/worldedit/WorldEdit.java b/src/main/java/com/sk89q/worldedit/WorldEdit.java
index 2e3a4d2df..92623795a 100644
--- a/src/main/java/com/sk89q/worldedit/WorldEdit.java
+++ b/src/main/java/com/sk89q/worldedit/WorldEdit.java
@@ -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.
*
diff --git a/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java b/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java
index 33070a6b3..1c40eb8f6 100644
--- a/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java
+++ b/src/main/java/com/sk89q/worldedit/command/WorldEditCommands.java
@@ -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!");
}
diff --git a/src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java b/src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java
new file mode 100644
index 000000000..73d23131c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/event/platform/ConfigurationLoadEvent.java
@@ -0,0 +1,53 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * 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 .
+ */
+
+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;
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/WorldEditPermissionException.java b/src/main/java/com/sk89q/worldedit/event/platform/PlatformInitializeEvent.java
similarity index 75%
rename from src/main/java/com/sk89q/worldedit/WorldEditPermissionException.java
rename to src/main/java/com/sk89q/worldedit/event/platform/PlatformInitializeEvent.java
index 8862590bd..49aeecd2b 100644
--- a/src/main/java/com/sk89q/worldedit/WorldEditPermissionException.java
+++ b/src/main/java/com/sk89q/worldedit/event/platform/PlatformInitializeEvent.java
@@ -17,15 +17,15 @@
* along with this program. If not, see .
*/
-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.
+ *
+ *
This event is fired once.
*/
-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 {
}
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java b/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java
index 6cc034891..883930915 100644
--- a/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/AbstractPlayerActor.java
@@ -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();
}
}
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java b/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java
index f9912de1a..43f665423 100644
--- a/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/Actor.java
@@ -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.
*
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java
index e8eb0b227..5c524c7ee 100644
--- a/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java
@@ -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 platforms = new ArrayList();
private final Map preferences = new EnumMap(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")
diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java b/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
index b1d8979aa..7ed6d20d1 100644
--- a/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
+++ b/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java
@@ -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 getFacet(Class extends T> cls) {
return basePlayer.getFacet(cls);
}
+
+ @Override
+ public SessionKey getSessionKey() {
+ return basePlayer.getSessionKey();
+ }
}
diff --git a/src/main/java/com/sk89q/worldedit/session/MissingSessionException.java b/src/main/java/com/sk89q/worldedit/session/MissingSessionException.java
new file mode 100644
index 000000000..46043b32e
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/session/MissingSessionException.java
@@ -0,0 +1,42 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * 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 .
+ */
+
+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);
+ }
+
+}
diff --git a/src/main/java/com/sk89q/worldedit/session/SessionKey.java b/src/main/java/com/sk89q/worldedit/session/SessionKey.java
new file mode 100644
index 000000000..bb3f1696c
--- /dev/null
+++ b/src/main/java/com/sk89q/worldedit/session/SessionKey.java
@@ -0,0 +1,62 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * 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 .
+ */
+
+package com.sk89q.worldedit.session;
+
+import com.sk89q.worldedit.util.Identifiable;
+
+import javax.annotation.Nullable;
+
+/**
+ * Provides information about a session.
+ *
+ *
A reference for this object may be kept around for a long time.
+ */
+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.
+ *
+ *
This method may be called from any thread, so this call
+ * must be thread safe.
- * Get a reference to one from {@link WorldEdit}.
- *
- * While this class is thread-safe, the returned session may not be.
+ *
+ *
Get a reference to one from {@link WorldEdit}.
+ *
+ *
While this class is thread-safe, the returned session may not be.
*/
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 sessions = new HashMap();
+ private final Map sessions = new HashMap();
+ 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 null.
+ * 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 null.
- *
- * @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 sessions) {
+ checkNotNull(sessions);
+
+ if (sessions.isEmpty()) {
+ return Futures.immediateFuture(sessions);
+ }
+
+ return executorService.submit(new Callable