[weaver-pojo] Coherent collection weaving
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.annotation;
|
||||
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCoherentCollectionConfigEntry;
|
||||
|
||||
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.TYPE_USE})
|
||||
public @interface CoherentCollectionWeaving {
|
||||
Class<? extends WeavableCoherentCollectionConfigEntry> entryClass() default WeavableCoherentCollectionConfigEntry.class;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.entry;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.CoherentCollectionConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Collection;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <br />
|
||||
* A constructor taking the value {@link Class}
|
||||
* and a {@link java.util.function.IntFunction} that allows to instantiate the value class with a single capacity argument.
|
||||
*/
|
||||
public interface WeavableCoherentCollectionConfigEntry<E, T extends Collection<E>>
|
||||
extends CoherentCollectionConfigEntry<E, T> {
|
||||
static <E, T extends Collection<E>, C extends WeavableCoherentCollectionConfigEntry<E, T>> C instantiate(
|
||||
Class<C> weavableClass, Class<T> valueClass, IntFunction<T> constructor
|
||||
) throws PojoWeavingException {
|
||||
try {
|
||||
Constructor<C> weavableEntryConstructor = weavableClass.getConstructor(Class.class, IntFunction.class);
|
||||
return weavableEntryConstructor.newInstance(valueClass, constructor);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new PojoWeavingException(
|
||||
"Class " + weavableClass.getName() + " must have constructor with value class and value constructor",
|
||||
e
|
||||
);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new PojoWeavingException(
|
||||
"Failed to instantiate class for weavable collection entry " + weavableClass.getName(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void elementEntry(ConfigEntry<E> elementEntry);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.api.weaving;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.extension.RegisteredExtensionData;
|
||||
import de.siphalor.tweed5.typeutils.api.type.ActualType;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.annotation.CoherentCollectionWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCoherentCollectionConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.PojoWeavingException;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.coherentcollection.CoherentCollectionWeavingConfig;
|
||||
import de.siphalor.tweed5.weaver.pojo.impl.weaving.coherentcollection.CoherentCollectionWeavingConfigImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.util.*;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class CoherentCollectionPojoWeaver implements TweedPojoWeaver {
|
||||
private static final CoherentCollectionWeavingConfig DEFAULT_WEAVING_CONFIG = CoherentCollectionWeavingConfigImpl.builder()
|
||||
.coherentCollectionEntryClass(de.siphalor.tweed5.weaver.pojo.impl.entry.CoherentCollectionConfigEntryImpl.class)
|
||||
.build();
|
||||
|
||||
private RegisteredExtensionData<WeavingContext.ExtensionsData, CoherentCollectionWeavingConfig> weavingConfigAccess;
|
||||
|
||||
@Override
|
||||
public void setup(SetupContext context) {
|
||||
this.weavingConfigAccess = context.registerWeavingContextExtensionData(CoherentCollectionWeavingConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||
List<ActualType<?>> collectionTypeParams = valueType.getTypesOfSuperArguments(Collection.class);
|
||||
if (collectionTypeParams == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CoherentCollectionWeavingConfig weavingConfig = getOrCreateWeavingConfig(context);
|
||||
WeavingContext.ExtensionsData newExtensionsData = context.extensionsData().copy();
|
||||
weavingConfigAccess.set(newExtensionsData, weavingConfig);
|
||||
|
||||
IntFunction<Collection<Object>> constructor = getCollectionConstructor(valueType);
|
||||
|
||||
//noinspection unchecked,rawtypes
|
||||
WeavableCoherentCollectionConfigEntry configEntry = WeavableCoherentCollectionConfigEntry.instantiate(
|
||||
(Class) weavingConfig.coherentCollectionEntryClass(),
|
||||
(Class) valueType.declaredType(),
|
||||
constructor
|
||||
);
|
||||
|
||||
configEntry.elementEntry(context.weaveEntry(
|
||||
collectionTypeParams.get(0),
|
||||
context.subContextBuilder("element")
|
||||
.annotations(collectionTypeParams.get(0))
|
||||
.extensionsData(newExtensionsData)
|
||||
.build()
|
||||
));
|
||||
configEntry.seal(context.configContainer());
|
||||
|
||||
return configEntry;
|
||||
} catch (Exception e) {
|
||||
throw new PojoWeavingException("Exception occurred trying to weave collectoin for class " + valueType, e);
|
||||
}
|
||||
}
|
||||
|
||||
private CoherentCollectionWeavingConfig getOrCreateWeavingConfig(WeavingContext context) {
|
||||
CoherentCollectionWeavingConfig parent;
|
||||
if (context.extensionsData().isPatchworkPartSet(CoherentCollectionWeavingConfig.class)) {
|
||||
parent = (CoherentCollectionWeavingConfig) context.extensionsData();
|
||||
} else {
|
||||
parent = DEFAULT_WEAVING_CONFIG;
|
||||
}
|
||||
|
||||
CoherentCollectionWeavingConfig local = createWeavingConfigFromAnnotations(context.annotations());
|
||||
if (local == null) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
return CoherentCollectionWeavingConfigImpl.withOverrides(parent, local);
|
||||
}
|
||||
|
||||
private CoherentCollectionWeavingConfig createWeavingConfigFromAnnotations(@NotNull AnnotatedElement annotations) {
|
||||
CoherentCollectionWeaving annotation = annotations.getAnnotation(CoherentCollectionWeaving.class);
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CoherentCollectionWeavingConfigImpl.CoherentCollectionWeavingConfigImplBuilder builder = CoherentCollectionWeavingConfigImpl.builder();
|
||||
if (annotation.entryClass() != null) {
|
||||
builder.coherentCollectionEntryClass(annotation.entryClass());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public IntFunction<Collection<Object>> getCollectionConstructor(ActualType<?> type) {
|
||||
if (type.declaredType() == List.class) {
|
||||
return ArrayList::new;
|
||||
} else if (type.declaredType() == Set.class) {
|
||||
return capacity -> new HashSet<>((int) Math.ceil(capacity * 1.4), 0.75F);
|
||||
}
|
||||
try {
|
||||
return findCompatibleConstructor(type);
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new PojoWeavingException("could not find no args constructor for " + type, e);
|
||||
}
|
||||
}
|
||||
|
||||
public IntFunction<Collection<Object>> findCompatibleConstructor(ActualType<?> type) throws
|
||||
NoSuchMethodException,
|
||||
IllegalAccessException {
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
MethodHandle constructor = lookup.findConstructor(type.declaredType(), MethodType.methodType(Void.class));
|
||||
return capacity -> {
|
||||
try {
|
||||
//noinspection unchecked
|
||||
return (Collection<Object>) constructor.invoke();
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,8 @@ public class CompoundPojoWeaver implements TweedPojoWeaver {
|
||||
}
|
||||
});
|
||||
|
||||
compoundEntry.seal(context.configContainer());
|
||||
|
||||
return compoundEntry;
|
||||
} catch (Exception e) {
|
||||
throw new PojoWeavingException("Exception occurred trying to weave compound for class " + valueType, e);
|
||||
|
||||
@@ -13,6 +13,8 @@ public class TrivialPojoWeaver implements TweedPojoWeaver {
|
||||
|
||||
@Override
|
||||
public @Nullable <T> ConfigEntry<T> weaveEntry(ActualType<T> valueType, WeavingContext context) {
|
||||
return new SimpleConfigEntryImpl<>(valueType.declaredType());
|
||||
SimpleConfigEntryImpl<T> entry = new SimpleConfigEntryImpl<>(valueType.declaredType());
|
||||
entry.seal(context.configContainer());
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public class WeavingContext implements TweedPojoWeavingFunction.NonNull {
|
||||
return new Builder(null, weavingFunction, configContainer, new String[]{ baseName });
|
||||
}
|
||||
|
||||
public Builder subContextBuilder(String subPathName) {
|
||||
public Builder subContextBuilder(@NotNull String subPathName) {
|
||||
String[] newPath = Arrays.copyOf(path, path.length + 1);
|
||||
newPath[path.length] = subPathName;
|
||||
return new Builder(this, weavingFunction, configContainer, newPath).extensionsData(extensionsData);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.impl.entry;
|
||||
|
||||
import de.siphalor.tweed5.core.api.entry.BaseConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntry;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryValueVisitor;
|
||||
import de.siphalor.tweed5.core.api.entry.ConfigEntryVisitor;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCoherentCollectionConfigEntry;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
@Getter
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class CoherentCollectionConfigEntryImpl<E, T extends Collection<E>> extends BaseConfigEntry<T> implements WeavableCoherentCollectionConfigEntry<E, T> {
|
||||
private final IntFunction<T> constructor;
|
||||
private ConfigEntry<E> elementEntry;
|
||||
|
||||
public CoherentCollectionConfigEntryImpl(@NotNull Class<T> valueClass, IntFunction<T> constructor) {
|
||||
super(valueClass);
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementEntry(ConfigEntry<E> elementEntry) {
|
||||
this.elementEntry = elementEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T instantiateCollection(int size) {
|
||||
try {
|
||||
return constructor.apply(size);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException("Failed to instantiate collection class", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInOrder(ConfigEntryVisitor visitor) {
|
||||
if (visitor.enterCollectionEntry(this)) {
|
||||
elementEntry.visitInOrder(visitor);
|
||||
visitor.leaveCollectionEntry(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInOrder(ConfigEntryValueVisitor visitor, T value) {
|
||||
if (visitor.enterCollectionEntry(this, value)) {
|
||||
for (E element : value) {
|
||||
elementEntry.visitInOrder(visitor, element);
|
||||
}
|
||||
visitor.leaveCollectionEntry(this, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull T deepCopy(@NotNull T value) {
|
||||
T copy = instantiateCollection(value.size());
|
||||
for (E element : value) {
|
||||
copy.add(elementEntry.deepCopy(element));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.coherentcollection;
|
||||
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCoherentCollectionConfigEntry;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface CoherentCollectionWeavingConfig {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Nullable
|
||||
Class<? extends WeavableCoherentCollectionConfigEntry> coherentCollectionEntryClass();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.impl.weaving.coherentcollection;
|
||||
|
||||
import de.siphalor.tweed5.weaver.pojo.api.entry.WeavableCoherentCollectionConfigEntry;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Builder
|
||||
@Value
|
||||
public class CoherentCollectionWeavingConfigImpl implements CoherentCollectionWeavingConfig {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Nullable
|
||||
Class<? extends WeavableCoherentCollectionConfigEntry> coherentCollectionEntryClass;
|
||||
|
||||
public static CoherentCollectionWeavingConfigImpl withOverrides(CoherentCollectionWeavingConfig self, CoherentCollectionWeavingConfig overrides) {
|
||||
return CoherentCollectionWeavingConfigImpl.builder()
|
||||
.coherentCollectionEntryClass(overrides.coherentCollectionEntryClass() != null ? overrides.coherentCollectionEntryClass() : self.coherentCollectionEntryClass())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
@Builder
|
||||
@Value
|
||||
public class CompoundWeavingConfigImpl implements CompoundWeavingConfig {
|
||||
private static final CompoundWeavingConfigImpl EMPTY = CompoundWeavingConfigImpl.builder().build();
|
||||
|
||||
NamingFormat compoundSourceNamingFormat;
|
||||
NamingFormat compoundTargetNamingFormat;
|
||||
|
||||
@@ -5,30 +5,35 @@ import de.siphalor.tweed5.core.api.container.ConfigContainer;
|
||||
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.PojoWeaving;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.CoherentCollectionPojoWeaver;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.CompoundPojoWeaver;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.TrivialPojoWeaver;
|
||||
import lombok.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isCompoundEntryForClassWith;
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.isSimpleEntryForClass;
|
||||
import java.util.List;
|
||||
|
||||
import static de.siphalor.tweed5.weaver.pojo.test.ConfigEntryAssertions.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class TweedPojoWeaverBootstrapperTest {
|
||||
@Test
|
||||
void defaultWeaving() {
|
||||
TweedPojoWeaverBootstrapper<DefaultWeaving> bootstrapper = TweedPojoWeaverBootstrapper.create(DefaultWeaving.class);
|
||||
ConfigContainer<DefaultWeaving> configContainer = bootstrapper.weave();
|
||||
TweedPojoWeaverBootstrapper<MainCompound> bootstrapper = TweedPojoWeaverBootstrapper.create(MainCompound.class);
|
||||
ConfigContainer<MainCompound> configContainer = bootstrapper.weave();
|
||||
|
||||
assertThat(configContainer.rootEntry()).satisfies(isCompoundEntryForClassWith(DefaultWeaving.class, rootCompound ->
|
||||
assertThat(rootCompound.subEntries())
|
||||
.hasEntrySatisfying("primitiveInteger", isSimpleEntryForClass(int.class))
|
||||
.hasEntrySatisfying("boxedDouble", isSimpleEntryForClass(Double.class))
|
||||
.hasEntrySatisfying("value", isSimpleEntryForClass(InnerValue.class))
|
||||
.hasEntrySatisfying("compound", isCompoundEntryForClassWith(InnerCompound.class, innerCompound ->
|
||||
assertThat(innerCompound.subEntries())
|
||||
.hasEntrySatisfying("string", isSimpleEntryForClass(String.class))
|
||||
.hasSize(1)))
|
||||
.hasSize(4)
|
||||
assertThat(configContainer.rootEntry()).satisfies(isCompoundEntryForClassWith(MainCompound.class, rootCompound ->
|
||||
assertThat(rootCompound.subEntries())
|
||||
.hasEntrySatisfying("primitiveInteger", isSimpleEntryForClass(int.class))
|
||||
.hasEntrySatisfying("boxedDouble", isSimpleEntryForClass(Double.class))
|
||||
.hasEntrySatisfying("value", isSimpleEntryForClass(InnerValue.class))
|
||||
.hasEntrySatisfying("list", isSimpleEntryForClass(List.class))
|
||||
.hasEntrySatisfying("compound", isCompoundEntryForClassWith(InnerCompound.class, innerCompound ->
|
||||
assertThat(innerCompound.subEntries())
|
||||
.hasEntrySatisfying("string", isSimpleEntryForClass(String.class))
|
||||
.hasSize(1)))
|
||||
.hasSize(5)
|
||||
));
|
||||
|
||||
configContainer.initialize();
|
||||
@@ -38,6 +43,21 @@ class TweedPojoWeaverBootstrapperTest {
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void weavingWithList() {
|
||||
TweedPojoWeaverBootstrapper<CompoundWithList> bootstrapper = TweedPojoWeaverBootstrapper.create(CompoundWithList.class);
|
||||
ConfigContainer<CompoundWithList> configContainer = bootstrapper.weave();
|
||||
|
||||
assertThat(configContainer.rootEntry()).satisfies(isCompoundEntryForClassWith(CompoundWithList.class, rootCompound ->
|
||||
assertThat(rootCompound.subEntries())
|
||||
.hasEntrySatisfying("strings", isCollectionEntryForClass(
|
||||
List.class,
|
||||
list -> assertThat(list.elementEntry()).satisfies(isSimpleEntryForClass(String.class))
|
||||
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
@AutoService(DummyExtension.class)
|
||||
public static class DummyExtension implements TweedExtension {
|
||||
@Override
|
||||
@@ -49,10 +69,11 @@ class TweedPojoWeaverBootstrapperTest {
|
||||
@PojoWeaving(extensions = {DummyExtension.class})
|
||||
@CompoundWeaving(namingFormat = "camel_case")
|
||||
@Data
|
||||
public static class DefaultWeaving {
|
||||
public static class MainCompound {
|
||||
int primitiveInteger;
|
||||
Double boxedDouble;
|
||||
InnerValue value;
|
||||
List<Integer> list;
|
||||
|
||||
InnerCompound compound;
|
||||
}
|
||||
@@ -68,4 +89,11 @@ class TweedPojoWeaverBootstrapperTest {
|
||||
int something;
|
||||
boolean somethingElse;
|
||||
}
|
||||
}
|
||||
|
||||
@PojoWeaving(weavers = {CompoundPojoWeaver.class, CoherentCollectionPojoWeaver.class, TrivialPojoWeaver.class})
|
||||
@CompoundWeaving(namingFormat = "camel_case")
|
||||
@Data
|
||||
public static class CompoundWithList {
|
||||
List<String> strings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package de.siphalor.tweed5.weaver.pojo.test;
|
||||
|
||||
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.core.api.entry.SimpleConfigEntry;
|
||||
import de.siphalor.tweed5.weaver.pojo.api.weaving.CoherentCollectionPojoWeaver;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -33,4 +36,20 @@ public class ConfigEntryAssertions {
|
||||
condition::accept
|
||||
);
|
||||
}
|
||||
|
||||
public static Consumer<Object> isCollectionEntryForClass(
|
||||
Class<?> collectionClass,
|
||||
Consumer<CoherentCollectionConfigEntry<?, ?>> condition
|
||||
) {
|
||||
return object -> assertThat(object)
|
||||
.as("Should be a collection config entry for class " + collectionClass.getName())
|
||||
.asInstanceOf(type(CoherentCollectionConfigEntry.class))
|
||||
.as("Collection entry for class " + collectionClass.getSimpleName())
|
||||
.satisfies(
|
||||
listEntry -> assertThat(listEntry.valueClass())
|
||||
.as("Value class of collection entry should match")
|
||||
.isEqualTo(collectionClass),
|
||||
condition::accept
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user