222 lines
8.9 KiB
Java
222 lines
8.9 KiB
Java
package io.gitlab.jfronny.libjf.config.impl;
|
|
|
|
import io.gitlab.jfronny.gson.JsonElement;
|
|
import io.gitlab.jfronny.gson.JsonObject;
|
|
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
|
import io.gitlab.jfronny.libjf.config.api.*;
|
|
import io.gitlab.jfronny.libjf.config.impl.entrypoint.JfConfigSafe;
|
|
import io.gitlab.jfronny.libjf.unsafe.JfLanguageAdapter;
|
|
import net.fabricmc.loader.impl.util.log.Log;
|
|
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.*;
|
|
import java.util.stream.Collectors;
|
|
|
|
public abstract class ConfigInstanceAbstract implements ConfigInstance {
|
|
public static final String CONFIG_PRESET_DEFAULT = "libjf-config-v0.default";
|
|
public final String modId;
|
|
private final String categoryPath;
|
|
public final Class<?> configClass;
|
|
public final List<String> referencedConfigs;
|
|
public final List<EntryInfo> entries = new ArrayList<>();
|
|
public final Map<String, Runnable> presets = new LinkedHashMap<>();
|
|
public final Set<Runnable> verifiers = new LinkedHashSet<>();
|
|
public final Map<String, ConfigInstance> subcategories = new LinkedHashMap<>();
|
|
public ConfigInstanceAbstract(String modId, String categoryPath, Class<?> configClass, AuxiliaryMetadata meta) {
|
|
this.modId = modId;
|
|
this.categoryPath = categoryPath;
|
|
this.configClass = configClass;
|
|
this.referencedConfigs = List.copyOf(meta.referencedConfigs);
|
|
for (Field field : configClass.getFields()) {
|
|
EntryInfo info = new EntryInfo();
|
|
info.field = field;
|
|
if (field.isAnnotationPresent(Entry.class)) {
|
|
info.entry = field.getAnnotation(Entry.class);
|
|
try {
|
|
info.defaultValue = field.get(null);
|
|
} catch (IllegalAccessException ignored) {}
|
|
}
|
|
entries.add(info);
|
|
}
|
|
presets.put(CONFIG_PRESET_DEFAULT, () -> {
|
|
for (EntryInfo entry : entries) {
|
|
try {
|
|
entry.field.set(null, entry.defaultValue);
|
|
} catch (IllegalAccessException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not reload default values", e);
|
|
}
|
|
}
|
|
});
|
|
|
|
for (Method method : configClass.getMethods()) {
|
|
if (method.isAnnotationPresent(Preset.class)) {
|
|
presets.put(modId + ".jfconfig." + method.getName(), () -> {
|
|
try {
|
|
method.invoke(null);
|
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not apply preset", e);
|
|
}
|
|
});
|
|
}
|
|
else if (method.isAnnotationPresent(Verifier.class)) {
|
|
verifiers.add(() -> {
|
|
try {
|
|
method.invoke(null);
|
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not run verifier", e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
verifiers.add(() -> {
|
|
for (EntryInfo entry : entries) {
|
|
Object value;
|
|
try {
|
|
value = entry.field.get(null);
|
|
} catch (IllegalAccessException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not read value", e);
|
|
continue;
|
|
}
|
|
final Object valueOriginal = value;
|
|
if (value instanceof final Integer v) {
|
|
if (v < entry.entry.min()) value = (int)entry.entry.min();
|
|
if (v > entry.entry.max()) value = (int)entry.entry.max();
|
|
} else if (value instanceof final Float v) {
|
|
if (v < entry.entry.min()) value = (float)entry.entry.min();
|
|
if (v > entry.entry.max()) value = (float)entry.entry.max();
|
|
} else if (value instanceof final Double v) {
|
|
if (v < entry.entry.min()) value = entry.entry.min();
|
|
if (v > entry.entry.max()) value = entry.entry.max();
|
|
}
|
|
if (valueOriginal != value) {
|
|
try {
|
|
entry.field.set(null, value);
|
|
} catch (IllegalAccessException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not write value", e);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
for (Class<?> categoryClass : configClass.getClasses()) {
|
|
if (categoryClass.isAnnotationPresent(Category.class)) {
|
|
String name = camelCase(categoryClass.getSimpleName());
|
|
//TODO allow custom auxiliary metadata
|
|
subcategories.put(name, new ConfigInstanceCategory(this, modId, categoryPath + name + ".", categoryClass, new AuxiliaryMetadata().sanitize()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private String camelCase(String source) {
|
|
if (source == null) return null;
|
|
if (source.length() == 0) return source;
|
|
return Character.toLowerCase(source.charAt(0)) + source.substring(1);
|
|
}
|
|
|
|
@Override
|
|
public void loadFrom(JsonObject source) {
|
|
for (EntryInfo entry : entries) {
|
|
if (source.has(entry.field.getName())) {
|
|
JsonElement el = source.get(entry.field.getName());
|
|
entry.value = ConfigHolderImpl.GSON.fromJson(el, entry.field.getGenericType());
|
|
} else Log.error(JfLanguageAdapter.LOG_CATEGORY, "Config does not contain entry for " + entry.field.getName());
|
|
}
|
|
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
|
|
if (source.has(entry.getKey())) {
|
|
JsonElement el = source.get(entry.getKey());
|
|
if (el.isJsonObject()) entry.getValue().loadFrom(el.getAsJsonObject());
|
|
else Log.error(JfLanguageAdapter.LOG_CATEGORY, "Config category is not a JSON object, skipping");
|
|
} else Log.error(JfLanguageAdapter.LOG_CATEGORY, "Config does not contain entry for subcategory " + entry.getKey());
|
|
}
|
|
syncToClass();
|
|
}
|
|
|
|
@Override
|
|
public void writeTo(JsonWriter writer) throws IOException {
|
|
syncFromClass();
|
|
writer.beginObject();
|
|
String val;
|
|
for (EntryInfo entry : entries) {
|
|
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(modId + ".jfconfig." + categoryPath + entry.field.getName() + ".tooltip")) != null)
|
|
writer.comment(val);
|
|
if (entry.field.getType().isEnum())
|
|
writer.comment("Valid: [" + Arrays.stream(entry.field.getType().getEnumConstants()).map(Objects::toString).collect(Collectors.joining(", ")) + "]");
|
|
writer.name(entry.field.getName());
|
|
ConfigHolderImpl.GSON.toJson(entry.value, entry.field.getGenericType(), writer);
|
|
}
|
|
for (Map.Entry<String, ConfigInstance> entry : subcategories.entrySet()) {
|
|
if ((val = JfConfigSafe.TRANSLATION_SUPPLIER.apply(modId + ".jfconfig." + categoryPath + entry.getKey() + ".title")) != null)
|
|
writer.comment(val);
|
|
writer.name(entry.getKey());
|
|
entry.getValue().writeTo(writer);
|
|
}
|
|
writer.endObject();
|
|
}
|
|
|
|
@Override
|
|
public void syncToClass() {
|
|
for (EntryInfo info : entries) {
|
|
try {
|
|
info.field.set(null, info.value);
|
|
} catch (IllegalAccessException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not write value", e);
|
|
}
|
|
}
|
|
syncFromClass();
|
|
}
|
|
|
|
@Override
|
|
public void syncFromClass() {
|
|
for (Runnable verifier : verifiers) {
|
|
verifier.run();
|
|
}
|
|
for (EntryInfo info : entries) {
|
|
try {
|
|
info.value = info.field.get(null);
|
|
if (info.value == null) info.value = info.defaultValue;
|
|
info.tempValue = info.value == null ? null : info.value.toString();
|
|
} catch (IllegalAccessException e) {
|
|
Log.error(JfLanguageAdapter.LOG_CATEGORY, "Could not read value", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean matchesConfigClass(Class<?> candidate) {
|
|
return candidate != null && candidate.isAssignableFrom(configClass);
|
|
}
|
|
|
|
@Override
|
|
public String getModId() {
|
|
return modId;
|
|
}
|
|
|
|
@Override
|
|
public String getCategoryPath() {
|
|
return categoryPath;
|
|
}
|
|
|
|
@Override
|
|
public List<EntryInfo> getEntries() {
|
|
return entries;
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Runnable> getPresets() {
|
|
return presets;
|
|
}
|
|
|
|
@Override
|
|
public List<String> getReferencedConfigs() {
|
|
return referencedConfigs;
|
|
}
|
|
|
|
@Override
|
|
public Map<String, ConfigInstance> getCategories() {
|
|
return subcategories;
|
|
}
|
|
}
|