1
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:
TheBusyBiscuit 2021-12-08 18:08:18 +01:00
parent 1f3d0f5c20
commit b2e391e099
9 changed files with 414 additions and 2 deletions

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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());
}
}
}
}

View File

@ -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_]+");

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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));
}
}