Skip to content

Commit

Permalink
Add support for subclass conversions to config manager
Browse files Browse the repository at this point in the history
- Subclasses of config-mappable classes can now be converted by storing the type in config
- Base commands registered to the command map now correctly report their usage
- Removed old/deprecated config manager
  • Loading branch information
boxbeam committed Jan 16, 2022
1 parent 43a1d4c commit 7e25e31
Show file tree
Hide file tree
Showing 24 changed files with 141 additions and 1,154 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repositories {
}
dependencies {
compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT'
api 'com.github.Redempt:RedCommands:1.5.5'
api 'com.github.Redempt:RedCommands:1.5.6'
}
sourceSets {
main {
Expand Down
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: 2022-01-11 18:34
version: 2022-01-16 05:50
author: Redempt
api-version: 1.13
load: STARTUP
55 changes: 39 additions & 16 deletions src/redempt/redlib/config/ConfigManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,20 @@
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.PrimitiveConverter;
import redempt.redlib.config.conversion.StaticRootConverter;
import redempt.redlib.config.conversion.StringConverter;
import redempt.redlib.config.conversion.TypeConverter;
import redempt.redlib.config.annotations.ConfigSubclassable;
import redempt.redlib.config.conversion.*;
import redempt.redlib.config.data.ConfigurationSectionDataHolder;
import redempt.redlib.config.data.DataHolder;
import redempt.redlib.config.instantiation.Instantiator;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
Expand All @@ -33,20 +28,22 @@ public class ConfigManager {

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

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

/**
Expand All @@ -56,7 +53,9 @@ public static ConfigManager create(Path path) {
* @return The ConfigManager
*/
public static ConfigManager create(Plugin plugin, String configName) {
return create(plugin.getDataFolder().toPath().resolve(configName));
ConfigManager manager = create(plugin, plugin.getDataFolder().toPath().resolve(configName));
manager.loader = plugin.getClass().getClassLoader();
return manager;
}

/**
Expand All @@ -69,6 +68,7 @@ public static ConfigManager create(Plugin plugin) {
}

private Map<ConfigType<?>, TypeConverter<?>> convertersByType;
private Map<String, Class<?>> classesByName = new ConcurrentHashMap<>();

{
convertersByType = new HashMap<>();
Expand All @@ -86,8 +86,10 @@ public static ConfigManager create(Plugin plugin) {
private TypeConverter<?> converter;
private Object target;
private Class<?> targetClass;
private ClassLoader loader;

private ConfigManager(File file) {
private ConfigManager(Plugin plugin, File file) {
loader = plugin.getClass().getClassLoader();
this.file = file;
file.getParentFile().mkdirs();
if (file.exists()) {
Expand Down Expand Up @@ -117,6 +119,22 @@ public ConfigManager target(Object obj) {
return this;
}

/**
* Loads a class by name, using a cache
* @param name The name of the class to load
* @return The class
*/
public Class<?> loadClass(String name) {
return classesByName.computeIfAbsent(name, k -> {
try {
return Class.forName(k, true, loader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
});
}

/**
* 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.
Expand Down Expand Up @@ -246,7 +264,7 @@ public <T> StringConverter<T> getStringConverter(ConfigType<T> type) {
return (StringConverter<T>) keyConverter;
}

private <T> TypeConverter<?> createConverter(ConfigType<?> type) {
private TypeConverter<?> createConverter(ConfigType<?> type) {
if (Enum.class.isAssignableFrom(type.getType())) {
return EnumConverter.create(type.getType());
}
Expand All @@ -257,6 +275,11 @@ private <T> TypeConverter<?> createConverter(ConfigType<?> type) {
return MapConverter.create(this, type);
}
if (type.getType().isAnnotationPresent(ConfigMappable.class) || Instantiator.isRecord(type.getType())) {
Class<?> clazz = type.getType();
boolean isAbstract = clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers());
if (isAbstract || clazz.isAnnotationPresent(ConfigSubclassable.class)) {
return SubclassConverter.create(this, clazz, isAbstract);
}
return ObjectConverter.create(this, type);
}
return NativeConverter.create();
Expand Down
18 changes: 18 additions & 0 deletions src/redempt/redlib/config/annotations/ConfigSubclassable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package redempt.redlib.config.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that this type can be
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Inherited
public @interface ConfigSubclassable {
}
9 changes: 0 additions & 9 deletions src/redempt/redlib/config/conversion/StaticRootConverter.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
package redempt.redlib.config.conversion;

import org.bukkit.configuration.ConfigurationSection;
import redempt.redlib.config.ConfigField;
import redempt.redlib.config.ConfigManager;
import redempt.redlib.config.ConfigType;
import redempt.redlib.config.data.DataHolder;
import redempt.redlib.config.instantiation.FieldSummary;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* A converter which saves to and loads from static fields and can only be used as a config target
* @author Redempt
Expand Down
51 changes: 51 additions & 0 deletions src/redempt/redlib/config/conversion/SubclassConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package redempt.redlib.config.conversion;

import redempt.redlib.config.ConfigManager;
import redempt.redlib.config.ConfigType;
import redempt.redlib.config.data.DataHolder;

/**
* A converter which can convert subclasses of mappable classes
* @author Redempt
*/
public class SubclassConverter {

/**
* Creates a TypeConverter that can convert subclasses
* @param manager The ConfigManager handling the data
* @param clazz The class to handle subclasses of
* @param isAbstract Whether the class is abstract or an interface
* @param <T> The type
* @return The converter
*/
public static <T> TypeConverter<T> create(ConfigManager manager, Class<T> clazz, boolean isAbstract) {
TypeConverter<T> parent = !isAbstract ? ObjectConverter.create(manager, new ConfigType<>(clazz)) : null;
return new TypeConverter<T>() {
@Override
public T loadFrom(DataHolder section, String path, T currentValue) {
String typeName = section.getSubsection(path).getString("=type");
if (typeName == null) {
throw new IllegalStateException("Could not determine subclass for object with path " + path);
}
Class<?> type = manager.loadClass(typeName);
if (!clazz.isAssignableFrom(type)) {
throw new IllegalStateException(type + " is not a subclass of " + clazz);
}
TypeConverter<T> converter = type.equals(clazz) ? parent : (TypeConverter<T>) manager.getConverter(new ConfigType<>(type));
return converter.loadFrom(section, path, currentValue);
}

@Override
public void saveTo(T t, DataHolder section, String path) {
Class<?> type = t.getClass();
if (!clazz.isAssignableFrom(type)) {
throw new IllegalStateException(type + " is not a subclass of " + clazz);
}
TypeConverter<T> converter = type.equals(clazz) ? parent : (TypeConverter<T>) manager.getConverter(new ConfigType<>(type));
converter.saveTo(t, section, path);
section.getSubsection(path).set("=type", type.getName());
}
};
}

}
57 changes: 31 additions & 26 deletions src/redempt/redlib/config/instantiation/FieldSummary.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import redempt.redlib.config.ConfigField;
import redempt.redlib.config.ConfigManager;
import redempt.redlib.config.ConfigType;
import redempt.redlib.config.annotations.ConfigMappable;
import redempt.redlib.config.annotations.ConfigName;
import redempt.redlib.config.annotations.ConfigPath;
import redempt.redlib.config.annotations.ConfigPostInit;
Expand Down Expand Up @@ -40,38 +41,42 @@ public static FieldSummary getFieldSummary(ConfigManager manager, Class<?> clazz
List<ConfigField> fields = new ArrayList<>();
Map<ConfigField, TypeConverter<?>> converters = new HashMap<>();
Method postInit = null;
for (Field field : clazz.getDeclaredFields()) {
int mod = field.getModifiers();
if (Modifier.isTransient(mod) || Modifier.isStatic(mod) != staticContext) {
continue;
}
field.setAccessible(true);
if (!staticContext && field.isAnnotationPresent(ConfigPath.class)) {
configPath = field;
configPathConverter = manager.getStringConverter(ConfigType.get(configPath));
continue;
}
ConfigField cf = new ConfigField(field);
fields.add(cf);
converters.put(cf, manager.getConverter(ConfigType.get(field)));
}

if (!staticContext && Instantiator.isRecord(clazz)) {
Constructor<?> constructor = clazz.getDeclaredConstructor(Arrays.stream(clazz.getDeclaredFields()).map(Field::getType).toArray(Class<?>[]::new));
Parameter[] params = constructor.getParameters();
int pos = 0;
for (int i = 0; i < params.length; i++) {
Parameter param = params[i];
if (param.isAnnotationPresent(ConfigPath.class)) {
while (clazz != null && (staticContext || clazz.isAnnotationPresent(ConfigMappable.class) || Instantiator.isRecord(clazz))) {
for (Field field : clazz.getDeclaredFields()) {
int mod = field.getModifiers();
if (field.isSynthetic() || Modifier.isTransient(mod) || Modifier.isStatic(mod) != staticContext) {
continue;
}
ConfigName name = param.getAnnotation(ConfigName.class);
if (name == null) {
field.setAccessible(true);
if (!staticContext && field.isAnnotationPresent(ConfigPath.class)) {
configPath = field;
configPathConverter = manager.getStringConverter(ConfigType.get(configPath));
continue;
}
fields.get(pos).setName(name.value());
pos++;
ConfigField cf = new ConfigField(field);
fields.add(cf);
converters.put(cf, manager.getConverter(ConfigType.get(field)));
}

if (!staticContext && Instantiator.isRecord(clazz)) {
Constructor<?> constructor = clazz.getDeclaredConstructor(Arrays.stream(clazz.getDeclaredFields()).map(Field::getType).toArray(Class<?>[]::new));
Parameter[] params = constructor.getParameters();
int pos = 0;
for (int i = 0; i < params.length; i++) {
Parameter param = params[i];
if (param.isAnnotationPresent(ConfigPath.class)) {
continue;
}
ConfigName name = param.getAnnotation(ConfigName.class);
if (name == null) {
continue;
}
fields.get(pos).setName(name.value());
pos++;
}
}
clazz = clazz.getSuperclass();
}

if (!staticContext) {
Expand Down
Loading

0 comments on commit 7e25e31

Please sign in to comment.