Implemented the map item limit (global and per-player).

* NEW: Implemented the per-player map limit.
* NEW: Implemented the global map limit.
* NEW: The map manager can now raise exceptions for some requests.
* NEW: The configuration can now have deprecated field names. 
  These fields are automatically migrated if present in the config file.
* NEW: The configuration now writes default values when loading the plugin.
* BUG: Fixed configuration loading.
* BUG: Fixed player store loading's thread-safety.
* OPT: Cleaned up map loading events initialisation.
This commit is contained in:
Prokopyl 2015-04-04 06:05:47 +02:00
parent 27f836977f
commit b87dfdf231
12 changed files with 238 additions and 27 deletions

View File

@ -80,13 +80,13 @@ public final class ImageOnMap extends JavaPlugin
}
//Init all the things !
PluginConfiguration.init(this);
MetricsLite.startMetrics();
ImageIOExecutor.start();
ImageRendererExecutor.start();
MapManager.init();
Commands.init(this);
getServer().getPluginManager().registerEvents(new MapInitEvent(), this);
MapInitEvent.init();
MapInitEvent.init(this);
MapItemManager.init();
}

View File

@ -19,21 +19,24 @@
package fr.moribus.imageonmap;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.Plugin;
public enum PluginConfiguration
{
//Configuration field Names, with default values
COLLECT_DATA("collect-data", true),
MAP_GLOBAL_LIMIT("map-global-limit", 0),
MAP_PLAYER_LIMIT("map-player-limit", 0);
MAP_GLOBAL_LIMIT("map-global-limit", 0, "Limit-map-by-server"),
MAP_PLAYER_LIMIT("map-player-limit", 0, "Limit-map-by-player");
private final String fieldName;
private final Object defaultValue;
private final String[] deprecatedNames;
private PluginConfiguration(String fieldName, Object defaultValue)
private PluginConfiguration(String fieldName, Object defaultValue, String ... deprecatedNames)
{
this.fieldName = fieldName;
this.defaultValue = defaultValue;
this.deprecatedNames = deprecatedNames;
}
public Object get()
@ -46,9 +49,9 @@ public enum PluginConfiguration
return defaultValue;
}
public boolean isDefaultValue()
public boolean isDefined()
{
return get().equals(defaultValue);
return getConfig().contains(fieldName);
}
@Override
@ -72,8 +75,51 @@ public enum PluginConfiguration
return getConfig().getBoolean(fieldName, (Boolean)defaultValue);
}
private boolean init()
{
boolean affected = false;
if(!isDefined())
{
getConfig().set(fieldName, defaultValue);
affected = true;
}
for(String deprecatedName : deprecatedNames)
{
if(getConfig().contains(deprecatedName))
{
getConfig().set(fieldName, getConfig().get(deprecatedName));
getConfig().set(deprecatedName, null);
affected = true;
}
}
return affected;
}
/* ===== Static API ===== */
static private Plugin plugin;
static public FileConfiguration getConfig()
{
return ImageOnMap.getPlugin().getConfig();
return plugin.getConfig();
}
static public void init(Plugin plugin)
{
PluginConfiguration.plugin = plugin;
loadDefaultValues();
}
static private void loadDefaultValues()
{
boolean affected = false;
for(PluginConfiguration configField : PluginConfiguration.values())
{
if(configField.init()) affected = true;
}
if(affected) plugin.saveConfig();
}
}

View File

@ -18,6 +18,8 @@
package fr.moribus.imageonmap.commands;
import fr.moribus.imageonmap.PluginLogger;
public class CommandException extends Exception
{
public enum Reason
@ -63,6 +65,7 @@ public class CommandException extends Exception
case SENDER_NOT_AUTHORIZED:
return "You do not have the permission to use this command.";
default:
PluginLogger.LogWarning("Unknown CommandException caught", this);
return "An unknown error suddenly happened.";
}
}

View File

@ -18,9 +18,11 @@
package fr.moribus.imageonmap.commands.maptool;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.commands.*;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.MapManagerException;
import java.util.List;
import org.bukkit.entity.Player;
@ -39,8 +41,16 @@ public class DeleteNoConfirmCommand extends Command
Player player = playerSender();
ImageMap map = getMapFromArgs();
MapManager.clear(player.getInventory(), map);
MapManager.deleteMap(map);
info("Map successfully deleted.");
try
{
MapManager.deleteMap(map);
info("Map successfully deleted.");
}
catch (MapManagerException ex)
{
PluginLogger.LogWarning("A non-existent map was requested to be deleted", ex);
warning("This map does not exist.");
}
}
@Override

View File

@ -86,6 +86,7 @@ public class ImageRendererExecutor extends Worker
static private ImageMap RenderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable
{
MapManager.checkMapLimit(1, playerUUID);
Future<Short> futureMapID = instance.submitToMainThread(new Callable<Short>()
{
@Override
@ -118,7 +119,8 @@ public class ImageRendererExecutor extends Worker
{
final PosterImage poster = new PosterImage(image);
final int mapCount = poster.getImagesCount();
MapManager.checkMapLimit(mapCount, playerUUID);
final Future<short[]> futureMapsIds = instance.submitToMainThread(new Callable<short[]>()
{
@Override

View File

@ -32,11 +32,14 @@ import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.map.MapView;
import org.bukkit.plugin.Plugin;
public class MapInitEvent implements Listener
{
static public void init()
static public void init(Plugin plugin)
{
plugin.getServer().getPluginManager().registerEvents(new MapInitEvent(), plugin);
for(World world : Bukkit.getWorlds())
{
for(ItemFrame frame : world.getEntitiesByClass(ItemFrame.class))

View File

@ -66,6 +66,7 @@ public abstract class ImageMap implements ConfigurationSerializable
public abstract short[] getMapsIDs();
public abstract boolean managesMap(short mapID);
public abstract int getMapCount();
public boolean managesMap(ItemStack item)
{

View File

@ -19,8 +19,10 @@
package fr.moribus.imageonmap.map;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginConfiguration;
import fr.moribus.imageonmap.image.ImageIOExecutor;
import fr.moribus.imageonmap.image.PosterImage;
import fr.moribus.imageonmap.map.MapManagerException.Reason;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ -72,14 +74,14 @@ abstract public class MapManager
return false;
}
static public ImageMap createMap(UUID playerUUID, short mapID)
static public ImageMap createMap(UUID playerUUID, short mapID) throws MapManagerException
{
ImageMap newMap = new SingleMap(playerUUID, mapID);
addMap(newMap);
return newMap;
}
static public ImageMap createMap(PosterImage image, UUID playerUUID, short[] mapsIDs)
static public ImageMap createMap(PosterImage image, UUID playerUUID, short[] mapsIDs) throws MapManagerException
{
ImageMap newMap;
if(image.getImagesCount() == 1)
@ -104,15 +106,15 @@ abstract public class MapManager
return mapsIds;
}
static public void addMap(ImageMap map)
static public void addMap(ImageMap map) throws MapManagerException
{
getPlayerMapStore(map.getUserUUID()).addMap(map);
}
static public void deleteMap(ImageMap map)
static public void deleteMap(ImageMap map) throws MapManagerException
{
ImageIOExecutor.deleteImage(map);
getPlayerMapStore(map.getUserUUID()).deleteMap(map);
ImageIOExecutor.deleteImage(map);
}
static public void notifyModification(UUID playerUUID)
@ -170,14 +172,48 @@ abstract public class MapManager
}
}
static public void checkMapLimit(ImageMap map) throws MapManagerException
{
checkMapLimit(map.getMapCount(), map.getUserUUID());
}
static public void checkMapLimit(int newMapsCount, UUID userUUID) throws MapManagerException
{
int limit = PluginConfiguration.MAP_GLOBAL_LIMIT.getInteger();
if(limit > 0)
{
if(getMapCount() + newMapsCount > limit)
throw new MapManagerException(Reason.MAXIMUM_SERVER_MAPS_EXCEEDED);
}
getPlayerMapStore(userUUID).checkMapLimit(newMapsCount);
}
static public int getMapCount()
{
int mapCount = 0;
synchronized(playerMaps)
{
for(PlayerMapStore tStore : playerMaps)
{
mapCount += tStore.getMapCount();
}
}
return mapCount;
}
static private PlayerMapStore getPlayerMapStore(UUID playerUUID)
{
PlayerMapStore store = getExistingPlayerMapStore(playerUUID);
if(store == null)
PlayerMapStore store;
synchronized(playerMaps)
{
store = new PlayerMapStore(playerUUID);
synchronized(playerMaps){playerMaps.add(store);}
store.load();
store = getExistingPlayerMapStore(playerUUID);
if(store == null)
{
store = new PlayerMapStore(playerUUID);
playerMaps.add(store);
store.load();
}
}
return store;
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.map;
import java.text.MessageFormat;
public class MapManagerException extends Exception
{
public enum Reason
{
MAXIMUM_PLAYER_MAPS_EXCEEDED("You have too many maps (maximum : {0})."),
MAXIMUM_SERVER_MAPS_EXCEEDED("The server ImageOnMap limit has been reached."),
IMAGEMAP_DOES_NOT_EXIST("The given map does not exist.");
private final String reasonString;
private Reason(String reasonString)
{
this.reasonString = reasonString;
}
public String getReasonString(Object ...arguments)
{
return MessageFormat.format(reasonString, arguments);
}
}
private final Reason reason;
public MapManagerException(Reason reason, Object ...arguments)
{
super(reason.getReasonString(arguments));
this.reason = reason;
}
public Reason getReason() { return reason; }
}

View File

@ -19,7 +19,9 @@
package fr.moribus.imageonmap.map;
import fr.moribus.imageonmap.ImageOnMap;
import fr.moribus.imageonmap.PluginConfiguration;
import fr.moribus.imageonmap.PluginLogger;
import fr.moribus.imageonmap.map.MapManagerException.Reason;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -39,6 +41,7 @@ public class PlayerMapStore implements ConfigurationSerializable
private final UUID playerUUID;
private final ArrayList<ImageMap> mapList = new ArrayList<ImageMap>();
private boolean modified = false;
private int mapCount = 0;
public PlayerMapStore(UUID playerUUID)
{
@ -63,18 +66,34 @@ public class PlayerMapStore implements ConfigurationSerializable
return false;
}
public synchronized void addMap(ImageMap map)
public synchronized void addMap(ImageMap map) throws MapManagerException
{
mapList.add(map);
checkMapLimit(map);
_addMap(map);
notifyModification();
}
public synchronized void deleteMap(ImageMap map)
private void _addMap(ImageMap map)
{
mapList.remove(map);
mapList.add(map);
mapCount += map.getMapCount();
}
public synchronized void deleteMap(ImageMap map) throws MapManagerException
{
_removeMap(map);
notifyModification();
}
private void _removeMap(ImageMap map) throws MapManagerException
{
if(!mapList.remove(map))
{
throw new MapManagerException(Reason.IMAGEMAP_DOES_NOT_EXIST);
}
mapCount -= map.getMapCount();
}
public synchronized boolean mapExists(String id)
{
for(ImageMap map : mapList)
@ -113,6 +132,20 @@ public class PlayerMapStore implements ConfigurationSerializable
return null;
}
public void checkMapLimit(ImageMap map) throws MapManagerException
{
checkMapLimit(map.getMapCount());
}
public void checkMapLimit(int newMapsCount) throws MapManagerException
{
int limit = PluginConfiguration.MAP_PLAYER_LIMIT.getInteger();
if(limit <= 0) return;
if(getMapCount() + newMapsCount > limit)
throw new MapManagerException(Reason.MAXIMUM_PLAYER_MAPS_EXCEEDED, limit);
}
/* ===== Getters & Setters ===== */
public UUID getUUID()
@ -130,6 +163,11 @@ public class PlayerMapStore implements ConfigurationSerializable
this.modified = true;
}
public synchronized int getMapCount()
{
return this.mapCount;
}
/* ****** Serializing ***** */
@Override
@ -159,13 +197,20 @@ public class PlayerMapStore implements ConfigurationSerializable
try
{
ImageMap newMap = ImageMap.fromConfig(tMap, playerUUID);
synchronized(this) {mapList.add(newMap);}
synchronized(this) {_addMap(newMap);}
}
catch(InvalidConfigurationException ex)
{
PluginLogger.LogWarning("Could not load map data : " + ex.getMessage());
}
}
try { checkMapLimit(0); }
catch(MapManagerException ex)
{
PluginLogger.LogWarning("Map limit exceeded for player " + playerUUID.toString() +
" (" + mapList.size() + " maps loaded).");
}
}
/* ****** Configuration Files management ***** */

View File

@ -101,4 +101,10 @@ public class PosterMap extends ImageMap
return (i / columnCount) + 1;
}
@Override
public int getMapCount()
{
return mapsIDs.length;
}
}

View File

@ -44,6 +44,12 @@ public class SingleMap extends ImageMap
return this.mapID == mapID;
}
@Override
public int getMapCount()
{
return 1;
}
/* ====== Serialization methods ====== */
public SingleMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException