feat(weaver-pojo-presets-ext): Implement POJO weaving for presets
This commit is contained in:
@@ -19,6 +19,7 @@ dependencies {
|
||||
implementation("de.siphalor.tweed5:tweed5-serde-extension")
|
||||
implementation("de.siphalor.tweed5:tweed5-weaver-pojo")
|
||||
implementation("de.siphalor.tweed5:tweed5-weaver-pojo-attributes-extension")
|
||||
implementation("de.siphalor.tweed5:tweed5-weaver-pojo-presets-extension")
|
||||
implementation("de.siphalor.tweed5:tweed5-weaver-pojo-serde-extension")
|
||||
implementation("de.siphalor.tweed5:tweed5-weaver-pojo-validation-extension")
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.validationfallback.api.ValidationFallbackExtension;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.*;
|
||||
import de.siphalor.tweed5.weaver.pojoext.attributes.api.AttributesPojoWeavingProcessor;
|
||||
import de.siphalor.tweed5.weaver.pojoext.presets.api.DefaultPresetWeavingProcessor;
|
||||
import de.siphalor.tweed5.weaver.pojoext.presets.api.PresetsWeavingProcessor;
|
||||
import de.siphalor.tweed5.weaver.pojoext.serde.api.auto.AutoReadWritePojoWeavingProcessor;
|
||||
import de.siphalor.tweed5.weaver.pojoext.serde.api.auto.DefaultReadWriteMappings;
|
||||
import de.siphalor.tweed5.weaver.pojoext.validation.api.ValidatorsPojoWeavingProcessor;
|
||||
@@ -26,7 +28,9 @@ import java.lang.annotation.Target;
|
||||
DefaultReadWriteMappings.class,
|
||||
CompoundWeaving.class
|
||||
})
|
||||
@PojoWeaving
|
||||
@TweedExtension(ReadWriteExtension.class)
|
||||
@TweedExtension(PresetsExtension.class)
|
||||
@TweedExtension(ValidationExtension.class)
|
||||
@TweedExtension(ValidationFallbackExtension.class)
|
||||
@TweedExtension(AttributesExtension.class)
|
||||
@@ -34,6 +38,8 @@ import java.lang.annotation.Target;
|
||||
@PojoWeavingExtension(AutoReadWritePojoWeavingProcessor.class)
|
||||
@PojoWeavingExtension(ValidatorsPojoWeavingProcessor.class)
|
||||
@PojoWeavingExtension(AttributesPojoWeavingProcessor.class)
|
||||
@PojoWeavingExtension(PresetsWeavingProcessor.class)
|
||||
@PojoWeavingExtension(DefaultPresetWeavingProcessor.class)
|
||||
@DefaultWeavingExtensions
|
||||
@DefaultReadWriteMappings
|
||||
@CompoundWeaving
|
||||
|
||||
@@ -8,6 +8,7 @@ import de.siphalor.tweed5.dataapi.api.TweedDataWriter;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedSerde;
|
||||
import de.siphalor.tweed5.defaultextensions.patch.api.PatchExtension;
|
||||
import de.siphalor.tweed5.defaultextensions.patch.api.PatchInfo;
|
||||
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
|
||||
import de.siphalor.tweed5.patchwork.api.Patchwork;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
@@ -29,6 +30,7 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
|
||||
private final ConfigContainer<T> configContainer;
|
||||
private final ReadWriteExtension readWriteExtension;
|
||||
private final @Nullable PatchExtension patchExtension;
|
||||
private final @Nullable PresetsExtension presetsExtension;
|
||||
private final TweedSerde serde;
|
||||
@Getter
|
||||
private final String modId;
|
||||
@@ -54,10 +56,16 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
|
||||
this.readWriteExtension = configContainer.extension(ReadWriteExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("ReadWriteExtension not declared in config container"));
|
||||
this.patchExtension = configContainer.extension(PatchExtension.class).orElse(null);
|
||||
this.presetsExtension = configContainer.extension(PresetsExtension.class).orElse(null);
|
||||
this.serde = serde;
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
public T loadAndUpdateInConfigDirectory() {
|
||||
T defaultPresetValue = getDefaultPresetValue();
|
||||
return loadAndUpdateInConfigDirectory(() -> configContainer.rootEntry().deepCopy(defaultPresetValue));
|
||||
}
|
||||
|
||||
public T loadAndUpdateInConfigDirectory(Supplier<T> defaultValueSupplier) {
|
||||
T configValue = readConfigInConfigDirectory(defaultValueSupplier);
|
||||
writeConfigInConfigDirectory(configValue);
|
||||
@@ -89,6 +97,11 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
|
||||
}
|
||||
}
|
||||
|
||||
public T readConfigInConfigDirectory() {
|
||||
T defaultPresetValue = getDefaultPresetValue();
|
||||
return readConfigInConfigDirectory(() -> configContainer.rootEntry().deepCopy(defaultPresetValue));
|
||||
}
|
||||
|
||||
public T readConfigInConfigDirectory(Supplier<T> defaultValueSupplier) {
|
||||
File configFile = getConfigFile();
|
||||
if (!configFile.exists()) {
|
||||
@@ -158,4 +171,13 @@ public class FabricConfigContainerHelper<T extends @Nullable Object> {
|
||||
}
|
||||
return tempConfigDirectory;
|
||||
}
|
||||
|
||||
private T getDefaultPresetValue() {
|
||||
if (presetsExtension == null) {
|
||||
throw new IllegalStateException(
|
||||
"No presets extension registered, either register such extension or provide a default value manually"
|
||||
);
|
||||
}
|
||||
return presetsExtension.presetValue(configContainer.rootEntry(), PresetsExtension.DEFAULT_PRESET_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ includeNormalModule("type-utils")
|
||||
includeNormalModule("utils")
|
||||
includeNormalModule("weaver-pojo")
|
||||
includeNormalModule("weaver-pojo-attributes-extension")
|
||||
includeNormalModule("weaver-pojo-presets-extension")
|
||||
includeNormalModule("weaver-pojo-serde-extension")
|
||||
includeNormalModule("weaver-pojo-validation-extension")
|
||||
|
||||
|
||||
9
tweed5/weaver-pojo-presets-extension/build.gradle.kts
Normal file
9
tweed5/weaver-pojo-presets-extension/build.gradle.kts
Normal file
@@ -0,0 +1,9 @@
|
||||
plugins {
|
||||
id("de.siphalor.tweed5.base-module")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":tweed5-construct"))
|
||||
api(project(":tweed5-default-extensions"))
|
||||
api(project(":tweed5-weaver-pojo"))
|
||||
}
|
||||
2
tweed5/weaver-pojo-presets-extension/gradle.properties
Normal file
2
tweed5/weaver-pojo-presets-extension/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
module.name = Presets Extension for Tweed 5 Weaver POJO
|
||||
module.description = Allows declaring presets on POJOs using annotations.
|
||||
@@ -0,0 +1,48 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.presets.api;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeavingExtension;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
@CommonsLog
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultPresetWeavingProcessor<T> implements TweedPojoWeavingExtension {
|
||||
private final ConfigContainer<T> configContainer;
|
||||
|
||||
@Override
|
||||
public void setup(SetupContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterWeave() {
|
||||
PresetsExtension presetsExtension = configContainer.extension(PresetsExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
"Can only use " + DefaultPresetWeavingProcessor.class.getSimpleName()
|
||||
+ " if " + PresetsExtension.class.getSimpleName() + " is registered"
|
||||
));
|
||||
|
||||
if (presetsExtension.presetValue(configContainer.rootEntry(), PresetsExtension.DEFAULT_PRESET_NAME) != null) {
|
||||
log.debug("Default preset already registered, skipping auto instantiation");
|
||||
return;
|
||||
}
|
||||
|
||||
T defaultValue = instantiateEntry(configContainer.rootEntry());
|
||||
|
||||
presetsExtension.presetValue(configContainer.rootEntry(), PresetsExtension.DEFAULT_PRESET_NAME, defaultValue);
|
||||
}
|
||||
|
||||
private T instantiateEntry(ConfigEntry<T> entry) {
|
||||
if (entry instanceof CompoundConfigEntry) {
|
||||
return ((CompoundConfigEntry<T>) entry).instantiateCompoundValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Can only determine default preset from instantiation for POJOs. "
|
||||
+ "Only apply " + getClass().getSimpleName() + " to POJOs."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.presets.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Preset {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.presets.api;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
|
||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeavingExtension;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
|
||||
import lombok.extern.apachecommons.CommonsLog;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@CommonsLog
|
||||
public class PresetsWeavingProcessor implements TweedPojoWeavingExtension {
|
||||
private static final int REQUIRED_FIELD_MODIFIERS = Modifier.STATIC | Modifier.FINAL;
|
||||
|
||||
@Override
|
||||
public void setup(SetupContext context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void afterWeaveEntry(ActualType<T> valueType, ConfigEntry<T> configEntry, WeavingContext context) {
|
||||
Map<String, T> presets = new HashMap<>();
|
||||
for (Field field : valueType.declaredType().getFields()) {
|
||||
Preset presetAnnotation = field.getAnnotation(Preset.class);
|
||||
if (presetAnnotation == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((field.getModifiers() & REQUIRED_FIELD_MODIFIERS) != REQUIRED_FIELD_MODIFIERS) {
|
||||
log.warn(
|
||||
"@Preset field " + field.getName() + " in class " + field.getDeclaringClass().getName()
|
||||
+ " is not static and final, skipping preset"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (presets.containsKey(presetAnnotation.value())) {
|
||||
log.warn(
|
||||
"Duplicate preset name " + presetAnnotation.value() + " in class "
|
||||
+ field.getDeclaringClass().getName() + ", skipping preset"
|
||||
);
|
||||
presets.remove(presetAnnotation.value());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!configEntry.valueClass().isAssignableFrom(field.getType())) {
|
||||
log.warn(
|
||||
"@Preset field " + field.getName() + " in class " + field.getDeclaringClass().getName()
|
||||
+ " has incompatible type, skipping preset"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
//noinspection unchecked
|
||||
presets.put(presetAnnotation.value(), (T) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
log.warn(
|
||||
"Failed to access preset field " + field.getName() + " in class "
|
||||
+ field.getDeclaringClass().getName(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!presets.isEmpty()) {
|
||||
PresetsExtension presetsExtension = configEntry.container().extension(PresetsExtension.class)
|
||||
.orElseThrow(() -> new IllegalStateException("PresetsExtension not declared in config container"));
|
||||
presets.forEach((name, value) -> presetsExtension.presetValue(configEntry, name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NullMarked
|
||||
package de.siphalor.tweed5.weaver.pojoext.presets.api;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -0,0 +1,82 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.presets.api;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.*;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.siphalor.tweed5.defaultextensions.presets.api.PresetsExtension.presetValue;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
|
||||
class PresetsWeavingProcessorTest {
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
ConfigContainer<Config> configContainer = TweedPojoWeaverBootstrapper.create(Config.class).weave();
|
||||
configContainer.initialize();
|
||||
PresetsExtension presetsExtension = configContainer.extension(PresetsExtension.class).orElseThrow();
|
||||
|
||||
Config defaultPreset =
|
||||
presetsExtension.presetValue(configContainer.rootEntry(), PresetsExtension.DEFAULT_PRESET_NAME);
|
||||
Config presetA = presetsExtension.presetValue(configContainer.rootEntry(), "a");
|
||||
Config presetB = presetsExtension.presetValue(configContainer.rootEntry(), "b");
|
||||
|
||||
assertThat(defaultPreset).isEqualTo(new Config());
|
||||
assertThat(presetA).isEqualTo(Config.PRESET_A);
|
||||
assertThat(presetB).isEqualTo(Config.PRESET_B);
|
||||
|
||||
assertThat(configContainer.rootEntry()).asInstanceOf(type(CompoundConfigEntry.class)).satisfies(
|
||||
compound -> assertThat(compound.subEntries().get("subConfig"))
|
||||
.asInstanceOf(type(ConfigEntry.class))
|
||||
.satisfies(
|
||||
entry -> assertThat(entry.call(presetValue(PresetsExtension.DEFAULT_PRESET_NAME)))
|
||||
.isEqualTo(new SubConfig("DEFAULT")),
|
||||
entry -> assertThat(entry.call(presetValue("a"))).isEqualTo(new SubConfig("AAA")),
|
||||
entry -> assertThat(entry.call(presetValue("b"))).isEqualTo(new SubConfig("BBB")),
|
||||
entry -> assertThat(entry.call(presetValue("special"))).isEqualTo(SubConfig.SPECIAL_PRESET)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@PojoWeaving
|
||||
@TweedExtension(PresetsExtension.class)
|
||||
@PojoWeavingExtension(DefaultPresetWeavingProcessor.class)
|
||||
@PojoWeavingExtension(PresetsWeavingProcessor.class)
|
||||
@DefaultWeavingExtensions
|
||||
@CompoundWeaving
|
||||
// lombok
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class Config {
|
||||
@Preset("a")
|
||||
public static final Config PRESET_A = new Config("a", 1, true, new SubConfig("AAA"));
|
||||
@Preset("b")
|
||||
public static final Config PRESET_B = new Config("b", 2, false, new SubConfig("BBB"));
|
||||
|
||||
public String string = "default";
|
||||
public int integer = 1234;
|
||||
public boolean bool;
|
||||
public SubConfig subConfig = new SubConfig("DEFAULT");
|
||||
}
|
||||
|
||||
@CompoundWeaving
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class SubConfig {
|
||||
@Preset("special")
|
||||
public static final SubConfig SPECIAL_PRESET = new SubConfig("SPECIAL");
|
||||
public String value;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ public interface TweedPojoWeavingExtension extends TweedPojoWeavingFunction {
|
||||
|
||||
default <T> void afterWeaveEntry(ActualType<T> valueType, ConfigEntry<T> configEntry, WeavingContext context) {}
|
||||
|
||||
default <T> void afterWeave() {}
|
||||
|
||||
interface SetupContext {
|
||||
<E> PatchworkPartAccess<E> registerWeavingContextExtensionData(Class<E> dataClass);
|
||||
}
|
||||
|
||||
@@ -123,6 +123,8 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
|
||||
configContainer.attachTree(rootEntry);
|
||||
|
||||
runAfterWeaveHooks();
|
||||
|
||||
return configContainer;
|
||||
}
|
||||
|
||||
@@ -145,7 +147,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
) {
|
||||
extensionsData = extensionsData.copy();
|
||||
|
||||
runBeforeWeaveHooks(valueType, extensionsData, protoContext);
|
||||
runBeforeWeaveEntryHooks(valueType, extensionsData, protoContext);
|
||||
|
||||
WeavingContext context = WeavingContext.builder()
|
||||
.parent(protoContext.parent())
|
||||
@@ -161,7 +163,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
try {
|
||||
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(valueType, context);
|
||||
if (configEntry != null) {
|
||||
runAfterWeaveHooks(valueType, configEntry, context);
|
||||
runAfterWeaveEntryHooks(valueType, configEntry, context);
|
||||
return configEntry;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -175,7 +177,12 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
|
||||
throw new PojoWeavingException(
|
||||
"Failed to weave entry for " + valueType + " at " + Arrays.toString(context.path())
|
||||
+ ": No matching weavers found"
|
||||
+ ": No matching weavers found.\n"
|
||||
+ "Registered weaving extensions: "
|
||||
+ weavingExtensions.stream()
|
||||
.map(TweedPojoWeavingExtension::getClass)
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -207,7 +214,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
try {
|
||||
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(valueType, context);
|
||||
if (configEntry != null) {
|
||||
runAfterWeaveHooks(valueType, configEntry, context);
|
||||
runAfterWeaveEntryHooks(valueType, configEntry, context);
|
||||
return configEntry;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -225,7 +232,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
);
|
||||
}
|
||||
|
||||
private <U> void runBeforeWeaveHooks(
|
||||
private <U> void runBeforeWeaveEntryHooks(
|
||||
ActualType<U> dataClass,
|
||||
Patchwork extensionsData,
|
||||
ProtoWeavingContext protoContext
|
||||
@@ -243,7 +250,7 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private <U> void runAfterWeaveHooks(ActualType<U> dataClass, ConfigEntry<U> configEntry, WeavingContext context) {
|
||||
private <U> void runAfterWeaveEntryHooks(ActualType<U> dataClass, ConfigEntry<U> configEntry, WeavingContext context) {
|
||||
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
|
||||
try {
|
||||
weavingExtension.afterWeaveEntry(dataClass, configEntry, context);
|
||||
@@ -256,4 +263,10 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runAfterWeaveHooks() {
|
||||
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
|
||||
weavingExtension.afterWeave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user