Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,24 @@ public sealed interface Value
* A primitive or {@link String} literal.
*/
record Literal(Object value) implements Value {

@Unmarshal
public Literal {
}

@Marshal
public void destructor(final Out<Object> value) {
value.set(this.value);
}

@Override
public String toString() {
return value.toString();
}

static {
Marshalling.register(Literal.class, MethodHandles.lookup());
}
}

/**
Expand All @@ -75,10 +89,24 @@ public String toString() {
* @param typeName the codemodel {@link TypeName} for the referenced class
*/
record ClassRef(TypeName typeName) implements Value {

@Unmarshal
public ClassRef {
}

@Marshal
public void destructor(final Out<TypeName> typeName) {
typeName.set(this.typeName);
}

@Override
public String toString() {
return typeName.canonicalName() + ".class";
}

static {
Marshalling.register(ClassRef.class, MethodHandles.lookup());
}
}

/**
Expand All @@ -88,10 +116,25 @@ public String toString() {
* @param constantName the constant name
*/
record EnumConstant(TypeName typeName, String constantName) implements Value {

@Unmarshal
public EnumConstant {
}

@Marshal
public void destructor(final Out<TypeName> typeName, final Out<String> constantName) {
typeName.set(this.typeName);
constantName.set(this.constantName);
}

@Override
public String toString() {
return typeName.canonicalName() + "." + constantName;
}

static {
Marshalling.register(EnumConstant.class, MethodHandles.lookup());
}
}

/**
Expand All @@ -100,10 +143,25 @@ public String toString() {
* @param annotation the {@link AnnotationTypeUsage} representing the nested annotation
*/
record Nested(AnnotationTypeUsage annotation) implements Value {

@Unmarshal
public Nested(final Marshaller marshaller, final Marshalled<AnnotationTypeUsage> annotation) {
this(marshaller.unmarshal(annotation));
}

@Marshal
public void destructor(final Marshaller marshaller, final Out<Marshalled<AnnotationTypeUsage>> annotation) {
annotation.set(marshaller.marshal(this.annotation));
}

@Override
public String toString() {
return annotation.toString();
}

static {
Marshalling.register(Nested.class, MethodHandles.lookup());
}
}

/**
Expand All @@ -112,47 +170,28 @@ public String toString() {
* @param elements the elements
*/
record Array(List<Value> elements) implements Value {

@Unmarshal
public Array(final Marshaller marshaller, final Stream<Marshalled<Value>> elements) {
this(elements.map(marshaller::unmarshal).toList());
}

@Marshal
public void destructor(final Marshaller marshaller, final Out<Stream<Marshalled<Value>>> elements) {
elements.set(this.elements.stream().map(marshaller::marshal));
}

@Override
public String toString() {
return elements.toString();
}
}
}

// --- Marshalling conversion helpers ---

/**
* Converts a raw marshalled {@link Object} back to a typed {@link Value}.
* Handles the legacy format (plain Object) and the structured format (AnnotationTypeUsage, List).
*/
private static Value toValue(final Object raw) {
return switch (raw) {
case Value v -> v;
case AnnotationTypeUsage a -> new Value.Nested(a);
case List<?> list -> new Value.Array(list.stream()
.map(AnnotationValue::toValue)
.toList());
default -> new Value.Literal(raw);
};
}

/**
* Converts a {@link Value} back to the raw object used in the marshal format.
*/
private static Object fromValue(final Value value) {
return switch (value) {
case Value.Literal(var v) -> v;
case Value.Nested(var a) -> a;
case Value.Array(var elements) -> elements.stream()
.map(AnnotationValue::fromValue)
.toList();
case Value.ClassRef(var typeName) -> typeName;
case Value.EnumConstant(var typeName, var constantName) -> typeName.canonicalName() + "." + constantName;
};
static {
Marshalling.register(Array.class, MethodHandles.lookup());
}
}
}

// ---

private final IrreducibleName name;
private final Value value;

Expand All @@ -169,20 +208,20 @@ public AnnotationValue(@Bound final CodeModel codeModel,
final Marshaller marshaller,
final Stream<Marshalled<Trait>> traits,
final IrreducibleName name,
final Object value) {
final Marshalled<Value> value) {
super(codeModel, marshaller, traits);
this.name = name;
this.value = toValue(value);
this.value = marshaller.unmarshal(value);
}

@Marshal
public void destructor(final Marshaller marshaller,
final Out<Stream<Marshalled<Trait>>> traits,
final Out<IrreducibleName> name,
final Out<Object> value) {
final Out<Marshalled<Value>> value) {
super.destructor(marshaller, traits);
name.set(this.name);
value.set(fromValue(this.value));
value.set(marshaller.marshal(this.value));
}

public IrreducibleName name() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand Down Expand Up @@ -87,6 +88,91 @@ void shouldMarshallAndTransportAndUnmarshallAnnotationTypeUsage()
AnnotationValue.of(codeModel, "Annotation2", "Value2")));
}

/**
* Ensures that an {@link AnnotationValue.Value.ClassRef} survives a marshal → transport →
* unmarshal round-trip. Regression test: the raw-{@link Object} marshalling this replaced
* serialized a {@link AnnotationValue.Value.ClassRef} to a bare {@link TypeName} with no
* matching deserialize case, so it silently came back as a {@link AnnotationValue.Value.Literal}
* instead.
*
* @throws IOException if an error occurs during marshalling, transport or unmarshalling
*/
@Test
void shouldMarshallAndTransportAndUnmarshallAnnotationValueClassRef()
throws IOException {

final var typeName = TypeName.of(
ModuleName.of("some.module", this.nameProvider),
Optional.empty(),
Optional.empty(),
IrreducibleName.of("MyType"));

marshallAndTransportAndUnMarshalAndAssert(
AnnotationValue.of(codeModel, "classValue", new AnnotationValue.Value.ClassRef(typeName)));
}

/**
* Ensures that an {@link AnnotationValue.Value.EnumConstant} survives a marshal → transport →
* unmarshal round-trip. Regression test: the raw-{@link Object} marshalling this replaced
* serialized an {@link AnnotationValue.Value.EnumConstant} to a formatted {@code "Type.CONSTANT"}
* string with no matching deserialize case, so it silently came back as a
* {@link AnnotationValue.Value.Literal} instead, losing the type name and constant name as
* separate fields.
*
* @throws IOException if an error occurs during marshalling, transport or unmarshalling
*/
@Test
void shouldMarshallAndTransportAndUnmarshallAnnotationValueEnumConstant()
throws IOException {

final var typeName = TypeName.of(
ModuleName.of("some.module", this.nameProvider),
Optional.empty(),
Optional.empty(),
IrreducibleName.of("MyEnum"));

marshallAndTransportAndUnMarshalAndAssert(
AnnotationValue.of(codeModel, "enumValue", new AnnotationValue.Value.EnumConstant(typeName, "CONSTANT")));
}

/**
* Ensures that an {@link AnnotationValue.Value.Nested} annotation value survives a marshal →
* transport → unmarshal round-trip.
*
* @throws IOException if an error occurs during marshalling, transport or unmarshalling
*/
@Test
void shouldMarshallAndTransportAndUnmarshallAnnotationValueNested()
throws IOException {

final var typeName = TypeName.of(
ModuleName.of("some.module", this.nameProvider),
Optional.empty(),
Optional.empty(),
IrreducibleName.of("NestedAnnotation"));
final var nested = AnnotationTypeUsage.of(codeModel, typeName,
AnnotationValue.of(codeModel, "inner", "innerValue"));

marshallAndTransportAndUnMarshalAndAssert(
AnnotationValue.of(codeModel, "nestedValue", new AnnotationValue.Value.Nested(nested)));
}

/**
* Ensures that an {@link AnnotationValue.Value.Array} survives a marshal → transport →
* unmarshal round-trip.
*
* @throws IOException if an error occurs during marshalling, transport or unmarshalling
*/
@Test
void shouldMarshallAndTransportAndUnmarshallAnnotationValueArray()
throws IOException {

marshallAndTransportAndUnMarshalAndAssert(
AnnotationValue.of(codeModel, "arrayValue", new AnnotationValue.Value.Array(List.of(
new AnnotationValue.Value.Literal("a"),
new AnnotationValue.Value.Literal("b")))));
}

/**
* Ensures that {@link ArrayTypeUsage} can be marshalled, transported and unmarshalled using a {@link JsonTransport}.
*
Expand Down