Rework arena sign data store.
This commit constitutes a major rewrite of how arena signs are stored and loaded. It fixes an issue where an unloaded or missing world would cause MobArena to throw errors if there were any arena signs recorded in said world. The solution is to load signs of a given world when it is available, rather than loading all signs at once indiscriminately. At startup, signs for all _currently available_ worlds are loaded. This fixes the errors. When a world loads and a WorldLoadEvent is fired, signs in that world are loaded. This ensures that all valid signs in existing worlds will _eventually_ load. To keep things tidy, a WorldUnloadEvent will unload all signs for the unloaded world. Bukkit's own YAML deserialization implementation doesn't re-throw all deserialization errors, which means we can't actually catch the problem of missing worlds without doing an awkward "scan" of the deserialized objects. This prompted a rewrite of the serialization and data storage into a custom CSV-like format which is both simpler and also provides a lot more control over the process. Instead of relying on world _names_, the new format uses world _UUIDs_. While the rest of the plugin won't necessarily adapt well to a world being renamed, the signs data store should be resilient enough to handle it. Most of the actual sign rendering code and almost all of the template code is intact, but quite a lot of other classes have been rewritten or replaced. Some of the rewrites weren't strictly necessary, but because the components were already fairly small, rewrites were much faster and a lot less awkward than attempting to adapt existing code. Fixes #645
This commit is contained in:
parent
cf296704b0
commit
0da90f3963
|
@ -11,6 +11,8 @@ These changes will (most likely) be included in the next version.
|
|||
|
||||
|
||||
## [Unreleased]
|
||||
### Fixed
|
||||
- Arena signs in unloaded or missing worlds no longer break the startup procedure. Sign data is stored in a new format that MobArena will automatically migrate to on a per-world basis during startup.
|
||||
|
||||
## [0.105] - 2020-11-08
|
||||
### Minor breaking changes
|
||||
|
|
|
@ -13,7 +13,6 @@ import com.garbagemule.MobArena.metrics.IsolatedChatChart;
|
|||
import com.garbagemule.MobArena.metrics.MonsterInfightChart;
|
||||
import com.garbagemule.MobArena.metrics.PvpEnabledChart;
|
||||
import com.garbagemule.MobArena.metrics.VaultChart;
|
||||
import com.garbagemule.MobArena.signs.ArenaSign;
|
||||
import com.garbagemule.MobArena.signs.SignBootstrap;
|
||||
import com.garbagemule.MobArena.signs.SignListeners;
|
||||
import com.garbagemule.MobArena.things.NothingPickerParser;
|
||||
|
@ -29,7 +28,6 @@ import org.bukkit.ChatColor;
|
|||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
|
@ -94,7 +92,6 @@ public class MobArena extends JavaPlugin
|
|||
arenaMaster = null;
|
||||
}
|
||||
loadsConfigFile = null;
|
||||
ConfigurationSerialization.unregisterClass(ArenaSign.class);
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
|
@ -103,7 +100,6 @@ public class MobArena extends JavaPlugin
|
|||
setupArenaMaster();
|
||||
setupCommandHandler();
|
||||
|
||||
registerConfigurationSerializers();
|
||||
setupVault();
|
||||
setupBossAbilities();
|
||||
setupListeners();
|
||||
|
@ -133,10 +129,6 @@ public class MobArena extends JavaPlugin
|
|||
getCommand("ma").setExecutor(new CommandHandler(this));
|
||||
}
|
||||
|
||||
private void registerConfigurationSerializers() {
|
||||
ConfigurationSerialization.registerClass(ArenaSign.class);
|
||||
}
|
||||
|
||||
private void setupVault() {
|
||||
Plugin vaultPlugin = this.getServer().getPluginManager().getPlugin("Vault");
|
||||
if (vaultPlugin == null) {
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.configuration.serialization.ConfigurationSerializable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ArenaSign implements ConfigurationSerializable {
|
||||
class ArenaSign {
|
||||
|
||||
final Location location;
|
||||
final String templateId;
|
||||
|
@ -20,28 +16,4 @@ public class ArenaSign implements ConfigurationSerializable {
|
|||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("location", location);
|
||||
result.put("templateId", templateId);
|
||||
result.put("arenaId", arenaId);
|
||||
result.put("type", type);
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static ArenaSign deserialize(Map<String, Object> map) {
|
||||
try {
|
||||
Location location = (Location) map.get("location");
|
||||
String templateId = (String) map.get("templateId");
|
||||
String arenaId = (String) map.get("arenaId");
|
||||
String type = (String) map.get("type");
|
||||
return new ArenaSign(location, templateId, arenaId, type);
|
||||
} catch (ClassCastException e) {
|
||||
String msg = "An arena sign in " + SignStore.FILENAME + " is invalid! You may have to delete the file.";
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,24 +1,35 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import com.garbagemule.MobArena.events.*;
|
||||
import com.garbagemule.MobArena.events.ArenaEndEvent;
|
||||
import com.garbagemule.MobArena.events.ArenaPlayerDeathEvent;
|
||||
import com.garbagemule.MobArena.events.ArenaPlayerJoinEvent;
|
||||
import com.garbagemule.MobArena.events.ArenaPlayerLeaveEvent;
|
||||
import com.garbagemule.MobArena.events.ArenaPlayerReadyEvent;
|
||||
import com.garbagemule.MobArena.events.ArenaStartEvent;
|
||||
import com.garbagemule.MobArena.events.NewWaveEvent;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
class RedrawsSignsOnUpdates implements Listener {
|
||||
import java.util.List;
|
||||
|
||||
private final RedrawsArenaSigns redrawsArenaSigns;
|
||||
class HandlesArenaUpdates implements Listener {
|
||||
|
||||
private final SignStore signStore;
|
||||
private final SignRenderer signRenderer;
|
||||
private final BukkitScheduler scheduler;
|
||||
private final MobArena plugin;
|
||||
|
||||
RedrawsSignsOnUpdates(
|
||||
RedrawsArenaSigns redrawsArenaSigns,
|
||||
HandlesArenaUpdates(
|
||||
SignStore signStore,
|
||||
SignRenderer signRenderer,
|
||||
MobArena plugin
|
||||
) {
|
||||
this.redrawsArenaSigns = redrawsArenaSigns;
|
||||
this.signStore = signStore;
|
||||
this.signRenderer = signRenderer;
|
||||
this.scheduler = plugin.getServer().getScheduler();
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
@ -59,7 +70,10 @@ class RedrawsSignsOnUpdates implements Listener {
|
|||
}
|
||||
|
||||
private void handle(Arena arena) {
|
||||
scheduler.runTask(plugin, () -> redrawsArenaSigns.redraw(arena));
|
||||
scheduler.runTask(plugin, () -> {
|
||||
List<ArenaSign> signs = signStore.findByArenaId(arena.configName());
|
||||
signs.forEach(signRenderer::render);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,10 @@ class HandlesSignClicks implements Listener {
|
|||
private final InvokesSignAction invokesSignAction;
|
||||
private final Map<UUID, Long> cooldowns;
|
||||
|
||||
HandlesSignClicks(SignStore signStore, InvokesSignAction invokesSignAction) {
|
||||
HandlesSignClicks(
|
||||
SignStore signStore,
|
||||
InvokesSignAction invokesSignAction
|
||||
) {
|
||||
this.signStore = signStore;
|
||||
this.invokesSignAction = invokesSignAction;
|
||||
this.cooldowns = new HashMap<>();
|
||||
|
@ -35,8 +38,11 @@ class HandlesSignClicks implements Listener {
|
|||
if (!(block.getState() instanceof Sign)) {
|
||||
return;
|
||||
}
|
||||
signStore.findByLocation(block.getLocation())
|
||||
.ifPresent(sign -> purgeAndInvoke(sign, event.getPlayer()));
|
||||
|
||||
ArenaSign sign = signStore.findByLocation(block.getLocation());
|
||||
if (sign != null) {
|
||||
purgeAndInvoke(sign, event.getPlayer());
|
||||
}
|
||||
}
|
||||
|
||||
private void purgeAndInvoke(ArenaSign sign, Player player) {
|
||||
|
|
|
@ -1,65 +1,79 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.Messenger;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class HandlesSignCreation implements Listener {
|
||||
|
||||
private final StoresNewSign storesNewSign;
|
||||
private final RendersTemplateById rendersTemplate;
|
||||
private final SignCreator creator;
|
||||
private final SignWriter writer;
|
||||
private final SignStore store;
|
||||
private final SignRenderer renderer;
|
||||
private final Messenger messenger;
|
||||
private final Logger log;
|
||||
|
||||
HandlesSignCreation(
|
||||
StoresNewSign storesNewSign,
|
||||
RendersTemplateById rendersTemplate,
|
||||
Messenger messenger
|
||||
SignCreator creator,
|
||||
SignWriter writer,
|
||||
SignStore store,
|
||||
SignRenderer renderer,
|
||||
Messenger messenger,
|
||||
Logger log
|
||||
) {
|
||||
this.storesNewSign = storesNewSign;
|
||||
this.rendersTemplate = rendersTemplate;
|
||||
this.creator = creator;
|
||||
this.writer = writer;
|
||||
this.store = store;
|
||||
this.renderer = renderer;
|
||||
this.messenger = messenger;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void on(SignChangeEvent event) {
|
||||
if (!trim(event, 0).equalsIgnoreCase("[MA]")) {
|
||||
ArenaSign sign;
|
||||
try {
|
||||
sign = creator.create(event);
|
||||
if (sign == null) {
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
messenger.tell(event.getPlayer(), e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
Location location = event.getBlock().getLocation();
|
||||
String arenaId = trim(event, 1);
|
||||
String signType = trim(event, 2).toLowerCase();
|
||||
String templateId = trim(event, 3).toLowerCase();
|
||||
|
||||
if (templateId.isEmpty()) {
|
||||
templateId = signType;
|
||||
}
|
||||
|
||||
Player player = event.getPlayer();
|
||||
try {
|
||||
storesNewSign.store(location, arenaId, templateId, signType);
|
||||
messenger.tell(player, "New " + signType + " sign created for arena " + arenaId);
|
||||
|
||||
String[] lines = rendersTemplate.render(templateId, arenaId);
|
||||
IntStream.range(0, 4)
|
||||
.forEach(i -> event.setLine(i, lines[i]));
|
||||
} catch (IllegalArgumentException e) {
|
||||
messenger.tell(player, e.getMessage());
|
||||
writer.write(sign);
|
||||
} catch (Exception e) {
|
||||
messenger.tell(event.getPlayer(), "Sign creation failed:\n" + ChatColor.RED + e.getMessage());
|
||||
log.log(Level.SEVERE, "Failed to write arena sign to data file", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private String trim(SignChangeEvent event, int index) {
|
||||
String line = event.getLine(index);
|
||||
if (line == null) {
|
||||
return "";
|
||||
}
|
||||
return line.trim();
|
||||
store.add(sign);
|
||||
renderer.render(sign, event);
|
||||
|
||||
messenger.tell(event.getPlayer(), String.format(
|
||||
"New %s sign created for arena %s.",
|
||||
ChatColor.YELLOW + sign.type + ChatColor.RESET,
|
||||
ChatColor.GREEN + sign.arenaId + ChatColor.RESET
|
||||
));
|
||||
log.info(String.format(
|
||||
"%s created %s sign for '%s' at (%d,%d,%d) in '%s'.",
|
||||
event.getPlayer().getName(),
|
||||
sign.type,
|
||||
sign.arenaId,
|
||||
sign.location.getBlockX(),
|
||||
sign.location.getBlockY(),
|
||||
sign.location.getBlockZ(),
|
||||
sign.location.getWorld().getName()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,33 +1,67 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.Messenger;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class HandlesSignDestruction implements Listener {
|
||||
|
||||
private final RemovesSignAtLocation removesSignAtLocation;
|
||||
private final SignStore store;
|
||||
private final SignWriter writer;
|
||||
private final Messenger messenger;
|
||||
private final Logger log;
|
||||
|
||||
HandlesSignDestruction(
|
||||
RemovesSignAtLocation removesSignAtLocation,
|
||||
Messenger messenger
|
||||
SignStore store,
|
||||
SignWriter writer,
|
||||
Messenger messenger,
|
||||
Logger log
|
||||
) {
|
||||
this.removesSignAtLocation = removesSignAtLocation;
|
||||
this.store = store;
|
||||
this.writer = writer;
|
||||
this.messenger = messenger;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void on(BlockBreakEvent event) {
|
||||
Location location = event.getBlock().getLocation();
|
||||
removesSignAtLocation.remove(location)
|
||||
.ifPresent(sign -> messenger.tell(
|
||||
event.getPlayer(),
|
||||
"Removed " + sign.type + " sign for arena " + sign.arenaId
|
||||
));
|
||||
|
||||
ArenaSign sign = store.removeByLocation(location);
|
||||
if (sign == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
writer.erase(sign);
|
||||
} catch (Exception e) {
|
||||
messenger.tell(event.getPlayer(), "Sign destruction failed:\n" + ChatColor.RED + e.getMessage());
|
||||
log.log(Level.SEVERE, "Failed to erase arena sign from data file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
messenger.tell(event.getPlayer(), String.format(
|
||||
"Removed %s sign for arena %s.",
|
||||
ChatColor.YELLOW + sign.type + ChatColor.RESET,
|
||||
ChatColor.GREEN + sign.arenaId + ChatColor.RESET
|
||||
));
|
||||
log.info(String.format(
|
||||
"%s destroyed %s sign for '%s' at (%d,%d,%d) in '%s'.",
|
||||
event.getPlayer().getName(),
|
||||
sign.type,
|
||||
sign.arenaId,
|
||||
sign.location.getBlockX(),
|
||||
sign.location.getBlockY(),
|
||||
sign.location.getBlockZ(),
|
||||
sign.location.getWorld().getName()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class HandlesWorldLoad implements Listener {
|
||||
|
||||
private final SignDataMigrator migrator;
|
||||
private final SignReader reader;
|
||||
private final SignStore store;
|
||||
private final Logger log;
|
||||
|
||||
HandlesWorldLoad(
|
||||
SignDataMigrator migrator,
|
||||
SignReader reader,
|
||||
SignStore store,
|
||||
Logger log
|
||||
) {
|
||||
this.migrator = migrator;
|
||||
this.reader = reader;
|
||||
this.store = store;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void on(WorldLoadEvent event) {
|
||||
World world = event.getWorld();
|
||||
|
||||
try {
|
||||
migrator.migrate(world);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Failed to migrate sign data for world '" + world.getName() + "'", e);
|
||||
}
|
||||
|
||||
List<ArenaSign> loaded;
|
||||
try {
|
||||
loaded = reader.read(world);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Failed to read from arena sign data file", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!loaded.isEmpty()) {
|
||||
loaded.forEach(store::add);
|
||||
log.info(loaded.size() + " arena sign(s) loaded due to loading of world '" + world.getName() + "' (" + world.getUID().toString() + ").");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class HandlesWorldUnload implements Listener {
|
||||
|
||||
private final SignStore store;
|
||||
private final Logger log;
|
||||
|
||||
HandlesWorldUnload(
|
||||
SignStore store,
|
||||
Logger log
|
||||
) {
|
||||
this.store = store;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void on(WorldUnloadEvent event) {
|
||||
World world = event.getWorld();
|
||||
List<ArenaSign> removed = store.removeByWorld(world);
|
||||
if (!removed.isEmpty()) {
|
||||
log.info(removed.size() + " arena sign(s) unloaded due to unloading of world '" + world.getName() + "' (" + world.getUID().toString() + ").");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class LoadsSignStore {
|
||||
|
||||
private final MobArena plugin;
|
||||
|
||||
LoadsSignStore(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
SignStore load() {
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
try {
|
||||
File data = new File(plugin.getDataFolder(), "data");
|
||||
yaml.load(new File(data, SignStore.FILENAME));
|
||||
} catch (FileNotFoundException e) {
|
||||
return new SignStore(Collections.emptyList());
|
||||
} catch (InvalidConfigurationException e) {
|
||||
String msg = SignStore.FILENAME + " is invalid! You may have to delete it.";
|
||||
throw new IllegalStateException(msg, e);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
List<ArenaSign> signs = yaml.getList("signs").stream()
|
||||
.filter(raw -> raw instanceof ArenaSign)
|
||||
.map(raw -> (ArenaSign) raw)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
plugin.getLogger().info("Loaded " + signs.size() + " arena signs.");
|
||||
|
||||
return new SignStore(signs);
|
||||
}
|
||||
|
||||
}
|
|
@ -13,13 +13,7 @@ import java.util.Map;
|
|||
|
||||
class LoadsTemplateStore {
|
||||
|
||||
private final MobArena plugin;
|
||||
|
||||
LoadsTemplateStore(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
TemplateStore read() {
|
||||
static TemplateStore load(MobArena plugin) {
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
try {
|
||||
File file = new File(plugin.getDataFolder(), TemplateStore.FILENAME);
|
||||
|
@ -48,7 +42,7 @@ class LoadsTemplateStore {
|
|||
return new TemplateStore(map);
|
||||
}
|
||||
|
||||
private void validateTemplateNode(String key, YamlConfiguration yaml) {
|
||||
private static void validateTemplateNode(String key, YamlConfiguration yaml) {
|
||||
List<?> list = yaml.getList(key);
|
||||
if (list == null) {
|
||||
String msg = "Template " + key + " in " + TemplateStore.FILENAME + " is not a list!";
|
||||
|
@ -62,21 +56,21 @@ class LoadsTemplateStore {
|
|||
});
|
||||
}
|
||||
|
||||
private String stripStateSuffix(String id) {
|
||||
private static String stripStateSuffix(String id) {
|
||||
if (hasStateSuffix(id)) {
|
||||
return id.split("-", -1)[0];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean hasStateSuffix(String id) {
|
||||
private static boolean hasStateSuffix(String id) {
|
||||
return id.endsWith("-idle")
|
||||
|| id.endsWith("-joining")
|
||||
|| id.endsWith("-ready")
|
||||
|| id.endsWith("-running");
|
||||
}
|
||||
|
||||
private Template loadTemplate(String id, YamlConfiguration yaml) {
|
||||
private static Template loadTemplate(String id, YamlConfiguration yaml) {
|
||||
String[] base = getLines(yaml, id);
|
||||
String[] idle = getLines(yaml, id + "-idle");
|
||||
String[] joining = getLines(yaml, id + "-joining");
|
||||
|
@ -92,7 +86,7 @@ class LoadsTemplateStore {
|
|||
.build();
|
||||
}
|
||||
|
||||
private String[] getLines(YamlConfiguration config, String id) {
|
||||
private static String[] getLines(YamlConfiguration config, String id) {
|
||||
List<String> list = config.getStringList(id);
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class RedrawsArenaSigns {
|
||||
|
||||
private final SignStore signStore;
|
||||
private final TemplateStore templateStore;
|
||||
private final RendersTemplate rendersTemplate;
|
||||
private final SetsLines setsSignLines;
|
||||
|
||||
RedrawsArenaSigns(
|
||||
SignStore signStore,
|
||||
TemplateStore templateStore,
|
||||
RendersTemplate rendersTemplate,
|
||||
SetsLines setsSignLines
|
||||
) {
|
||||
this.signStore = signStore;
|
||||
this.templateStore = templateStore;
|
||||
this.rendersTemplate = rendersTemplate;
|
||||
this.setsSignLines = setsSignLines;
|
||||
}
|
||||
|
||||
void redraw(Arena arena) {
|
||||
List<ArenaSign> signs = signStore.findByArenaId(arena.configName());
|
||||
|
||||
Map<String, String[]> rendered = signs.stream()
|
||||
.map(sign -> sign.templateId)
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(
|
||||
templateId -> templateId,
|
||||
templateId -> render(templateId, arena)
|
||||
));
|
||||
|
||||
signs.forEach(sign -> setsSignLines.set(
|
||||
sign.location,
|
||||
rendered.get(sign.templateId)
|
||||
));
|
||||
}
|
||||
|
||||
private String[] render(String templateId, Arena arena) {
|
||||
return templateStore.findById(templateId)
|
||||
.map(template -> rendersTemplate.render(template, arena))
|
||||
.orElseGet(() -> notFound(templateId));
|
||||
}
|
||||
|
||||
private static String[] notFound(String templateId) {
|
||||
return new String[]{
|
||||
String.join(ChatColor.MAGIC + "b" + ChatColor.RESET, "BROKEN".split("")),
|
||||
"Template",
|
||||
ChatColor.BOLD + templateId,
|
||||
"not found! :("
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
class RemovesSignAtLocation {
|
||||
|
||||
private final SignStore signStore;
|
||||
private final SavesSignStore savesSignStore;
|
||||
|
||||
RemovesSignAtLocation(
|
||||
SignStore signStore,
|
||||
SavesSignStore savesSignStore
|
||||
) {
|
||||
this.signStore = signStore;
|
||||
this.savesSignStore = savesSignStore;
|
||||
}
|
||||
|
||||
Optional<ArenaSign> remove(Location location) {
|
||||
Optional<ArenaSign> sign = signStore.remove(location);
|
||||
sign.ifPresent(s -> savesSignStore.save(signStore));
|
||||
return sign;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static java.lang.String.valueOf;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -13,6 +11,8 @@ import com.garbagemule.MobArena.framework.Arena;
|
|||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import static java.lang.String.valueOf;
|
||||
|
||||
class RendersTemplate {
|
||||
|
||||
// Regex Pattern for player list variables.
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
|
||||
class RendersTemplateById {
|
||||
|
||||
private final ArenaMaster arenaMaster;
|
||||
private final TemplateStore templateStore;
|
||||
private final RendersTemplate rendersTemplate;
|
||||
|
||||
RendersTemplateById(
|
||||
ArenaMaster arenaMaster,
|
||||
TemplateStore templateStore,
|
||||
RendersTemplate rendersTemplate
|
||||
) {
|
||||
this.arenaMaster = arenaMaster;
|
||||
this.templateStore = templateStore;
|
||||
this.rendersTemplate = rendersTemplate;
|
||||
}
|
||||
|
||||
String[] render(String templateId, String arenaId) {
|
||||
Template template = templateStore.findById(templateId)
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Template " + templateId + " not found"
|
||||
));
|
||||
|
||||
Arena arena = arenaMaster.getArenaWithName(arenaId);
|
||||
if (arena == null) {
|
||||
throw new IllegalStateException("Arena " + arenaId + " not found");
|
||||
}
|
||||
|
||||
return rendersTemplate.render(template, arena);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class SavesSignStore {
|
||||
|
||||
private final MobArena plugin;
|
||||
|
||||
SavesSignStore(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
void save(SignStore signStore) {
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
List<ArenaSign> values = new ArrayList<>(signStore.findAll());
|
||||
yaml.set("signs", values);
|
||||
try {
|
||||
File data = new File(plugin.getDataFolder(), "data");
|
||||
yaml.options().header("MobArena Sign Store\n\nPlease DON'T edit this file by hand!\n");
|
||||
yaml.save(new File(data, SignStore.FILENAME));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Sign;
|
||||
|
||||
class SetsLines {
|
||||
|
||||
void set(Location location, String[] lines) {
|
||||
BlockState state = location.getBlock().getState();
|
||||
if (!(state instanceof Sign)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Sign sign = (Sign) state;
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
sign.setLine(i, lines[i]);
|
||||
}
|
||||
sign.update();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +1,54 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.MobArena;
|
||||
import org.bukkit.World;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SignBootstrap {
|
||||
|
||||
private final MobArena plugin;
|
||||
private final SignStore signStore;
|
||||
private final TemplateStore templateStore;
|
||||
|
||||
private InvokesSignAction invokesSignAction;
|
||||
private RedrawsArenaSigns redrawsArenaSigns;
|
||||
private RemovesSignAtLocation removesSignAtLocation;
|
||||
private RendersTemplate rendersTemplate;
|
||||
private RendersTemplateById rendersTemplateById;
|
||||
private SetsLines setsLines;
|
||||
private StoresNewSign storesNewSign;
|
||||
private SavesSignStore savesSignStore;
|
||||
private SignCreator signCreator;
|
||||
private SignDataMigrator signDataMigrator;
|
||||
private SignFile signFile;
|
||||
private SignReader signReader;
|
||||
private SignRenderer signRenderer;
|
||||
private SignSerializer signSerializer;
|
||||
private SignStore signStore;
|
||||
private SignWriter signWriter;
|
||||
private TemplateStore templateStore;
|
||||
|
||||
private SignBootstrap(
|
||||
MobArena plugin,
|
||||
SignStore signStore,
|
||||
TemplateStore templateStore
|
||||
) {
|
||||
private SignBootstrap(MobArena plugin) {
|
||||
this.plugin = plugin;
|
||||
this.signStore = signStore;
|
||||
this.templateStore = templateStore;
|
||||
}
|
||||
|
||||
MobArena getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
SignStore getSignStore() {
|
||||
return signStore;
|
||||
SignFile getSignFile() {
|
||||
if (signFile == null) {
|
||||
Path root = plugin.getDataFolder().toPath();
|
||||
Path data = root.resolve("data");
|
||||
Path file = data.resolve("signs.csv");
|
||||
signFile = new SignFile(file);
|
||||
}
|
||||
return signFile;
|
||||
}
|
||||
|
||||
TemplateStore getTemplateStore() {
|
||||
if (templateStore == null) {
|
||||
templateStore = LoadsTemplateStore.load(plugin);
|
||||
}
|
||||
return templateStore;
|
||||
}
|
||||
|
||||
|
@ -50,28 +62,6 @@ public class SignBootstrap {
|
|||
return invokesSignAction;
|
||||
}
|
||||
|
||||
RedrawsArenaSigns getRedrawsArenaSigns() {
|
||||
if (redrawsArenaSigns == null) {
|
||||
redrawsArenaSigns = new RedrawsArenaSigns(
|
||||
getSignStore(),
|
||||
getTemplateStore(),
|
||||
getRendersTemplate(),
|
||||
getSetsLines()
|
||||
);
|
||||
}
|
||||
return redrawsArenaSigns;
|
||||
}
|
||||
|
||||
RemovesSignAtLocation getRemovesSignAtLocation() {
|
||||
if (removesSignAtLocation == null) {
|
||||
removesSignAtLocation = new RemovesSignAtLocation(
|
||||
getSignStore(),
|
||||
getSavesSignStore()
|
||||
);
|
||||
}
|
||||
return removesSignAtLocation;
|
||||
}
|
||||
|
||||
RendersTemplate getRendersTemplate() {
|
||||
if (rendersTemplate == null) {
|
||||
rendersTemplate = new RendersTemplate();
|
||||
|
@ -79,52 +69,140 @@ public class SignBootstrap {
|
|||
return rendersTemplate;
|
||||
}
|
||||
|
||||
RendersTemplateById getRendersTemplateById() {
|
||||
if (rendersTemplateById == null) {
|
||||
rendersTemplateById = new RendersTemplateById(
|
||||
SignCreator getSignCreator() {
|
||||
if (signCreator == null) {
|
||||
signCreator = new SignCreator(
|
||||
plugin.getArenaMaster(),
|
||||
getTemplateStore()
|
||||
);
|
||||
}
|
||||
return signCreator;
|
||||
}
|
||||
|
||||
SignDataMigrator getSignDataMigrator() {
|
||||
if (signDataMigrator == null) {
|
||||
Path root = plugin.getDataFolder().toPath();
|
||||
Path data = root.resolve("data");
|
||||
Path legacyFile = data.resolve("signs.data");
|
||||
Path pendingFile = data.resolve("signs.tmp");
|
||||
signDataMigrator = new SignDataMigrator(
|
||||
legacyFile,
|
||||
pendingFile,
|
||||
new Yaml(),
|
||||
getSignFile(),
|
||||
plugin.getLogger()
|
||||
);
|
||||
}
|
||||
return signDataMigrator;
|
||||
}
|
||||
|
||||
SignReader getSignReader() {
|
||||
if (signReader == null) {
|
||||
signReader = new SignReader(
|
||||
getSignFile(),
|
||||
getSignSerializer(),
|
||||
plugin.getLogger()
|
||||
);
|
||||
}
|
||||
return signReader;
|
||||
}
|
||||
|
||||
SignRenderer getSignRenderer() {
|
||||
if (signRenderer == null) {
|
||||
signRenderer = new SignRenderer(
|
||||
getTemplateStore(),
|
||||
plugin.getArenaMaster(),
|
||||
getRendersTemplate()
|
||||
);
|
||||
}
|
||||
return rendersTemplateById;
|
||||
return signRenderer;
|
||||
}
|
||||
|
||||
SetsLines getSetsLines() {
|
||||
if (setsLines == null) {
|
||||
setsLines = new SetsLines();
|
||||
SignSerializer getSignSerializer() {
|
||||
if (signSerializer == null) {
|
||||
signSerializer = new SignSerializer();
|
||||
}
|
||||
return setsLines;
|
||||
return signSerializer;
|
||||
}
|
||||
|
||||
StoresNewSign getStoresNewSign() {
|
||||
if (storesNewSign == null) {
|
||||
storesNewSign = new StoresNewSign(
|
||||
plugin.getArenaMaster(),
|
||||
getTemplateStore(),
|
||||
getSignStore(),
|
||||
getSavesSignStore()
|
||||
SignStore getSignStore() {
|
||||
if (signStore == null) {
|
||||
signStore = new SignStore();
|
||||
}
|
||||
return signStore;
|
||||
}
|
||||
|
||||
SignWriter getSignWriter() {
|
||||
if (signWriter == null) {
|
||||
signWriter = new SignWriter(
|
||||
getSignFile(),
|
||||
getSignSerializer(),
|
||||
plugin.getLogger()
|
||||
);
|
||||
}
|
||||
return storesNewSign;
|
||||
}
|
||||
|
||||
SavesSignStore getSavesSignStore() {
|
||||
if (savesSignStore == null) {
|
||||
savesSignStore = new SavesSignStore(plugin);
|
||||
}
|
||||
return savesSignStore;
|
||||
return signWriter;
|
||||
}
|
||||
|
||||
public static SignBootstrap create(MobArena plugin) {
|
||||
TemplateStore templateStore = new LoadsTemplateStore(plugin).read();
|
||||
SignStore signStore = new LoadsSignStore(plugin).load();
|
||||
SignBootstrap bootstrap = new SignBootstrap(plugin);
|
||||
|
||||
SignBootstrap bootstrap = new SignBootstrap(plugin, signStore, templateStore);
|
||||
|
||||
plugin.getArenaMaster().getArenas()
|
||||
.forEach(bootstrap.getRedrawsArenaSigns()::redraw);
|
||||
migrateData(bootstrap);
|
||||
loadSigns(bootstrap);
|
||||
initialRender(bootstrap);
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
private static void migrateData(SignBootstrap bootstrap) {
|
||||
SignDataMigrator migrator = bootstrap.getSignDataMigrator();
|
||||
MobArena plugin = bootstrap.getPlugin();
|
||||
Logger log = plugin.getLogger();
|
||||
|
||||
try {
|
||||
migrator.init();
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Failed initial sign data migration step", e);
|
||||
return;
|
||||
}
|
||||
|
||||
for (World world : plugin.getServer().getWorlds()) {
|
||||
try {
|
||||
migrator.migrate(world);
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Failed to migrate sign data for world '" + world.getName() + "'", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadSigns(SignBootstrap bootstrap) {
|
||||
SignReader reader = bootstrap.getSignReader();
|
||||
SignStore store = bootstrap.getSignStore();
|
||||
MobArena plugin = bootstrap.getPlugin();
|
||||
Logger log = plugin.getLogger();
|
||||
|
||||
List<ArenaSign> loaded = new ArrayList<>();
|
||||
try {
|
||||
for (World world : plugin.getServer().getWorlds()) {
|
||||
loaded.addAll(reader.read(world));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.log(Level.SEVERE, "Failed to read from arena sign data file", e);
|
||||
}
|
||||
|
||||
if (!loaded.isEmpty()) {
|
||||
loaded.forEach(store::add);
|
||||
log.info(loaded.size() + " arena sign(s) loaded.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void initialRender(SignBootstrap bootstrap) {
|
||||
SignStore store = bootstrap.getSignStore();
|
||||
SignRenderer renderer = bootstrap.getSignRenderer();
|
||||
|
||||
bootstrap.getPlugin().getArenaMaster().getArenas().forEach(arena -> {
|
||||
List<ArenaSign> signs = store.findByArenaId(arena.configName());
|
||||
signs.forEach(renderer::render);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
|
||||
class SignCreator {
|
||||
|
||||
private final ArenaMaster arenaMaster;
|
||||
private final TemplateStore templateStore;
|
||||
|
||||
SignCreator(
|
||||
ArenaMaster arenaMaster,
|
||||
TemplateStore templateStore
|
||||
) {
|
||||
this.arenaMaster = arenaMaster;
|
||||
this.templateStore = templateStore;
|
||||
}
|
||||
|
||||
ArenaSign create(SignChangeEvent event) {
|
||||
if (!trim(event, 0).equalsIgnoreCase("[MA]")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Location location = event.getBlock().getLocation();
|
||||
String arenaId = getArenaId(event);
|
||||
String signType = getSignType(event);
|
||||
String templateId = getTemplateId(event, signType);
|
||||
|
||||
return new ArenaSign(location, templateId, arenaId, signType);
|
||||
}
|
||||
|
||||
private String getArenaId(SignChangeEvent event) {
|
||||
String arenaId = trim(event, 1);
|
||||
if (arenaId.isEmpty()) {
|
||||
throw new IllegalArgumentException("Missing arena name on line 2");
|
||||
}
|
||||
|
||||
Arena arena = arenaMaster.getArenaWithName(arenaId);
|
||||
if (arena == null) {
|
||||
throw new IllegalArgumentException("Arena " + arenaId + " not found");
|
||||
}
|
||||
|
||||
return arena.getSlug();
|
||||
}
|
||||
|
||||
private String getSignType(SignChangeEvent event) {
|
||||
String signType = trim(event, 2).toLowerCase();
|
||||
if (signType.isEmpty()) {
|
||||
throw new IllegalArgumentException("Missing sign type on line 3");
|
||||
}
|
||||
|
||||
switch (signType) {
|
||||
case "info":
|
||||
case "join":
|
||||
case "leave": {
|
||||
return signType;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Invalid sign type: " + signType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getTemplateId(SignChangeEvent event, String signType) {
|
||||
String line = trim(event, 3);
|
||||
String templateId = !line.isEmpty() ? line : signType;
|
||||
|
||||
templateStore
|
||||
.findById(templateId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Template " + templateId + " not found"));
|
||||
|
||||
return templateId;
|
||||
}
|
||||
|
||||
private String trim(SignChangeEvent event, int index) {
|
||||
String line = event.getLine(index);
|
||||
if (line == null) {
|
||||
return "";
|
||||
}
|
||||
return line.trim();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.util.Slugs;
|
||||
import org.bukkit.World;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class SignDataMigrator {
|
||||
|
||||
private static final OpenOption[] WRITE_OPTIONS = {
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.WRITE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING,
|
||||
StandardOpenOption.SYNC
|
||||
};
|
||||
|
||||
private final Path legacyFile;
|
||||
private final Path pendingFile;
|
||||
private final Yaml yaml;
|
||||
private final SignFile signFile;
|
||||
private final Logger log;
|
||||
|
||||
SignDataMigrator(
|
||||
Path legacyFile,
|
||||
Path pendingFile,
|
||||
Yaml yaml,
|
||||
SignFile signFile,
|
||||
Logger log
|
||||
) {
|
||||
this.legacyFile = legacyFile;
|
||||
this.pendingFile = pendingFile;
|
||||
this.yaml = yaml;
|
||||
this.signFile = signFile;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
void init() throws IOException {
|
||||
if (!Files.exists(legacyFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Legacy sign data found, migrating...");
|
||||
List<Map<String, ?>> signs = loadSignsInLegacyFile();
|
||||
List<String> lines = new ArrayList<>(signs.size());
|
||||
for (Map<String, ?> sign : signs) {
|
||||
String line = convert(sign);
|
||||
lines.add(line);
|
||||
}
|
||||
Files.write(pendingFile, lines, WRITE_OPTIONS);
|
||||
|
||||
Files.delete(legacyFile);
|
||||
log.info("Legacy sign data migrated to temporary format.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Map<String, ?>> loadSignsInLegacyFile() throws IOException {
|
||||
byte[] bytes = Files.readAllBytes(legacyFile);
|
||||
Map<String, ?> map = yaml.load(new String(bytes));
|
||||
if (map != null && map.containsKey("signs")) {
|
||||
List<Map<String, ?>> signs = (List<Map<String, ?>>) map.get("signs");
|
||||
if (signs != null) {
|
||||
return signs;
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private String convert(Map<String, ?> sign) {
|
||||
Map<String, ?> location = (Map<String, Object>) sign.get("location");
|
||||
String world = (String) location.get("world");
|
||||
String x = String.valueOf(((Number) location.get("x")).intValue());
|
||||
String y = String.valueOf(((Number) location.get("y")).intValue());
|
||||
String z = String.valueOf(((Number) location.get("z")).intValue());
|
||||
String arenaId = Slugs.create((String) sign.get("arenaId"));
|
||||
String type = (String) sign.get("type");
|
||||
String templateId = (String) sign.get("templateId");
|
||||
return String.join(";", world, x, y, z, arenaId, type, templateId);
|
||||
}
|
||||
|
||||
void migrate(World world) throws IOException {
|
||||
if (!Files.exists(pendingFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> lines = Files.readAllLines(pendingFile);
|
||||
|
||||
// Partition the lines into two lists; the ones to take from
|
||||
// the file for migration and the ones to skip.
|
||||
String name = world.getName();
|
||||
String namePrefix = name + ";";
|
||||
List<String> take = new ArrayList<>(lines.size());
|
||||
List<String> skip = new ArrayList<>(lines.size());
|
||||
for (String line : lines) {
|
||||
if (line.startsWith(namePrefix)) {
|
||||
take.add(line);
|
||||
} else {
|
||||
skip.add(line);
|
||||
}
|
||||
}
|
||||
if (take.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate all matching lines to the new sign data file.
|
||||
log.info("Found temporary sign data for world '" + name + "', migrating...");
|
||||
String id = world.getUID().toString();
|
||||
String idPrefix = id + ";";
|
||||
for (String line : take) {
|
||||
signFile.append(idPrefix + line);
|
||||
}
|
||||
signFile.save();
|
||||
log.info("Temporary data for " + take.size() + " sign(s) in world '" + name + "' migrated.");
|
||||
|
||||
// Write the remaining lines back down into the pending file
|
||||
// if there are any left. Otherwise, migration has completed
|
||||
// and we are done.
|
||||
if (!skip.isEmpty()) {
|
||||
Files.write(pendingFile, skip, WRITE_OPTIONS);
|
||||
} else {
|
||||
Files.delete(pendingFile);
|
||||
log.info("Sign data migration complete.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class SignFile {
|
||||
|
||||
private static final OpenOption[] WRITE_OPTIONS = {
|
||||
StandardOpenOption.WRITE,
|
||||
StandardOpenOption.TRUNCATE_EXISTING,
|
||||
StandardOpenOption.SYNC
|
||||
};
|
||||
|
||||
private final Path file;
|
||||
private final List<String> lines;
|
||||
|
||||
private boolean stale;
|
||||
|
||||
SignFile(Path file) {
|
||||
this.file = file;
|
||||
this.lines = new ArrayList<>();
|
||||
this.stale = true;
|
||||
}
|
||||
|
||||
List<String> lines() throws IOException {
|
||||
if (stale) {
|
||||
load();
|
||||
}
|
||||
return new ArrayList<>(lines);
|
||||
}
|
||||
|
||||
void append(String line) {
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
void erase(String line) {
|
||||
lines.remove(line);
|
||||
}
|
||||
|
||||
void load() throws IOException {
|
||||
if (Files.notExists(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
lines.clear();
|
||||
lines.addAll(Files.readAllLines(file));
|
||||
|
||||
stale = false;
|
||||
}
|
||||
|
||||
void save() throws IOException {
|
||||
if (Files.notExists(file)) {
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.createFile(file);
|
||||
}
|
||||
|
||||
Files.write(file, lines, WRITE_OPTIONS);
|
||||
|
||||
stale = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -16,14 +16,32 @@ public class SignListeners {
|
|||
}
|
||||
|
||||
public void register(SignBootstrap bootstrap) {
|
||||
listeners.add(load(bootstrap));
|
||||
listeners.add(unload(bootstrap));
|
||||
listeners.add(clicks(bootstrap));
|
||||
listeners.add(creation(bootstrap));
|
||||
listeners.add(destruction(bootstrap));
|
||||
listeners.add(redraw(bootstrap));
|
||||
listeners.add(updates(bootstrap));
|
||||
|
||||
listeners.forEach(listener -> register(listener, bootstrap));
|
||||
}
|
||||
|
||||
private HandlesWorldLoad load(SignBootstrap bootstrap) {
|
||||
return new HandlesWorldLoad(
|
||||
bootstrap.getSignDataMigrator(),
|
||||
bootstrap.getSignReader(),
|
||||
bootstrap.getSignStore(),
|
||||
bootstrap.getPlugin().getLogger()
|
||||
);
|
||||
}
|
||||
|
||||
private HandlesWorldUnload unload(SignBootstrap bootstrap) {
|
||||
return new HandlesWorldUnload(
|
||||
bootstrap.getSignStore(),
|
||||
bootstrap.getPlugin().getLogger()
|
||||
);
|
||||
}
|
||||
|
||||
private HandlesSignClicks clicks(SignBootstrap bootstrap) {
|
||||
return new HandlesSignClicks(
|
||||
bootstrap.getSignStore(),
|
||||
|
@ -33,22 +51,28 @@ public class SignListeners {
|
|||
|
||||
private HandlesSignCreation creation(SignBootstrap bootstrap) {
|
||||
return new HandlesSignCreation(
|
||||
bootstrap.getStoresNewSign(),
|
||||
bootstrap.getRendersTemplateById(),
|
||||
bootstrap.getPlugin().getGlobalMessenger()
|
||||
bootstrap.getSignCreator(),
|
||||
bootstrap.getSignWriter(),
|
||||
bootstrap.getSignStore(),
|
||||
bootstrap.getSignRenderer(),
|
||||
bootstrap.getPlugin().getGlobalMessenger(),
|
||||
bootstrap.getPlugin().getLogger()
|
||||
);
|
||||
}
|
||||
|
||||
private HandlesSignDestruction destruction(SignBootstrap bootstrap) {
|
||||
return new HandlesSignDestruction(
|
||||
bootstrap.getRemovesSignAtLocation(),
|
||||
bootstrap.getPlugin().getGlobalMessenger()
|
||||
bootstrap.getSignStore(),
|
||||
bootstrap.getSignWriter(),
|
||||
bootstrap.getPlugin().getGlobalMessenger(),
|
||||
bootstrap.getPlugin().getLogger()
|
||||
);
|
||||
}
|
||||
|
||||
private RedrawsSignsOnUpdates redraw(SignBootstrap bootstrap) {
|
||||
return new RedrawsSignsOnUpdates(
|
||||
bootstrap.getRedrawsArenaSigns(),
|
||||
private HandlesArenaUpdates updates(SignBootstrap bootstrap) {
|
||||
return new HandlesArenaUpdates(
|
||||
bootstrap.getSignStore(),
|
||||
bootstrap.getSignRenderer(),
|
||||
bootstrap.getPlugin()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class SignReader {
|
||||
|
||||
private final SignFile file;
|
||||
private final SignSerializer serializer;
|
||||
private final Logger log;
|
||||
|
||||
SignReader(
|
||||
SignFile file,
|
||||
SignSerializer serializer,
|
||||
Logger log
|
||||
) {
|
||||
this.file = file;
|
||||
this.serializer = serializer;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
List<ArenaSign> read(World world) throws IOException {
|
||||
List<ArenaSign> result = new ArrayList<>();
|
||||
String id = world.getUID().toString();
|
||||
String prefix = id + ";";
|
||||
for (String line : file.lines()) {
|
||||
if (line.startsWith(prefix)) {
|
||||
try {
|
||||
ArenaSign sign = serializer.deserialize(line, world);
|
||||
result.add(sign);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.SEVERE, "Failed to deserialize arena sign from line:\n" + line, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
|
||||
class SignRenderer {
|
||||
|
||||
private final TemplateStore templateStore;
|
||||
private final ArenaMaster arenaMaster;
|
||||
private final RendersTemplate rendersTemplate;
|
||||
|
||||
SignRenderer(
|
||||
TemplateStore templateStore,
|
||||
ArenaMaster arenaMaster,
|
||||
RendersTemplate rendersTemplate
|
||||
) {
|
||||
this.templateStore = templateStore;
|
||||
this.arenaMaster = arenaMaster;
|
||||
this.rendersTemplate = rendersTemplate;
|
||||
}
|
||||
|
||||
void render(ArenaSign sign) {
|
||||
BlockState state = sign.location.getBlock().getState();
|
||||
if (state instanceof Sign) {
|
||||
Sign target = (Sign) state;
|
||||
String[] lines = renderLines(sign);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
target.setLine(i, lines[i]);
|
||||
}
|
||||
target.update();
|
||||
}
|
||||
}
|
||||
|
||||
void render(ArenaSign sign, SignChangeEvent event) {
|
||||
String[] lines = renderLines(sign);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
event.setLine(i, lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] renderLines(ArenaSign sign) {
|
||||
String templateId = sign.templateId;
|
||||
String arenaId = sign.arenaId;
|
||||
|
||||
Template template = templateStore.findById(templateId).orElse(null);
|
||||
if (template == null) {
|
||||
return templateNotFound(templateId);
|
||||
}
|
||||
|
||||
Arena arena = arenaMaster.getArenaWithName(arenaId);
|
||||
if (arena == null) {
|
||||
return arenaNotFound(arenaId);
|
||||
}
|
||||
|
||||
return rendersTemplate.render(template, arena);
|
||||
}
|
||||
|
||||
private String[] templateNotFound(String templateId) {
|
||||
return new String[]{
|
||||
ChatColor.RED + "[ERROR]",
|
||||
"Template",
|
||||
ChatColor.YELLOW + templateId,
|
||||
"not found :("
|
||||
};
|
||||
}
|
||||
|
||||
private String[] arenaNotFound(String arenaId) {
|
||||
return new String[]{
|
||||
ChatColor.RED + "[ERROR]",
|
||||
"Arena",
|
||||
ChatColor.YELLOW + arenaId,
|
||||
"not found :("
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
class SignSerializer {
|
||||
|
||||
String serialize(ArenaSign sign) {
|
||||
String id = sign.location.getWorld().getUID().toString();
|
||||
String name = sign.location.getWorld().getName();
|
||||
|
||||
String x = String.valueOf(sign.location.getBlockX());
|
||||
String y = String.valueOf(sign.location.getBlockY());
|
||||
String z = String.valueOf(sign.location.getBlockZ());
|
||||
|
||||
String arenaId = sign.arenaId;
|
||||
String type = sign.type;
|
||||
String templateId = sign.templateId;
|
||||
|
||||
return String.join(";", id, name, x, y, z, arenaId, type, templateId);
|
||||
}
|
||||
|
||||
ArenaSign deserialize(String input, World world) {
|
||||
String[] parts = input.split(";");
|
||||
if (parts.length != 8) {
|
||||
throw new IllegalArgumentException("Invalid input; expected 8 parts, got " + parts.length);
|
||||
}
|
||||
|
||||
String id = parts[0];
|
||||
if (!id.equals(world.getUID().toString())) {
|
||||
throw new IllegalArgumentException("World mismatch");
|
||||
}
|
||||
|
||||
int x = Integer.parseInt(parts[2]);
|
||||
int y = Integer.parseInt(parts[3]);
|
||||
int z = Integer.parseInt(parts[4]);
|
||||
|
||||
Location location = new Location(world, x, y, z);
|
||||
String arenaId = parts[5];
|
||||
String type = parts[6];
|
||||
String templateId = parts[7];
|
||||
|
||||
return new ArenaSign(location, templateId, arenaId, type);
|
||||
}
|
||||
|
||||
boolean equal(String line1, String line2) {
|
||||
if (line1.equals(line2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String[] parts1 = line1.split(";");
|
||||
String[] parts2 = line2.split(";");
|
||||
|
||||
// World ID and (x,y,z) are all that matter
|
||||
return parts1[0].equals(parts2[0])
|
||||
&& parts1[2].equals(parts2[2])
|
||||
&& parts1[3].equals(parts2[3])
|
||||
&& parts1[4].equals(parts2[4]);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,43 +1,21 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class SignStore {
|
||||
|
||||
static final String FILENAME = "signs.data";
|
||||
private final Map<Location, ArenaSign> signs = new HashMap<>();
|
||||
|
||||
private final Map<Location, ArenaSign> signs;
|
||||
|
||||
SignStore(List<ArenaSign> signs) {
|
||||
this.signs = signs.stream()
|
||||
.collect(Collectors.toMap(
|
||||
sign -> sign.location,
|
||||
sign -> sign
|
||||
));
|
||||
}
|
||||
|
||||
void store(ArenaSign sign) {
|
||||
signs.put(sign.location, sign);
|
||||
}
|
||||
|
||||
Optional<ArenaSign> remove(Location location) {
|
||||
ArenaSign sign = signs.remove(location);
|
||||
return Optional.ofNullable(sign);
|
||||
}
|
||||
|
||||
Collection<ArenaSign> findAll() {
|
||||
return signs.values();
|
||||
}
|
||||
|
||||
Optional<ArenaSign> findByLocation(Location location) {
|
||||
ArenaSign sign = signs.get(location);
|
||||
return Optional.ofNullable(sign);
|
||||
ArenaSign findByLocation(Location location) {
|
||||
return signs.get(location);
|
||||
}
|
||||
|
||||
List<ArenaSign> findByArenaId(String arenaId) {
|
||||
|
@ -46,4 +24,25 @@ class SignStore {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
void add(ArenaSign sign) {
|
||||
signs.put(sign.location, sign);
|
||||
}
|
||||
|
||||
ArenaSign removeByLocation(Location location) {
|
||||
return signs.remove(location);
|
||||
}
|
||||
|
||||
List<ArenaSign> removeByWorld(World world) {
|
||||
List<ArenaSign> removed = new ArrayList<>();
|
||||
Iterator<ArenaSign> iterator = signs.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ArenaSign sign = iterator.next();
|
||||
if (sign.location.getWorld().equals(world)) {
|
||||
removed.add(sign);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
class SignWriter {
|
||||
|
||||
private final SignFile file;
|
||||
private final SignSerializer serializer;
|
||||
private final Logger log;
|
||||
|
||||
SignWriter(
|
||||
SignFile file,
|
||||
SignSerializer serializer,
|
||||
Logger log
|
||||
) {
|
||||
this.file = file;
|
||||
this.serializer = serializer;
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
void write(ArenaSign sign) throws IOException {
|
||||
String line = serializer.serialize(sign);
|
||||
|
||||
for (String candidate : file.lines()) {
|
||||
if (serializer.equal(candidate, line)) {
|
||||
log.warning("Erasing conflicting sign entry:\n" + candidate);
|
||||
file.erase(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
file.append(line);
|
||||
file.save();
|
||||
}
|
||||
|
||||
void erase(ArenaSign sign) throws IOException {
|
||||
String line = serializer.serialize(sign);
|
||||
|
||||
for (String candidate : file.lines()) {
|
||||
if (serializer.equal(candidate, line)) {
|
||||
file.erase(candidate);
|
||||
file.save();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log.warning("No match found in sign data file for sign:\n" + line);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import org.bukkit.Location;
|
||||
|
||||
class StoresNewSign {
|
||||
|
||||
private final ArenaMaster arenaMaster;
|
||||
private final TemplateStore templateStore;
|
||||
private final SignStore signStore;
|
||||
private final SavesSignStore savesSignStore;
|
||||
|
||||
StoresNewSign(
|
||||
ArenaMaster arenaMaster,
|
||||
TemplateStore templateStore,
|
||||
SignStore signStore,
|
||||
SavesSignStore savesSignStore
|
||||
) {
|
||||
this.arenaMaster = arenaMaster;
|
||||
this.templateStore = templateStore;
|
||||
this.signStore = signStore;
|
||||
this.savesSignStore = savesSignStore;
|
||||
}
|
||||
|
||||
void store(
|
||||
Location location,
|
||||
String arenaId,
|
||||
String templateId,
|
||||
String signType
|
||||
) {
|
||||
Arena arena = arenaMaster.getArenaWithName(arenaId);
|
||||
if (arena == null) {
|
||||
throw new IllegalArgumentException("Arena " + arenaId + " not found");
|
||||
}
|
||||
|
||||
templateStore.findById(templateId)
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Template " + templateId + " not found"
|
||||
));
|
||||
|
||||
switch (signType) {
|
||||
case "info":
|
||||
case "join":
|
||||
case "leave":
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid sign type: " + signType);
|
||||
}
|
||||
|
||||
signStore.store(new ArenaSign(location, templateId, arenaId, signType));
|
||||
|
||||
savesSignStore.save(signStore);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ class Template {
|
|||
|
||||
static class Builder {
|
||||
|
||||
private String id;
|
||||
private final String id;
|
||||
private String[] base;
|
||||
private String[] idle;
|
||||
private String[] joining;
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class ArenaSignTest {
|
||||
|
||||
@Test
|
||||
public void serialize() {
|
||||
World world = mock(World.class);
|
||||
Location location = new Location(world, 1, 2, 3);
|
||||
String templateId = "a good template";
|
||||
String arenaId = "cool arena";
|
||||
String type = "join";
|
||||
ArenaSign sign = new ArenaSign(location, templateId, arenaId, type);
|
||||
|
||||
Map<String, Object> result = sign.serialize();
|
||||
|
||||
assertThat(result.get("location"), equalTo(location));
|
||||
assertThat(result.get("templateId"), equalTo(templateId));
|
||||
assertThat(result.get("arenaId"), equalTo(arenaId));
|
||||
assertThat(result.get("type"), equalTo(type));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserialize() {
|
||||
World world = mock(World.class);
|
||||
Location location = new Location(world, 1, 2, 3);
|
||||
String templateId = "a good template";
|
||||
String arenaId = "cool arena";
|
||||
String type = "join";
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("location", location);
|
||||
map.put("templateId", templateId);
|
||||
map.put("arenaId", arenaId);
|
||||
map.put("type", type);
|
||||
|
||||
ArenaSign result = ArenaSign.deserialize(map);
|
||||
|
||||
assertThat(result.location, is(equalTo(location)));
|
||||
assertThat(result.templateId, is(equalTo(templateId)));
|
||||
assertThat(result.arenaId, is(equalTo(arenaId)));
|
||||
assertThat(result.type, is(equalTo(type)));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Chest;
|
||||
|
@ -13,7 +11,7 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
|
@ -27,8 +25,6 @@ public class HandlesSignClicksTest {
|
|||
@Before
|
||||
public void setup() {
|
||||
signStore = mock(SignStore.class);
|
||||
when(signStore.findByLocation(any()))
|
||||
.thenReturn(Optional.empty());
|
||||
invokesSignAction = mock(InvokesSignAction.class);
|
||||
|
||||
subject = new HandlesSignClicks(signStore, invokesSignAction);
|
||||
|
@ -40,7 +36,7 @@ public class HandlesSignClicksTest {
|
|||
|
||||
subject.on(event);
|
||||
|
||||
verifyZeroInteractions(signStore);
|
||||
verifyNoInteractions(signStore, invokesSignAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -51,18 +47,19 @@ public class HandlesSignClicksTest {
|
|||
|
||||
subject.on(event);
|
||||
|
||||
verifyZeroInteractions(signStore);
|
||||
verifyNoInteractions(signStore, invokesSignAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonArenaSignNoFun() {
|
||||
Block block = mock(Block.class);
|
||||
when(block.getState()).thenReturn(mock(Sign.class));
|
||||
when(signStore.findByLocation(any())).thenReturn(null);
|
||||
PlayerInteractEvent event = event(null, block);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyZeroInteractions(signStore);
|
||||
verifyNoInteractions(invokesSignAction);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -72,8 +69,7 @@ public class HandlesSignClicksTest {
|
|||
when(block.getLocation()).thenReturn(location);
|
||||
when(block.getState()).thenReturn(mock(Sign.class));
|
||||
ArenaSign sign = new ArenaSign(location, "", "", "");
|
||||
when(signStore.findByLocation(location))
|
||||
.thenReturn(Optional.of(sign));
|
||||
when(signStore.findByLocation(location)).thenReturn(sign);
|
||||
Player player = mock(Player.class);
|
||||
PlayerInteractEvent event = event(player, block);
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.garbagemule.MobArena.Messenger;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
import org.junit.Before;
|
||||
|
@ -12,135 +10,139 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class HandlesSignCreationTest {
|
||||
|
||||
StoresNewSign storesNewSign;
|
||||
RendersTemplateById rendersTemplate;
|
||||
SignCreator creator;
|
||||
SignWriter writer;
|
||||
SignStore store;
|
||||
SignRenderer renderer;
|
||||
Messenger messenger;
|
||||
Logger log;
|
||||
|
||||
HandlesSignCreation subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
storesNewSign = mock(StoresNewSign.class);
|
||||
|
||||
rendersTemplate = mock(RendersTemplateById.class);
|
||||
when(rendersTemplate.render(any(), any()))
|
||||
.thenReturn(new String[]{"", "", "", ""});
|
||||
|
||||
creator = mock(SignCreator.class);
|
||||
writer = mock(SignWriter.class);
|
||||
store = mock(SignStore.class);
|
||||
renderer = mock(SignRenderer.class);
|
||||
messenger = mock(Messenger.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new HandlesSignCreation(
|
||||
storesNewSign,
|
||||
rendersTemplate,
|
||||
messenger
|
||||
creator,
|
||||
writer,
|
||||
store,
|
||||
renderer,
|
||||
messenger,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noHeaderNoAction() {
|
||||
String[] lines = {"why", "so", "serious", "?"};
|
||||
SignChangeEvent event = event(lines, null);
|
||||
public void noSignCreationNoAction() {
|
||||
SignChangeEvent event = new SignChangeEvent(null, null, null);
|
||||
when(creator.create(event)).thenReturn(null);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyZeroInteractions(storesNewSign, rendersTemplate, messenger);
|
||||
verifyNoInteractions(writer, store, messenger, log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullLinesHandledGracefully() {
|
||||
String[] lines = {"[MA]", null, null, null};
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
subject.on(event);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useSignTypeIfTemplateNotAvailable() {
|
||||
String arenaId = "castle";
|
||||
String type = "join";
|
||||
String[] lines = {"[MA]", arenaId, type, null};
|
||||
Location location = mock(Location.class);
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(storesNewSign).store(location, arenaId, type, type);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useTemplateIfAvailable() {
|
||||
String arenaId = "castle";
|
||||
String type = "join";
|
||||
String templateId = "potato";
|
||||
String[] lines = {"[MA]", arenaId, type, templateId};
|
||||
Location location = mock(Location.class);
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(storesNewSign).store(location, arenaId, templateId, type);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeIsLowercased() {
|
||||
String type = "JOIN";
|
||||
String[] lines = {"[MA]", "", type, ""};
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
String lower = type.toLowerCase();
|
||||
verify(storesNewSign).store(any(), any(), any(), eq(lower));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void templateIdIsLowercased() {
|
||||
String templateId = "BEST-TEMPLATE";
|
||||
String[] lines = {"[MA]", "", "", templateId};
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
String lower = templateId.toLowerCase();
|
||||
verify(storesNewSign).store(any(), any(), eq(lower), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersTemplateAfterStoring() {
|
||||
String arenaId = "castle";
|
||||
String type = "join";
|
||||
String templateId = "potato";
|
||||
String[] lines = {"[MA]", arenaId, type, templateId};
|
||||
Location location = mock(Location.class);
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(rendersTemplate).render(templateId, arenaId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorPassedToMessenger() {
|
||||
String msg = "you messed up";
|
||||
doThrow(new IllegalArgumentException(msg))
|
||||
.when(storesNewSign).store(any(), any(), any(), any());
|
||||
String[] lines = {"[MA]", "", "", ""};
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyZeroInteractions(rendersTemplate);
|
||||
verify(messenger).tell(event.getPlayer(), msg);
|
||||
}
|
||||
|
||||
private SignChangeEvent event(String[] lines, Location location) {
|
||||
Block block = mock(Block.class);
|
||||
when(block.getLocation()).thenReturn(location);
|
||||
public void passesSignFromCreator() throws Exception {
|
||||
Player player = mock(Player.class);
|
||||
return new SignChangeEvent(block, player, lines);
|
||||
SignChangeEvent event = new SignChangeEvent(null, player, null);
|
||||
ArenaSign sign = new ArenaSign(location(), null, null, null);
|
||||
when(creator.create(event)).thenReturn(sign);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(writer).write(sign);
|
||||
verify(store).add(sign);
|
||||
verify(renderer).render(sign, event);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successMessageOnCreation() {
|
||||
Player player = mock(Player.class);
|
||||
SignChangeEvent event = new SignChangeEvent(null, player, null);
|
||||
ArenaSign sign = new ArenaSign(location(), null, "castle", "join");
|
||||
when(creator.create(event)).thenReturn(sign);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(messenger).tell(eq(player), anyString());
|
||||
verify(log).info(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noWriteIfCreatorThrows() {
|
||||
SignChangeEvent event = new SignChangeEvent(null, null, null);
|
||||
doThrow(IllegalArgumentException.class).when(creator).create(event);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyNoInteractions(writer, store, renderer, log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessageIfCreatorThrows() {
|
||||
Player player = mock(Player.class);
|
||||
SignChangeEvent event = new SignChangeEvent(null, player, null);
|
||||
String message = "it's bad";
|
||||
doThrow(new IllegalArgumentException(message)).when(creator).create(event);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(messenger).tell(player, message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noStorageIfWriterThrows() throws Exception {
|
||||
SignChangeEvent event = new SignChangeEvent(null, null, null);
|
||||
ArenaSign sign = new ArenaSign(null, null, null, null);
|
||||
when(creator.create(event)).thenReturn(sign);
|
||||
doThrow(IOException.class).when(writer).write(sign);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyNoInteractions(store, renderer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessageIfWriterThrows() throws Exception {
|
||||
Player player = mock(Player.class);
|
||||
SignChangeEvent event = new SignChangeEvent(null, player, null);
|
||||
ArenaSign sign = new ArenaSign(null, null, null, null);
|
||||
when(creator.create(event)).thenReturn(sign);
|
||||
IOException exception = new IOException("it's bad");
|
||||
doThrow(exception).when(writer).write(sign);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(messenger).tell(eq(player), anyString());
|
||||
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
|
||||
}
|
||||
|
||||
private Location location() {
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn("world");
|
||||
Location location = mock(Location.class);
|
||||
when(location.getBlockX()).thenReturn(1);
|
||||
when(location.getBlockY()).thenReturn(2);
|
||||
when(location.getBlockZ()).thenReturn(3);
|
||||
when(location.getWorld()).thenReturn(world);
|
||||
return location;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.garbagemule.MobArena.Messenger;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
@ -11,53 +11,109 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class HandlesSignDestructionTest {
|
||||
|
||||
RemovesSignAtLocation removesSignAtLocation;
|
||||
SignStore store;
|
||||
SignWriter writer;
|
||||
Messenger messenger;
|
||||
Logger log;
|
||||
|
||||
HandlesSignDestruction subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
removesSignAtLocation = mock(RemovesSignAtLocation.class);
|
||||
store = mock(SignStore.class);
|
||||
writer = mock(SignWriter.class);
|
||||
messenger = mock(Messenger.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new HandlesSignDestruction(
|
||||
removesSignAtLocation,
|
||||
messenger
|
||||
store,
|
||||
writer,
|
||||
messenger,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNothingWithNonArenaSign() {
|
||||
public void noSignNoAction() {
|
||||
Location location = mock(Location.class);
|
||||
Block block = mock(Block.class);
|
||||
BlockBreakEvent event = new BlockBreakEvent(block, null);
|
||||
when(block.getLocation()).thenReturn(location);
|
||||
when(store.removeByLocation(location)).thenReturn(null);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyNoInteractions(writer, messenger, log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void erasesFoundSign() throws IOException {
|
||||
Location location = location();
|
||||
Block block = mock(Block.class);
|
||||
Player player = mock(Player.class);
|
||||
when(removesSignAtLocation.remove(any()))
|
||||
.thenReturn(Optional.empty());
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
when(block.getLocation()).thenReturn(location);
|
||||
when(store.removeByLocation(location)).thenReturn(sign);
|
||||
BlockBreakEvent event = new BlockBreakEvent(block, player);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyZeroInteractions(messenger);
|
||||
verify(writer).erase(sign);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reportsBreakageWithArenaSign() {
|
||||
public void successMessageOnDestruction() {
|
||||
Location location = location();
|
||||
Block block = mock(Block.class);
|
||||
Player player = mock(Player.class);
|
||||
ArenaSign sign = new ArenaSign(null, "", "", "");
|
||||
when(removesSignAtLocation.remove(any()))
|
||||
.thenReturn(Optional.of(sign));
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
when(block.getLocation()).thenReturn(location);
|
||||
when(store.removeByLocation(location)).thenReturn(sign);
|
||||
BlockBreakEvent event = new BlockBreakEvent(block, player);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(messenger).tell(eq(player), anyString());
|
||||
verify(log).info(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessageIfWriterThrows() throws Exception {
|
||||
Location location = mock(Location.class);
|
||||
Block block = mock(Block.class);
|
||||
Player player = mock(Player.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
IOException exception = new IOException("it's bad");
|
||||
when(block.getLocation()).thenReturn(location);
|
||||
when(store.removeByLocation(location)).thenReturn(sign);
|
||||
doThrow(exception).when(writer).erase(sign);
|
||||
BlockBreakEvent event = new BlockBreakEvent(block, player);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(messenger).tell(eq(player), anyString());
|
||||
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
|
||||
}
|
||||
|
||||
private Location location() {
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn("world");
|
||||
Location location = mock(Location.class);
|
||||
when(location.getBlockX()).thenReturn(1);
|
||||
when(location.getBlockY()).thenReturn(2);
|
||||
when(location.getBlockZ()).thenReturn(3);
|
||||
when(location.getWorld()).thenReturn(world);
|
||||
return location;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class HandlesWorldLoadTest {
|
||||
|
||||
SignDataMigrator migrator;
|
||||
SignReader reader;
|
||||
SignStore store;
|
||||
Logger log;
|
||||
|
||||
HandlesWorldLoad subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
migrator = mock(SignDataMigrator.class);
|
||||
reader = mock(SignReader.class);
|
||||
store = mock(SignStore.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new HandlesWorldLoad(
|
||||
migrator,
|
||||
reader,
|
||||
store,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessageIfDataMigrationThrows() throws IOException {
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn("world");
|
||||
IOException exception = new IOException("it's bad");
|
||||
doThrow(exception).when(migrator).migrate(world);
|
||||
WorldLoadEvent event = new WorldLoadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsLoadedSignsToStore() throws IOException {
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn("world");
|
||||
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
when(reader.read(world)).thenReturn(Collections.singletonList(sign));
|
||||
WorldLoadEvent event = new WorldLoadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(store).add(sign);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsMessageOnSignAddition() throws IOException {
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn("world");
|
||||
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
when(reader.read(world)).thenReturn(Collections.singletonList(sign));
|
||||
WorldLoadEvent event = new WorldLoadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(log).info(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addsNothingIfNoSignsLoaded() throws IOException {
|
||||
World world = mock(World.class);
|
||||
when(reader.read(world)).thenReturn(Collections.emptyList());
|
||||
WorldLoadEvent event = new WorldLoadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyNoInteractions(store);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsNothingIfNoSignsLoaded() throws IOException {
|
||||
World world = mock(World.class);
|
||||
when(reader.read(world)).thenReturn(Collections.emptyList());
|
||||
WorldLoadEvent event = new WorldLoadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyNoInteractions(log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessageIfReaderThrows() throws IOException {
|
||||
World world = mock(World.class);
|
||||
IOException exception = new IOException("it's bad");
|
||||
when(reader.read(world)).thenThrow(exception);
|
||||
WorldLoadEvent event = new WorldLoadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class HandlesWorldUnloadTest {
|
||||
|
||||
SignStore store;
|
||||
Logger log;
|
||||
|
||||
HandlesWorldUnload subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
store = mock(SignStore.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new HandlesWorldUnload(
|
||||
store,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsMessagesOnSignRemoval() {
|
||||
UUID id = UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe");
|
||||
String name = "world";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(id);
|
||||
when(world.getName()).thenReturn(name);
|
||||
List<ArenaSign> signs = Arrays.asList(null, null);
|
||||
when(store.removeByWorld(world)).thenReturn(signs);
|
||||
WorldUnloadEvent event = new WorldUnloadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verify(log).info(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsNothingIfNoSignsRemoved() {
|
||||
World world = mock(World.class);
|
||||
when(store.removeByWorld(world)).thenReturn(Collections.emptyList());
|
||||
WorldUnloadEvent event = new WorldUnloadEvent(world);
|
||||
|
||||
subject.on(event);
|
||||
|
||||
verifyNoInteractions(log);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.garbagemule.MobArena.Messenger;
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
|
@ -11,6 +9,8 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class InvokesSignActionTest {
|
||||
|
@ -36,7 +36,7 @@ public class InvokesSignActionTest {
|
|||
|
||||
subject.invoke(sign, player);
|
||||
|
||||
verifyZeroInteractions(arenaMaster);
|
||||
verifyNoInteractions(arenaMaster);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import org.bukkit.Location;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class RedrawsArenaSignsTest {
|
||||
|
||||
SignStore signStore;
|
||||
TemplateStore templateStore;
|
||||
RendersTemplate rendersTemplate;
|
||||
SetsLines setsSignLines;
|
||||
|
||||
RedrawsArenaSigns subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
signStore = mock(SignStore.class);
|
||||
when(signStore.findByArenaId(any()))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
templateStore = mock(TemplateStore.class);
|
||||
when(templateStore.findById(any()))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
rendersTemplate = mock(RendersTemplate.class);
|
||||
when(rendersTemplate.render(any(), any()))
|
||||
.thenReturn(new String[]{"a", "b", "c", "d"});
|
||||
|
||||
setsSignLines = mock(SetsLines.class);
|
||||
|
||||
subject = new RedrawsArenaSigns(
|
||||
signStore,
|
||||
templateStore,
|
||||
rendersTemplate,
|
||||
setsSignLines
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSignsMeansNoRenderingOrLineSetting() {
|
||||
Arena arena = arena("castle");
|
||||
|
||||
subject.redraw(arena);
|
||||
|
||||
verifyZeroInteractions(rendersTemplate);
|
||||
verifyZeroInteractions(setsSignLines);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderFoundTemplate() {
|
||||
String arenaId = "castle";
|
||||
Arena arena = arena(arenaId);
|
||||
ArenaSign sign = sign("join", arenaId);
|
||||
when(signStore.findByArenaId(arenaId))
|
||||
.thenReturn(Collections.singletonList(sign));
|
||||
Template template = template("template", "some", "info", "about", "arena");
|
||||
when(templateStore.findById(sign.templateId))
|
||||
.thenReturn(Optional.of(template));
|
||||
|
||||
subject.redraw(arena);
|
||||
|
||||
verify(rendersTemplate).render(template, arena);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setRenderedTemplateOnSign() {
|
||||
String arenaId = "castle";
|
||||
Arena arena = arena(arenaId);
|
||||
ArenaSign sign = sign("join", arenaId);
|
||||
when(signStore.findByArenaId(sign.arenaId))
|
||||
.thenReturn(Collections.singletonList(sign));
|
||||
Template template = template("template", "try", "with", "more", "fireballs");
|
||||
when(templateStore.findById(sign.templateId))
|
||||
.thenReturn(Optional.of(template));
|
||||
String[] lines = new String[]{"this", "is", "a", "sign"};
|
||||
when(rendersTemplate.render(template, arena))
|
||||
.thenReturn(lines);
|
||||
|
||||
subject.redraw(arena);
|
||||
|
||||
verify(setsSignLines).set(sign.location, lines);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void renderEachTemplateOnlyOnce() {
|
||||
String arenaId = "castle";
|
||||
Arena arena = arena(arenaId);
|
||||
String templateId1 = "join";
|
||||
String templateId2 = "info";
|
||||
List<ArenaSign> signs = new ArrayList<>();
|
||||
signs.add(sign(templateId1, arenaId));
|
||||
signs.add(sign(templateId1, arenaId));
|
||||
signs.add(sign(templateId1, arenaId));
|
||||
signs.add(sign(templateId2, arenaId));
|
||||
when(signStore.findByArenaId(arenaId))
|
||||
.thenReturn(signs);
|
||||
Template template1 = template(templateId1, "join", "a", "MobArena", "today!");
|
||||
Template template2 = template(templateId2, "join", "another", "MobArena", "tomorrow!");
|
||||
when(templateStore.findById(templateId1))
|
||||
.thenReturn(Optional.of(template1));
|
||||
when(templateStore.findById(templateId2))
|
||||
.thenReturn(Optional.of(template2));
|
||||
|
||||
subject.redraw(arena);
|
||||
|
||||
verify(rendersTemplate, times(1)).render(template1, arena);
|
||||
verify(rendersTemplate, times(1)).render(template2, arena);
|
||||
verify(setsSignLines, times(signs.size())).set(any(), any());
|
||||
}
|
||||
|
||||
private Arena arena(String arenaId) {
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.configName()).thenReturn(arenaId);
|
||||
return arena;
|
||||
}
|
||||
|
||||
private ArenaSign sign(String templateId, String arenaId) {
|
||||
Location location = mock(Location.class);
|
||||
return new ArenaSign(location, templateId, arenaId, "join");
|
||||
}
|
||||
|
||||
private Template template(String id, String l1, String l2, String l3, String l4) {
|
||||
return new Template.Builder(id)
|
||||
.withBase(new String[]{l1, l2, l3, l4})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class RemovesSignAtLocationTest {
|
||||
|
||||
SignStore signStore;
|
||||
SavesSignStore savesSignStore;
|
||||
|
||||
RemovesSignAtLocation subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
signStore = mock(SignStore.class);
|
||||
savesSignStore = mock(SavesSignStore.class);
|
||||
|
||||
subject = new RemovesSignAtLocation(
|
||||
signStore,
|
||||
savesSignStore
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSignMeansNoWrite() {
|
||||
Location location = mock(Location.class);
|
||||
when(signStore.remove(location))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
subject.remove(location);
|
||||
|
||||
verifyZeroInteractions(savesSignStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signRemovedWritesStore() {
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "", "", "");
|
||||
when(signStore.remove(location))
|
||||
.thenReturn(Optional.of(sign));
|
||||
|
||||
subject.remove(location);
|
||||
|
||||
verify(savesSignStore).save(signStore);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,5 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.waves.WaveManager;
|
||||
import org.bukkit.entity.Player;
|
||||
|
@ -14,6 +10,10 @@ import org.mockito.junit.MockitoJUnitRunner;
|
|||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class RendersTemplateTest {
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignCreatorTest {
|
||||
|
||||
ArenaMaster arenaMaster;
|
||||
TemplateStore templateStore;
|
||||
|
||||
SignCreator subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
arenaMaster = mock(ArenaMaster.class);
|
||||
templateStore = mock(TemplateStore.class);
|
||||
|
||||
subject = new SignCreator(
|
||||
arenaMaster,
|
||||
templateStore
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noHeaderNoAction() {
|
||||
String[] lines = {"ma", "castle", "join", "cool-sign"};
|
||||
SignChangeEvent event = new SignChangeEvent(null, null, lines);
|
||||
|
||||
ArenaSign result = subject.create(event);
|
||||
|
||||
assertThat(result, nullValue());
|
||||
verifyNoInteractions(arenaMaster, templateStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsOnMissingArena() {
|
||||
String[] lines = {"[MA]", null, "join", "cool-sign"};
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.create(event)
|
||||
);
|
||||
verifyNoInteractions(arenaMaster);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsIfArenaNotFound() {
|
||||
String arenaId = "castle";
|
||||
String[] lines = {"[MA]", arenaId, "join", "cool-sign"};
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(null);
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.create(event)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsOnMissingType() {
|
||||
String arenaId = "castle";
|
||||
String[] lines = {"[MA]", arenaId, null, "cool-sign"};
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaId);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.create(event)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsOnInvalidType() {
|
||||
String arenaId = "castle";
|
||||
String[] lines = {"[MA]", arenaId, "bob", "cool-sign"};
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaId);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.create(event)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwsIfTemplateNotFound() {
|
||||
String arenaId = "castle";
|
||||
String templateId = "cool-sign";
|
||||
String[] lines = {"[MA]", arenaId, "join", templateId};
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaId);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.empty());
|
||||
SignChangeEvent event = event(lines, null);
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.create(event)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completeSignDefinition() {
|
||||
String arenaId = "castle";
|
||||
String signType = "join";
|
||||
String templateId = "cool-sign";
|
||||
String[] lines = {"[MA]", arenaId, signType, templateId};
|
||||
Location location = mock(Location.class);
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaId);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(mock(Template.class)));
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
ArenaSign result = subject.create(event);
|
||||
|
||||
assertThat(result.location, equalTo(location));
|
||||
assertThat(result.arenaId, equalTo(arenaId));
|
||||
assertThat(result.type, equalTo(signType));
|
||||
assertThat(result.templateId, equalTo(templateId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void slugifiedArenaId() {
|
||||
String arenaId = "Area 52";
|
||||
String arenaSlug = "area-52";
|
||||
String signType = "join";
|
||||
String templateId = "cool-sign";
|
||||
String[] lines = {"[MA]", arenaId, signType, templateId};
|
||||
Location location = mock(Location.class);
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaSlug);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(mock(Template.class)));
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
ArenaSign result = subject.create(event);
|
||||
|
||||
assertThat(result.location, equalTo(location));
|
||||
assertThat(result.arenaId, equalTo(arenaSlug));
|
||||
assertThat(result.type, equalTo(signType.toLowerCase()));
|
||||
assertThat(result.templateId, equalTo(templateId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void caseInsensitiveSignType() {
|
||||
String arenaId = "castle";
|
||||
String signType = "jOiN";
|
||||
String templateId = "cool-sign";
|
||||
String[] lines = {"[MA]", arenaId, signType, templateId};
|
||||
Location location = mock(Location.class);
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaId);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(mock(Template.class)));
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
ArenaSign result = subject.create(event);
|
||||
|
||||
assertThat(result.location, equalTo(location));
|
||||
assertThat(result.arenaId, equalTo(arenaId));
|
||||
assertThat(result.type, equalTo(signType.toLowerCase()));
|
||||
assertThat(result.templateId, equalTo(templateId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signWithoutTemplateUsesType() {
|
||||
String arenaId = "castle";
|
||||
String signType = "join";
|
||||
String[] lines = {"[MA]", arenaId, signType, null};
|
||||
Location location = mock(Location.class);
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arena.getSlug()).thenReturn(arenaId);
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(templateStore.findById(signType)).thenReturn(Optional.of(mock(Template.class)));
|
||||
SignChangeEvent event = event(lines, location);
|
||||
|
||||
ArenaSign result = subject.create(event);
|
||||
|
||||
assertThat(result.templateId, equalTo(signType));
|
||||
}
|
||||
|
||||
private SignChangeEvent event(String[] lines, Location location) {
|
||||
Block block = mock(Block.class);
|
||||
when(block.getLocation()).thenReturn(location);
|
||||
Player player = mock(Player.class);
|
||||
return new SignChangeEvent(block, player, lines);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignDataMigratorTest {
|
||||
|
||||
Path legacyFile;
|
||||
Path pendingFile;
|
||||
Yaml yaml;
|
||||
SignFile signFile;
|
||||
Logger log;
|
||||
|
||||
SignDataMigrator subject;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
legacyFile = Files.createTempFile("SignDataMigratorTest-", ".data.tmp");
|
||||
pendingFile = Files.createTempFile("SignDataMigratorTest-", ".tmp.tmp");
|
||||
yaml = mock(Yaml.class);
|
||||
signFile = mock(SignFile.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new SignDataMigrator(
|
||||
legacyFile,
|
||||
pendingFile,
|
||||
yaml,
|
||||
signFile,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws IOException {
|
||||
Files.deleteIfExists(legacyFile);
|
||||
Files.deleteIfExists(pendingFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initDoesNothingIfNoLegacyFile() throws IOException {
|
||||
Files.delete(pendingFile);
|
||||
Files.delete(legacyFile);
|
||||
|
||||
subject.init();
|
||||
|
||||
verifyNoInteractions(yaml, log);
|
||||
assertThat(Files.exists(legacyFile), equalTo(false));
|
||||
assertThat(Files.exists(pendingFile), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initCreatesPendingFileAndDeletesLegacyFile() throws IOException {
|
||||
Files.delete(pendingFile);
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
when(yaml.load(anyString())).thenReturn(map);
|
||||
|
||||
subject.init();
|
||||
|
||||
assertThat(Files.exists(legacyFile), equalTo(false));
|
||||
assertThat(Files.exists(pendingFile), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initLogsInfoMessagesOnConversion() throws IOException {
|
||||
Files.delete(pendingFile);
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
when(yaml.load(anyString())).thenReturn(map);
|
||||
|
||||
subject.init();
|
||||
|
||||
verify(log, times(2)).info(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initMigratesToPartialFormat() throws IOException {
|
||||
Files.delete(pendingFile);
|
||||
List<Map<String, Object>> signs = new ArrayList<>();
|
||||
signs.add(sign("world", 46.0, 101.0, -191.0, "Area 52", "join", "cool-sign"));
|
||||
signs.add(sign("lazy", 47.0, 102.0, -192.0, "Mr. Bob's Bus", "info", "status"));
|
||||
signs.add(sign("eager", 48.0, 103.0, -194.0, "Mission: Impossible", "leave", "coward"));
|
||||
signs.add(sign("world", 49.0, 104.0, -198.0, "castle", "leave", "bye-bye"));
|
||||
Map<String, Object> root = new LinkedHashMap<>();
|
||||
root.put("signs", signs);
|
||||
when(yaml.load(anyString())).thenReturn(root);
|
||||
|
||||
subject.init();
|
||||
|
||||
List<String> lines = Files.readAllLines(pendingFile);
|
||||
assertThat(lines.size(), equalTo(signs.size()));
|
||||
assertThat(lines, hasItems(
|
||||
"world;46;101;-191;area-52;join;cool-sign",
|
||||
"lazy;47;102;-192;mr-bobs-bus;info;status",
|
||||
"eager;48;103;-194;mission-impossible;leave;coward",
|
||||
"world;49;104;-198;castle;leave;bye-bye"
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateDoesNothingIfNoPendingFile() throws IOException {
|
||||
Files.delete(pendingFile);
|
||||
World world = mock(World.class);
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
verifyNoInteractions(signFile, log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateDoesNothingIfNoMatchingLines() throws IOException {
|
||||
List<String> lines = Arrays.asList(
|
||||
"world;46;101;-191;castle;join;cool-sign",
|
||||
"lazy;47;102;-192;jungle;info;status",
|
||||
"world;48;103;-194;island;leave;coward"
|
||||
);
|
||||
Files.write(pendingFile, lines);
|
||||
String name = "not-world";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
verifyNoInteractions(signFile, log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migratePassesMatchingLinesToSignFile() throws IOException {
|
||||
List<String> lines = Arrays.asList(
|
||||
"world;46;101;-191;castle;join;cool-sign",
|
||||
"lazy;47;102;-192;jungle;info;status",
|
||||
"world;48;103;-194;island;leave;coward"
|
||||
);
|
||||
Files.write(pendingFile, lines);
|
||||
String name = "world";
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
verify(signFile).append(id + ";" + lines.get(0));
|
||||
verify(signFile).append(id + ";" + lines.get(2));
|
||||
verify(signFile).save();
|
||||
verifyNoMoreInteractions(signFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateDeletesMatchingLinesFromPendingFile() throws IOException {
|
||||
List<String> lines = Arrays.asList(
|
||||
"world;46;101;-191;castle;join;cool-sign",
|
||||
"lazy;47;102;-192;jungle;info;status",
|
||||
"world;48;103;-194;island;leave;coward"
|
||||
);
|
||||
Files.write(pendingFile, lines);
|
||||
String name = "world";
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
List<String> remaining = Files.readAllLines(pendingFile);
|
||||
assertThat(remaining, equalTo(lines.subList(1, 2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateLogsInfoMessageOnMigration() throws IOException {
|
||||
List<String> lines = Arrays.asList(
|
||||
"world;46;101;-191;castle;join;cool-sign",
|
||||
"lazy;47;102;-192;jungle;info;status",
|
||||
"world;48;103;-194;island;leave;coward"
|
||||
);
|
||||
Files.write(pendingFile, lines);
|
||||
String name = "world";
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
verify(log, times(2)).info(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateDeletesPendingFileOnCompletion() throws IOException {
|
||||
List<String> lines = Arrays.asList(
|
||||
"world;46;101;-191;castle;join;cool-sign",
|
||||
"world;48;103;-194;island;leave;coward"
|
||||
);
|
||||
Files.write(pendingFile, lines);
|
||||
String name = "world";
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
assertThat(Files.exists(pendingFile), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateLogsAdditionallyOnCompletion() throws IOException {
|
||||
List<String> lines = Arrays.asList(
|
||||
"world;46;101;-191;castle;join;cool-sign",
|
||||
"world;48;103;-194;island;leave;coward"
|
||||
);
|
||||
Files.write(pendingFile, lines);
|
||||
String name = "world";
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
|
||||
subject.migrate(world);
|
||||
|
||||
verify(log, times(3)).info(anyString());
|
||||
}
|
||||
|
||||
private Map<String, Object> sign(
|
||||
String world,
|
||||
double x,
|
||||
double y,
|
||||
double z,
|
||||
String arenaId,
|
||||
String type,
|
||||
String templateId
|
||||
) {
|
||||
Map<String, Object> location = new LinkedHashMap<>();
|
||||
location.put("==", "org.bukkit.Location");
|
||||
location.put("world", world);
|
||||
location.put("x", x);
|
||||
location.put("y", y);
|
||||
location.put("z", z);
|
||||
location.put("pitch", 0.0);
|
||||
location.put("yaw", 0.0);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("==", "com.garbagemule.MobArena.signs.ArenaSign");
|
||||
result.put("arenaId", arenaId);
|
||||
result.put("location", location);
|
||||
result.put("templateId", templateId);
|
||||
result.put("type", type);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignFileTest {
|
||||
|
||||
Path file;
|
||||
|
||||
SignFile subject;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException {
|
||||
file = Files.createTempFile("SignFileTest-", ".tmp");
|
||||
subject = new SignFile(file);
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws IOException {
|
||||
Files.delete(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyFileEmptyLines() throws IOException {
|
||||
List<String> result = subject.lines();
|
||||
|
||||
assertThat(result.isEmpty(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyLinesIfAppendWithoutSave() throws IOException {
|
||||
String line1 = "We will";
|
||||
String line2 = "not be saved!";
|
||||
|
||||
subject.append(line1);
|
||||
subject.append(line2);
|
||||
List<String> result = subject.lines();
|
||||
|
||||
assertThat(result.isEmpty(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void linePersistedOnSave() throws IOException {
|
||||
String line1 = "We will";
|
||||
String line2 = "be saved!";
|
||||
|
||||
subject.append(line1);
|
||||
subject.append(line2);
|
||||
subject.save();
|
||||
List<String> result = subject.lines();
|
||||
|
||||
assertThat(result, equalTo(Arrays.asList(line1, line2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lineLingersWithoutSave() throws IOException {
|
||||
String line1 = "We will";
|
||||
String line2 = "be saved!";
|
||||
|
||||
subject.append(line1);
|
||||
subject.append(line2);
|
||||
subject.save();
|
||||
subject.erase(line1);
|
||||
List<String> result = subject.lines();
|
||||
|
||||
assertThat(result, equalTo(Arrays.asList(line1, line2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lineRemovedOnSave() throws IOException {
|
||||
String line1 = "We will";
|
||||
String line2 = "be saved!";
|
||||
|
||||
subject.append(line1);
|
||||
subject.append(line2);
|
||||
subject.save();
|
||||
subject.erase(line1);
|
||||
subject.save();
|
||||
List<String> result = subject.lines();
|
||||
|
||||
assertThat(result, equalTo(Collections.singletonList(line2)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignReaderTest {
|
||||
|
||||
SignFile file;
|
||||
SignSerializer serializer;
|
||||
Logger log;
|
||||
|
||||
SignReader subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
file = mock(SignFile.class);
|
||||
serializer = mock(SignSerializer.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new SignReader(
|
||||
file,
|
||||
serializer,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializesNothingIfEmptyFile() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
when(file.lines()).thenReturn(Collections.emptyList());
|
||||
|
||||
subject.read(world);
|
||||
|
||||
verifyNoInteractions(serializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsNothingIfEmptyFile() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
when(file.lines()).thenReturn(Collections.emptyList());
|
||||
|
||||
subject.read(world);
|
||||
|
||||
verifyNoInteractions(log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializesLinesWithMatchingWorldId() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
String line1 = "cafebeef-ea75-dead-babe-deadcafebeef;world;1;2;3;jungle;info;status";
|
||||
String line2 = id + ";world;1;2;3;castle;join;cool-sign";
|
||||
when(file.lines()).thenReturn(Arrays.asList(line1, line2));
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
when(serializer.deserialize(line2, world)).thenReturn(sign);
|
||||
|
||||
List<ArenaSign> result = subject.read(world);
|
||||
|
||||
assertThat(result.size(), equalTo(1));
|
||||
assertThat(result, hasItem(sign));
|
||||
verifyNoMoreInteractions(serializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logsNothingOnSuccess() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
String line = id + ";world;1;2;3;castle;join;cool-sign";
|
||||
when(file.lines()).thenReturn(Collections.singletonList(line));
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
when(serializer.deserialize(line, world)).thenReturn(sign);
|
||||
|
||||
subject.read(world);
|
||||
|
||||
verifyNoInteractions(log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipLineIfSerializerThrows() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
String line1 = id + ";world;1;2;3;jungle;info;status";
|
||||
String line2 = id + ";world;4;5;6;castle;join;cool-sign";
|
||||
when(file.lines()).thenReturn(Arrays.asList(line1, line2));
|
||||
IllegalArgumentException exception = new IllegalArgumentException("it's bad");
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
when(serializer.deserialize(line1, world)).thenThrow(exception);
|
||||
when(serializer.deserialize(line2, world)).thenReturn(sign);
|
||||
|
||||
List<ArenaSign> result = subject.read(world);
|
||||
|
||||
assertThat(result.size(), equalTo(1));
|
||||
assertThat(result, hasItem(sign));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorMessageIfSerializerThrows() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
String line1 = id + ";world;1;2;3;jungle;info;status";
|
||||
String line2 = id + ";world;4;5;6;castle;join;cool-sign";
|
||||
when(file.lines()).thenReturn(Arrays.asList(line1, line2));
|
||||
IllegalArgumentException exception = new IllegalArgumentException("it's bad");
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
when(serializer.deserialize(line1, world)).thenThrow(exception);
|
||||
when(serializer.deserialize(line2, world)).thenReturn(sign);
|
||||
|
||||
subject.read(world);
|
||||
|
||||
verify(log).log(eq(Level.SEVERE), anyString(), eq(exception));
|
||||
verifyNoMoreInteractions(log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propagatesExceptionsFromFile() throws IOException {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
IOException exception = new IOException("it's bad");
|
||||
when(file.lines()).thenThrow(exception);
|
||||
|
||||
Assert.assertThrows(
|
||||
IOException.class,
|
||||
() -> subject.read(world)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.event.block.SignChangeEvent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignRendererTest {
|
||||
|
||||
TemplateStore templateStore;
|
||||
ArenaMaster arenaMaster;
|
||||
RendersTemplate rendersTemplate;
|
||||
|
||||
SignRenderer subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
templateStore = mock(TemplateStore.class);
|
||||
arenaMaster = mock(ArenaMaster.class);
|
||||
rendersTemplate = mock(RendersTemplate.class);
|
||||
|
||||
subject = new SignRenderer(
|
||||
templateStore,
|
||||
arenaMaster,
|
||||
rendersTemplate
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersErrorMessageOnSignIfTemplateNotFound() {
|
||||
Location location = mock(Location.class);
|
||||
Block block = mock(Block.class);
|
||||
Sign target = mock(Sign.class);
|
||||
when(location.getBlock()).thenReturn(block);
|
||||
when(block.getState()).thenReturn(target);
|
||||
String templateId = "cool-sign";
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.empty());
|
||||
ArenaSign sign = new ArenaSign(location, templateId, "castle", "join");
|
||||
|
||||
subject.render(sign);
|
||||
|
||||
verify(target).setLine(0, ChatColor.RED + "[ERROR]");
|
||||
verify(target).setLine(1, "Template");
|
||||
verify(target).setLine(2, ChatColor.YELLOW + "cool-sign");
|
||||
verify(target).setLine(3, "not found :(");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersErrorMessageOnSignIfArenaNotFound() {
|
||||
Location location = mock(Location.class);
|
||||
Block block = mock(Block.class);
|
||||
Sign target = mock(Sign.class);
|
||||
when(location.getBlock()).thenReturn(block);
|
||||
when(block.getState()).thenReturn(target);
|
||||
String templateId = "cool-sign";
|
||||
Template template = mock(Template.class);
|
||||
String arenaId = "castle";
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(null);
|
||||
ArenaSign sign = new ArenaSign(location, templateId, arenaId, "join");
|
||||
|
||||
subject.render(sign);
|
||||
|
||||
verify(target).setLine(0, ChatColor.RED + "[ERROR]");
|
||||
verify(target).setLine(1, "Arena");
|
||||
verify(target).setLine(2, ChatColor.YELLOW + arenaId);
|
||||
verify(target).setLine(3, "not found :(");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersResultsFromDelegateOnSign() {
|
||||
Location location = mock(Location.class);
|
||||
Block block = mock(Block.class);
|
||||
Sign target = mock(Sign.class);
|
||||
when(location.getBlock()).thenReturn(block);
|
||||
when(block.getState()).thenReturn(target);
|
||||
String templateId = "cool-sign";
|
||||
Template template = mock(Template.class);
|
||||
String arenaId = "castle";
|
||||
Arena arena = mock(Arena.class);
|
||||
String[] lines = new String[]{"this", "is", "a", "sign"};
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(rendersTemplate.render(template, arena)).thenReturn(lines);
|
||||
ArenaSign sign = new ArenaSign(location, templateId, arenaId, "join");
|
||||
|
||||
subject.render(sign);
|
||||
|
||||
verify(target).setLine(0, lines[0]);
|
||||
verify(target).setLine(1, lines[1]);
|
||||
verify(target).setLine(2, lines[2]);
|
||||
verify(target).setLine(3, lines[3]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersErrorMessageInEventIfTemplateNotFound() {
|
||||
String templateId = "cool-sign";
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.empty());
|
||||
ArenaSign sign = new ArenaSign(null, templateId, "castle", "join");
|
||||
SignChangeEvent event = mock(SignChangeEvent.class);
|
||||
|
||||
subject.render(sign, event);
|
||||
|
||||
verify(event).setLine(0, ChatColor.RED + "[ERROR]");
|
||||
verify(event).setLine(1, "Template");
|
||||
verify(event).setLine(2, ChatColor.YELLOW + "cool-sign");
|
||||
verify(event).setLine(3, "not found :(");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersErrorMessageInEventIfArenaNotFound() {
|
||||
String templateId = "cool-sign";
|
||||
Template template = mock(Template.class);
|
||||
String arenaId = "castle";
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(null);
|
||||
ArenaSign sign = new ArenaSign(null, templateId, arenaId, "join");
|
||||
SignChangeEvent event = mock(SignChangeEvent.class);
|
||||
|
||||
subject.render(sign, event);
|
||||
|
||||
verify(event).setLine(0, ChatColor.RED + "[ERROR]");
|
||||
verify(event).setLine(1, "Arena");
|
||||
verify(event).setLine(2, ChatColor.YELLOW + arenaId);
|
||||
verify(event).setLine(3, "not found :(");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rendersResultsFromDelegateInEvent() {
|
||||
String templateId = "cool-sign";
|
||||
Template template = mock(Template.class);
|
||||
String arenaId = "castle";
|
||||
Arena arena = mock(Arena.class);
|
||||
String[] lines = new String[]{"this", "is", "a", "sign"};
|
||||
when(templateStore.findById(templateId)).thenReturn(Optional.of(template));
|
||||
when(arenaMaster.getArenaWithName(arenaId)).thenReturn(arena);
|
||||
when(rendersTemplate.render(template, arena)).thenReturn(lines);
|
||||
ArenaSign sign = new ArenaSign(null, templateId, arenaId, "join");
|
||||
SignChangeEvent event = mock(SignChangeEvent.class);
|
||||
|
||||
subject.render(sign, event);
|
||||
|
||||
verify(event).setLine(0, lines[0]);
|
||||
verify(event).setLine(1, lines[1]);
|
||||
verify(event).setLine(2, lines[2]);
|
||||
verify(event).setLine(3, lines[3]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignSerializerTest {
|
||||
|
||||
SignSerializer subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
subject = new SignSerializer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeReturnsSemicolonSeparatedRepresentation() {
|
||||
World world = mock(World.class);
|
||||
Location location = new Location(world, 1, 2, 3);
|
||||
String arenaId = "castle";
|
||||
String type = "join";
|
||||
String templateId = "status";
|
||||
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
|
||||
when(world.getName()).thenReturn("world");
|
||||
ArenaSign sign = new ArenaSign(location, templateId, arenaId, type);
|
||||
|
||||
String result = subject.serialize(sign);
|
||||
|
||||
String expected = "cafebabe-ea75-dead-beef-deadcafebabe;world;1;2;3;castle;join;status";
|
||||
assertThat(result, equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeThrowsIfLengthLessThan8() {
|
||||
World world = mock(World.class);
|
||||
String input = "1;2;3;4;5;6;7";
|
||||
|
||||
Assert.assertThrows(
|
||||
"Invalid input; expected 8 parts, got 7",
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.deserialize(input, world)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeThrowsIfLengthGreaterThan8() {
|
||||
World world = mock(World.class);
|
||||
String input = "1;2;3;4;5;6;7;8;9";
|
||||
|
||||
Assert.assertThrows(
|
||||
"Invalid input; expected 8 parts, got 9",
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.deserialize(input, world)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeThrowsIfWorldDoesNotMatchId() {
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString("deadbeef-ea75-cafe-babe-deadcafebeef"));
|
||||
String input = "wrong-id;world;1;2;3;castle;join;status";
|
||||
|
||||
Assert.assertThrows(
|
||||
"World mismatch",
|
||||
IllegalArgumentException.class,
|
||||
() -> subject.deserialize(input, world)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeReturnsIfWorldMatchesId() {
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString("cafebabe-ea75-dead-beef-deadcafebabe"));
|
||||
String input = "cafebabe-ea75-dead-beef-deadcafebabe;wrong-world;1;2;3;castle;join;status";
|
||||
|
||||
ArenaSign result = subject.deserialize(input, world);
|
||||
|
||||
Location location = new Location(world, 1, 2, 3);
|
||||
assertThat(result.location, equalTo(location));
|
||||
assertThat(result.arenaId, equalTo("castle"));
|
||||
assertThat(result.type, equalTo("join"));
|
||||
assertThat(result.templateId, equalTo("status"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeSerializeReflexivity() {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
String name = "world";
|
||||
World world = mock(World.class);
|
||||
when(world.getName()).thenReturn(name);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
String line = id + ";" + name + ";1;2;3;castle;join;cool-sign";
|
||||
|
||||
String result = subject.serialize(subject.deserialize(line, world));
|
||||
|
||||
assertThat(result, equalTo(line));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeDeserializeReflexivity() {
|
||||
String id = "cafebabe-ea75-dead-beef-deadcafebabe";
|
||||
World world = mock(World.class);
|
||||
when(world.getUID()).thenReturn(UUID.fromString(id));
|
||||
Location location = new Location(world, 1, 2, 3);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
|
||||
ArenaSign result = subject.deserialize(subject.serialize(sign), world);
|
||||
|
||||
assertThat(result.location, equalTo(location));
|
||||
assertThat(result.arenaId, equalTo("castle"));
|
||||
assertThat(result.type, equalTo("join"));
|
||||
assertThat(result.templateId, equalTo("cool-sign"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalReturnsTrueIfStringsAreEqual() {
|
||||
String line = "cafebabe-ea75-dead-beef-deadcafebabe;world;1;2;3;castle;join;status";
|
||||
|
||||
boolean result = subject.equal(line, line);
|
||||
|
||||
assertThat(result, equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalReturnsTrueIfWorldIdAndLocationMatch() {
|
||||
String line1 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;1;2;3;jungle;leave;out";
|
||||
String line2 = "cafebabe-ea75-dead-beef-deadcafebabe;wrong-world;1;2;3;castle;join;status";
|
||||
|
||||
boolean result = subject.equal(line1, line2);
|
||||
|
||||
assertThat(result, equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalReturnsFalseIfOnlyWorldIdIsDifferent() {
|
||||
String line1 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;1;2;3;castle;join;status";
|
||||
String line2 = "deadbeef-feed-cafe-babe-a70ff1cecafe;right-world;1;2;3;castle;join;status";
|
||||
|
||||
boolean result = subject.equal(line1, line2);
|
||||
|
||||
assertThat(result, equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalReturnsFalseIfOnlyCoordsAreDifferent() {
|
||||
String line1 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;1;2;3;castle;join;status";
|
||||
String line2 = "cafebabe-ea75-dead-beef-deadcafebabe;right-world;4;5;6;castle;join;status";
|
||||
|
||||
boolean result = subject.equal(line1, line2);
|
||||
|
||||
assertThat(result, equalTo(false));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignStoreTest {
|
||||
|
||||
SignStore subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
subject = new SignStore();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByLocationReturnsNullIfSignDoesNotExist() {
|
||||
Location location = mock(Location.class);
|
||||
|
||||
ArenaSign result = subject.findByLocation(location);
|
||||
|
||||
assertThat(result, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByLocationReturnsSignIfItExists() {
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
|
||||
subject.add(sign);
|
||||
ArenaSign result = subject.findByLocation(location);
|
||||
|
||||
assertThat(result, equalTo(sign));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByArenaIdReturnsEmptyListIfNoSignsMatch() {
|
||||
Location location1 = mock(Location.class);
|
||||
Location location2 = mock(Location.class);
|
||||
ArenaSign sign1 = new ArenaSign(location1, "cool-sign", "castle", "join");
|
||||
ArenaSign sign2 = new ArenaSign(location2, "lame-sign", "island", "leave");
|
||||
|
||||
subject.add(sign1);
|
||||
subject.add(sign2);
|
||||
List<ArenaSign> result = subject.findByArenaId("jungle");
|
||||
|
||||
assertThat(result.isEmpty(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByArenaIdReturnsOnlyMatchingSigns() {
|
||||
Location location1 = mock(Location.class);
|
||||
Location location2 = mock(Location.class);
|
||||
Location location3 = mock(Location.class);
|
||||
ArenaSign sign1 = new ArenaSign(location1, "cool-sign", "castle", "join");
|
||||
ArenaSign sign2 = new ArenaSign(location2, "lame-sign", "island", "leave");
|
||||
ArenaSign sign3 = new ArenaSign(location3, "very-sign", "jungle", "info");
|
||||
|
||||
subject.add(sign1);
|
||||
subject.add(sign2);
|
||||
subject.add(sign3);
|
||||
List<ArenaSign> result = subject.findByArenaId("island");
|
||||
|
||||
assertThat(result.size(), equalTo(1));
|
||||
assertThat(result, hasItem(sign2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeReturnsNullIfSignDoesNotExist() {
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
|
||||
ArenaSign result = subject.removeByLocation(location);
|
||||
|
||||
assertThat(result, nullValue());
|
||||
|
||||
subject.add(sign);
|
||||
assertThat(subject.removeByLocation(location), equalTo(sign));
|
||||
|
||||
assertThat(subject.removeByLocation(location), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeReturnsSignIfItExists() {
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
|
||||
subject.add(sign);
|
||||
ArenaSign result = subject.removeByLocation(location);
|
||||
|
||||
assertThat(result, equalTo(sign));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeReturnsNullIfSignWasRemoved() {
|
||||
Location location = mock(Location.class);
|
||||
ArenaSign sign = new ArenaSign(location, "cool-sign", "castle", "join");
|
||||
|
||||
subject.add(sign);
|
||||
subject.removeByLocation(location);
|
||||
ArenaSign result = subject.removeByLocation(location);
|
||||
|
||||
assertThat(result, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeByWorldReturnsOnlyMatchingSigns() {
|
||||
World world1 = mock(World.class);
|
||||
World world2 = mock(World.class);
|
||||
Location location1 = mock(Location.class);
|
||||
Location location2 = mock(Location.class);
|
||||
when(location1.getWorld()).thenReturn(world1);
|
||||
when(location2.getWorld()).thenReturn(world2);
|
||||
ArenaSign sign1 = new ArenaSign(location1, "lame-sign", "jungle", "info");
|
||||
ArenaSign sign2 = new ArenaSign(location2, "cool-sign", "castle", "join");
|
||||
|
||||
subject.add(sign1);
|
||||
subject.add(sign2);
|
||||
List<ArenaSign> result = subject.removeByWorld(world2);
|
||||
|
||||
assertThat(result, not(hasItem(sign1)));
|
||||
assertThat(result, hasItem(sign2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeByWorldReturnsEmptyListIfSignsWereRemoved() {
|
||||
World world1 = mock(World.class);
|
||||
World world2 = mock(World.class);
|
||||
Location location1 = mock(Location.class);
|
||||
Location location2 = mock(Location.class);
|
||||
when(location1.getWorld()).thenReturn(world1);
|
||||
when(location2.getWorld()).thenReturn(world2);
|
||||
ArenaSign sign1 = new ArenaSign(location1, "lame-sign", "jungle", "info");
|
||||
ArenaSign sign2 = new ArenaSign(location2, "cool-sign", "castle", "join");
|
||||
|
||||
subject.add(sign1);
|
||||
subject.add(sign2);
|
||||
subject.removeByWorld(world2);
|
||||
List<ArenaSign> result = subject.removeByWorld(world2);
|
||||
|
||||
assertThat(result.isEmpty(), equalTo(true));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class SignWriterTest {
|
||||
|
||||
SignFile file;
|
||||
SignSerializer serializer;
|
||||
Logger log;
|
||||
|
||||
SignWriter subject;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
file = mock(SignFile.class);
|
||||
serializer = mock(SignSerializer.class);
|
||||
log = mock(Logger.class);
|
||||
|
||||
subject = new SignWriter(
|
||||
file,
|
||||
serializer,
|
||||
log
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCallsAppendWithSerializedSign() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "some arbitrary serialization";
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
|
||||
subject.write(sign);
|
||||
|
||||
verify(file).append(line);
|
||||
verify(file).save();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeCallsEraseOnConflicts() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "some arbitrary serialization";
|
||||
String c1 = "some conflicting line";
|
||||
String c2 = "some non-conflicting line";
|
||||
String c3 = "some other conflicting line";
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
when(serializer.equal(c1, line)).thenReturn(true);
|
||||
when(serializer.equal(c2, line)).thenReturn(false);
|
||||
when(serializer.equal(c3, line)).thenReturn(true);
|
||||
when(file.lines()).thenReturn(Arrays.asList(c1, c2, c3));
|
||||
|
||||
subject.write(sign);
|
||||
|
||||
verify(file).erase(c1);
|
||||
verify(file).erase(c3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeLogsWarningOnConflicts() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "some arbitrary serialization";
|
||||
String c1 = "some conflicting line";
|
||||
String c2 = "some non-conflicting line";
|
||||
String c3 = "some other conflicting line";
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
when(serializer.equal(c1, line)).thenReturn(true);
|
||||
when(serializer.equal(c2, line)).thenReturn(false);
|
||||
when(serializer.equal(c3, line)).thenReturn(true);
|
||||
when(file.lines()).thenReturn(Arrays.asList(c1, c2, c3));
|
||||
|
||||
subject.write(sign);
|
||||
|
||||
verify(log, times(2)).warning(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulWriteLogsNothing() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "some arbitrary serialization";
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
|
||||
subject.write(sign);
|
||||
|
||||
verifyNoInteractions(log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eraseCallsEraseWithIdentifiedLineOnly() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "right-id;wrong-name;some other stuff";
|
||||
String c1 = "wrong-id;wrong-name;some stuff";
|
||||
String c2 = "right-id;right-name;some other stuff";
|
||||
String c3 = "wrong-id;wrong-name;some more stuff";
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
when(serializer.equal(anyString(), anyString())).thenReturn(false);
|
||||
when(serializer.equal(c2, line)).thenReturn(true);
|
||||
when(file.lines()).thenReturn(Arrays.asList(c1, c2, c3));
|
||||
|
||||
subject.erase(sign);
|
||||
|
||||
verify(file).erase(c2);
|
||||
verify(file).save();
|
||||
verifyNoMoreInteractions(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulEraseLogsNothing() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "right-id;wrong-name;some other stuff";
|
||||
String candiate1 = "wrong-id;wrong-name;some stuff";
|
||||
String candiate2 = "right-id;right-name;some other stuff";
|
||||
String candiate3 = "wrong-id;wrong-name;some more stuff";
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
when(serializer.equal(anyString(), anyString())).thenReturn(false);
|
||||
when(serializer.equal(candiate2, line)).thenReturn(true);
|
||||
when(file.lines()).thenReturn(Arrays.asList(candiate1, candiate2, candiate3));
|
||||
|
||||
subject.erase(sign);
|
||||
|
||||
verifyNoInteractions(log);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eraseBailsOnNoMatch() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "right-id;right-name;some right stuff";
|
||||
List<String> candidates = Arrays.asList("a", "b", "c");
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
when(serializer.equal(anyString(), anyString())).thenReturn(false);
|
||||
when(file.lines()).thenReturn(candidates);
|
||||
|
||||
subject.erase(sign);
|
||||
|
||||
verify(file, never()).erase(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void eraseLogsWarningOnNoMatch() throws IOException {
|
||||
ArenaSign sign = new ArenaSign(null, "cool-sign", "castle", "join");
|
||||
String line = "right-id;right-name;some right stuff";
|
||||
List<String> candidates = Arrays.asList("a", "b", "c");
|
||||
when(serializer.serialize(sign)).thenReturn(line);
|
||||
when(serializer.equal(anyString(), anyString())).thenReturn(false);
|
||||
when(file.lines()).thenReturn(candidates);
|
||||
|
||||
subject.erase(sign);
|
||||
|
||||
verify(log).warning(anyString());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package com.garbagemule.MobArena.signs;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import com.garbagemule.MobArena.framework.Arena;
|
||||
import com.garbagemule.MobArena.framework.ArenaMaster;
|
||||
import org.bukkit.Location;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@RunWith(MockitoJUnitRunner.StrictStubs.class)
|
||||
public class StoresNewSignTest {
|
||||
|
||||
ArenaMaster arenaMaster;
|
||||
TemplateStore templateStore;
|
||||
SignStore signStore;
|
||||
SavesSignStore savesSignStore;
|
||||
|
||||
StoresNewSign subject;
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
arenaMaster = mock(ArenaMaster.class);
|
||||
when(arenaMaster.getArenaWithName(any()))
|
||||
.thenReturn(null);
|
||||
|
||||
templateStore = mock(TemplateStore.class);
|
||||
when(templateStore.findById(any()))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
signStore = mock(SignStore.class);
|
||||
|
||||
savesSignStore = mock(SavesSignStore.class);
|
||||
|
||||
subject = new StoresNewSign(
|
||||
arenaMaster,
|
||||
templateStore,
|
||||
signStore,
|
||||
savesSignStore
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwOnNonExistentArena() {
|
||||
Location location = mock(Location.class);
|
||||
String arenaId = "castle";
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
|
||||
subject.store(location, arenaId, "", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwOnNonExistentTemplate() {
|
||||
Location location = mock(Location.class);
|
||||
String arenaId = "castle";
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arenaMaster.getArenaWithName(arenaId))
|
||||
.thenReturn(arena);
|
||||
String templateId = "template";
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
|
||||
subject.store(location, arenaId, templateId, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throwOnNonInvalidSignType() {
|
||||
Location location = mock(Location.class);
|
||||
String arenaId = "castle";
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arenaMaster.getArenaWithName(arenaId))
|
||||
.thenReturn(arena);
|
||||
String templateId = "a very nice template";
|
||||
Template template = template(templateId);
|
||||
when(templateStore.findById(templateId))
|
||||
.thenReturn(Optional.of(template));
|
||||
String signType = "not a real sign type";
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
|
||||
subject.store(location, arenaId, templateId, signType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void storesSignAndWritesToDisk() {
|
||||
Location location = mock(Location.class);
|
||||
String arenaId = "castle";
|
||||
Arena arena = mock(Arena.class);
|
||||
when(arenaMaster.getArenaWithName(arenaId))
|
||||
.thenReturn(arena);
|
||||
String templateId = "a very nice template";
|
||||
Template template = template(templateId);
|
||||
when(templateStore.findById(templateId))
|
||||
.thenReturn(Optional.of(template));
|
||||
String signType = "join";
|
||||
|
||||
subject.store(location, arenaId, templateId, signType);
|
||||
|
||||
ArgumentCaptor<ArenaSign> captor = ArgumentCaptor.forClass(ArenaSign.class);
|
||||
verify(signStore).store(captor.capture());
|
||||
verify(savesSignStore).save(signStore);
|
||||
ArenaSign sign = captor.getValue();
|
||||
assertThat(sign.location, equalTo(location));
|
||||
assertThat(sign.arenaId, equalTo(arenaId));
|
||||
assertThat(sign.templateId, equalTo(templateId));
|
||||
assertThat(sign.type, equalTo(signType));
|
||||
}
|
||||
|
||||
private Template template(String id) {
|
||||
return new Template.Builder(id)
|
||||
.withBase(new String[]{"", "", "", ""})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue