mirror of
https://github.com/StarWishsama/Slimefun4.git
synced 2024-09-19 19:25:48 +00:00
First steps towards biome maps
This commit is contained in:
parent
1f3d0f5c20
commit
b2e391e099
2
pom.xml
2
pom.xml
@ -309,7 +309,9 @@
|
||||
|
||||
<include>wiki.json</include>
|
||||
<include>languages/translators.json</include>
|
||||
|
||||
<include>tags/*.json</include>
|
||||
<include>biome-maps/*.json</include>
|
||||
|
||||
<include>languages/**/*.yml</include>
|
||||
</includes>
|
||||
|
@ -0,0 +1,48 @@
|
||||
package io.github.thebusybiscuit.slimefun4.api.exceptions;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
|
||||
import io.github.thebusybiscuit.slimefun4.utils.biomes.BiomeMap;
|
||||
|
||||
/**
|
||||
* An {@link BiomeMapException} is thrown whenever a {@link BiomeMap}
|
||||
* contains illegal, invalid or unknown values.
|
||||
*
|
||||
* @author TheBusyBiscuit
|
||||
*
|
||||
*/
|
||||
public class BiomeMapException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -1894334121194788527L;
|
||||
|
||||
/**
|
||||
* This constructs a new {@link BiomeMapException} for the given
|
||||
* {@link BiomeMap}'s {@link NamespacedKey} with the provided context.
|
||||
*
|
||||
* @param key
|
||||
* The {@link NamespacedKey} of our {@link BiomeMap}
|
||||
* @param message
|
||||
* The message to display
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public BiomeMapException(NamespacedKey key, String message) {
|
||||
super("Biome Map '" + key + "' has been misconfigured: " + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs a new {@link BiomeMapException} for the given
|
||||
* {@link BiomeMap}'s {@link NamespacedKey} with the provided context.
|
||||
*
|
||||
* @param key
|
||||
* The {@link NamespacedKey} of our {@link BiomeMap}
|
||||
* @param cause
|
||||
* The {@link Throwable} which has caused this to happen
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public BiomeMapException(NamespacedKey key, Throwable cause) {
|
||||
super("Tag '" + key + "' has been misconfigured (" + cause.getMessage() + ')', cause);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package io.github.thebusybiscuit.slimefun4.implementation.resources;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
@ -7,8 +9,12 @@ import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import io.github.thebusybiscuit.slimefun4.api.exceptions.BiomeMapException;
|
||||
import io.github.thebusybiscuit.slimefun4.api.geo.GEOResource;
|
||||
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
|
||||
import io.github.thebusybiscuit.slimefun4.utils.biomes.BiomeMap;
|
||||
|
||||
/**
|
||||
* This is an abstract parent class for any {@link GEOResource}
|
||||
@ -70,4 +76,34 @@ abstract class SlimefunResource implements GEOResource {
|
||||
return geoMiner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper method for reading a {@link BiomeMap} of {@link Integer} type values from
|
||||
* a resource file.
|
||||
*
|
||||
* @param resource
|
||||
* The {@link SlimefunResource} instance
|
||||
* @param path
|
||||
* The path to our biome map file
|
||||
*
|
||||
* @return A {@link BiomeMap} for this resource
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
static final @Nonnull BiomeMap<Integer> getBiomeMap(SlimefunResource resource, String path) {
|
||||
Validate.notNull(resource, "Resource cannot be null");
|
||||
Validate.notNull(path, "Path cannot be null");
|
||||
|
||||
try {
|
||||
return BiomeMap.fromResource(resource.getKey(), path, JsonElement::getAsInt);
|
||||
} catch (BiomeMapException x) {
|
||||
if (Slimefun.instance().isUnitTest()) {
|
||||
// Unit Tests should always fail here, so we re-throw the exception
|
||||
throw new IllegalStateException(x);
|
||||
} else {
|
||||
// In a server environment, we should just print a warning and carry on
|
||||
Slimefun.logger().log(Level.WARNING, x, () -> "Failed to load BiomeMap for GEO-resource: " + resource.getKey());
|
||||
return new BiomeMap<>(resource.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ public final class PatternUtils {
|
||||
|
||||
public static final Pattern SLASH_SEPARATOR = Pattern.compile(" / ");
|
||||
|
||||
public static final Pattern MINECRAFT_MATERIAL = Pattern.compile("minecraft:[a-z_]+");
|
||||
public static final Pattern MINECRAFT_NAMESPACEDKEY = Pattern.compile("minecraft:[a-z_]+");
|
||||
|
||||
public static final Pattern MINECRAFT_TAG = Pattern.compile("#minecraft:[a-z_]+");
|
||||
public static final Pattern SLIMEFUN_TAG = Pattern.compile("#slimefun:[a-z_]+");
|
||||
|
||||
|
@ -0,0 +1,107 @@
|
||||
package io.github.thebusybiscuit.slimefun4.utils.biomes;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Keyed;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import io.github.thebusybiscuit.slimefun4.api.exceptions.BiomeMapException;
|
||||
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
|
||||
|
||||
public class BiomeMap<T> implements Keyed {
|
||||
|
||||
private final Map<Biome, T> dataMap = new EnumMap<>(Biome.class);
|
||||
private final NamespacedKey namespacedKey;
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public BiomeMap(NamespacedKey namespacedKey) {
|
||||
Validate.notNull(namespacedKey, "The key must not be null.");
|
||||
|
||||
this.namespacedKey = namespacedKey;
|
||||
}
|
||||
|
||||
public @Nullable T get(@Nonnull Biome biome) {
|
||||
Validate.notNull(biome, "The biome shall not be null.");
|
||||
|
||||
return dataMap.get(biome);
|
||||
}
|
||||
|
||||
public @Nonnull T getOrDefault(@Nonnull Biome biome, T defaultValue) {
|
||||
Validate.notNull(biome, "The biome should not be null.");
|
||||
|
||||
return dataMap.getOrDefault(biome, defaultValue);
|
||||
}
|
||||
|
||||
public boolean contains(@Nonnull Biome biome) {
|
||||
Validate.notNull(biome, "The biome must not be null.");
|
||||
|
||||
return dataMap.containsKey(biome);
|
||||
}
|
||||
|
||||
public boolean put(@Nonnull Biome biome, @Nonnull T value) {
|
||||
Validate.notNull(biome, "The biome should not be null.");
|
||||
Validate.notNull(value, "Values cannot be null.");
|
||||
|
||||
return dataMap.put(biome, value) == null;
|
||||
}
|
||||
|
||||
public void putAll(@Nonnull Map<Biome, T> map) {
|
||||
Validate.notNull(map, "The map should not be null.");
|
||||
|
||||
dataMap.putAll(map);
|
||||
}
|
||||
|
||||
public void putAll(@Nonnull BiomeMap<T> map) {
|
||||
Validate.notNull(map, "The map should not be null.");
|
||||
|
||||
dataMap.putAll(map.dataMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @Nonnull NamespacedKey getKey() {
|
||||
return namespacedKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BiomeMap " + dataMap.toString();
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public static <T> @Nonnull BiomeMap<T> fromJson(NamespacedKey key, String json, Function<JsonElement, T> valueConverter) throws BiomeMapException {
|
||||
// All parameters are validated by the Parser.
|
||||
BiomeMapParser<T> parser = new BiomeMapParser<>(key, valueConverter);
|
||||
parser.read(json);
|
||||
return parser.buildBiomeMap();
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public static <T> @Nonnull BiomeMap<T> fromResource(NamespacedKey key, String path, Function<JsonElement, T> valueConverter) throws BiomeMapException {
|
||||
Validate.notNull(key, "The key shall not be null.");
|
||||
Validate.notNull(path, "The path should not be null!");
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Slimefun.class.getResourceAsStream(path), StandardCharsets.UTF_8))) {
|
||||
return fromJson(key, reader.lines().collect(Collectors.joining("")), valueConverter);
|
||||
} catch (IOException x) {
|
||||
throw new BiomeMapException(key, x);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package io.github.thebusybiscuit.slimefun4.utils.biomes;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Biome;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import io.github.bakedlibs.dough.common.CommonPatterns;
|
||||
import io.github.thebusybiscuit.slimefun4.api.exceptions.BiomeMapException;
|
||||
import io.github.thebusybiscuit.slimefun4.utils.PatternUtils;
|
||||
|
||||
class BiomeMapParser<T> {
|
||||
|
||||
private static final String VALUE_KEY = "value";
|
||||
private static final String BIOMES_KEY = "biomes";
|
||||
|
||||
private final NamespacedKey key;
|
||||
private final Function<JsonElement, T> valueConverter;
|
||||
private final Map<Biome, T> map = new EnumMap<>(Biome.class);
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
BiomeMapParser(NamespacedKey key, Function<JsonElement, T> valueConverter) {
|
||||
Validate.notNull(key, "The key shall not be null.");
|
||||
Validate.notNull(valueConverter, "You must provide a Function to convert raw json values to your desired data type.");
|
||||
|
||||
this.key = key;
|
||||
this.valueConverter = valueConverter;
|
||||
}
|
||||
|
||||
void read(@Nonnull String json) throws BiomeMapException {
|
||||
Validate.notNull(json, "The JSON string should not be null!");
|
||||
JsonArray root = null;
|
||||
|
||||
try {
|
||||
JsonParser parser = new JsonParser();
|
||||
root = parser.parse(json).getAsJsonArray();
|
||||
} catch (IllegalStateException | JsonParseException x) {
|
||||
throw new BiomeMapException(key, x);
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't include this in our try/catch, as this type of exception
|
||||
* is already specified in the throws-declaration.
|
||||
*/
|
||||
read(root);
|
||||
}
|
||||
|
||||
void read(@Nonnull JsonArray json) throws BiomeMapException {
|
||||
Validate.notNull(json, "The JSON Array should not be null!");
|
||||
|
||||
for (JsonElement element : json) {
|
||||
if (element instanceof JsonObject) {
|
||||
readEntry(element.getAsJsonObject());
|
||||
} else {
|
||||
throw new BiomeMapException(key, "Unexpected array element: " + element.getClass().getSimpleName() + " - " + element.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readEntry(@Nonnull JsonObject entry) throws BiomeMapException {
|
||||
Validate.notNull(entry, "The JSON entry should not be null!");
|
||||
|
||||
if (entry.has(VALUE_KEY)) {
|
||||
T value = valueConverter.apply(entry.get(VALUE_KEY));
|
||||
|
||||
if (entry.has(BIOMES_KEY) && entry.get(BIOMES_KEY).isJsonArray()) {
|
||||
Set<Biome> biomes = readBiomes(entry.get(BIOMES_KEY).getAsJsonArray());
|
||||
|
||||
for (Biome biome : biomes) {
|
||||
T prev = map.put(biome, value);
|
||||
|
||||
if (prev != null) {
|
||||
throw new BiomeMapException(key, "Biome '" + biome.getKey() + "' is registered twice");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new BiomeMapException(key, "Entry is missing a 'biomes' child of type array.");
|
||||
}
|
||||
} else {
|
||||
throw new BiomeMapException(key, "Entry is missing a 'value' child.");
|
||||
}
|
||||
}
|
||||
|
||||
private @Nonnull Set<Biome> readBiomes(@Nonnull JsonArray array) throws BiomeMapException {
|
||||
Validate.notNull(array, "The JSON array should not be null!");
|
||||
Set<Biome> biomes = EnumSet.noneOf(Biome.class);
|
||||
|
||||
for (JsonElement element : array) {
|
||||
if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) {
|
||||
String value = element.getAsString();
|
||||
|
||||
if (PatternUtils.MINECRAFT_NAMESPACEDKEY.matcher(value).matches()) {
|
||||
String formattedValue = CommonPatterns.COLON.split(value)[1].toUpperCase(Locale.ROOT);
|
||||
|
||||
try {
|
||||
Biome biome = Biome.valueOf(formattedValue);
|
||||
biomes.add(biome);
|
||||
} catch (IllegalArgumentException x) {
|
||||
throw new BiomeMapException(key, "The Biome '" + value + "' does not exist!");
|
||||
}
|
||||
} else {
|
||||
// The regular expression did not match
|
||||
throw new BiomeMapException(key, "Could not recognize value '" + value + "'");
|
||||
}
|
||||
} else {
|
||||
throw new BiomeMapException(key, "Unexpected array element: " + element.getClass().getSimpleName() + " - " + element.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return biomes;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
BiomeMap<T> buildBiomeMap() {
|
||||
BiomeMap<T> biomeMap = new BiomeMap<>(key);
|
||||
biomeMap.putAll(map);
|
||||
return biomeMap;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This package contains classes centered around our {@link io.github.thebusybiscuit.slimefun4.utils.biomes.BiomeMap}
|
||||
* utility.
|
||||
*/
|
||||
package io.github.thebusybiscuit.slimefun4.utils.biomes;
|
@ -134,7 +134,7 @@ public class TagParser implements Keyed {
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
private void parsePrimitiveValue(String value, Set<Material> materials, Set<Tag<Material>> tags, boolean throwException) throws TagMisconfigurationException {
|
||||
if (PatternUtils.MINECRAFT_MATERIAL.matcher(value).matches()) {
|
||||
if (PatternUtils.MINECRAFT_NAMESPACEDKEY.matcher(value).matches()) {
|
||||
// Match the NamespacedKey against Materials
|
||||
Material material = Material.matchMaterial(value);
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
package io.github.thebusybiscuit.slimefun4.utils.biomes;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import io.github.thebusybiscuit.slimefun4.api.exceptions.BiomeMapException;
|
||||
import io.github.thebusybiscuit.slimefun4.implementation.Slimefun;
|
||||
|
||||
import be.seeseemelk.mockbukkit.MockBukkit;
|
||||
|
||||
class TestBiomeMapParser {
|
||||
|
||||
private static final Function<JsonElement, String> AS_STRING = JsonElement::getAsString;
|
||||
private static final Function<JsonElement, Integer> AS_INT = JsonElement::getAsInt;
|
||||
|
||||
private static Slimefun plugin;
|
||||
private static NamespacedKey key;
|
||||
|
||||
@BeforeAll
|
||||
public static void load() {
|
||||
MockBukkit.mock();
|
||||
plugin = MockBukkit.load(Slimefun.class);
|
||||
key = new NamespacedKey(plugin, "test");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void unload() {
|
||||
MockBukkit.unmock();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test JSON Parsing Error handling")
|
||||
void testInvalidJson() {
|
||||
assertMisconfiguration(AS_STRING, "");
|
||||
assertMisconfiguration(AS_STRING, "1234");
|
||||
assertMisconfiguration(AS_STRING, "hello world");
|
||||
assertMisconfiguration(AS_STRING, "{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test Array not having proper children")
|
||||
void testInvalidArrayChildren() {
|
||||
assertMisconfiguration(AS_STRING, "[1, 2, 3]");
|
||||
assertMisconfiguration(AS_STRING, "[[1], [2]]");
|
||||
assertMisconfiguration(AS_STRING, "[\"foo\", \"bar\"]");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test Array entries being misconfigured")
|
||||
void testInvalidEntries() {
|
||||
assertMisconfiguration(AS_STRING, "[{}]");
|
||||
assertMisconfiguration(AS_STRING, "[{\"id\": \"one\"}]");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test Array entries being incomplete")
|
||||
void testIncompleteEntries() {
|
||||
assertMisconfiguration(AS_STRING, "[{\"value\": \"cool\"}]");
|
||||
assertMisconfiguration(AS_STRING, "[{\"biomes\": []}]");
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
private <T> void assertMisconfiguration(Function<JsonElement, T> function, String json) {
|
||||
BiomeMapParser<T> parser = new BiomeMapParser<>(key, function);
|
||||
Assertions.assertThrows(BiomeMapException.class, () -> parser.read(json));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user