Skip to content

Commit

Permalink
Complete rewrite of ConfigManager
Browse files Browse the repository at this point in the history
- The old config manager, and the redempt.redlib.configmanager package, are now deprecated and will be removed at a later date
- New config manager is in redempt.redlib.config
- Now can deserialize to record types (ConfigMappable annotation not required)
- No longer uses ConfigValue, all fields are automatically deserialized unless marked transient
- No longer requires ConfigManager.map/list/collection, can natively serialize and deserialize regular collections and maps
- Major code cleanup, more extensible
- Can now only deserialize to static fields in the root of the config
- Can now reload config from disk
  • Loading branch information
boxbeam committed Dec 20, 2021
1 parent 582bd3d commit e444bb2
Show file tree
Hide file tree
Showing 26 changed files with 1,221 additions and 20 deletions.
2 changes: 1 addition & 1 deletion res/plugin.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: RedLib
main: redempt.redlib.RedLib
version: 2021-12-18 21:20
version: 2021-12-20 06:09
author: Redempt
api-version: 1.13
load: STARTUP
10 changes: 3 additions & 7 deletions src/redempt/redlib/RedLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import redempt.redlib.commandmanager.ArgType;
import redempt.redlib.commandmanager.CommandParser;
import redempt.redlib.commandmanager.Messages;
import redempt.redlib.configmanager.ConfigManager;
import redempt.redlib.configmanager.annotations.ConfigValue;
import redempt.redlib.config.ConfigManager;
import redempt.redlib.dev.ChainCommand;
import redempt.redlib.dev.StructureTool;
import redempt.redlib.dev.profiler.ProfilerCommands;
Expand Down Expand Up @@ -42,9 +41,6 @@ public static String msg(String msg) {
return globalMessages.get(msg);
}

@ConfigValue
public static boolean devMode = false;

/**
* The middle number of the server version - for example, if the server version is 1.15.2, this will be 15
*/
Expand All @@ -71,8 +67,8 @@ public void onLoad() {

@Override
public void onEnable() {
new ConfigManager(this).register(this).saveDefaults().load();
if (devMode) {
ConfigManager.create(this).target(RedLibConfig.class).load();
if (RedLibConfig.devMode) {
ChainCommand chain = new ChainCommand();
new CommandParser(this.getResource("command.rdcml"))
.setArgTypes(ArgType.of("material", Material.class), chain.getArgType())
Expand Down
7 changes: 7 additions & 0 deletions src/redempt/redlib/RedLibConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package redempt.redlib;

public class RedLibConfig {

public static boolean devMode = false;

}
7 changes: 7 additions & 0 deletions src/redempt/redlib/blockdata/BlockDataManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import redempt.redlib.misc.LocationUtils;
import redempt.redlib.sql.SQLHelper;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

Expand Down Expand Up @@ -50,6 +52,11 @@ public static List<BlockDataManager> getAllManagers() {
* @param saveFile The Path to load from immediately, and save to when save is called
*/
public BlockDataManager(Path saveFile) {
try {
Files.createDirectories(saveFile.getParent());
} catch (IOException e) {
e.printStackTrace();
}
Bukkit.getPluginManager().registerEvents(this, RedLib.getInstance());
sql = new SQLHelper(SQLHelper.openSQLite(saveFile));
sql.execute("CREATE TABLE IF NOT EXISTS blocks (world TEXT, cx INT, cz INT, x INT, y INT, z INT, data TEXT, PRIMARY KEY (world, x, y, z));");
Expand Down
255 changes: 255 additions & 0 deletions src/redempt/redlib/config/ConfigManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
package redempt.redlib.config;

import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import redempt.redlib.config.annotations.ConfigMappable;
import redempt.redlib.config.conversion.CollectionConverter;
import redempt.redlib.config.conversion.EnumConverter;
import redempt.redlib.config.conversion.MapConverter;
import redempt.redlib.config.conversion.NativeConverter;
import redempt.redlib.config.conversion.ObjectConverter;
import redempt.redlib.config.conversion.StaticRootConverter;
import redempt.redlib.config.conversion.StringConverter;
import redempt.redlib.config.conversion.TypeConverter;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
* Serializes and deserializes configuration data into objects and fields
* @author Redempt
*/
public class ConfigManager {

/**
* Creates a ConfigManager targetting a specific config file, which will be created if it does not exist
* @param file The config file to manage
* @return The ConfigManager
*/
public static ConfigManager create(File file) {
return new ConfigManager(file);
}

/**
* Creates a ConfigManager targetting a specific config file, which will be created if it does not exist
* @param path The config file to manage
* @return The ConfigManager
*/
public static ConfigManager create(Path path) {
return create(path.toFile());
}

/**
* Creates a ConfigManager for a file in a plugin's data folder
* @param plugin The plugin whose data folder should be used
* @param configName The name of the config file to manage
* @return The ConfigManager
*/
public static ConfigManager create(Plugin plugin, String configName) {
return create(plugin.getDataFolder().toPath().resolve(configName));
}

/**
* Creates a ConfigManager for the default config in a plugin's data folder, called config.yml
* @param plugin The plugin whose data folder should be used
* @return The ConfigManager
*/
public static ConfigManager create(Plugin plugin) {
return create(plugin, "config.yml");
}

private Map<ConfigType<?>, TypeConverter<?>> convertersByType;

{
convertersByType = new HashMap<>();
convertersByType.put(new ConfigType<>(String.class), StringConverter.create(s -> s, s -> s));
convertersByType.put(new ConfigType<>(int.class), StringConverter.create(Integer::parseInt, String::valueOf));
convertersByType.put(new ConfigType<>(double.class), StringConverter.create(Double::parseDouble, String::valueOf));
convertersByType.put(new ConfigType<>(float.class), StringConverter.create(Float::parseFloat, String::valueOf));
convertersByType.put(new ConfigType<>(boolean.class), StringConverter.create(Boolean::parseBoolean, String::valueOf));
convertersByType.put(new ConfigType<>(long.class), StringConverter.create(Long::parseLong, String::valueOf));
}

private FileConfiguration config;
private File file;
private TypeConverter<?> converter;
private Object target;
private Class<?> targetClass;

private ConfigManager(File file) {
this.file = file;
file.getParentFile().mkdirs();
if (file.exists()) {
config = YamlConfiguration.loadConfiguration(file);
} else {
config = new YamlConfiguration();
}
}

/**
* Specifies the given Object to load config values to and save config values from. The Object
* must be from a ConfigMappable class, and all of its non-transient fields will be worked with.
* @param obj The Object to operate on
* @return This ConfigManager
*/
public ConfigManager target(Object obj) {
if (target != null || targetClass != null) {
throw new IllegalStateException("ConfigManager already has a target");
}
target = obj;
converter = ObjectConverter.create(this, new ConfigType<>(obj.getClass()));
return this;
}

/**
* Specifies the given Class to load config values to and save config values from. The Class's
* static non-transient fields will be worked with.
* @param clazz The Class to operate on
* @return This ConfigManager
*/
public ConfigManager target(Class<?> clazz) {
if (target != null || targetClass != null) {
throw new IllegalStateException("ConfigManager already has a target");
}
targetClass = clazz;
converter = StaticRootConverter.create(this, clazz);
return this;
}

/**
* Saves all of the values from the target to config
* @return This ConfigManager
*/
public ConfigManager save() {
save(converter, true);
return this;
}

/**
* Saves only values which are not already specified in config
* @return This ConfigManager
*/
public ConfigManager saveDefaults() {
save(converter, false);
return this;
}

/**
* Loads all values from the current in-memory config into the target
* @return This ConfigManager
*/
public ConfigManager load() {
load(converter);
return this;
}

private <T> void load(TypeConverter<T> converter) {
converter.loadFrom(config, null, (T) target);
}

/**
* Loads the config from disk, then loads all values into the target
* @return This ConfigManager
*/
public ConfigManager reload() {
config = YamlConfiguration.loadConfiguration(file);
return load();
}

private <T> void save(TypeConverter<T> converter, boolean overwrite) {
converter.saveTo((T) target, config, null, overwrite);
try {
config.save(file);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* @return The configuration file wrapped by this ConfigManager
*/
public FileConfiguration getConfig() {
return config;
}

/**
* Gets the TypeConverter for a given type
* @param type The ConfigType
* @param <T> The parameter type
* @return The TypeConverter, making a new one if none exists for the given type
*/
public <T> TypeConverter<T> getConverter(ConfigType<T> type) {
if (type == null) {
return null;
}
TypeConverter<T> converter = (TypeConverter<T>) convertersByType.get(type);
if (converter == null) {
converter = (TypeConverter<T>) createConverter(type);
convertersByType.put(type, converter);
}
return converter;
}

/**
* Registers a string converter to this ConfigManager
* @param clazz The class type the converter is for
* @param loader A function to convert a string to the given type
* @param saver A function to convert the given type to a string
* @param <T> The type
* @return This ConfigManager
*/
public <T> ConfigManager addConverter(Class<T> clazz, Function<String, T> loader, Function<T, String> saver) {
convertersByType.put(new ConfigType<>(clazz), StringConverter.create(loader, saver));
return this;
}

/**
* Registers a converter to this ConfigManager
* @param type The type the converter is for
* @param converter The converter
* @param <T> The type
* @return This ConfigManager
*/
public <T> ConfigManager addConverter(ConfigType<T> type, TypeConverter<T> converter) {
convertersByType.put(type, converter);
return this;
}

/**
* Gets a StringConverter for a given type
* @param type The type to get a StringConverter for
* @param <T> The type
* @return The StringConverter associated with the given type
* @throws IllegalStateException If a StringConverter does not exist for the given type
*/
public <T> StringConverter<T> getStringConverter(ConfigType<T> type) {
TypeConverter<T> keyConverter = (TypeConverter<T>) convertersByType.get(type);
if (!(keyConverter instanceof StringConverter)) {
throw new IllegalStateException("No appropriate string converter for key type " + type);
}
return (StringConverter<T>) keyConverter;
}

private <T> TypeConverter<?> createConverter(ConfigType<?> type) {
if (Enum.class.isAssignableFrom(type.getType())) {
return EnumConverter.create(type.getType());
}
if (Collection.class.isAssignableFrom(type.getType())) {
return CollectionConverter.create(this, type);
}
if (Map.class.isAssignableFrom(type.getType())) {
return MapConverter.create(this, type);
}
if (type.getType().isAnnotationPresent(ConfigMappable.class) || type.getType().getSuperclass().getName().equals("java.lang.Record")) {
return ObjectConverter.create(this, type);
}
return NativeConverter.create();
}

}
Loading

0 comments on commit e444bb2

Please sign in to comment.