Refactor config parsing

This commit is contained in:
Alexander
2021-06-22 10:03:16 +01:00
parent 1081001f87
commit 6f9301a0bb
8 changed files with 681 additions and 626 deletions

View File

@@ -3,10 +3,10 @@ package vg.civcraft.mc.civmodcore;
import java.sql.SQLException;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.HumanEntity;
import vg.civcraft.mc.civmodcore.chat.dialog.DialogManager;
import vg.civcraft.mc.civmodcore.commands.CommandManager;
import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials;
import vg.civcraft.mc.civmodcore.dao.ManagedDatasource;
import vg.civcraft.mc.civmodcore.events.CustomEventMapper;
import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils;
@@ -53,7 +53,7 @@ public final class CivModCorePlugin extends ACivMod {
super.onEnable();
// Load Database
try {
this.database = (ManagedDatasource) getConfig().get("database");
this.database = ManagedDatasource.construct(this, (DatabaseCredentials) getConfig().get("database"));
if (this.database != null) {
CMCWorldDAO dao = new CMCWorldDAO(this.database, this);
if (dao.updateDatabase()) {
@@ -66,7 +66,7 @@ public final class CivModCorePlugin extends ACivMod {
}
}
}
catch (Exception error) {
catch (final Exception error) {
warning("Cannot get database from config.", error);
this.database = null;
}

View File

@@ -1,134 +0,0 @@
package vg.civcraft.mc.civmodcore;
import com.google.common.base.Strings;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Logger;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
/**
* This is a config parsing class intended to make handling configs a little easier, and automatically parse commonly
* seen things within civ configs.
*/
public class CoreConfigManager {
protected final ACivMod plugin;
protected final Logger logger;
private boolean debug;
private boolean logReplies;
public CoreConfigManager(ACivMod plugin) {
this.plugin = plugin;
this.logger = plugin.getLogger();
}
/**
* Parses this civ plugin's config. It will also save a default config (if one is not already present) and reload
* the config, so there's no need to do so yourself beforehand.
*
* @return Returns true if the config was successfully parsed.
*/
public final boolean parse() {
this.plugin.info(ChatColor.BLUE + "Parsing config.");
this.plugin.saveDefaultConfig();
FileConfiguration config = this.plugin.getConfig();
// Parse debug value
this.debug = config.getBoolean("debug", false);
this.plugin.info("Debug mode: " + (this.debug ? "enabled" : "disabled"));
// Parse reply logging value
this.logReplies = config.getBoolean("logReplies", false);
this.plugin.info("Logging replies: " + (this.logReplies ? "enabled" : "disabled"));
// Allow child class parsing
boolean worked = parseInternal(config);
if (worked) {
plugin.info(ChatColor.BLUE + "Config parsed.");
}
else {
plugin.warning("Failed to parse config!");
}
return worked;
}
/**
* An internal parser method intended to be overriden by child classes.
*
* @param config The root config section.
* @return Return true if the
*/
protected boolean parseInternal(ConfigurationSection config) {
return true;
}
/**
* This should reset all config values back to their defaults. Child classes should override this if they parse
* additional values that should be reset.
*/
public void reset() {
this.debug = false;
this.logReplies = false;
}
// ------------------------------------------------------------ //
// Getters
// ------------------------------------------------------------ //
public final boolean isDebugEnabled() {
return this.debug;
}
public final boolean logReplies() {
return this.logReplies;
}
// ------------------------------------------------------------ //
// Predefined parsing utilities
// ------------------------------------------------------------ //
/**
* Attempts to retrieve a list of materials from a config section.
*
* @param config The config section.
* @param key The key of the list.
* @return Returns a list of materials, or null.
*/
protected final List<Material> parseMaterialList(ConfigurationSection config, String key) {
return parseList(config, key, slug -> {
Material found = MaterialUtils.getMaterial(slug);
if (found == null) {
this.logger.warning("Could not parse material \"" + slug + "\" at: " + config.getCurrentPath());
return null;
}
return found;
});
}
/**
* Attempts to retrieve a list from a config section.
*
* @param <T> The type to parse the list into.
* @param config The config section.
* @param key The key of the list.
* @param parser The parser to convert the string value into the correct type.
* @return Returns a list, or null.
*/
protected static <T> List<T> parseList(ConfigurationSection config, String key, Function<String, T> parser) {
if (config == null || Strings.isNullOrEmpty(key) || !config.isList(key) || parser == null) {
return null;
}
List<T> result = new ArrayList<>();
for (String entry : config.getStringList(key)) {
T item = parser.apply(entry);
if (item != null) {
result.add(item);
}
}
return result;
}
}

View File

@@ -1,13 +1,65 @@
package vg.civcraft.mc.civmodcore.config;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils;
import vg.civcraft.mc.civmodcore.inventory.items.ItemMap;
import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils;
import vg.civcraft.mc.civmodcore.world.model.EllipseArea;
import vg.civcraft.mc.civmodcore.world.model.GlobalYLimitedArea;
import vg.civcraft.mc.civmodcore.world.model.IArea;
import vg.civcraft.mc.civmodcore.world.model.RectangleArea;
@UtilityClass
public final class ConfigHelper {
private static final Logger LOGGER = Bukkit.getLogger();
/**
* Retrieves the configuration section at the given key on the given configuration section.
*
* @param config The config to get the keyed section from.
* @param key The key of the section to retrieve.
* @return Returns the configuration section at the given key, or returns a new, empty section.
*/
@Nonnull
public static ConfigurationSection getSection(@NonNull final ConfigurationSection config,
@NonNull final String key) {
ConfigurationSection found = config.getConfigurationSection(key);
if (found == null) {
found = config.createSection(key);
}
return found;
}
/**
* Retrieves a string list from a given config section. If the keyed value is a standalone string instead of a
* list, that value will be converted to a list.
@@ -16,9 +68,9 @@ public final class ConfigHelper {
* @param key The key to get the list of.
* @return Returns a list of strings, which is never null.
*/
public static List<String> getStringList(@Nonnull final ConfigurationSection config, @Nonnull final String key) {
Preconditions.checkNotNull(config, "Config cannot be null!");
Preconditions.checkNotNull(key, "Key cannot be null!");
@Nonnull
public static List<String> getStringList(@NonNull final ConfigurationSection config,
@NonNull final String key) {
if (config.isString(key)) {
final var list = new ArrayList<String>(1);
list.add(config.getString(key));
@@ -27,4 +79,483 @@ public final class ConfigHelper {
return config.getStringList(key);
}
/**
* Attempts to retrieve a list from a config section.
*
* @param <T> The type to parse the list into.
* @param config The config section.
* @param key The key of the list.
* @param parser The parser to convert the string value into the correct type.
* @return Returns a list, or null.
*/
@Nonnull
public static <T> List<T> parseList(@NonNull final ConfigurationSection config,
@NonNull final String key,
@NonNull final Function<String, T> parser) {
if (!config.isList(key)) {
return new ArrayList<>(0);
}
final var entries = getStringList(config, key);
final var result = new ArrayList<T>(entries.size());
for (final String entry : entries) {
final T item = parser.apply(entry);
if (item != null) {
result.add(item);
}
}
return result;
}
/**
* Attempts to retrieve a list of materials from a config section.
*
* @param config The config section.
* @param key The key of the list.
* @return Returns a list of materials, or null.
*/
@Nonnull
public static List<Material> parseMaterialList(@Nonnull final ConfigurationSection config,
@Nonnull final String key) {
return parseList(config, key, MaterialUtils::getMaterial);
}
/**
* Creates an item map containing all the items listed in the given config
* section
*
* @param config ConfigurationSection to parse the items from
* @return The item map created
*/
@Nonnull
public static ItemMap parseItemMap(@Nullable final ConfigurationSection config) {
final var result = new ItemMap();
if (config == null) {
return result;
}
for (final String key : config.getKeys(false)) {
ConfigurationSection current = config.getConfigurationSection(key);
ItemMap partMap = parseItemMapDirectly(current);
result.merge(partMap);
}
return result;
}
@Nonnull
public static ItemMap parseItemMapDirectly(@Nullable final ConfigurationSection current) {
ItemMap im = new ItemMap();
if (current == null) {
return im;
}
Material m = null;
try {
m = Material.valueOf(current.getString("material"));
} catch (IllegalArgumentException iae) {
m = null;
} finally {
if (m == null) {
LOGGER.severe("Failed to find material " + current.getString("material") + " in section " + current.getCurrentPath());
return im;
}
}
ItemStack toAdd = new ItemStack(m);
if (current.isInt("durability")) {
LOGGER.warning("Item durability as specified at " + current.getCurrentPath() + " is no longer supported");
}
ItemMeta meta = toAdd.getItemMeta();
if (meta == null) {
LOGGER.severe("No item meta found for" + current.getCurrentPath());
} else {
String name = current.getString("name");
if (name != null) {
meta.setDisplayName(name);
}
List<String> lore = current.getStringList("lore");
if (lore != null) {
meta.setLore(lore);
}
if (current.isBoolean("unbreakable")) {
meta.setUnbreakable(current.getBoolean("unbreakable"));
}
if (current.isBoolean("hideFlags") && current.getBoolean("hideFlags")) {
for (ItemFlag flag : ItemFlag.values()) {
meta.addItemFlags(flag);
}
}
if (current.contains("enchants")) {
for (String enchantKey : current.getConfigurationSection("enchants").getKeys(false)) {
ConfigurationSection enchantConfig = current.getConfigurationSection("enchants")
.getConfigurationSection(enchantKey);
if (!enchantConfig.isString("enchant")) {
LOGGER.warning("No enchant specified for enchantment entry at " + enchantConfig.getCurrentPath()
+ ". Entry was ignored");
continue;
}
Enchantment enchant;
enchant = Enchantment
.getByKey(NamespacedKey.minecraft((enchantConfig.getString("enchant").toLowerCase())));
if (enchant == null) {
LOGGER.severe("Failed to parse enchantment " + enchantConfig.getString("enchant")
+ ", the entry was ignored");
continue;
}
int level = enchantConfig.getInt("level", 1);
meta.addEnchant(enchant, level, true);
}
}
if (m == Material.LEATHER_BOOTS || m == Material.LEATHER_CHESTPLATE || m == Material.LEATHER_HELMET
|| m == Material.LEATHER_LEGGINGS) {
ConfigurationSection color = current.getConfigurationSection("color");
Color leatherColor = null;
if (color != null) {
int red = color.getInt("red");
int blue = color.getInt("blue");
int green = color.getInt("green");
leatherColor = Color.fromRGB(red, green, blue);
} else {
String hexColorCode = current.getString("color");
if (hexColorCode != null) {
Integer hexColor = Integer.parseInt(hexColorCode, 16);
if (hexColor != null) {
leatherColor = Color.fromRGB(hexColor);
}
}
}
if (leatherColor != null) {
((LeatherArmorMeta) meta).setColor(leatherColor);
}
}
if (m == Material.ENCHANTED_BOOK) {
ConfigurationSection storedEnchantSection = current.getConfigurationSection("stored_enchants");
if (storedEnchantSection != null) {
EnchantmentStorageMeta enchantMeta = (EnchantmentStorageMeta) meta;
for (String sEKey : storedEnchantSection.getKeys(false)) {
ConfigurationSection currentStoredEnchantSection = storedEnchantSection
.getConfigurationSection(sEKey);
if (currentStoredEnchantSection != null) {
Enchantment enchant = EnchantUtils.getEnchantment(currentStoredEnchantSection.getString("enchant"));
int level = currentStoredEnchantSection.getInt("level", 1);
if (enchant != null) {
enchantMeta.addStoredEnchant(enchant, level, true);
}
else {
LOGGER.severe("Failed to parse enchantment at " + currentStoredEnchantSection.getCurrentPath()
+ ", it was not applied");
}
}
}
}
}
if (m == Material.POTION || m == Material.SPLASH_POTION || m == Material.LINGERING_POTION
|| m == Material.TIPPED_ARROW) {
ConfigurationSection potion = current.getConfigurationSection("potion_effects");
if (potion != null) {
PotionType potType;
try {
potType = PotionType.valueOf(potion.getString("type", "AWKWARD"));
} catch (IllegalArgumentException e) {
LOGGER.warning("Expected potion type at " + potion.getCurrentPath() + ", but "
+ potion.getString("type") + " is not a valid potion type");
potType = PotionType.AWKWARD;
}
boolean upgraded = potion.getBoolean("upgraded", false);
boolean extended = potion.getBoolean("extended", false);
PotionMeta potMeta = (PotionMeta) meta;
potMeta.setBasePotionData(new PotionData(potType, extended, upgraded));
ConfigurationSection customEffects = potion.getConfigurationSection("custom_effects");
if (customEffects != null) {
List<PotionEffect> pots = parsePotionEffects(potion);
for (PotionEffect pe : pots) {
potMeta.addCustomEffect(pe, true);
}
}
}
}
toAdd.setItemMeta(meta);
if (current.contains("nbt")) {
toAdd = ItemMap.enrichWithNBT(toAdd, 1, current.getConfigurationSection("nbt").getValues(true));
}
}
// Setting amount must be last just in cast enrichWithNBT is called,
// which
// resets the amount to 1.
int amount = current.getInt("amount", 1);
toAdd.setAmount(amount);
im.addItemStack(toAdd);
return im;
}
public static int parseTimeAsTicks(@Nonnull final String arg) {
return (int) (parseTime(arg, TimeUnit.MILLISECONDS) / 50L);
}
public static long parseTime(@Nonnull final String arg,
@Nonnull final TimeUnit unit) {
long millis = parseTime(arg);
return unit.convert(millis, TimeUnit.MILLISECONDS);
}
/**
* Parses a time value specified in a config. This allows to specify human
* readable time values easily, instead of having to specify every amount in
* ticks or seconds. The unit of a number specifed by the letter added after it,
* for example 5h means 5 hours or 34s means 34 seconds. Possible modifiers are:
* t (ticks), s (seconds), m (minutes), h (hours) and d (days)
* <p>
* Additionally you can combine those amounts in any way you want, for example
* you can specify 3h5m43s as 3 hours, 5 minutes and 43 seconds. This doesn't
* have to be sorted and may even list the same unit multiple times for
* different values, but the values are not allowed to be separated by anything
*
* @param input Parsed string containing the time format
* @return How many milliseconds the given time value is
*/
public static long parseTime(@Nonnull String input) {
input = input.replace(" ", "").replace(",", "").toLowerCase();
long result = 0;
try {
result += Long.parseLong(input);
return result;
} catch (NumberFormatException e) {
}
while (!input.equals("")) {
String typeSuffix = getSuffix(input, Character::isLetter);
input = input.substring(0, input.length() - typeSuffix.length());
String numberSuffix = getSuffix(input, Character::isDigit);
input = input.substring(0, input.length() - numberSuffix.length());
long duration;
if (numberSuffix.length() == 0) {
duration = 1;
} else {
duration = Long.parseLong(numberSuffix);
}
switch (typeSuffix) {
case "ms":
case "milli":
case "millis":
result += duration;
break;
case "s": // seconds
case "sec":
case "second":
case "seconds":
result += TimeUnit.SECONDS.toMillis(duration);
break;
case "m": // minutes
case "min":
case "minute":
case "minutes":
result += TimeUnit.MINUTES.toMillis(duration);
break;
case "h": // hours
case "hour":
case "hours":
result += TimeUnit.HOURS.toMillis(duration);
break;
case "d": // days
case "day":
case "days":
result += TimeUnit.DAYS.toMillis(duration);
break;
case "w": // weeks
case "week":
case "weeks":
result += TimeUnit.DAYS.toMillis(duration * 7);
break;
case "month": // weeks
case "months":
result += TimeUnit.DAYS.toMillis(duration * 30);
break;
case "y":
case "year":
case "years":
result += TimeUnit.DAYS.toMillis(duration * 365);
break;
case "never":
case "inf":
case "infinite":
case "perm":
case "perma":
case "forever":
// 1000 years counts as perma
result += TimeUnit.DAYS.toMillis(365 * 1000);
default:
// just ignore it
}
}
return result;
}
@Nonnull
private static String getSuffix(@Nonnull final String arg,
@Nonnull final Predicate<Character> selector) {
StringBuilder number = new StringBuilder();
for (int i = arg.length() - 1; i >= 0; i--) {
if (selector.test(arg.charAt(i))) {
number.insert(0, arg.substring(i, i + 1));
} else {
break;
}
}
return number.toString();
}
/**
* Parses a potion effect
*
* @param configurationSection ConfigurationSection to parse the effect from
* @return The potion effect parsed
*/
@Nonnull
public static List<PotionEffect> parsePotionEffects(@Nullable final ConfigurationSection configurationSection) {
List<PotionEffect> potionEffects = Lists.newArrayList();
if (configurationSection != null) {
for (String name : configurationSection.getKeys(false)) {
ConfigurationSection configEffect = configurationSection.getConfigurationSection(name);
String type = configEffect.getString("type");
if (type == null) {
LOGGER.severe("Expected potion type to be specified, but found no \"type\" option at "
+ configEffect.getCurrentPath());
continue;
}
PotionEffectType effect = PotionEffectType.getByName(type);
if (effect == null) {
LOGGER.severe("Expected potion type to be specified at " + configEffect.getCurrentPath()
+ " but found " + type + " which is no valid type");
}
int duration = configEffect.getInt("duration", 200);
int amplifier = configEffect.getInt("amplifier", 0);
potionEffects.add(new PotionEffect(effect, duration, amplifier));
}
}
return potionEffects;
}
@Nullable
public static IArea parseArea(@Nullable final ConfigurationSection config) {
if (config == null) {
LOGGER.warning("Tried to parse area on null section");
return null;
}
String type = config.getString("type");
if (type == null) {
LOGGER.warning("Found no area type at " + config.getCurrentPath());
return null;
}
int lowerYBound = config.getInt("lowerYBound", 0);
int upperYBound = config.getInt("upperYBound", 255);
String worldName = config.getString("world");
if (worldName == null) {
LOGGER.warning("Found no world specified for area at " + config.getCurrentPath());
return null;
}
World world = Bukkit.getWorld(worldName);
if (world == null) {
LOGGER.warning("Found no world with name " + worldName + " as specified at " + config.getCurrentPath());
return null;
}
Location center = null;
if (config.isConfigurationSection("center")) {
ConfigurationSection centerSection = config.getConfigurationSection("center");
int x = centerSection.getInt("x", 0);
int y = centerSection.getInt("y", 0);
int z = centerSection.getInt("z", 0);
if (world != null) {
center = new Location(world, x, y, z);
}
}
int xSize = config.getInt("xSize", -1);
int zSize = config.getInt("zSize", -1);
IArea area = null;
switch (type) {
case "GLOBAL":
area = new GlobalYLimitedArea(lowerYBound, upperYBound, world);
break;
case "ELLIPSE":
if (center == null) {
LOGGER.warning("Found no center for area at " + config.getCurrentPath());
return null;
}
if (xSize == -1) {
LOGGER.warning("Found no xSize for area at " + config.getCurrentPath());
return null;
}
if (zSize == -1) {
LOGGER.warning("Found no zSize for area at " + config.getCurrentPath());
return null;
}
area = new EllipseArea(lowerYBound, upperYBound, center, xSize, zSize);
break;
case "RECTANGLE":
if (center == null) {
LOGGER.warning("Found no center for area at " + config.getCurrentPath());
return null;
}
if (xSize == -1) {
LOGGER.warning("Found no xSize for area at " + config.getCurrentPath());
return null;
}
if (zSize == -1) {
LOGGER.warning("Found no zSize for area at " + config.getCurrentPath());
return null;
}
area = new RectangleArea(lowerYBound, upperYBound, center, xSize, zSize);
break;
default:
LOGGER.warning("Invalid area type " + type + " at " + config.getCurrentPath());
}
return area;
}
/**
* Parses a section which contains key-value mappings of a type to another type
*
* @param <K> Key type
* @param <V> Value type
* @param parent Configuration section containing the section with the values
* @param identifier Config identifier of the section containing the entries
* @param logger The logger to write in progress work to
* @param keyConverter Converts strings to type K
* @param valueConverter Converts strings to type V
* @param mapToUse The map to place parsed keys and values.
*/
public static <K, V> void parseKeyValueMap(@Nonnull final ConfigurationSection parent,
@Nonnull final String identifier,
@Nonnull final Logger logger,
@Nonnull final Function<String, K> keyConverter,
@Nonnull final Function<String, V> valueConverter,
@Nonnull final Map<K, V> mapToUse) {
if (!parent.isConfigurationSection(identifier)) {
return;
}
ConfigurationSection section = parent.getConfigurationSection(identifier);
for (String keyString : section.getKeys(false)) {
if (section.isConfigurationSection(keyString)) {
logger.warning(
"Ignoring invalid " + identifier + " entry " + keyString + " at " + section.getCurrentPath());
continue;
}
K keyinstance;
try {
keyinstance = keyConverter.apply(keyString);
} catch (IllegalArgumentException e) {
logger.warning("Failed to parse " + identifier + " " + keyString + " at " + section.getCurrentPath()
+ ": " + e.toString());
continue;
}
V value = valueConverter.apply(section.getString(keyString));
mapToUse.put(keyinstance, value);
}
}
/**
* @deprecated Use {@link EnchantUtils#getEnchantment(String)} instead.
*/
@Nullable
@Deprecated(forRemoval = true)
public static Enchantment parseEnchantment(@Nonnull final ConfigurationSection config,
@Nonnull final String key) {
return EnchantUtils.getEnchantment(config.getString(key));
}
}

View File

@@ -0,0 +1,82 @@
package vg.civcraft.mc.civmodcore.config;
import java.util.logging.Logger;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.Plugin;
/**
* This is a config parsing class intended to make handling configs a little easier, and automatically parse commonly
* seen things within civ configs.
*/
public abstract class ConfigParser {
protected final Plugin plugin;
protected final Logger logger;
private boolean debug;
private boolean logReplies;
public ConfigParser(final Plugin plugin) {
this.plugin = plugin;
this.logger = plugin.getLogger();
}
/**
* Parses this civ plugin's config. It will also save a default config (if one is not already present) and reload
* the config, so there's no need to do so yourself beforehand.
*
* @return Returns true if the config was successfully parsed.
*/
public final boolean parse() {
this.logger.info(ChatColor.BLUE + "Parsing config.");
this.plugin.saveDefaultConfig();
FileConfiguration config = this.plugin.getConfig();
// Parse debug value
this.debug = config.getBoolean("debug", false);
this.logger.info("Debug mode: " + (this.debug ? "enabled" : "disabled"));
// Parse reply logging value
this.logReplies = config.getBoolean("logReplies", false);
this.logger.info("Logging replies: " + (this.logReplies ? "enabled" : "disabled"));
// Allow child class parsing
final boolean worked = parseInternal(config);
if (worked) {
this.logger.info(ChatColor.BLUE + "Config parsed.");
}
else {
this.logger.warning("Failed to parse config!");
}
return worked;
}
/**
* An internal parser method intended to be overridden by child classes.
*
* @param config The root config section.
* @return Return true if the
*/
protected abstract boolean parseInternal(final ConfigurationSection config);
/**
* This should reset all config values back to their defaults. Child classes should override this if they parse
* additional values that should be reset.
*/
public void reset() {
this.debug = false;
this.logReplies = false;
}
// ------------------------------------------------------------ //
// Getters
// ------------------------------------------------------------ //
public final boolean isDebugEnabled() {
return this.debug;
}
public final boolean logReplies() {
return this.logReplies;
}
}

View File

@@ -0,0 +1,24 @@
package vg.civcraft.mc.civmodcore.config;
import java.util.Map;
import javax.annotation.Nonnull;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
/**
* Class is intended to be used by implementations of {@link ConfigurationSerializable} during the deserialization
* process.
*/
public class ConfigSection extends MemoryConfiguration {
/**
* @param data The data to create a new ConfigSection from.
* @return Returns a new ConfigSection.
*/
public static ConfigSection fromData(@Nonnull final Map<String, Object> data) {
final var section = new ConfigSection();
section.map.putAll(data);
return section;
}
}

View File

@@ -1,472 +0,0 @@
package vg.civcraft.mc.civmodcore.utilities;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import vg.civcraft.mc.civmodcore.areas.EllipseArea;
import vg.civcraft.mc.civmodcore.areas.GlobalYLimitedArea;
import vg.civcraft.mc.civmodcore.areas.IArea;
import vg.civcraft.mc.civmodcore.areas.RectangleArea;
import vg.civcraft.mc.civmodcore.itemHandling.ItemMap;
public class ConfigParsing {
private static final Logger log = Bukkit.getLogger();
/**
* Creates an itemmap containing all the items listed in the given config
* section
*
* @param config ConfigurationSection to parse the items from
* @return The item map created
*/
public static ItemMap parseItemMap(ConfigurationSection config) {
ItemMap result = new ItemMap();
if (config == null) {
return result;
}
for (String key : config.getKeys(false)) {
ConfigurationSection current = config.getConfigurationSection(key);
ItemMap partMap = parseItemMapDirectly(current);
result.merge(partMap);
}
return result;
}
public static ItemMap parseItemMapDirectly(ConfigurationSection current) {
ItemMap im = new ItemMap();
if (current == null) {
return im;
}
Material m = null;
try {
m = Material.valueOf(current.getString("material"));
} catch (IllegalArgumentException iae) {
m = null;
} finally {
if (m == null) {
log.severe("Failed to find material " + current.getString("material") + " in section " + current.getCurrentPath());
return im;
}
}
ItemStack toAdd = new ItemStack(m);
if (current.isInt("durability")) {
log.warning("Item durability as specified at " + current.getCurrentPath() + " is no longer supported");
}
ItemMeta meta = toAdd.getItemMeta();
if (meta == null) {
log.severe("No item meta found for" + current.getCurrentPath());
} else {
String name = current.getString("name");
if (name != null) {
meta.setDisplayName(name);
}
List<String> lore = current.getStringList("lore");
if (lore != null) {
meta.setLore(lore);
}
if (current.isBoolean("unbreakable")) {
meta.setUnbreakable(current.getBoolean("unbreakable"));
}
if (current.isBoolean("hideFlags") && current.getBoolean("hideFlags")) {
for (ItemFlag flag : ItemFlag.values()) {
meta.addItemFlags(flag);
}
}
if (current.contains("enchants")) {
for (String enchantKey : current.getConfigurationSection("enchants").getKeys(false)) {
ConfigurationSection enchantConfig = current.getConfigurationSection("enchants")
.getConfigurationSection(enchantKey);
if (!enchantConfig.isString("enchant")) {
log.warning("No enchant specified for enchantment entry at " + enchantConfig.getCurrentPath()
+ ". Entry was ignored");
continue;
}
Enchantment enchant;
enchant = Enchantment
.getByKey(NamespacedKey.minecraft((enchantConfig.getString("enchant").toLowerCase())));
if (enchant == null) {
log.severe("Failed to parse enchantment " + enchantConfig.getString("enchant")
+ ", the entry was ignored");
continue;
}
int level = enchantConfig.getInt("level", 1);
meta.addEnchant(enchant, level, true);
}
}
if (m == Material.LEATHER_BOOTS || m == Material.LEATHER_CHESTPLATE || m == Material.LEATHER_HELMET
|| m == Material.LEATHER_LEGGINGS) {
ConfigurationSection color = current.getConfigurationSection("color");
Color leatherColor = null;
if (color != null) {
int red = color.getInt("red");
int blue = color.getInt("blue");
int green = color.getInt("green");
leatherColor = Color.fromRGB(red, green, blue);
} else {
String hexColorCode = current.getString("color");
if (hexColorCode != null) {
Integer hexColor = Integer.parseInt(hexColorCode, 16);
if (hexColor != null) {
leatherColor = Color.fromRGB(hexColor);
}
}
}
if (leatherColor != null) {
((LeatherArmorMeta) meta).setColor(leatherColor);
}
}
if (m == Material.ENCHANTED_BOOK) {
ConfigurationSection storedEnchantSection = current.getConfigurationSection("stored_enchants");
if (storedEnchantSection != null) {
EnchantmentStorageMeta enchantMeta = (EnchantmentStorageMeta) meta;
for (String sEKey : storedEnchantSection.getKeys(false)) {
ConfigurationSection currentStoredEnchantSection = storedEnchantSection
.getConfigurationSection(sEKey);
if (currentStoredEnchantSection != null) {
Enchantment enchant = parseEnchantment(currentStoredEnchantSection, "enchant");
int level = currentStoredEnchantSection.getInt("level", 1);
if (enchant != null) {
enchantMeta.addStoredEnchant(enchant, level, true);
}
else {
log.severe("Failed to parse enchantment at " + currentStoredEnchantSection.getCurrentPath()
+ ", it was not applied");
}
}
}
}
}
if (m == Material.POTION || m == Material.SPLASH_POTION || m == Material.LINGERING_POTION
|| m == Material.TIPPED_ARROW) {
ConfigurationSection potion = current.getConfigurationSection("potion_effects");
if (potion != null) {
PotionType potType;
try {
potType = PotionType.valueOf(potion.getString("type", "AWKWARD"));
} catch (IllegalArgumentException e) {
log.warning("Expected potion type at " + potion.getCurrentPath() + ", but "
+ potion.getString("type") + " is not a valid potion type");
potType = PotionType.AWKWARD;
}
boolean upgraded = potion.getBoolean("upgraded", false);
boolean extended = potion.getBoolean("extended", false);
PotionMeta potMeta = (PotionMeta) meta;
potMeta.setBasePotionData(new PotionData(potType, extended, upgraded));
ConfigurationSection customEffects = potion.getConfigurationSection("custom_effects");
if (customEffects != null) {
List<PotionEffect> pots = parsePotionEffects(potion);
for (PotionEffect pe : pots) {
potMeta.addCustomEffect(pe, true);
}
}
}
}
toAdd.setItemMeta(meta);
if (current.contains("nbt")) {
toAdd = ItemMap.enrichWithNBT(toAdd, 1, current.getConfigurationSection("nbt").getValues(true));
}
}
// Setting amount must be last just in cast enrichWithNBT is called,
// which
// resets the amount to 1.
int amount = current.getInt("amount", 1);
toAdd.setAmount(amount);
im.addItemStack(toAdd);
return im;
}
public static int parseTimeAsTicks(String arg) {
return (int) (parseTime(arg, TimeUnit.MILLISECONDS) / 50L);
}
public static long parseTime(String arg, TimeUnit unit) {
long millis = parseTime(arg);
return unit.convert(millis, TimeUnit.MILLISECONDS);
}
/**
* Parses a time value specified in a config. This allows to specify human
* readable time values easily, instead of having to specify every amount in
* ticks or seconds. The unit of a number specifed by the letter added after it,
* for example 5h means 5 hours or 34s means 34 seconds. Possible modifiers are:
* t (ticks), s (seconds), m (minutes), h (hours) and d (days)
* <p>
* Additionally you can combine those amounts in any way you want, for example
* you can specify 3h5m43s as 3 hours, 5 minutes and 43 seconds. This doesn't
* have to be sorted and may even list the same unit multiple times for
* different values, but the values are not allowed to be separated by anything
*
* @param input Parsed string containing the time format
* @return How many milliseconds the given time value is
*/
public static long parseTime(String input) {
input = input.replace(" ", "").replace(",", "").toLowerCase();
long result = 0;
try {
result += Long.parseLong(input);
return result;
} catch (NumberFormatException e) {
}
while (!input.equals("")) {
String typeSuffix = getSuffix(input, Character::isLetter);
input = input.substring(0, input.length() - typeSuffix.length());
String numberSuffix = getSuffix(input, Character::isDigit);
input = input.substring(0, input.length() - numberSuffix.length());
long duration;
if (numberSuffix.length() == 0) {
duration = 1;
} else {
duration = Long.parseLong(numberSuffix);
}
switch (typeSuffix) {
case "ms":
case "milli":
case "millis":
result += duration;
break;
case "s": // seconds
case "sec":
case "second":
case "seconds":
result += TimeUnit.SECONDS.toMillis(duration);
break;
case "m": // minutes
case "min":
case "minute":
case "minutes":
result += TimeUnit.MINUTES.toMillis(duration);
break;
case "h": // hours
case "hour":
case "hours":
result += TimeUnit.HOURS.toMillis(duration);
break;
case "d": // days
case "day":
case "days":
result += TimeUnit.DAYS.toMillis(duration);
break;
case "w": // weeks
case "week":
case "weeks":
result += TimeUnit.DAYS.toMillis(duration * 7);
break;
case "month": // weeks
case "months":
result += TimeUnit.DAYS.toMillis(duration * 30);
break;
case "y":
case "year":
case "years":
result += TimeUnit.DAYS.toMillis(duration * 365);
break;
case "never":
case "inf":
case "infinite":
case "perm":
case "perma":
case "forever":
// 1000 years counts as perma
result += TimeUnit.DAYS.toMillis(365 * 1000);
default:
// just ignore it
}
}
return result;
}
private static String getSuffix(String arg, Predicate<Character> selector) {
StringBuilder number = new StringBuilder();
for (int i = arg.length() - 1; i >= 0; i--) {
if (selector.test(arg.charAt(i))) {
number.insert(0, arg.substring(i, i + 1));
} else {
break;
}
}
return number.toString();
}
/**
* Parses a potion effect
*
* @param configurationSection ConfigurationSection to parse the effect from
* @return The potion effect parsed
*/
public static List<PotionEffect> parsePotionEffects(ConfigurationSection configurationSection) {
List<PotionEffect> potionEffects = Lists.newArrayList();
if (configurationSection != null) {
for (String name : configurationSection.getKeys(false)) {
ConfigurationSection configEffect = configurationSection.getConfigurationSection(name);
String type = configEffect.getString("type");
if (type == null) {
log.severe("Expected potion type to be specified, but found no \"type\" option at "
+ configEffect.getCurrentPath());
continue;
}
PotionEffectType effect = PotionEffectType.getByName(type);
if (effect == null) {
log.severe("Expected potion type to be specified at " + configEffect.getCurrentPath()
+ " but found " + type + " which is no valid type");
}
int duration = configEffect.getInt("duration", 200);
int amplifier = configEffect.getInt("amplifier", 0);
potionEffects.add(new PotionEffect(effect, duration, amplifier));
}
}
return potionEffects;
}
public static IArea parseArea(ConfigurationSection config) {
if (config == null) {
log.warning("Tried to parse area on null section");
return null;
}
String type = config.getString("type");
if (type == null) {
log.warning("Found no area type at " + config.getCurrentPath());
return null;
}
int lowerYBound = config.getInt("lowerYBound", 0);
int upperYBound = config.getInt("upperYBound", 255);
String worldName = config.getString("world");
if (worldName == null) {
log.warning("Found no world specified for area at " + config.getCurrentPath());
return null;
}
World world = Bukkit.getWorld(worldName);
if (world == null) {
log.warning("Found no world with name " + worldName + " as specified at " + config.getCurrentPath());
return null;
}
Location center = null;
if (config.isConfigurationSection("center")) {
ConfigurationSection centerSection = config.getConfigurationSection("center");
int x = centerSection.getInt("x", 0);
int y = centerSection.getInt("y", 0);
int z = centerSection.getInt("z", 0);
if (world != null) {
center = new Location(world, x, y, z);
}
}
int xSize = config.getInt("xSize", -1);
int zSize = config.getInt("zSize", -1);
IArea area = null;
switch (type) {
case "GLOBAL":
area = new GlobalYLimitedArea(lowerYBound, upperYBound, world);
break;
case "ELLIPSE":
if (center == null) {
log.warning("Found no center for area at " + config.getCurrentPath());
return null;
}
if (xSize == -1) {
log.warning("Found no xSize for area at " + config.getCurrentPath());
return null;
}
if (zSize == -1) {
log.warning("Found no zSize for area at " + config.getCurrentPath());
return null;
}
area = new EllipseArea(lowerYBound, upperYBound, center, xSize, zSize);
break;
case "RECTANGLE":
if (center == null) {
log.warning("Found no center for area at " + config.getCurrentPath());
return null;
}
if (xSize == -1) {
log.warning("Found no xSize for area at " + config.getCurrentPath());
return null;
}
if (zSize == -1) {
log.warning("Found no zSize for area at " + config.getCurrentPath());
return null;
}
area = new RectangleArea(lowerYBound, upperYBound, center, xSize, zSize);
break;
default:
log.warning("Invalid area type " + type + " at " + config.getCurrentPath());
}
return area;
}
/**
* Parses a section which contains key-value mappings of a type to another type
*
* @param <K> Key type
* @param <V> Value type
* @param parent Configuration section containing the section with the values
* @param identifier Config identifier of the section containing the entries
* @param logger The logger to write in progress work to
* @param keyConverter Converts strings to type K
* @param valueConverter Converts strings to type V
* @param mapToUse The map to place parsed keys and values.
*/
public static <K, V> void parseKeyValueMap(ConfigurationSection parent, String identifier, Logger logger,
Function<String, K> keyConverter, Function<String, V> valueConverter, Map<K, V> mapToUse) {
if (!parent.isConfigurationSection(identifier)) {
return;
}
ConfigurationSection section = parent.getConfigurationSection(identifier);
for (String keyString : section.getKeys(false)) {
if (section.isConfigurationSection(keyString)) {
logger.warning(
"Ignoring invalid " + identifier + " entry " + keyString + " at " + section.getCurrentPath());
continue;
}
K keyinstance;
try {
keyinstance = keyConverter.apply(keyString);
} catch (IllegalArgumentException e) {
logger.warning("Failed to parse " + identifier + " " + keyString + " at " + section.getCurrentPath()
+ ": " + e.toString());
continue;
}
V value = valueConverter.apply(section.getString(keyString));
mapToUse.put(keyinstance, value);
}
}
public static Enchantment parseEnchantment(ConfigurationSection config, String key) {
if (!config.isString(key)) {
return null;
}
String val = config.getString(key);
if (!Pattern.matches("[a-z0-9/._-]+", val)) {
return null;
}
NamespacedKey nsKey = NamespacedKey.minecraft(val);
if (nsKey == null) {
return null;
}
return Enchantment.getByKey(nsKey);
}
}

View File

@@ -1,15 +1,13 @@
# Database connection details, should you want to have a database connection.
# KEEP THIS COMMENTED OUT IN THIS DEFAULT CONFIG OTHERWISE BAD THINGS!
# Do NOT change the ==: value.
#database:
# ==: vg.civcraft.mc.civmodcore.dao.ManagedDatasource
# plugin: CivModCore
# user: username
# password: password
# host: localhost
# port: 3306
# database: minecraft
# poolsize: 5
# connection_timeout: 10000
# idle_timeout: 600000
# max_lifetime: 7200000
database:
==: vg.civcraft.mc.civmodcore.dao.DatabaseCredentials
username: username
password: password
host: localhost
port: 3306
database: minecraft
poolsize: 5
connection_timeout: 10000
idle_timeout: 600000
max_lifetime: 7200000

View File

@@ -0,0 +1,26 @@
package vg.civcraft.mc.civmodcore.config;
import java.util.HashMap;
import org.bukkit.Material;
import org.junit.Assert;
import org.junit.Test;
public class ConfigTests {
@Test
public void pseudoConfigTest() {
// Setup
final var data = new HashMap<String, Object>() {{
put("key", "value");
put("material", Material.NAME_TAG.name());
put("amount", 100);
}};
// Process
final var config = ConfigSection.fromData(data);
// Check
Assert.assertEquals("value", config.getString("key"));
Assert.assertEquals(Material.NAME_TAG.name(), config.getString("material"));
Assert.assertEquals(100, config.getInt("amount"));
}
}