[weaver-pojo] Introduce pojo weaver post processors
This commit is contained in:
@@ -9,7 +9,7 @@ import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
||||
import de.siphalor.tweed5.utils.api.collection.ClassToInstanceMap;
|
||||
import de.siphalor.tweed5.utils.api.collection.InheritanceMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -20,14 +20,18 @@ import java.util.*;
|
||||
public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
@Getter
|
||||
private ConfigContainerSetupPhase setupPhase = ConfigContainerSetupPhase.EXTENSIONS_SETUP;
|
||||
private final ClassToInstanceMap<TweedExtension> extensions = new ClassToInstanceMap<>();
|
||||
private final InheritanceMap<TweedExtension> extensions = new InheritanceMap<>(TweedExtension.class);
|
||||
private ConfigEntry<T> rootEntry;
|
||||
private PatchworkClass<EntryExtensionsData> entryExtensionsDataPatchworkClass;
|
||||
private Map<Class<?>, RegisteredExtensionDataImpl<EntryExtensionsData, ?>> registeredEntryDataExtensions;
|
||||
|
||||
@Override
|
||||
public <E extends TweedExtension> @Nullable E extension(Class<E> extensionClass) {
|
||||
return extensions.get(extensionClass);
|
||||
try {
|
||||
return extensions.getSingleInstance(extensionClass);
|
||||
} catch (InheritanceMap.NonUniqueResultException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,8 +43,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
public void registerExtension(TweedExtension extension) {
|
||||
requireSetupPhase(ConfigContainerSetupPhase.EXTENSIONS_SETUP);
|
||||
|
||||
TweedExtension previous = extensions.put(extension);
|
||||
if (previous != null) {
|
||||
if (!extensions.putIfAbsent(extension)) {
|
||||
throw new IllegalArgumentException("Extension " + extension.getClass().getName() + " is already registered");
|
||||
}
|
||||
}
|
||||
@@ -69,7 +72,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
|
||||
@Override
|
||||
public void registerExtension(TweedExtension extension) {
|
||||
if (!extensions.containsClass(extension.getClass())) {
|
||||
if (!extensions.containsAnyInstanceForClass(extension.getClass())) {
|
||||
additionalExtensions.add(extension);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +85,7 @@ public class DefaultConfigContainer<T> implements ConfigContainer<T> {
|
||||
}
|
||||
|
||||
for (TweedExtension additionalExtension : additionalExtensions) {
|
||||
extensions.put(additionalExtension);
|
||||
extensions.putIfAbsent(additionalExtension);
|
||||
}
|
||||
extensionsToSetup = new ArrayList<>(additionalExtensions);
|
||||
additionalExtensions.clear();
|
||||
|
||||
@@ -119,12 +119,12 @@ class ValidationFallbackExtensionImplTest {
|
||||
readerWriterData.set(intEntry.extensionsData(), new EntryReaderWriterDefinition() {
|
||||
@Override
|
||||
public TweedEntryReader<?, ?> reader() {
|
||||
return TweedEntryReaderWriters.nullableReaderWriter(TweedEntryReaderWriters.intReaderWriter());
|
||||
return TweedEntryReaderWriters.nullableReader(TweedEntryReaderWriters.intReaderWriter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TweedEntryWriter<?, ?> writer() {
|
||||
return TweedEntryReaderWriters.nullableReaderWriter(TweedEntryReaderWriters.intReaderWriter());
|
||||
return TweedEntryReaderWriters.nullableWriter(TweedEntryReaderWriters.intReaderWriter());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import de.siphalor.tweed5.dataapi.api.TweedDataReader;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||
|
||||
public interface ReadWriteExtension extends TweedExtension {
|
||||
void setEntryReaderWriterDefinition(ConfigEntry<?> entry, EntryReaderWriterDefinition readerWriterDefinition);
|
||||
|
||||
ReadWriteContextExtensionsData createReadWriteContextExtensionsData();
|
||||
|
||||
<T> T read(TweedDataReader reader, ConfigEntry<T> entry, ReadWriteContextExtensionsData contextExtensionsData) throws TweedEntryReadException;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.siphalor.tweed5.data.extension.api;
|
||||
|
||||
import de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
@@ -13,14 +12,11 @@ public interface TweedReaderWriterProvider {
|
||||
/**
|
||||
* The context where reader and writer factories may be registered.<br />
|
||||
* The reader and writer ids must be globally unique. It is therefore recommended to scope custom reader and writer ids in your own namespace (e.g. "de.siphalor.custom.blub")
|
||||
* Ids may consist of alphanumeric characters and dots.
|
||||
*/
|
||||
interface ProviderContext {
|
||||
void registerReaderFactory(String id, ReaderWriterFactory<? extends TweedEntryReader<?, ?>> readerFactory);
|
||||
void registerWriterFactory(String id, ReaderWriterFactory<? extends TweedEntryWriter<?, ?>> writerFactory);
|
||||
default void registerReaderWriterFactory(String id, ReaderWriterFactory<? extends TweedEntryReaderWriter<?, ?>> readerWriterFactory) {
|
||||
registerReaderFactory(id, readerWriterFactory);
|
||||
registerWriterFactory(id, readerWriterFactory);
|
||||
}
|
||||
void registerReaderFactory(String id, ReaderWriterFactory<TweedEntryReader<?, ?>> readerFactory);
|
||||
void registerWriterFactory(String id, ReaderWriterFactory<TweedEntryWriter<?, ?>> writerFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@ package de.siphalor.tweed5.data.extension.api.readwrite;
|
||||
import de.siphalor.tweed5.core.api.entry.CoherentCollectionConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
@@ -43,8 +45,12 @@ public class TweedEntryReaderWriters {
|
||||
return TweedEntryReaderWriterImpls.STRING_READER_WRITER;
|
||||
}
|
||||
|
||||
public static <T, C extends ConfigEntry<T>> TweedEntryReaderWriter<T, C> nullableReaderWriter(TweedEntryReaderWriter<T, C> delegate) {
|
||||
return new TweedEntryReaderWriterImpls.NullableReaderWriter<>(delegate);
|
||||
public static <T, C extends ConfigEntry<T>> TweedEntryReader<T, C> nullableReader(TweedEntryReader<T, C> delegate) {
|
||||
return new TweedEntryReaderWriterImpls.NullableReader<>(delegate);
|
||||
}
|
||||
|
||||
public static <T, C extends ConfigEntry<T>> TweedEntryWriter<T, C> nullableWriter(TweedEntryWriter<T, C> delegate) {
|
||||
return new TweedEntryReaderWriterImpls.NullableWriter<>(delegate);
|
||||
}
|
||||
|
||||
public static <T, C extends Collection<T>> TweedEntryReaderWriter<C, CoherentCollectionConfigEntry<T, C>> coherentCollectionReaderWriter() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package de.siphalor.tweed5.data.extension.impl;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryReader;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedEntryWriter;
|
||||
import de.siphalor.tweed5.data.extension.api.TweedReaderWriterProvider;
|
||||
|
||||
import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWriters.*;
|
||||
@@ -9,22 +11,48 @@ import static de.siphalor.tweed5.data.extension.api.readwrite.TweedEntryReaderWr
|
||||
public class DefaultTweedEntryReaderWriterImplsProvider implements TweedReaderWriterProvider {
|
||||
@Override
|
||||
public void provideReaderWriters(ProviderContext context) {
|
||||
context.registerReaderWriterFactory("boolean", new StaticReaderWriterFactory<>(booleanReaderWriter()));
|
||||
context.registerReaderWriterFactory("byte", new StaticReaderWriterFactory<>(byteReaderWriter()));
|
||||
context.registerReaderWriterFactory("short", new StaticReaderWriterFactory<>(shortReaderWriter()));
|
||||
context.registerReaderWriterFactory("int", new StaticReaderWriterFactory<>(intReaderWriter()));
|
||||
context.registerReaderWriterFactory("long", new StaticReaderWriterFactory<>(longReaderWriter()));
|
||||
context.registerReaderWriterFactory("float", new StaticReaderWriterFactory<>(floatReaderWriter()));
|
||||
context.registerReaderWriterFactory("double", new StaticReaderWriterFactory<>(doubleReaderWriter()));
|
||||
context.registerReaderWriterFactory("string", new StaticReaderWriterFactory<>(stringReaderWriter()));
|
||||
context.registerReaderWriterFactory("coherent_collection", new StaticReaderWriterFactory<>(coherentCollectionReaderWriter()));
|
||||
context.registerReaderWriterFactory("compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
||||
StaticReaderWriterFactory<TweedEntryReader<?, ?>> booleanReaderFactory = new StaticReaderWriterFactory<>(booleanReaderWriter());
|
||||
StaticReaderWriterFactory<TweedEntryWriter<?, ?>> booleanWriterFactory = new StaticReaderWriterFactory<>(booleanReaderWriter());
|
||||
context.registerReaderFactory("tweed5.bool", booleanReaderFactory);
|
||||
context.registerReaderFactory("tweed5.boolean", booleanReaderFactory);
|
||||
context.registerWriterFactory("tweed5.bool", booleanWriterFactory);
|
||||
context.registerWriterFactory("tweed5.boolean", booleanWriterFactory);
|
||||
context.registerReaderFactory("tweed5.byte", new StaticReaderWriterFactory<>(byteReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.byte", new StaticReaderWriterFactory<>(byteReaderWriter()));
|
||||
context.registerReaderFactory("tweed5.short", new StaticReaderWriterFactory<>(shortReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.short", new StaticReaderWriterFactory<>(shortReaderWriter()));
|
||||
StaticReaderWriterFactory<TweedEntryReader<?, ?>> integerReaderFactory =
|
||||
new StaticReaderWriterFactory<>(intReaderWriter());
|
||||
StaticReaderWriterFactory<TweedEntryWriter<?, ?>> integerWriterFactory =
|
||||
new StaticReaderWriterFactory<>(intReaderWriter());
|
||||
context.registerReaderFactory("tweed5.int", integerReaderFactory);
|
||||
context.registerReaderFactory("tweed5.integer", integerReaderFactory);
|
||||
context.registerWriterFactory("tweed5.int", integerWriterFactory);
|
||||
context.registerWriterFactory("tweed5.integer", integerWriterFactory);
|
||||
context.registerReaderFactory("tweed5.long", new StaticReaderWriterFactory<>(longReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.long", new StaticReaderWriterFactory<>(longReaderWriter()));
|
||||
context.registerReaderFactory("tweed5.float", new StaticReaderWriterFactory<>(floatReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.float", new StaticReaderWriterFactory<>(floatReaderWriter()));
|
||||
context.registerReaderFactory("tweed5.double", new StaticReaderWriterFactory<>(doubleReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.double", new StaticReaderWriterFactory<>(doubleReaderWriter()));
|
||||
context.registerReaderFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.string", new StaticReaderWriterFactory<>(stringReaderWriter()));
|
||||
context.registerReaderFactory("tweed5.collection.coherent", new StaticReaderWriterFactory<>(coherentCollectionReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.collection.coherent", new StaticReaderWriterFactory<>(coherentCollectionReaderWriter()));
|
||||
context.registerReaderFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
||||
context.registerWriterFactory("tweed5.compound", new StaticReaderWriterFactory<>(compoundReaderWriter()));
|
||||
|
||||
context.registerReaderWriterFactory("nullable", delegateReaderWriters -> {
|
||||
if (delegateReaderWriters.length != 1) {
|
||||
throw new IllegalArgumentException("Nullable reader writer requires a single delegate argument, got " + delegateReaderWriters.length);
|
||||
context.registerReaderFactory("tweed5.nullable", delegateReaders -> {
|
||||
if (delegateReaders.length != 1) {
|
||||
throw new IllegalArgumentException("Nullable reader requires a single delegate argument, got " + delegateReaders.length);
|
||||
}
|
||||
return nullableReaderWriter(delegateReaderWriters[0]);
|
||||
return nullableReader(delegateReaders[0]);
|
||||
});
|
||||
context.registerWriterFactory("tweed5.nullable", delegateWriters -> {
|
||||
if (delegateWriters.length != 1) {
|
||||
throw new IllegalArgumentException("Nullable writer requires a single delegate argument, got " + delegateWriters.length);
|
||||
}
|
||||
return nullableWriter(delegateWriters[0]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.Map;
|
||||
@AutoService(ReadWriteExtension.class)
|
||||
public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
|
||||
private RegisteredExtensionData<EntryExtensionsData, EntryReaderWriterDefinition> readerWriterDefinitionExtension;
|
||||
private RegisteredExtensionData<EntryExtensionsData, ReadWriteEntryDataExtension> readWriteEntryDataExtension;
|
||||
private DefaultMiddlewareContainer<TweedEntryReader<?, ?>> entryReaderMiddlewareContainer;
|
||||
private DefaultMiddlewareContainer<TweedEntryWriter<?, ?>> entryWriterMiddlewareContainer;
|
||||
@@ -44,8 +45,8 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
|
||||
@Override
|
||||
public void setup(TweedExtensionSetupContext context) {
|
||||
readerWriterDefinitionExtension = context.registerEntryExtensionData(EntryReaderWriterDefinition.class);
|
||||
readWriteEntryDataExtension = context.registerEntryExtensionData(ReadWriteEntryDataExtension.class);
|
||||
context.registerEntryExtensionData(EntryReaderWriterDefinition.class);
|
||||
|
||||
Collection<TweedExtension> extensions = context.configContainer().extensions();
|
||||
|
||||
@@ -120,6 +121,11 @@ public class ReadWriteExtensionImpl implements ReadWriteExtension {
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryReaderWriterDefinition(ConfigEntry<?> entry, EntryReaderWriterDefinition readerWriterDefinition) {
|
||||
readerWriterDefinitionExtension.set(entry.extensionsData(), readerWriterDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadWriteContextExtensionsData createReadWriteContextExtensionsData() {
|
||||
try {
|
||||
|
||||
@@ -32,16 +32,22 @@ public class TweedEntryReaderWriterImpls {
|
||||
public static final TweedEntryReaderWriter<Object, ConfigEntry<Object>> NOOP_READER_WRITER = new NoopReaderWriter();
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class NullableReaderWriter<T, C extends ConfigEntry<T>> implements TweedEntryReaderWriter<T, C> {
|
||||
private final TweedEntryReaderWriter<T, C> delegate;
|
||||
public static class NullableReader<T, C extends ConfigEntry<T>> implements TweedEntryReader<T, C> {
|
||||
private final TweedEntryReader<T, C> delegate;
|
||||
|
||||
@Override
|
||||
public T read(TweedDataReader reader, C entry, TweedReadContext context) throws TweedEntryReadException, TweedDataReadException {
|
||||
if (reader.peekToken().isNull()) {
|
||||
reader.readToken();
|
||||
return null;
|
||||
}
|
||||
return delegate.read(reader, entry, context);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class NullableWriter<T, C extends ConfigEntry<T>> implements TweedEntryWriter<T, C> {
|
||||
private final TweedEntryWriter<T, C> delegate;
|
||||
|
||||
@Override
|
||||
public void write(TweedDataVisitor writer, T value, C entry, TweedWriteContext context) throws TweedEntryWriteException, TweedDataWriteException {
|
||||
@@ -137,9 +143,10 @@ public class TweedEntryReaderWriterImpls {
|
||||
//noinspection unchecked
|
||||
ConfigEntry<Object> subEntry = (ConfigEntry<Object>) compoundEntries.get(key);
|
||||
TweedEntryReader<Object, ConfigEntry<Object>> subEntryReaderChain = ReadWriteExtensionImpl.getReaderChain(subEntry);
|
||||
|
||||
if (subEntryReaderChain != null) {
|
||||
Object subEntryValue = subEntryReaderChain.read(reader, subEntry, context);
|
||||
entry.set(compoundValue, key, subEntryValue);
|
||||
}
|
||||
} else {
|
||||
throw new TweedEntryReadException("Unexpected token " + token + ": Expected map key or map end");
|
||||
}
|
||||
@@ -159,11 +166,13 @@ public class TweedEntryReaderWriterImpls {
|
||||
String key = e.getKey();
|
||||
ConfigEntry<Object> subEntry = e.getValue();
|
||||
|
||||
writer.visitMapEntryKey(key);
|
||||
|
||||
TweedEntryWriter<Object, ConfigEntry<Object>> subEntryWriterChain = ReadWriteExtensionImpl.getWriterChain(subEntry);
|
||||
|
||||
if (subEntryWriterChain != null) {
|
||||
writer.visitMapEntryKey(key);
|
||||
subEntryWriterChain.write(writer, entry.get(value, key), subEntry, context);
|
||||
}
|
||||
}
|
||||
|
||||
writer.visitMapEnd();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@AllArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public class InheritanceMap<T> {
|
||||
private static final InheritanceMap<Object> EMPTY = unmodifiable(new InheritanceMap<>(Object.class));
|
||||
|
||||
private final Class<T> baseClass;
|
||||
private final Map<T, Collection<Class<? extends T>>> instanceToClasses;
|
||||
private final Map<Class<? extends T>, Collection<T>> classToInstances;
|
||||
|
||||
public static <T> InheritanceMap<T> empty() {
|
||||
return (InheritanceMap<T>) EMPTY;
|
||||
}
|
||||
public static <T> InheritanceMap<T> unmodifiable(InheritanceMap<T> map) {
|
||||
return new Unmodifiable<>(map);
|
||||
}
|
||||
|
||||
public InheritanceMap(Class<T> baseClass) {
|
||||
this(baseClass, new IdentityHashMap<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return instanceToClasses.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return instanceToClasses.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsAnyInstanceForClass(Class<? extends T> clazz) {
|
||||
return !classToInstances.getOrDefault(clazz, Collections.emptyList()).isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsSingleInstanceForClass(Class<? extends T> clazz) {
|
||||
return classToInstances.getOrDefault(clazz, Collections.emptyList()).size() == 1;
|
||||
}
|
||||
|
||||
public boolean containsInstance(T instance) {
|
||||
return instanceToClasses.containsKey(instance);
|
||||
}
|
||||
|
||||
public <V extends T> Collection<V> getAllInstances(Class<V> clazz) {
|
||||
return (Collection<V>) classToInstances.getOrDefault(clazz, Collections.emptyList());
|
||||
}
|
||||
|
||||
public <V extends T> V getSingleInstance(Class<V> clazz) throws NonUniqueResultException {
|
||||
Collection<T> instances = classToInstances.getOrDefault(clazz, Collections.emptyList());
|
||||
if (instances.isEmpty()) {
|
||||
return null;
|
||||
} else if (instances.size() == 1) {
|
||||
return (V) instances.iterator().next();
|
||||
} else {
|
||||
throw new NonUniqueResultException("Multiple instances for class " + clazz.getName() + " exist.");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putAll(T... instances) {
|
||||
boolean changed = false;
|
||||
for (T instance : instances) {
|
||||
changed = put(instance) || changed;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean put(T instance) {
|
||||
if (instanceToClasses.containsKey(instance)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
putInternal(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean putIfAbsent(T instance) {
|
||||
Collection<T> existingInstances = classToInstances.getOrDefault(instance.getClass(), Collections.emptyList());
|
||||
if (existingInstances.isEmpty()) {
|
||||
putInternal(instance);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public <V extends T> V removeInstance(V instance) {
|
||||
if (!instanceToClasses.containsKey(instance)) {
|
||||
return null;
|
||||
}
|
||||
Collection<Class<? extends T>> classes = instanceToClasses.getOrDefault(instance, Collections.emptyList());
|
||||
for (Class<? extends T> implemented : classes) {
|
||||
classToInstances.getOrDefault(implemented, Collections.emptyList()).remove(instance);
|
||||
}
|
||||
instanceToClasses.remove(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
instanceToClasses.clear();
|
||||
classToInstances.clear();
|
||||
}
|
||||
|
||||
public Set<T> values() {
|
||||
return instanceToClasses.keySet();
|
||||
}
|
||||
|
||||
private void putInternal(T instance) {
|
||||
Collection<Class<? extends T>> classes = findClasses((Class<? extends T>) instance.getClass());
|
||||
|
||||
instanceToClasses.put(instance, classes);
|
||||
for (Class<? extends T> implementedClass : classes) {
|
||||
classToInstances.computeIfAbsent(implementedClass, c -> new ArrayList<>()).add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Class<? extends T>> findClasses(Class<? extends T> clazz) {
|
||||
List<Class<? extends T>> classes = new ArrayList<>();
|
||||
|
||||
Class<?> superClass = clazz;
|
||||
while (superClass != Object.class && superClass != baseClass && baseClass.isAssignableFrom(superClass)) {
|
||||
classes.add((Class<? extends T>) superClass);
|
||||
|
||||
if (baseClass == Object.class || baseClass.isInterface()) {
|
||||
classes.addAll(findOnlyInterfaces((Class<? extends T>) superClass));
|
||||
}
|
||||
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
private Collection<Class<? extends T>> findOnlyInterfaces(Class<? extends T> clazz) {
|
||||
List<Class<? extends T>> classes = new ArrayList<>();
|
||||
|
||||
for (Class<?> implemented : clazz.getInterfaces()) {
|
||||
if (baseClass != implemented && baseClass.isAssignableFrom(implemented)) {
|
||||
classes.add((Class<? extends T>) implemented);
|
||||
classes.addAll(findOnlyInterfaces((Class<? extends T>) implemented));
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
public static class NonUniqueResultException extends Exception {
|
||||
public NonUniqueResultException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Unmodifiable<T> extends InheritanceMap<T> {
|
||||
public Unmodifiable(InheritanceMap<T> delegate) {
|
||||
super(delegate.baseClass, delegate.instanceToClasses, delegate.classToInstances);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean put(T instance) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putIfAbsent(T instance) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V extends T> V removeInstance(V instance) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.siphalor.tweed5.utils.api.collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InheritanceMapTest {
|
||||
@Test
|
||||
void full() {
|
||||
InheritanceMap<Object> map = new InheritanceMap<>(Object.class);
|
||||
map.put(123L);
|
||||
map.put(123);
|
||||
map.put(456);
|
||||
|
||||
assertThat(map.getAllInstances(Long.class)).containsExactlyInAnyOrder(123L);
|
||||
assertThat(map.getAllInstances(Integer.class)).containsExactlyInAnyOrder(123, 456);
|
||||
assertThat(map.getAllInstances(Number.class)).containsExactlyInAnyOrder(123L, 123, 456);
|
||||
}
|
||||
}
|
||||
6
tweed5-weaver-pojo-serde-extension/build.gradle.kts
Normal file
6
tweed5-weaver-pojo-serde-extension/build.gradle.kts
Normal file
@@ -0,0 +1,6 @@
|
||||
dependencies {
|
||||
api(project(":tweed5-weaver-pojo"))
|
||||
api(project(":tweed5-serde-extension"))
|
||||
|
||||
testImplementation(project(":tweed5-serde-hjson"))
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.serde.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@code <spec> = <id> [ "(" <spec> ( "," <spec> )* ")" ] }
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface EntryReadWriteConfig {
|
||||
String value() default "";
|
||||
String writer() default "";
|
||||
String reader() default "";
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.serde.api;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.data.extension.api.*;
|
||||
import de.siphalor.tweed5.data.extension.impl.TweedEntryReaderWriterImpls;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class ReadWritePojoPostProcessor implements TweedPojoWeavingPostProcessor {
|
||||
private final Map<String, TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryReader<?, ?>>> readerFactories = new HashMap<>();
|
||||
private final Map<String, TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryWriter<?, ?>>> writerFactories = new HashMap<>();
|
||||
|
||||
public ReadWritePojoPostProcessor() {
|
||||
loadProviders();
|
||||
}
|
||||
|
||||
private void loadProviders() {
|
||||
ServiceLoader<TweedReaderWriterProvider> serviceLoader = ServiceLoader.load(TweedReaderWriterProvider.class);
|
||||
|
||||
for (TweedReaderWriterProvider readerWriterProvider : serviceLoader) {
|
||||
TweedReaderWriterProvider.ProviderContext providerContext = new TweedReaderWriterProvider.ProviderContext() {
|
||||
@Override
|
||||
public void registerReaderFactory(
|
||||
String id,
|
||||
TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryReader<?, ?>> readerFactory
|
||||
) {
|
||||
if (readerFactories.putIfAbsent(id, readerFactory) != null) {
|
||||
log.warn(
|
||||
"Found duplicate Tweed entry reader id \"{}\" in provider class {}",
|
||||
id,
|
||||
readerWriterProvider.getClass().getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerWriterFactory(
|
||||
String id,
|
||||
TweedReaderWriterProvider.ReaderWriterFactory<TweedEntryWriter<?, ?>> writerFactory
|
||||
) {
|
||||
if (writerFactories.putIfAbsent(id, writerFactory) != null) {
|
||||
log.warn(
|
||||
"Found duplicate Tweed entry writer id \"{}\" in provider class {}",
|
||||
id,
|
||||
readerWriterProvider.getClass().getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
readerWriterProvider.provideReaderWriters(providerContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(ConfigEntry<?> configEntry, WeavingContext context) {
|
||||
EntryReadWriteConfig entryConfig = context.annotations().getAnnotation(EntryReadWriteConfig.class);
|
||||
if (entryConfig == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReadWriteExtension readWriteExtension = context.configContainer().extension(ReadWriteExtension.class);
|
||||
if (readWriteExtension == null) {
|
||||
log.error("You must not use {} without the {}", this.getClass().getSimpleName(), ReadWriteExtension.class.getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
readWriteExtension.setEntryReaderWriterDefinition(configEntry, createDefinitionFromEntryConfig(entryConfig, context));
|
||||
}
|
||||
|
||||
private EntryReaderWriterDefinition createDefinitionFromEntryConfig(EntryReadWriteConfig entryConfig, WeavingContext context) {
|
||||
String readerSpecText = entryConfig.reader().isEmpty() ? entryConfig.value() : entryConfig.reader();
|
||||
String writerSpecText = entryConfig.writer().isEmpty() ? entryConfig.value() : entryConfig.writer();
|
||||
|
||||
SerdePojoReaderWriterSpec readerSpec;
|
||||
SerdePojoReaderWriterSpec writerSpec;
|
||||
if (readerSpecText.equals(writerSpecText)) {
|
||||
readerSpec = writerSpec = specFromText(readerSpecText, context);
|
||||
} else {
|
||||
readerSpec = specFromText(readerSpecText, context);
|
||||
writerSpec = specFromText(writerSpecText, context);
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
TweedEntryReader<?, ?> reader = readerSpec == null
|
||||
? TweedEntryReaderWriterImpls.NOOP_READER_WRITER
|
||||
: resolveReaderWriterFromSpec((Class<TweedEntryReader<?, ?>>)(Object) TweedEntryReader.class, readerFactories, readerSpec, context);
|
||||
//noinspection unchecked
|
||||
TweedEntryWriter<?, ?> writer = writerSpec == null
|
||||
? TweedEntryReaderWriterImpls.NOOP_READER_WRITER
|
||||
: resolveReaderWriterFromSpec((Class<TweedEntryWriter<?, ?>>)(Object) TweedEntryWriter.class, writerFactories, writerSpec, context);
|
||||
|
||||
return new EntryReaderWriterDefinition() {
|
||||
@Override
|
||||
public TweedEntryReader<?, ?> reader() {
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TweedEntryWriter<?, ?> writer() {
|
||||
return writer;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SerdePojoReaderWriterSpec specFromText(String specText, WeavingContext context) {
|
||||
if (specText.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return SerdePojoReaderWriterSpec.parse(specText);
|
||||
} catch (SerdePojoReaderWriterSpec.ParseException e) {
|
||||
log.warn(
|
||||
"Failed to parse definition for reader or writer on entry {}, entry will not be included in serde",
|
||||
context.path(),
|
||||
e
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T resolveReaderWriterFromSpec(
|
||||
Class<T> baseClass,
|
||||
Map<String, TweedReaderWriterProvider.ReaderWriterFactory<T>> factories,
|
||||
SerdePojoReaderWriterSpec spec,
|
||||
WeavingContext context
|
||||
) {
|
||||
//noinspection unchecked
|
||||
T[] arguments = spec.arguments()
|
||||
.stream()
|
||||
.map(argSpec -> resolveReaderWriterFromSpec(baseClass, factories, argSpec, context))
|
||||
.toArray(length -> (T[]) Array.newInstance(baseClass, length));
|
||||
|
||||
TweedReaderWriterProvider.ReaderWriterFactory<T> factory = factories.get(spec.identifier());
|
||||
|
||||
T instance;
|
||||
if (factory != null) {
|
||||
instance = factory.create(arguments);
|
||||
} else {
|
||||
instance = loadClassIfExists(baseClass, spec.identifier(), arguments);
|
||||
}
|
||||
|
||||
if (instance == null) {
|
||||
log.warn(
|
||||
"Failed to resolve reader or writer factory \"{}\" for entry {}, entry will not be included in serde",
|
||||
spec.identifier(),
|
||||
context.path()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private <T> T loadClassIfExists(Class<T> baseClass, String className, T[] arguments) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Class<?>[] argClassses = new Class<?>[arguments.length];
|
||||
Arrays.fill(argClassses, baseClass);
|
||||
|
||||
Constructor<?> constructor = clazz.getConstructor(argClassses);
|
||||
|
||||
//noinspection unchecked
|
||||
return (T) constructor.newInstance((Object[]) arguments);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||
log.warn("Failed to instantiate class {}", className, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.serde.impl;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.PrimitiveIterator;
|
||||
|
||||
@Value
|
||||
public class SerdePojoReaderWriterSpec {
|
||||
String identifier;
|
||||
List<SerdePojoReaderWriterSpec> arguments;
|
||||
|
||||
public static SerdePojoReaderWriterSpec parse(String input) throws ParseException {
|
||||
Lexer lexer = new Lexer(input.codePoints().iterator());
|
||||
SerdePojoReaderWriterSpec spec = parseSpec(lexer);
|
||||
lexer.chompWhitespace();
|
||||
int codePoint = lexer.nextCodePoint();
|
||||
if (codePoint != -1) {
|
||||
throw lexer.createException("Found trailing text after spec", codePoint);
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
private static SerdePojoReaderWriterSpec parseSpec(Lexer lexer) throws ParseException {
|
||||
lexer.chompWhitespace();
|
||||
String identifier = lexer.nextIdentifier();
|
||||
lexer.chompWhitespace();
|
||||
int codePoint = lexer.peekCodePoint();
|
||||
if (codePoint == '(') {
|
||||
lexer.nextCodePoint();
|
||||
lexer.chompWhitespace();
|
||||
if (lexer.peekCodePoint() == ')') {
|
||||
lexer.nextCodePoint();
|
||||
return new SerdePojoReaderWriterSpec(identifier, Collections.emptyList());
|
||||
}
|
||||
SerdePojoReaderWriterSpec spec = new SerdePojoReaderWriterSpec(identifier, parseSpecList(lexer));
|
||||
codePoint = lexer.nextCodePoint();
|
||||
if (codePoint != ')') {
|
||||
throw lexer.createException("Argument list must be ended with a closing parenthesis", codePoint);
|
||||
}
|
||||
return spec;
|
||||
} else {
|
||||
return new SerdePojoReaderWriterSpec(identifier, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<SerdePojoReaderWriterSpec> parseSpecList(Lexer lexer) throws ParseException {
|
||||
List<SerdePojoReaderWriterSpec> specs = new ArrayList<>();
|
||||
while (true) {
|
||||
specs.add(parseSpec(lexer));
|
||||
lexer.chompWhitespace();
|
||||
int codePoint = lexer.peekCodePoint();
|
||||
if (codePoint != ',') {
|
||||
break;
|
||||
}
|
||||
lexer.nextCodePoint();
|
||||
}
|
||||
return Collections.unmodifiableList(specs);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class Lexer {
|
||||
private static final int EMPTY = -2;
|
||||
private final PrimitiveIterator.OfInt codePointIterator;
|
||||
private int peek = EMPTY;
|
||||
private int index;
|
||||
|
||||
public String nextIdentifier() throws ParseException {
|
||||
int codePoint = nextCodePoint();
|
||||
if (codePoint == -1) {
|
||||
throw createException("Expected identifier, got end of input", codePoint);
|
||||
} else if (!isIdentifierChar(codePoint)) {
|
||||
throw createException("Expected identifier (alphanumeric character)", codePoint);
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.appendCodePoint(codePoint);
|
||||
boolean dot = false;
|
||||
while ((codePoint = peekCodePoint()) >= 0) {
|
||||
if (isIdentifierChar(codePoint)) {
|
||||
stringBuilder.appendCodePoint(nextCodePoint());
|
||||
dot = false;
|
||||
} else if (codePoint == '.') {
|
||||
if (dot) {
|
||||
throw createException("Unexpected double dot in identifier", codePoint);
|
||||
} else {
|
||||
stringBuilder.appendCodePoint(nextCodePoint());
|
||||
dot = true;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dot) {
|
||||
throw createException("Identifier must not end with dot", codePoint);
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private boolean isIdentifierChar(int codePoint) {
|
||||
return (codePoint >= '0' && codePoint <= '9')
|
||||
|| (codePoint >= 'a' && codePoint <= 'z')
|
||||
|| (codePoint >= 'A' && codePoint <= 'Z');
|
||||
}
|
||||
|
||||
public void chompWhitespace() {
|
||||
while (Character.isWhitespace(peekCodePoint())) {
|
||||
nextCodePoint();
|
||||
}
|
||||
}
|
||||
|
||||
private int peekCodePoint() {
|
||||
if (peek == EMPTY) {
|
||||
peek = nextCodePoint();
|
||||
}
|
||||
return peek;
|
||||
}
|
||||
|
||||
private int nextCodePoint() {
|
||||
if (peek != EMPTY) {
|
||||
int codePoint = peek;
|
||||
peek = EMPTY;
|
||||
return codePoint;
|
||||
}
|
||||
if (codePointIterator.hasNext()) {
|
||||
index++;
|
||||
return codePointIterator.nextInt();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public ParseException createException(String message, int codePoint) {
|
||||
return new ParseException(message, index, codePoint);
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public static class ParseException extends Exception {
|
||||
private final int index;
|
||||
private final int codePoint;
|
||||
|
||||
public ParseException(String message, int index, int codePoint) {
|
||||
super(message);
|
||||
this.index = index;
|
||||
this.codePoint = codePoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String message = super.getMessage();
|
||||
StringBuilder stringBuilder = new StringBuilder(30 + message.length())
|
||||
.append("Parse error at index ")
|
||||
.append(index)
|
||||
.append(" \"");
|
||||
if (codePoint == -1) {
|
||||
stringBuilder.append("EOF");
|
||||
} else {
|
||||
stringBuilder.appendCodePoint(codePoint);
|
||||
}
|
||||
return stringBuilder
|
||||
.append("\": ")
|
||||
.append(message)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.serde;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.data.extension.api.*;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonLexer;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonReader;
|
||||
import de.siphalor.tweed5.data.hjson.HjsonWriter;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataVisitor;
|
||||
import de.siphalor.tweed5.dataapi.api.TweedDataWriteException;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||
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 org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class WeaverPojoSerdeExtensionTest {
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
void testAnnotated() {
|
||||
TweedPojoWeaverBootstrapper<AnnotatedConfig> weaverBootstrapper = TweedPojoWeaverBootstrapper.create(AnnotatedConfig.class);
|
||||
|
||||
ConfigContainer<AnnotatedConfig> configContainer = weaverBootstrapper.weave();
|
||||
configContainer.initialize();
|
||||
|
||||
ReadWriteExtension readWriteExtension = configContainer.extension(ReadWriteExtension.class);
|
||||
assertThat(readWriteExtension).isNotNull();
|
||||
|
||||
AnnotatedConfig config = new AnnotatedConfig(123, "test", new TestClass(987));
|
||||
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
HjsonWriter hjsonWriter = new HjsonWriter(stringWriter, new HjsonWriter.Options());
|
||||
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");
|
||||
|
||||
HjsonReader reader = new HjsonReader(new HjsonLexer(new StringReader(
|
||||
"{\n\tanInt: 987\n\ttext: abdef\n\ttest: { inner: 29 }\n}"
|
||||
)));
|
||||
assertThat(readWriteExtension.read(
|
||||
reader,
|
||||
configContainer.rootEntry(),
|
||||
readWriteExtension.createReadWriteContextExtensionsData()
|
||||
)).isEqualTo(new AnnotatedConfig(987, "abdef", new TestClass(29)));
|
||||
}
|
||||
|
||||
@AutoService(TweedReaderWriterProvider.class)
|
||||
public static class TestWriterProvider implements TweedReaderWriterProvider {
|
||||
@Override
|
||||
public void provideReaderWriters(ProviderContext context) {
|
||||
context.registerWriterFactory("tweed5.test.dummy", delegates -> new TweedEntryWriter<Object, ConfigEntry<Object>>() {
|
||||
@Override
|
||||
public void write(
|
||||
TweedDataVisitor writer,
|
||||
Object value,
|
||||
ConfigEntry<Object> entry,
|
||||
TweedWriteContext context
|
||||
) throws TweedDataWriteException {
|
||||
writer.visitString("my cool custom writer");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@PojoWeaving(extensions = ReadWriteExtension.class, postProcessors = ReadWritePojoPostProcessor.class)
|
||||
@CompoundWeaving
|
||||
@EntryReadWriteConfig("tweed5.compound")
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class AnnotatedConfig {
|
||||
@EntryReadWriteConfig("tweed5.integer")
|
||||
public int anInt;
|
||||
|
||||
@EntryReadWriteConfig("tweed5.nullable(tweed5.string)")
|
||||
public String text;
|
||||
|
||||
@EntryReadWriteConfig(writer = "tweed5.test.dummy", reader = "tweed5.compound")
|
||||
@CompoundWeaving
|
||||
public TestClass test;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class TestClass {
|
||||
@EntryReadWriteConfig("tweed5.integer")
|
||||
public int inner;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.siphalor.tweed5.weaver.pojoext.serde.impl;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
|
||||
class SerdePojoReaderWriterSpecTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(ignoreLeadingAndTrailingWhitespace = false, value = {
|
||||
" abc ,abc",
|
||||
" abc() ,abc",
|
||||
" abc.123 ,abc.123",
|
||||
"abc.123 ( ) ,abc.123",
|
||||
"123.abc,123.abc",
|
||||
})
|
||||
@SneakyThrows
|
||||
void parseSimpleIdentifier(String input, String identifier) {
|
||||
SerdePojoReaderWriterSpec spec = SerdePojoReaderWriterSpec.parse(input);
|
||||
assertThat(spec.identifier()).isEqualTo(identifier);
|
||||
assertThat(spec.arguments()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SneakyThrows
|
||||
void parseNested() {
|
||||
SerdePojoReaderWriterSpec spec = SerdePojoReaderWriterSpec.parse("abc.def ( 12 ( def, ghi ( ) ), jkl ) ");
|
||||
assertThat(spec).isEqualTo(new SerdePojoReaderWriterSpec("abc.def", Arrays.asList(
|
||||
new SerdePojoReaderWriterSpec("12", Arrays.asList(
|
||||
new SerdePojoReaderWriterSpec("def", Collections.emptyList()),
|
||||
new SerdePojoReaderWriterSpec("ghi", Collections.emptyList())
|
||||
)),
|
||||
new SerdePojoReaderWriterSpec("jkl", Collections.emptyList())
|
||||
)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(ignoreLeadingAndTrailingWhitespace = false, nullValues = "EOF", delimiter = ';', value = {
|
||||
" abc def ;6;d",
|
||||
"abcäöüdef;4;ä",
|
||||
"abc.def(;8;EOF",
|
||||
"'';0;EOF",
|
||||
",;1;,",
|
||||
"abc(,);5;,",
|
||||
"abc..def;5;.",
|
||||
})
|
||||
@SneakyThrows
|
||||
void parseError(String input, int index, String codePoint) {
|
||||
assertThatThrownBy(() -> SerdePojoReaderWriterSpec.parse(input))
|
||||
.asInstanceOf(type(SerdePojoReaderWriterSpec.ParseException.class))
|
||||
.isInstanceOf(SerdePojoReaderWriterSpec.ParseException.class)
|
||||
.satisfies(
|
||||
exception -> assertThat(exception.index()).as("index of: " + exception.getMessage()).isEqualTo(index),
|
||||
exception -> assertThat(exception.codePoint()).as("code point of: " + exception.getMessage())
|
||||
.isEqualTo(codePoint == null ? -1 : codePoint.codePointAt(0))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
dependencies {
|
||||
api(project(":tweed5-core"))
|
||||
api(project(":tweed5-naming-format"))
|
||||
compileOnly(project(":tweed5-default-extensions"))
|
||||
compileOnly(project(":tweed5-serde-extension"))
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import java.lang.annotation.Target;
|
||||
* Marks this class as a class that should be woven as a {@link de.siphalor.tweed5.core.api.entry.CompoundConfigEntry}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Target({ElementType.TYPE, ElementType.FIELD})
|
||||
public @interface CompoundWeaving {
|
||||
/**
|
||||
* The naming format to use for this POJO.
|
||||
|
||||
@@ -6,6 +6,7 @@ import de.siphalor.tweed5.core.impl.DefaultConfigContainer;
|
||||
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.TweedPojoWeaver;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -22,5 +23,7 @@ public @interface PojoWeaving {
|
||||
TrivialPojoWeaver.class,
|
||||
};
|
||||
|
||||
Class<? extends TweedPojoWeavingPostProcessor>[] postProcessors() default {};
|
||||
|
||||
Class<? extends TweedExtension>[] extensions() default {};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents multi-level annotations across multiple Java elements.
|
||||
* E.g. annotations on a field overriding annotations declared on the field type.
|
||||
*/
|
||||
public class Annotations {
|
||||
private static final List<ElementType> ELEMENT_TYPE_ORDER = Arrays.asList(
|
||||
ElementType.TYPE_USE,
|
||||
ElementType.FIELD,
|
||||
ElementType.CONSTRUCTOR,
|
||||
ElementType.METHOD,
|
||||
ElementType.LOCAL_VARIABLE,
|
||||
ElementType.TYPE_PARAMETER,
|
||||
ElementType.TYPE,
|
||||
ElementType.ANNOTATION_TYPE,
|
||||
ElementType.PACKAGE
|
||||
);
|
||||
private final Map<ElementType, AnnotatedElement> elements = new EnumMap<>(ElementType.class);
|
||||
|
||||
public void addAnnotationsFrom(ElementType elementType, AnnotatedElement element) {
|
||||
elements.put(elementType, element);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
|
||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
||||
if (annotatedElement != null) {
|
||||
T annotation = annotatedElement.getAnnotation(annotationClass);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T extends Annotation> T getAnnotation(ElementType elementType, Class<T> annotationType) {
|
||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
||||
if (annotatedElement == null) {
|
||||
return null;
|
||||
}
|
||||
return annotatedElement.getAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T extends Annotation> T[] getAnnotationHierarchy(Class<T> annotationClass) {
|
||||
List<T> hierarchy = new ArrayList<>(elements.size());
|
||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
||||
AnnotatedElement element = elements.get(elementType);
|
||||
if (element != null) {
|
||||
T annotation = element.getAnnotation(annotationClass);
|
||||
if (annotation != null) {
|
||||
hierarchy.add(annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
//noinspection unchecked
|
||||
return hierarchy.toArray((T[]) Array.newInstance(annotationClass, hierarchy.size()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T extends Annotation> T[] getAnnotations(Class<T> annotationClass) {
|
||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
||||
if (annotatedElement != null) {
|
||||
T[] annotations = annotatedElement.getAnnotationsByType(annotationClass);
|
||||
if (annotations.length != 0) {
|
||||
return annotations;
|
||||
}
|
||||
}
|
||||
}
|
||||
//noinspection unchecked
|
||||
return (T[]) Array.newInstance(annotationClass, 0);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T extends Annotation> T[] getAnnotations(ElementType elementType, Class<T> annotationType) {
|
||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
||||
if (annotatedElement == null) {
|
||||
//noinspection unchecked
|
||||
return (T[]) Array.newInstance(annotationType, 0);
|
||||
}
|
||||
return annotatedElement.getAnnotationsByType(annotationType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public <T extends Annotation> T[][] getAnnotationsHierachy(Class<T> annotationClass) {
|
||||
List<T[]> hierarchy = new ArrayList<>(ELEMENT_TYPE_ORDER.size());
|
||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
||||
if (annotatedElement != null) {
|
||||
T[] annotations = annotatedElement.getAnnotationsByType(annotationClass);
|
||||
if (annotations.length != 0) {
|
||||
hierarchy.add(annotations);
|
||||
}
|
||||
}
|
||||
}
|
||||
//noinspection unchecked
|
||||
return hierarchy.toArray((T[][]) Array.newInstance(annotationClass, 0, 0));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Annotation[] getAllAnnotations() {
|
||||
Map<Class<? extends Annotation>, Annotation[]> annotations = new HashMap<>();
|
||||
for (ElementType elementType : ELEMENT_TYPE_ORDER) {
|
||||
AnnotatedElement annotatedElement = elements.get(elementType);
|
||||
if (annotatedElement != null) {
|
||||
for (Annotation annotation : annotatedElement.getAnnotations()) {
|
||||
annotations.putIfAbsent(annotation.annotationType(), new Annotation[]{annotation});
|
||||
|
||||
Repeatable repeatable = annotation.annotationType().getAnnotation(Repeatable.class);
|
||||
if (repeatable != null) {
|
||||
annotations.put(repeatable.value(), annotatedElement.getAnnotationsByType(repeatable.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (annotations.isEmpty()) {
|
||||
return new Annotation[0];
|
||||
} else if (annotations.size() == 1) {
|
||||
return annotations.values().iterator().next();
|
||||
} else {
|
||||
return annotations.values().stream().flatMap(Arrays::stream).toArray(Annotation[]::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||
|
||||
import de.siphalor.tweed5.core.api.collection.TypedMultimap;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.namingformat.api.NamingFormat;
|
||||
@@ -8,19 +7,18 @@ import de.siphalor.tweed5.namingformat.api.NamingFormatCollector;
|
||||
import de.siphalor.tweed5.namingformat.api.NamingFormats;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCompoundConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.entry.StaticPojoCompoundConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoClassIntrospector;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.entry.StaticPojoCompoundConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfig;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.compound.CompoundWeavingConfigImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A weaver that weaves classes with the {@link CompoundWeaving} annotation as compound entries.
|
||||
@@ -43,11 +41,11 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
|
||||
@Override
|
||||
public @Nullable <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context) {
|
||||
if (!valueClass.isAnnotationPresent(CompoundWeaving.class)) {
|
||||
if (context.annotations().getAnnotation(CompoundWeaving.class) == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CompoundWeavingConfig weavingConfig = getOrCreateWeavingConfig(valueClass, context);
|
||||
CompoundWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
||||
|
||||
@@ -68,7 +66,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
}
|
||||
}
|
||||
|
||||
private CompoundWeavingConfig getOrCreateWeavingConfig(Class<?> valueClass, WeavingContext context) {
|
||||
private CompoundWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||
CompoundWeavingConfig parent;
|
||||
if (context.extensionsData().isPatchworkPartSet(CompoundWeavingConfig.class)) {
|
||||
parent = (CompoundWeavingConfig) context.extensionsData();
|
||||
@@ -76,7 +74,7 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
parent = DEFAULT_WEAVING_CONFIG;
|
||||
}
|
||||
|
||||
CompoundWeavingConfig local = getWeavingConfigFromClassAnnotation(valueClass);
|
||||
CompoundWeavingConfig local = createWeavingConfigFromAnnotations(context.annotations());
|
||||
if (local == null) {
|
||||
return parent;
|
||||
}
|
||||
@@ -91,23 +89,21 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
WeavingContext parentContext
|
||||
) {
|
||||
return parentContext.subContextBuilder(name)
|
||||
.additionalData(createAdditionalDataFromAnnotations(property.field().getAnnotations()))
|
||||
.annotations(collectAnnotationsForField(property.field()))
|
||||
.extensionsData(newExtensionsData)
|
||||
.build();
|
||||
}
|
||||
|
||||
private TypedMultimap<Object> createAdditionalDataFromAnnotations(Annotation[] annotations) {
|
||||
if (annotations.length == 0) {
|
||||
return TypedMultimap.empty();
|
||||
}
|
||||
TypedMultimap<Object> additionalData = new TypedMultimap<>(new HashMap<>(), ArrayList::new);
|
||||
Collections.addAll(additionalData, annotations);
|
||||
return TypedMultimap.unmodifiable(additionalData);
|
||||
private Annotations collectAnnotationsForField(Field field) {
|
||||
Annotations annotations = new Annotations();
|
||||
annotations.addAnnotationsFrom(ElementType.TYPE, field.getType());
|
||||
annotations.addAnnotationsFrom(ElementType.FIELD, field);
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CompoundWeavingConfig getWeavingConfigFromClassAnnotation(Class<?> clazz) {
|
||||
CompoundWeaving annotation = clazz.getAnnotation(CompoundWeaving.class);
|
||||
private CompoundWeavingConfig createWeavingConfigFromAnnotations(Annotations annotations) {
|
||||
CompoundWeaving annotation = annotations.getAnnotation(CompoundWeaving.class);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||
|
||||
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.core.api.collection.TypedMultimap;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -14,26 +14,30 @@ import java.util.Arrays;
|
||||
public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
||||
@Nullable
|
||||
WeavingContext parent;
|
||||
ExtensionsData extensionsData;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@NotNull
|
||||
TweedPojoWeavingFunction.NonNull weavingFunction;
|
||||
@NotNull
|
||||
ConfigContainer<?> configContainer;
|
||||
@NotNull
|
||||
String[] path;
|
||||
TypedMultimap<Object> additionalData;
|
||||
@NotNull
|
||||
ExtensionsData extensionsData;
|
||||
@NotNull
|
||||
Annotations annotations;
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder(null, new String[0]);
|
||||
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer) {
|
||||
return new Builder(null, weavingFunction, configContainer, new String[0]);
|
||||
}
|
||||
|
||||
public static Builder builder(String baseName) {
|
||||
return new Builder(null, new String[]{ baseName });
|
||||
public static Builder builder(TweedPojoWeavingFunction.NonNull weavingFunction, ConfigContainer<?> configContainer, String baseName) {
|
||||
return new Builder(null, weavingFunction, configContainer, new String[]{ baseName });
|
||||
}
|
||||
|
||||
public Builder subContextBuilder(String subPathName) {
|
||||
String[] newPath = Arrays.copyOf(path, path.length + 1);
|
||||
newPath[path.length] = subPathName;
|
||||
return new Builder(this, newPath)
|
||||
.extensionsData(extensionsData)
|
||||
.weavingFunction(weavingFunction);
|
||||
return new Builder(this, weavingFunction, configContainer, newPath).extensionsData(extensionsData);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,18 +53,20 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
||||
public static class Builder {
|
||||
@Nullable
|
||||
private final WeavingContext parent;
|
||||
private final TweedPojoWeavingFunction.NonNull weavingFunction;
|
||||
private final ConfigContainer<?> configContainer;
|
||||
private final String[] path;
|
||||
private ExtensionsData extensionsData;
|
||||
private TweedPojoWeavingFunction.NonNull weavingFunction;
|
||||
private TypedMultimap<Object> additionalData;
|
||||
private Annotations annotations;
|
||||
|
||||
public WeavingContext build() {
|
||||
return new WeavingContext(
|
||||
parent,
|
||||
extensionsData,
|
||||
weavingFunction,
|
||||
configContainer,
|
||||
path,
|
||||
additionalData
|
||||
extensionsData,
|
||||
annotations
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
|
||||
|
||||
public interface TweedPojoWeavingPostProcessor {
|
||||
void apply(ConfigEntry<?> configEntry, WeavingContext context);
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> implements WeavableCompoundConfigEntry<T> {
|
||||
private final MethodHandle noArgsConstructor;
|
||||
private final Map<String, SubEntry> subEntries = new HashMap<>();
|
||||
private final Map<String, ConfigEntry<?>> subConfigEntries = new HashMap<>();
|
||||
private final Map<String, SubEntry> subEntries = new LinkedHashMap<>();
|
||||
private final Map<String, ConfigEntry<?>> subConfigEntries = new LinkedHashMap<>();
|
||||
|
||||
public StaticPojoCompoundConfigEntry(@NotNull Class<T> valueClass, @NotNull MethodHandle noArgsConstructor) {
|
||||
super(valueClass);
|
||||
@@ -67,7 +67,7 @@ public class StaticPojoCompoundConfigEntry<T> extends BaseConfigEntry<T> impleme
|
||||
public T instantiateCompoundValue() {
|
||||
try {
|
||||
//noinspection unchecked
|
||||
return (T) noArgsConstructor.invokeExact();
|
||||
return (T) noArgsConstructor.invoke();
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to instantiate compound class", e);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@@ -46,7 +46,7 @@ public class PojoClassIntrospector {
|
||||
|
||||
public Map<String, Property> properties() {
|
||||
if (this.properties == null) {
|
||||
this.properties = new HashMap<>();
|
||||
this.properties = new LinkedHashMap<>();
|
||||
Class<?> currentClass = clazz;
|
||||
while (currentClass != null) {
|
||||
appendClassProperties(currentClass);
|
||||
|
||||
@@ -8,27 +8,36 @@ import de.siphalor.tweed5.patchwork.api.PatchworkClassCreator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClass;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassGenerator;
|
||||
import de.siphalor.tweed5.patchwork.impl.PatchworkClassPart;
|
||||
import de.siphalor.tweed5.utils.api.collection.ClassToInstancesMultimap;
|
||||
import de.siphalor.tweed5.utils.api.collection.InheritanceMap;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.Annotations;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TweedPojoWeaver;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.WeavingContext;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.postprocess.TweedPojoWeavingPostProcessor;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A class that sets up and handles all the bits and bobs for weaving a {@link ConfigContainer} out of a POJO.
|
||||
* The POJO must be annotated with {@link PojoWeaving}.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class TweedPojoWeaverBootstrapper<T> {
|
||||
private final Class<T> pojoClass;
|
||||
private final ConfigContainer<T> configContainer;
|
||||
private final Collection<TweedPojoWeaver> weavers;
|
||||
private final Collection<TweedPojoWeavingPostProcessor> postProcessors;
|
||||
private PatchworkClass<WeavingContext.ExtensionsData> contextExtensionsDataClass;
|
||||
|
||||
public static <T> TweedPojoWeaverBootstrapper<T> create(Class<T> pojoClass) {
|
||||
@@ -37,11 +46,14 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
//noinspection unchecked
|
||||
ConfigContainer<T> configContainer = (ConfigContainer<T>) createConfigContainer((Class<? extends ConfigContainer<?>>) rootWeavingConfig.container());
|
||||
|
||||
Collection<TweedPojoWeaver> weavers = loadWeavers(Arrays.asList(rootWeavingConfig.weavers()));
|
||||
Collection<TweedPojoWeavingPostProcessor> postProcessors = loadPostProcessors(Arrays.asList(rootWeavingConfig.postProcessors()));
|
||||
|
||||
Collection<TweedExtension> extensions = loadExtensions(Arrays.asList(rootWeavingConfig.extensions()));
|
||||
configContainer.registerExtensions(extensions.toArray(new TweedExtension[0]));
|
||||
configContainer.finishExtensionSetup();
|
||||
|
||||
return new TweedPojoWeaverBootstrapper<>(pojoClass, configContainer, loadWeavers(Arrays.asList(rootWeavingConfig.weavers())));
|
||||
return new TweedPojoWeaverBootstrapper<>(pojoClass, configContainer, weavers, postProcessors);
|
||||
}
|
||||
|
||||
private static Collection<TweedExtension> loadExtensions(Collection<Class<? extends TweedExtension>> extensionClasses) {
|
||||
@@ -53,11 +65,15 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
}
|
||||
|
||||
private static Collection<TweedPojoWeaver> loadWeavers(Collection<Class<? extends TweedPojoWeaver>> weaverClasses) {
|
||||
List<TweedPojoWeaver> weavers = new ArrayList<>();
|
||||
for (Class<? extends TweedPojoWeaver> weaverClass : weaverClasses) {
|
||||
weavers.add(checkImplementsAndInstantiate(TweedPojoWeaver.class, weaverClass));
|
||||
return weaverClasses.stream()
|
||||
.map(weaverClass -> checkImplementsAndInstantiate(TweedPojoWeaver.class, weaverClass))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return weavers;
|
||||
|
||||
private static Collection<TweedPojoWeavingPostProcessor> loadPostProcessors(Collection<Class<? extends TweedPojoWeavingPostProcessor>> postProcessorClasses) {
|
||||
return postProcessorClasses.stream()
|
||||
.map(postProcessorClass -> checkImplementsAndInstantiate(TweedPojoWeavingPostProcessor.class, postProcessorClass))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static ConfigContainer<?> createConfigContainer(Class<? extends ConfigContainer<?>> containerClass) {
|
||||
@@ -197,9 +213,13 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
private WeavingContext createWeavingContext() {
|
||||
try {
|
||||
WeavingContext.ExtensionsData extensionsData = (WeavingContext.ExtensionsData) contextExtensionsDataClass.constructor().invoke();
|
||||
return WeavingContext.builder()
|
||||
|
||||
Annotations annotations = new Annotations();
|
||||
annotations.addAnnotationsFrom(ElementType.TYPE, pojoClass);
|
||||
|
||||
return WeavingContext.builder(this::weaveEntry, configContainer)
|
||||
.extensionsData(extensionsData)
|
||||
.weavingFunction(this::weaveEntry)
|
||||
.annotations(annotations)
|
||||
.build();
|
||||
} catch (Throwable e) {
|
||||
throw new PojoWeavingException("Failed to create weaving context's extension data");
|
||||
@@ -210,7 +230,10 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
for (TweedPojoWeaver weaver : weavers) {
|
||||
ConfigEntry<U> configEntry = weaver.weaveEntry(dataClass, context);
|
||||
if (configEntry != null) {
|
||||
if (!configEntry.sealed()) {
|
||||
configEntry.seal(configContainer);
|
||||
}
|
||||
applyPostProcessors(configEntry, context);
|
||||
return configEntry;
|
||||
}
|
||||
}
|
||||
@@ -218,6 +241,16 @@ public class TweedPojoWeaverBootstrapper<T> {
|
||||
throw new PojoWeavingException("Failed to weave " + dataClass.getName() + ": No matching weavers found");
|
||||
}
|
||||
|
||||
private void applyPostProcessors(ConfigEntry<?> configEntry, WeavingContext context) {
|
||||
for (TweedPojoWeavingPostProcessor postProcessor : postProcessors) {
|
||||
try {
|
||||
postProcessor.apply(configEntry, context);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to apply Tweed POJO weaver post processor", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
private static class RegisteredExtensionDataImpl<E> implements RegisteredExtensionData<WeavingContext.ExtensionsData, E> {
|
||||
private MethodHandle setter;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||
|
||||
import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.SimpleConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
@@ -12,6 +13,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -30,9 +33,10 @@ class CompoundPojoWeaverTest {
|
||||
}
|
||||
});
|
||||
|
||||
WeavingContext weavingContext = WeavingContext.builder()
|
||||
.extensionsData(new ExtensionsDataMock(null))
|
||||
.weavingFunction(new TweedPojoWeavingFunction.NonNull() {
|
||||
Annotations annotations = new Annotations();
|
||||
annotations.addAnnotationsFrom(ElementType.TYPE, Compound.class);
|
||||
|
||||
WeavingContext weavingContext = WeavingContext.builder(new TweedPojoWeavingFunction.NonNull() {
|
||||
@Override
|
||||
public @NotNull <T> ConfigEntry<T> weaveEntry(Class<T> valueClass, WeavingContext context) {
|
||||
ConfigEntry<T> entry = compoundWeaver.weaveEntry(valueClass, context);
|
||||
@@ -45,7 +49,9 @@ class CompoundPojoWeaverTest {
|
||||
return configEntry;
|
||||
}
|
||||
}
|
||||
})
|
||||
}, mock(ConfigContainer.class))
|
||||
.extensionsData(new ExtensionsDataMock(null))
|
||||
.annotations(annotations)
|
||||
.build();
|
||||
|
||||
ConfigEntry<Compound> resultEntry = compoundWeaver.weaveEntry(Compound.class, weavingContext);
|
||||
|
||||
Reference in New Issue
Block a user