[weaver-pojo*] Refactor POJO weaver extensibility

This commit is contained in:
2025-06-21 15:27:15 +02:00
parent 95b2cbc7dd
commit dc318722d3
26 changed files with 414 additions and 203 deletions

View File

@@ -4,12 +4,14 @@ import de.siphalor.tweed5.annotationinheritance.impl.AnnotationInheritanceResolv
import de.siphalor.tweed5.typeutils.api.annotations.AnnotationRepeatType; import de.siphalor.tweed5.typeutils.api.annotations.AnnotationRepeatType;
import de.siphalor.tweed5.utils.api.collection.ClassToInstanceMap; import de.siphalor.tweed5.utils.api.collection.ClassToInstanceMap;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.var;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
@RequiredArgsConstructor @RequiredArgsConstructor
public class AnnotationInheritanceAwareAnnotatedElement implements AnnotatedElement { public class AnnotationInheritanceAwareAnnotatedElement implements AnnotatedElement {
@@ -48,6 +50,28 @@ public class AnnotationInheritanceAwareAnnotatedElement implements AnnotatedElem
return getOrResolveAnnotations().get(annotationClass); return getOrResolveAnnotations().get(annotationClass);
} }
@Override
public @NonNull <T extends Annotation> @NotNull T[] getAnnotationsByType(@NonNull Class<T> annotationClass) {
T annotation = getOrResolveAnnotations().get(annotationClass);
if (annotation != null) {
//noinspection unchecked
T[] array = (T[]) Array.newInstance(annotationClass, 1);
array[0] = annotation;
return array;
}
AnnotationRepeatType repeatType = AnnotationRepeatType.getType(annotationClass);
if (repeatType instanceof AnnotationRepeatType.Repeatable) {
var containerRepeatType = ((AnnotationRepeatType.Repeatable) repeatType).containerRepeatType();
Annotation containerAnnotation = getOrResolveAnnotations().get(containerRepeatType.annotationClass());
if (containerAnnotation != null) {
return containerRepeatType.elements(containerAnnotation);
}
}
//noinspection unchecked
return (T[]) Array.newInstance(annotationClass, 0);
}
@Override @Override
public @NotNull Annotation[] getAnnotations() { public @NotNull Annotation[] getAnnotations() {
return getOrResolveAnnotations().values().toArray(new Annotation[0]); return getOrResolveAnnotations().values().toArray(new Annotation[0]);

View File

@@ -27,7 +27,7 @@ public class AnnotationInheritanceResolver {
public ClassToInstanceMap<Annotation> resolve() { public ClassToInstanceMap<Annotation> resolve() {
resolve(main, Collections.emptySet()); resolve(main, Collections.emptySet());
ClassToInstanceMap<Annotation> resolvedAnnotations = new ClassToInstanceMap<>(); ClassToInstanceMap<Annotation> resolvedAnnotations = ClassToInstanceMap.backedBy(new LinkedHashMap<>());
List<Aggregator> aggregatorList = new ArrayList<>(aggregators.values()); List<Aggregator> aggregatorList = new ArrayList<>(aggregators.values());
for (int i = aggregatorList.size() - 1; i >= 0; i--) { for (int i = aggregatorList.size() - 1; i >= 0; i--) {

View File

@@ -18,6 +18,15 @@ class AnnotationInheritanceAwareAnnotatedElementTest {
.isEqualTo(1); .isEqualTo(1);
} }
@Test
void getAnnotationsByTypeAForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
assertThat(element.getAnnotationsByType(A.class))
.singleElement()
.extracting(A::value)
.isEqualTo(1);
}
@Test @Test
void getAnnotationBForTarget1() { void getAnnotationBForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class); var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
@@ -27,6 +36,15 @@ class AnnotationInheritanceAwareAnnotatedElementTest {
.isEqualTo(2); .isEqualTo(2);
} }
@Test
void getAnnotationsByTypeBForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
assertThat(element.getAnnotationsByType(B.class))
.singleElement()
.extracting(B::value)
.isEqualTo(2);
}
@Test @Test
void getAnnotationCForTarget1() { void getAnnotationCForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class); var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
@@ -36,12 +54,33 @@ class AnnotationInheritanceAwareAnnotatedElementTest {
.isEqualTo(10); .isEqualTo(10);
} }
@Test
void getAnnotationsByTypeCForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
assertThat(element.getAnnotationsByType(C.class))
.singleElement()
.extracting(C::value)
.isEqualTo(10);
}
@Test @Test
void getAnnotationRForTarget1() { void getAnnotationRForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class); var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
assertThat(element.getAnnotation(R.class)).isNull(); assertThat(element.getAnnotation(R.class)).isNull();
} }
@Test
void getAnnotationsByTypeRForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
assertThat(element.getAnnotationsByType(R.class))
.satisfiesExactly(
r -> assertThat(r.value()).isEqualTo(4),
r -> assertThat(r.value()).isEqualTo(2),
r -> assertThat(r.value()).isEqualTo(3),
r -> assertThat(r.value()).isEqualTo(10)
);
}
@Test @Test
void getAnnotationRsForTarget1() { void getAnnotationRsForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class); var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
@@ -58,6 +97,21 @@ class AnnotationInheritanceAwareAnnotatedElementTest {
); );
} }
@Test
void getAnnotationsByTypeRsForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);
assertThat(element.getAnnotationsByType(Rs.class))
.singleElement()
.extracting(Rs::value)
.asInstanceOf(array(R[].class))
.satisfiesExactly(
r -> assertThat(r.value()).isEqualTo(4),
r -> assertThat(r.value()).isEqualTo(2),
r -> assertThat(r.value()).isEqualTo(3),
r -> assertThat(r.value()).isEqualTo(10)
);
}
@Test @Test
void getAnnotationsForTarget1() { void getAnnotationsForTarget1() {
var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class); var element = new AnnotationInheritanceAwareAnnotatedElement(Target1.class);

View File

@@ -1,6 +1,5 @@
package de.siphalor.tweed5.patchwork.api; package de.siphalor.tweed5.patchwork.api;
import org.jetbrains.annotations.CheckReturnValue;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@@ -10,7 +9,6 @@ public interface Patchwork {
@Contract(mutates = "this") @Contract(mutates = "this")
<T extends @Nullable Object> void set(PatchworkPartAccess<T> access, T value) throws InvalidPatchworkAccessException; <T extends @Nullable Object> void set(PatchworkPartAccess<T> access, T value) throws InvalidPatchworkAccessException;
@CheckReturnValue
@Contract(pure = true) @Contract(pure = true)
Patchwork copy(); Patchwork copy();
} }

View File

@@ -38,7 +38,7 @@ public class ClassToInstanceMap<T extends @NonNull Object> implements Iterable<T
return delegate.containsValue(value); return delegate.containsValue(value);
} }
public <V extends T> V get(Class<V> key) { public <V extends T> @Nullable V get(Class<V> key) {
return (V) delegate.get(key); return (V) delegate.get(key);
} }

View File

@@ -1,13 +1,15 @@
package de.siphalor.tweed5.weaver.pojoext.serde.api; package de.siphalor.tweed5.weaver.pojoext.serde.api;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.data.extension.api.ReadWriteExtension; import de.siphalor.tweed5.data.extension.api.ReadWriteExtension;
import de.siphalor.tweed5.data.extension.api.TweedEntryReader; import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter; import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider; import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls; import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
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 de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
import de.siphalor.tweed5.weaver.pojoext.serde.impl.SerdePojoReaderWriterSpec; import de.siphalor.tweed5.weaver.pojoext.serde.impl.SerdePojoReaderWriterSpec;
import lombok.extern.apachecommons.CommonsLog; import lombok.extern.apachecommons.CommonsLog;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@@ -18,11 +20,21 @@ import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
@CommonsLog @CommonsLog
public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor { public class ReadWritePojoWeavingProcessor implements TweedPojoWeavingExtension {
private final Map<String, TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryReader<?, ?>>> readerFactories = new HashMap<>(); private final Map<String, TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryReader<?, ?>>> readerFactories = new HashMap<>();
private final Map<String, TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryWriter<?, ?>>> writerFactories = new HashMap<>(); private final Map<String, TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryWriter<?, ?>>> writerFactories = new HashMap<>();
private final ReadWriteExtension readWriteExtension;
public ReadWritePojoPostProcessor() { public ReadWritePojoWeavingProcessor(ConfigContainer<?> configContainer) {
this.readWriteExtension = configContainer.extension(ReadWriteExtension.class)
.orElseThrow(() -> new IllegalStateException(
"You must register a " + ReadWriteExtension.class.getSimpleName()
+ " to use the " + getClass().getSimpleName()
));
}
@Override
public void setup(SetupContext context) {
loadProviders(); loadProviders();
} }
@@ -63,18 +75,12 @@ public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor
} }
@Override @Override
public void apply(ConfigEntry<?> configEntry, WeavingContext context) { public <T> void afterWeaveEntry(ActualType<T> valueType, ConfigEntry<T> configEntry, WeavingContext context) {
EntryReadWriteConfig entryConfig = context.annotations().getAnnotation(EntryReadWriteConfig.class); EntryReadWriteConfig entryConfig = context.annotations().getAnnotation(EntryReadWriteConfig.class);
if (entryConfig == null) { if (entryConfig == null) {
return; return;
} }
ReadWriteExtension readWriteExtension = context.configContainer().extension(ReadWriteExtension.class).orElse(null);
if (readWriteExtension == null && log.isErrorEnabled()) {
log.error("You must not use " + getClass().getName() + " without the " + ReadWriteExtension.class.getName());
return;
}
//noinspection rawtypes,unchecked //noinspection rawtypes,unchecked
readWriteExtension.setEntryReaderWriter( readWriteExtension.setEntryReaderWriter(
(ConfigEntry) configEntry, (ConfigEntry) configEntry,

View File

@@ -1,4 +1,4 @@
package de.siphalor.tweed5.weaver.pojoext.serde; package de.siphalor.tweed5.weaver.pojoext.serde.api;
import com.google.auto.service.AutoService; import com.google.auto.service.AutoService;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
@@ -10,11 +10,12 @@ import de.siphalor.tweed5.data.hjson.HjsonWriter;
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor; import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException; import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
import de.siphalor.tweed5.weaver.pojo.api.annotation.DefaultWeavingExtensions;
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension;
import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper; import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper;
import de.siphalor.tweed5.weaver.pojoext.serde.api.EntryReadWriteConfig;
import de.siphalor.tweed5.weaver.pojoext.serde.api.ReadWritePojoPostProcessor;
import lombok.*; import lombok.*;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.StringReader; import java.io.StringReader;
@@ -22,12 +23,13 @@ import java.io.StringWriter;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class WeaverPojoSerdeExtensionTest { class ReadWritePojoWeavingProcessorTest {
@Test @Test
@SneakyThrows @SneakyThrows
void testAnnotated() { void testAnnotated() {
TweedPojoWeaverBootstrapper<AnnotatedConfig> weaverBootstrapper = TweedPojoWeaverBootstrapper.create(AnnotatedConfig.class); TweedPojoWeaverBootstrapper<AnnotatedConfig> weaverBootstrapper = TweedPojoWeaverBootstrapper.create(
AnnotatedConfig.class);
ConfigContainer<AnnotatedConfig> configContainer = weaverBootstrapper.weave(); ConfigContainer<AnnotatedConfig> configContainer = weaverBootstrapper.weave();
configContainer.initialize(); configContainer.initialize();
@@ -38,12 +40,27 @@ class WeaverPojoSerdeExtensionTest {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
HjsonWriter hjsonWriter = new HjsonWriter(stringWriter, new HjsonWriter.Options()); HjsonWriter hjsonWriter = new HjsonWriter(stringWriter, new HjsonWriter.Options());
readWriteExtension.write(hjsonWriter, config, configContainer.rootEntry(), readWriteExtension.createReadWriteContextExtensionsData()); readWriteExtension.write(
hjsonWriter,
config,
configContainer.rootEntry(),
readWriteExtension.createReadWriteContextExtensionsData()
);
assertThat(stringWriter).hasToString("{\n\tanInt: 123\n\ttext: test\n\ttest: my cool custom writer\n}\n"); assertThat(stringWriter).hasToString("""
{
\tanInt: 123
\ttext: test
\ttest: my cool custom writer
}
""");
HjsonReader reader = new HjsonReader(new HjsonLexer(new StringReader( HjsonReader reader = new HjsonReader(new HjsonLexer(new StringReader("""
"{\n\tanInt: 987\n\ttext: abdef\n\ttest: { inner: 29 }\n}" {
\tanInt: 987
\ttext: abdef
\ttest: { inner: 29 }
}"""
))); )));
assertThat(readWriteExtension.read( assertThat(readWriteExtension.read(
reader, reader,
@@ -59,10 +76,10 @@ class WeaverPojoSerdeExtensionTest {
context.registerWriterFactory("tweed5.test.dummy", delegates -> new TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>() { context.registerWriterFactory("tweed5.test.dummy", delegates -> new TweedEntryWriter<Object, @NonNull ConfigEntry<Object>>() {
@Override @Override
public void write( public void write(
@NonNull TweedDataVisitor writer, TweedDataVisitor writer,
Object value, @Nullable Object value,
ConfigEntry<Object> entry, ConfigEntry<Object> entry,
@NonNull TweedWriteContext context TweedWriteContext context
) throws TweedDataWriteException { ) throws TweedDataWriteException {
writer.visitString("my cool custom writer"); writer.visitString("my cool custom writer");
} }
@@ -70,9 +87,12 @@ class WeaverPojoSerdeExtensionTest {
} }
} }
@PojoWeaving(extensions = ReadWriteExtension.class, postProcessors = ReadWritePojoPostProcessor.class) @PojoWeaving(extensions = ReadWriteExtension.class)
@DefaultWeavingExtensions
@PojoWeavingExtension(de.siphalor.tweed5.weaver.pojoext.serde.api.ReadWritePojoWeavingProcessor.class)
@CompoundWeaving @CompoundWeaving
@EntryReadWriteConfig("tweed5.compound") @EntryReadWriteConfig("tweed5.compound")
// lombok
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@EqualsAndHashCode @EqualsAndHashCode

View File

@@ -1,6 +1,7 @@
package de.siphalor.tweed5.weaver.pojoext.serde.impl; package de.siphalor.tweed5.weaver.pojoext.serde.impl;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.CsvSource;
@@ -52,7 +53,7 @@ class SerdePojoReaderWriterSpecTest {
"abc..def;5;.", "abc..def;5;.",
}) })
@SneakyThrows @SneakyThrows
void parseError(String input, int index, String codePoint) { void parseError(String input, int index, @Nullable String codePoint) {
assertThatThrownBy(() -> SerdePojoReaderWriterSpec.parse(input)) assertThatThrownBy(() -> SerdePojoReaderWriterSpec.parse(input))
.asInstanceOf(type(SerdePojoReaderWriterSpec.ParseException.class)) .asInstanceOf(type(SerdePojoReaderWriterSpec.ParseException.class))
.isInstanceOf(SerdePojoReaderWriterSpec.ParseException.class) .isInstanceOf(SerdePojoReaderWriterSpec.ParseException.class)

View File

@@ -4,6 +4,7 @@ plugins {
dependencies { dependencies {
implementation(project(":tweed5-construct")) implementation(project(":tweed5-construct"))
api(project(":tweed5-annotation-inheritance"))
api(project(":tweed5-core")) api(project(":tweed5-core"))
api(project(":tweed5-naming-format")) api(project(":tweed5-naming-format"))
api(project(":tweed5-type-utils")) api(project(":tweed5-type-utils"))

View File

@@ -0,0 +1,18 @@
package de.siphalor.tweed5.weaver.pojo.api.annotation;
import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritance;
import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@AnnotationInheritance(passOn = PojoWeavingExtension.class)
@PojoWeavingExtension(CompoundPojoWeaver.class)
@PojoWeavingExtension(TrivialPojoWeaver.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface DefaultWeavingExtensions {
}

View File

@@ -5,8 +5,7 @@ import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.core.impl.DefaultConfigContainer; import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeavingExtension;
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -14,16 +13,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface PojoWeaving { public @interface PojoWeaving {
Class<? extends ConfigContainer> container() default DefaultConfigContainer.class; Class<? extends ConfigContainer> container() default DefaultConfigContainer.class;
Class<? extends TweedPojoWeaver>[] weavers() default {
CompoundPojoWeaver.class,
TrivialPojoWeaver.class,
};
Class<? extends TweedPojoWeavingPostProcessor>[] postProcessors() default {};
Class<? extends TweedExtension>[] extensions() default {}; Class<? extends TweedExtension>[] extensions() default {};
} }

View File

@@ -0,0 +1,12 @@
package de.siphalor.tweed5.weaver.pojo.api.annotation;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeavingExtension;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Repeatable(PojoWeavingExtensions.class)
public @interface PojoWeavingExtension {
Class<? extends TweedPojoWeavingExtension> value();
}

View File

@@ -0,0 +1,12 @@
package de.siphalor.tweed5.weaver.pojo.api.annotation;
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.TYPE, ElementType.ANNOTATION_TYPE})
public @interface PojoWeavingExtensions {
PojoWeavingExtension[] value();
}

View File

@@ -20,7 +20,7 @@ import java.lang.reflect.AnnotatedElement;
import java.util.*; import java.util.*;
import java.util.function.IntFunction; import java.util.function.IntFunction;
public class CollectionPojoWeaver implements TweedPojoWeaver { public class CollectionPojoWeaver implements TweedPojoWeavingExtension {
private static final CollectionWeavingConfig DEFAULT_WEAVING_CONFIG = CollectionWeavingConfigImpl.builder() private static final CollectionWeavingConfig DEFAULT_WEAVING_CONFIG = CollectionWeavingConfigImpl.builder()
.collectionEntryClass(CollectionConfigEntryImpl.class) .collectionEntryClass(CollectionConfigEntryImpl.class)
.build(); .build();
@@ -51,10 +51,8 @@ public class CollectionPojoWeaver implements TweedPojoWeaver {
ConfigEntry<?> elementEntry = context.weaveEntry( ConfigEntry<?> elementEntry = context.weaveEntry(
collectionTypeParams.get(0), collectionTypeParams.get(0),
context.subContextBuilder("element") newExtensionsData,
.annotations(collectionTypeParams.get(0)) ProtoWeavingContext.subContextFor(context, "element", collectionTypeParams.get(0))
.extensionsData(newExtensionsData)
.build()
); );
return WeavableCollectionConfigEntry.FACTORY return WeavableCollectionConfigEntry.FACTORY

View File

@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
/** /**
* A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries. * A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries.
*/ */
public class CompoundPojoWeaver implements TweedPojoWeaver { public class CompoundPojoWeaver implements TweedPojoWeavingExtension {
private static final CompoundWeavingConfig DEFAULT_WEAVING_CONFIG = CompoundWeavingConfigImpl.builder() private static final CompoundWeavingConfig DEFAULT_WEAVING_CONFIG = CompoundWeavingConfigImpl.builder()
.compoundSourceNamingFormat(NamingFormats.camelCase()) .compoundSourceNamingFormat(NamingFormats.camelCase())
.compoundTargetNamingFormat(NamingFormats.camelCase()) .compoundTargetNamingFormat(NamingFormats.camelCase())
@@ -147,21 +147,28 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
private WeavableCompoundConfigEntry.SubEntry weaveCompoundSubEntry( private WeavableCompoundConfigEntry.SubEntry weaveCompoundSubEntry(
PojoClassIntrospector.Property property, PojoClassIntrospector.Property property,
Patchwork newExtensionsData, Patchwork newExtensionsData,
WeavingContext parentContext WeavingContext context
) { ) {
assert weavingConfigAccess != null; assert weavingConfigAccess != null;
CompoundWeavingConfig weavingConfig = newExtensionsData.get(weavingConfigAccess); CompoundWeavingConfig weavingConfig = newExtensionsData.get(weavingConfigAccess);
assert weavingConfig != null; assert weavingConfig != null;
String name = convertName(property.field().getName(), weavingConfig); String name = convertName(property.field().getName(), weavingConfig);
WeavingContext subContext = createSubContextForProperty(property, name, newExtensionsData, parentContext);
ConfigEntry<?> subEntry; ConfigEntry<?> subEntry;
if (property.isFinal()) { if (property.isFinal()) {
// TODO // TODO
throw new UnsupportedOperationException("Final config entries are not supported in weaving yet."); throw new UnsupportedOperationException("Final config entries are not supported in weaving yet.");
} else { } else {
subEntry = subContext.weaveEntry(ActualType.ofUsedType(property.field().getAnnotatedType()), subContext); subEntry = context.weaveEntry(
ActualType.ofUsedType(property.field().getAnnotatedType()),
newExtensionsData,
ProtoWeavingContext.subContextFor(
context,
property.field().getName(),
collectAnnotationsForField(property.field())
)
);
} }
return new StaticPojoCompoundConfigEntry.SubEntry( return new StaticPojoCompoundConfigEntry.SubEntry(
@@ -193,18 +200,6 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
return namingFormat; return namingFormat;
} }
private WeavingContext createSubContextForProperty(
PojoClassIntrospector.Property property,
String name,
Patchwork newExtensionsData,
WeavingContext parentContext
) {
return parentContext.subContextBuilder(name)
.annotations(collectAnnotationsForField(property.field()))
.extensionsData(newExtensionsData)
.build();
}
private AnnotatedElement collectAnnotationsForField(Field field) { private AnnotatedElement collectAnnotationsForField(Field field) {
LayeredTypeAnnotations annotations = new LayeredTypeAnnotations(); LayeredTypeAnnotations annotations = new LayeredTypeAnnotations();
annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, field.getType()); annotations.appendLayerFrom(TypeAnnotationLayer.TYPE_DECLARATION, field.getType());

View File

@@ -0,0 +1,43 @@
package de.siphalor.tweed5.weaver.pojo.api.weaving;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import org.jspecify.annotations.Nullable;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class ProtoWeavingContext {
@Nullable WeavingContext parent;
ConfigContainer<?> configContainer;
String[] path;
AnnotatedElement annotations;
public static ProtoWeavingContext subContextFor(
WeavingContext weavingContext,
String subPathName,
AnnotatedElement annotations
) {
String[] path = Arrays.copyOf(weavingContext.path(), weavingContext.path().length + 1, String[].class);
path[path.length - 1] = subPathName;
return new ProtoWeavingContext(
weavingContext,
weavingContext.configContainer(),
path,
annotations
);
}
public static ProtoWeavingContext create(ConfigContainer<?> configContainer, AnnotatedElement annotations) {
return new ProtoWeavingContext(
null,
configContainer,
new String[0],
annotations
);
}
}

View File

@@ -4,7 +4,7 @@ import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.typeutils.api.type.ActualType; import de.siphalor.tweed5.typeutils.api.type.ActualType;
public class TrivialPojoWeaver implements TweedPojoWeaver { public class TrivialPojoWeaver implements TweedPojoWeavingExtension {
@Override @Override
public void setup(SetupContext context) { public void setup(SetupContext context) {
// nothing to set up here // nothing to set up here

View File

@@ -1,17 +0,0 @@
package de.siphalor.tweed5.weaver.pojo.api.weaving;
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import org.jetbrains.annotations.ApiStatus;
public interface TweedPojoWeaver extends TweedPojoWeavingFunction {
TweedConstructFactory<TweedPojoWeaver> FACTORY = TweedConstructFactory.builder(TweedPojoWeaver.class).build();
@ApiStatus.OverrideOnly
void setup(SetupContext context);
interface SetupContext {
<E> PatchworkPartAccess<E> registerWeavingContextExtensionData(Class<E> dataClass);
}
}

View File

@@ -0,0 +1,33 @@
package de.siphalor.tweed5.weaver.pojo.api.weaving;
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess;
import de.siphalor.tweed5.typeutils.api.type.ActualType;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
public interface TweedPojoWeavingExtension extends TweedPojoWeavingFunction {
TweedConstructFactory<TweedPojoWeavingExtension> FACTORY =
TweedConstructFactory.builder(TweedPojoWeavingExtension.class)
.typedArg(ConfigContainer.class)
.build();
@ApiStatus.OverrideOnly
void setup(SetupContext context);
default <T> void beforeWeaveEntry(ActualType<T> valueType, Patchwork extensionsData, ProtoWeavingContext context) {}
@Override
default @Nullable <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
return null;
}
default <T> void afterWeaveEntry(ActualType<T> valueType, ConfigEntry<T> configEntry, WeavingContext context) {}
interface SetupContext {
<E> PatchworkPartAccess<E> registerWeavingContextExtensionData(Class<E> dataClass);
}
}

View File

@@ -4,63 +4,30 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.typeutils.api.type.ActualType; import de.siphalor.tweed5.typeutils.api.type.ActualType;
import lombok.*; import lombok.AccessLevel;
import lombok.experimental.Accessors; import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
@Value @Value
public class WeavingContext implements TweedPojoWeavingFunction.NonNull { @Builder
public class WeavingContext {
@Nullable WeavingContext parent; @Nullable WeavingContext parent;
@Getter(AccessLevel.NONE) @Getter(AccessLevel.NONE)
TweedPojoWeavingFunction.NonNull weavingFunction; WeavingFn weavingFunction;
ConfigContainer<?> configContainer; ConfigContainer<?> configContainer;
String[] path; String[] path;
Patchwork extensionsData; Patchwork extensionsData;
AnnotatedElement annotations; AnnotatedElement annotations;
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) { public <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, Patchwork extensionsData, ProtoWeavingContext context) {
return new Builder(null, weavingFunction, configContainer, new String[0]); return weavingFunction.weaveEntry(valueType, extensionsData, context);
} }
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer, String baseName) { public interface WeavingFn {
return new Builder(null, weavingFunction, configContainer, new String[]{ baseName }); <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, Patchwork extensionsData, ProtoWeavingContext context);
}
public Builder subContextBuilder(String subPathName) {
String[] newPath = Arrays.copyOf(path, path.length + 1);
newPath[path.length] = subPathName;
return new Builder(this, weavingFunction, configContainer, newPath).extensionsData(extensionsData);
}
@Override
public <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
return weavingFunction.weaveEntry(valueType, context);
}
@Accessors(fluent = true, chain = true)
@Setter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class Builder {
@Nullable
private final WeavingContext parent;
private final TweedPojoWeavingFunction.NonNull weavingFunction;
private final ConfigContainer<?> configContainer;
private final String[] path;
private Patchwork extensionsData;
private AnnotatedElement annotations;
public WeavingContext build() {
return new WeavingContext(
parent,
weavingFunction,
configContainer,
path,
extensionsData,
annotations
);
}
} }
} }

View File

@@ -1,12 +0,0 @@
package de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess;
import de.siphalor.tweed5.construct.api.TweedConstructFactory;
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
public interface TweedPojoWeavingPostProcessor {
TweedConstructFactory<TweedPojoWeavingPostProcessor> FACTORY =
TweedConstructFactory.builder(TweedPojoWeavingPostProcessor.class).build();
void apply(ConfigEntry<?> configEntry, WeavingContext context);
}

View File

@@ -1,4 +0,0 @@
@NullMarked
package de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,20 +1,23 @@
package de.siphalor.tweed5.weaver.pojo.impl.weaving; package de.siphalor.tweed5.weaver.pojo.impl.weaving;
import de.siphalor.tweed5.annotationinheritance.api.AnnotationInheritanceAwareAnnotatedElement;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.patchwork.api.Patchwork; import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkFactory; import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
import de.siphalor.tweed5.typeutils.api.type.ActualType; import de.siphalor.tweed5.typeutils.api.type.ActualType;
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension;
import de.siphalor.tweed5.weaver.pojo.api.weaving.ProtoWeavingContext;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeavingExtension;
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext; import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.apachecommons.CommonsLog; import lombok.extern.apachecommons.CommonsLog;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -27,36 +30,43 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class TweedPojoWeaverBootstrapper<T> { public class TweedPojoWeaverBootstrapper<T> {
private final Class<T> pojoClass; private final Class<T> pojoClass;
private final AnnotatedElement pojoAnnotations;
private final ConfigContainer<T> configContainer; private final ConfigContainer<T> configContainer;
private final Collection<TweedPojoWeaver> weavers; private final Collection<TweedPojoWeavingExtension> weavingExtensions;
private final Collection<TweedPojoWeavingPostProcessor> postProcessors;
@Nullable @Nullable
private PatchworkFactory contextExtensionsPatchworkFactory; private PatchworkFactory weavingExtensionsPatchworkFactory;
public static <T> TweedPojoWeaverBootstrapper<T> create(Class<T> pojoClass) { public static <T> TweedPojoWeaverBootstrapper<T> create(Class<T> pojoClass) {
PojoWeaving rootWeavingConfig = expectAnnotation(pojoClass, PojoWeaving.class); AnnotatedElement pojoAnnotations = new AnnotationInheritanceAwareAnnotatedElement(pojoClass);
PojoWeaving rootWeavingConfig = expectAnnotation(pojoAnnotations, PojoWeaving.class);
PojoWeavingExtension[] extensionAnnotations = pojoAnnotations.getAnnotationsByType(PojoWeavingExtension.class);
//noinspection unchecked //noinspection unchecked
ConfigContainer<T> configContainer = (ConfigContainer<T>) createConfigContainer((Class<? extends ConfigContainer<?>>) rootWeavingConfig.container()); ConfigContainer<T>
configContainer
Collection<TweedPojoWeaver> weavers = loadWeavers(Arrays.asList(rootWeavingConfig.weavers())); = (ConfigContainer<T>) createConfigContainer((Class<? extends ConfigContainer<?>>) rootWeavingConfig.container());
Collection<TweedPojoWeavingPostProcessor> postProcessors = loadPostProcessors(Arrays.asList(rootWeavingConfig.postProcessors()));
configContainer.registerExtensions(rootWeavingConfig.extensions()); configContainer.registerExtensions(rootWeavingConfig.extensions());
configContainer.finishExtensionSetup(); configContainer.finishExtensionSetup();
return new TweedPojoWeaverBootstrapper<>(pojoClass, configContainer, weavers, postProcessors); Collection<TweedPojoWeavingExtension> extensions = loadWeavingExtensions(
Arrays.stream(extensionAnnotations).map(PojoWeavingExtension::value).collect(Collectors.toList()),
configContainer
);
return new TweedPojoWeaverBootstrapper<>(pojoClass, pojoAnnotations, configContainer, extensions);
} }
private static Collection<TweedPojoWeaver> loadWeavers(Collection<Class<? extends TweedPojoWeaver>> weaverClasses) { private static Collection<TweedPojoWeavingExtension> loadWeavingExtensions(
Collection<Class<? extends TweedPojoWeavingExtension>> weaverClasses,
ConfigContainer<?> configContainer
) {
return weaverClasses.stream() return weaverClasses.stream()
.map(weaverClass -> TweedPojoWeaver.FACTORY.construct(weaverClass).finish()) .map(weaverClass ->
.collect(Collectors.toList()); TweedPojoWeavingExtension.FACTORY.construct(weaverClass)
} .typedArg(ConfigContainer.class, configContainer)
.finish()
private static Collection<TweedPojoWeavingPostProcessor> loadPostProcessors(Collection<Class<? extends TweedPojoWeavingPostProcessor>> postProcessorClasses) { )
return postProcessorClasses.stream()
.map(postProcessorClass -> TweedPojoWeavingPostProcessor.FACTORY.construct(postProcessorClass).finish())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@@ -68,69 +78,107 @@ public class TweedPojoWeaverBootstrapper<T> {
} }
} }
private static <A extends Annotation> A expectAnnotation(Class<?> clazz, Class<A> annotationClass) { private static <A extends Annotation> A expectAnnotation(AnnotatedElement element, Class<A> annotationClass) {
A annotation = clazz.getAnnotation(annotationClass); A annotation = element.getAnnotation(annotationClass);
if (annotation == null) { if (annotation == null) {
throw new PojoWeavingException("Annotation " + annotationClass.getName() + " must be defined on class " + clazz); throw new PojoWeavingException("Annotation "
+ annotationClass.getName()
+ " must be defined on class "
+ element);
} else { } else {
return annotation; return annotation;
} }
} }
public ConfigContainer<T> weave() { public ConfigContainer<T> weave() {
setupWeavers(); setupWeavingExtensions();
WeavingContext weavingContext = createWeavingContext();
assert weavingExtensionsPatchworkFactory != null;
ConfigEntry<T> rootEntry = this.weaveEntry(
ActualType.ofClass(pojoClass),
weavingExtensionsPatchworkFactory.create(),
ProtoWeavingContext.create(configContainer, pojoAnnotations)
);
ConfigEntry<T> rootEntry = this.weaveEntry(ActualType.ofClass(pojoClass), weavingContext);
configContainer.attachTree(rootEntry); configContainer.attachTree(rootEntry);
return configContainer; return configContainer;
} }
private void setupWeavers() { private void setupWeavingExtensions() {
PatchworkFactory.Builder contextExtensionsPatchworkFactoryBuilder = PatchworkFactory.builder(); PatchworkFactory.Builder weavingExtensionsPatchworkFactoryBuilder = PatchworkFactory.builder();
TweedPojoWeaver.SetupContext setupContext = contextExtensionsPatchworkFactoryBuilder::registerPart; TweedPojoWeavingExtension.SetupContext setupContext = weavingExtensionsPatchworkFactoryBuilder::registerPart;
for (TweedPojoWeaver weaver : weavers) { for (TweedPojoWeavingExtension weaver : weavingExtensions) {
weaver.setup(setupContext); weaver.setup(setupContext);
} }
contextExtensionsPatchworkFactory = contextExtensionsPatchworkFactoryBuilder.build(); weavingExtensionsPatchworkFactory = weavingExtensionsPatchworkFactoryBuilder.build();
} }
private WeavingContext createWeavingContext() { private <U> ConfigEntry<U> weaveEntry(
try { ActualType<U> dataClass,
assert contextExtensionsPatchworkFactory != null; Patchwork extensionsData,
Patchwork extensionsData = contextExtensionsPatchworkFactory.create(); ProtoWeavingContext protoContext
) {
extensionsData = extensionsData.copy();
return WeavingContext.builder(this::weaveEntry, configContainer) runBeforeWeaveHooks(dataClass, extensionsData, protoContext);
.extensionsData(extensionsData)
.annotations(pojoClass)
.build();
} catch (Throwable e) {
throw new PojoWeavingException("Failed to create weaving context's extension data");
}
}
private <U> ConfigEntry<U> weaveEntry(ActualType<U> dataClass, WeavingContext context) { WeavingContext context = WeavingContext.builder()
for (TweedPojoWeaver weaver : weavers) { .parent(protoContext.parent())
ConfigEntry<U> configEntry = weaver.weaveEntry(dataClass, context); .weavingFunction(this::weaveEntry)
if (configEntry != null) { .configContainer(configContainer)
applyPostProcessors(configEntry, context); .path(protoContext.path())
return configEntry; .extensionsData(extensionsData)
.annotations(new AnnotationInheritanceAwareAnnotatedElement(protoContext.annotations()))
.build();
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
try {
ConfigEntry<U> configEntry = weavingExtension.weaveEntry(dataClass, context);
if (configEntry != null) {
runAfterWeaveHooks(dataClass, configEntry, context);
return configEntry;
}
} catch (Exception e) {
log.error("Failed to run Tweed POJO weaver (" + weavingExtension.getClass().getName() + ")", e);
} }
} }
throw new PojoWeavingException("Failed to weave " + dataClass + ": No matching weavers found"); throw new PojoWeavingException("Failed to weave " + dataClass + ": No matching weavers found");
} }
private void applyPostProcessors(ConfigEntry<?> configEntry, WeavingContext context) { private <U> void runBeforeWeaveHooks(
for (TweedPojoWeavingPostProcessor postProcessor : postProcessors) { ActualType<U> dataClass,
Patchwork extensionsData,
ProtoWeavingContext protoContext
) {
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
try { try {
postProcessor.apply(configEntry, context); weavingExtension.beforeWeaveEntry(dataClass, extensionsData, protoContext);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to apply Tweed POJO weaver post processor", e); log.error(
"Failed to apply Tweed POJO weaver before weave hook ("
+ weavingExtension.getClass().getName() + ")",
e
);
}
}
}
private <U> void runAfterWeaveHooks(ActualType<U> dataClass, ConfigEntry<U> configEntry, WeavingContext context) {
for (TweedPojoWeavingExtension weavingExtension : weavingExtensions) {
try {
weavingExtension.afterWeaveEntry(dataClass, configEntry, context);
} catch (Exception e) {
log.error(
"Failed to apply Tweed POJO weaver after weave hook ("
+ weavingExtension.getClass().getName() + ")",
e
);
} }
} }
} }

View File

@@ -3,13 +3,15 @@ package de.siphalor.tweed5.weaver.pojo.api.weaving;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.entry.ConfigEntry; import de.siphalor.tweed5.core.api.entry.ConfigEntry;
import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl; import de.siphalor.tweed5.core.impl.entry.SimpleConfigEntryImpl;
import de.siphalor.tweed5.patchwork.api.Patchwork;
import de.siphalor.tweed5.patchwork.api.PatchworkFactory; import de.siphalor.tweed5.patchwork.api.PatchworkFactory;
import de.siphalor.tweed5.typeutils.api.type.ActualType; import de.siphalor.tweed5.typeutils.api.type.ActualType;
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.NullUnmarked;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Objects;
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith; import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass; import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@@ -28,19 +30,31 @@ class CompoundPojoWeaverTest {
PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build(); PatchworkFactory weavingContextExtensionDataFactory = weavingContextExtensionDataFactoryBuilder.build();
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() { WeavingContext weavingContext = WeavingContext.builder()
.weavingFunction(new WeavingContext.WeavingFn() {
@Override @Override
public @NotNull <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) { public <T> ConfigEntry<T> weaveEntry(
ConfigEntry<T> entry = compoundWeaver.weaveEntry(valueType, context); ActualType<T> valueType,
if (entry != null) { Patchwork extensionsData,
return entry; ProtoWeavingContext protoContext
} else { ) {
return new SimpleConfigEntryImpl<>(context.configContainer(), valueType.declaredType()); WeavingContext context = WeavingContext.builder()
} .configContainer(protoContext.configContainer())
.annotations(valueType)
.extensionsData(extensionsData.copy())
.path(protoContext.path())
.weavingFunction(protoContext.parent()::weaveEntry)
.build();
return Objects.requireNonNullElseGet(
compoundWeaver.weaveEntry(valueType, context),
() -> new SimpleConfigEntryImpl<>(context.configContainer(), valueType.declaredType())
);
} }
}, mock(ConfigContainer.class)) })
.extensionsData(weavingContextExtensionDataFactory.create()) .extensionsData(weavingContextExtensionDataFactory.create())
.annotations(Compound.class) .annotations(Compound.class)
.path(new String[0])
.configContainer(mock(ConfigContainer.class))
.build(); .build();
ConfigEntry<Compound> resultEntry = compoundWeaver.weaveEntry(ActualType.ofClass(Compound.class), weavingContext); ConfigEntry<Compound> resultEntry = compoundWeaver.weaveEntry(ActualType.ofClass(Compound.class), weavingContext);

View File

@@ -0,0 +1,5 @@
@NullMarked
package de.siphalor.tweed5.weaver.pojo.api.weaving;
import org.jspecify.annotations.NullMarked;

View File

@@ -4,10 +4,10 @@ import com.google.auto.service.AutoService;
import de.siphalor.tweed5.core.api.container.ConfigContainer; import de.siphalor.tweed5.core.api.container.ConfigContainer;
import de.siphalor.tweed5.core.api.extension.TweedExtension; import de.siphalor.tweed5.core.api.extension.TweedExtension;
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
import de.siphalor.tweed5.weaver.pojo.api.annotation.DefaultWeavingExtensions;
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving; import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension;
import de.siphalor.tweed5.weaver.pojo.api.weaving.CollectionPojoWeaver; import de.siphalor.tweed5.weaver.pojo.api.weaving.CollectionPojoWeaver;
import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver;
import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver;
import lombok.Data; import lombok.Data;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -65,6 +65,7 @@ class TweedPojoWeaverBootstrapperTest {
} }
@PojoWeaving(extensions = {DummyExtension.class}) @PojoWeaving(extensions = {DummyExtension.class})
@DefaultWeavingExtensions
@CompoundWeaving(namingFormat = "camel_case") @CompoundWeaving(namingFormat = "camel_case")
@Data @Data
public static class MainCompound { public static class MainCompound {
@@ -88,7 +89,9 @@ class TweedPojoWeaverBootstrapperTest {
boolean somethingElse; boolean somethingElse;
} }
@PojoWeaving(weavers = {CompoundPojoWeaver.class, CollectionPojoWeaver.class, TrivialPojoWeaver.class}) @PojoWeaving
@PojoWeavingExtension(CollectionPojoWeaver.class)
@DefaultWeavingExtensions
@CompoundWeaving(namingFormat = "camel_case") @CompoundWeaving(namingFormat = "camel_case")
@Data @Data
public static class CompoundWithList { public static class CompoundWithList {