Skip to content

Commit b0a0a83

Browse files
committed
Better optional/defaulted behavior and fix transitive-annotated beans
1 parent fd77afb commit b0a0a83

File tree

17 files changed

+295
-147
lines changed

17 files changed

+295
-147
lines changed

src/groovy/java/dev/lukebemish/codecextras/groovy/structured/reflective/implementation/GroovyReflectiveStructureCreator.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Objects;
2425
import java.util.function.Function;
2526
import java.util.stream.Collectors;
2627
import org.codehaus.groovy.reflection.CachedField;
@@ -105,15 +106,18 @@ public Function<CreationContext, List<Discoverer>> make() {
105106
@Override
106107
public void modifyProperties(Class<?> clazz, Map<String, java.lang.reflect.Type> known) {
107108
var metaClass = DefaultGroovyMethods.getMetaClass(clazz);
108-
if (known.get("metaClass").equals(MetaClass.class)) {
109+
if (Objects.equals(known.get("metaClass"), MetaClass.class)) {
109110
known.remove("metaClass");
110111
}
111112
metaClass.getProperties().forEach(metaProperty -> {
113+
if ((metaProperty.getModifiers() & Modifier.TRANSIENT) != 0) {
114+
return;
115+
}
112116
var name = metaProperty.getName();
113117
var type = metaProperty.getType();
114118
var modifiers = metaProperty.getModifiers();
115119
// We can only handle bean properties, due to needing to introspect them
116-
if ((modifiers & Modifier.PUBLIC) != 0 && metaProperty instanceof MetaBeanProperty) {
120+
if ((modifiers & Modifier.PUBLIC) != 0 && metaProperty instanceof MetaBeanProperty metaBeanProperty) {
117121
if (!known.containsKey(name)) {
118122
known.put(name, type);
119123
}
@@ -162,13 +166,14 @@ public int priority() {
162166
@Override
163167
public List<AnnotatedElement> context(Class<?> clazz, String property) {
164168
var metaProperty = DefaultGroovyMethods.getMetaClass(clazz).getMetaProperty(property);
169+
var list = new ArrayList<AnnotatedElement>();
165170
if (metaProperty instanceof MetaBeanProperty beanProperty) {
166171
if (beanProperty.getField() instanceof CachedField field) {
167-
return List.of(field.getCachedField());
172+
list.add(field.getCachedField());
168173
}
169174
}
170-
// Unfortunately can't do too much with this
171-
return List.of();
175+
// And that's about all we can do...
176+
return list;
172177
}
173178
});
174179
}

src/main/java/dev/lukebemish/codecextras/structured/CodecInterpreter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public <A> DataResult<App<Holder.Mu, List<A>>> list(App<Holder.Mu, A> single) {
8888
}
8989

9090
@Override
91-
public <A> DataResult<App<Holder.Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, A> creator) {
91+
public <A> DataResult<App<Holder.Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, DataResult<A>> creator) {
9292
return StructuredMapCodec.of(fields, creator, this, CodecInterpreter::unbox)
9393
.map(mapCodec -> new Holder<>(mapCodec.codec()));
9494
}

src/main/java/dev/lukebemish/codecextras/structured/IdentityInterpreter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ public <A> DataResult<App<Identity.Mu, A>> keyed(Key<A> key) {
6060
}
6161

6262
@Override
63-
public <A> DataResult<App<Identity.Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, A> creator) {
63+
public <A> DataResult<App<Identity.Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, DataResult<A>> creator) {
6464
var builder = RecordStructure.Container.builder();
6565
for (var field : fields) {
6666
DataResult<App<Identity.Mu, A>> result = forField(field, builder);
6767
if (result != null) return result;
6868
}
69-
return DataResult.success(new Identity<>(creator.apply(builder.build())));
69+
return creator.apply(builder.build()).map(Identity::new);
7070
}
7171

7272
private <A, F> @Nullable DataResult<App<Identity.Mu, A>> forField(RecordStructure.Field<A, F> field, RecordStructure.Container.Builder builder) {

src/main/java/dev/lukebemish/codecextras/structured/Interpreter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface Interpreter<Mu extends K1> {
3737

3838
<A> DataResult<App<Mu, A>> keyed(Key<A> key);
3939

40-
<A> DataResult<App<Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, A> creator);
40+
<A> DataResult<App<Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, DataResult<A>> creator);
4141

4242
<A, B> DataResult<App<Mu, B>> flatXmap(App<Mu, A> input, Function<A, DataResult<B>> to, Function<B, DataResult<A>> from);
4343

src/main/java/dev/lukebemish/codecextras/structured/MapCodecInterpreter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public <A> DataResult<App<Holder.Mu, List<A>>> list(App<Holder.Mu, A> single) {
5555
}
5656

5757
@Override
58-
public <A> DataResult<App<Holder.Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, A> creator) {
58+
public <A> DataResult<App<Holder.Mu, A>> record(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, DataResult<A>> creator) {
5959
return StructuredMapCodec.of(fields, creator, codecInterpreter(), CodecInterpreter::unbox)
6060
.map(Holder::new);
6161
}

src/main/java/dev/lukebemish/codecextras/structured/RecordStructure.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ private <T, F> void partialField(Function<A, T> getter, Field<T, F> field) {
279279
* @return a new structure
280280
* @param <A> the type of the data represented
281281
*/
282-
static <A> Structure<A> create(RecordStructure.Builder<A> builder) {
282+
static <A> Structure<A> create(RecordStructure.FlatBuilder<A> builder) {
283283
RecordStructure<A> instance = new RecordStructure<>();
284284
var creator = builder.build(instance);
285285
return new Structure<>() {
@@ -305,5 +305,21 @@ public interface Builder<A> {
305305
* @return a function to assemble the final type from a {@link Container}
306306
*/
307307
Function<Container, A> build(RecordStructure<A> builder);
308+
309+
default FlatBuilder<A> asFlatBuilder() {
310+
return builder -> build(builder).andThen(DataResult::success);
311+
}
312+
}
313+
314+
@FunctionalInterface
315+
public interface FlatBuilder<A> {
316+
/**
317+
* Assemble a record structure for the given type. Should collect {@link Key}s for every field needed and return
318+
* a function that uses those keys to assemble the final type from a {@link Container}. Unlike a {@link Builder},
319+
* allows for failures.
320+
* @param builder a blank record structure to add fields to
321+
* @return a function to assemble the final type from a {@link Container}
322+
*/
323+
Function<Container, DataResult<A>> build(RecordStructure<A> builder);
308324
}
309325
}

src/main/java/dev/lukebemish/codecextras/structured/Structure.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,17 @@ default Structure<A> validate(Function<A, DataResult<A>> verifier) {
626626
* @see RecordStructure
627627
*/
628628
static <A> Structure<A> record(RecordStructure.Builder<A> builder) {
629+
return RecordStructure.create(builder.asFlatBuilder());
630+
}
631+
632+
/**
633+
* {@return a structure representing a collection of key-value pairs with defined structures, which may be optionally present, and which can handle failures}
634+
* @param builder the builder to use to create the record structure
635+
* @param <A> the type of data the structure represents
636+
* @see RecordStructure
637+
* @see #record(RecordStructure.Builder)
638+
*/
639+
static <A> Structure<A> flatRecord(RecordStructure.FlatBuilder<A> builder) {
629640
return RecordStructure.create(builder);
630641
}
631642

src/main/java/dev/lukebemish/codecextras/structured/StructuredMapCodec.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ class StructuredMapCodec<A> extends MapCodec<A> {
2222
private record Field<A, T>(String name, MapCodec<T> codec, RecordStructure.Key<T> key, Function<A, T> getter) {}
2323

2424
private final List<Field<A, ?>> fields;
25-
private final Function<RecordStructure.Container, A> creator;
25+
private final Function<RecordStructure.Container, DataResult<A>> creator;
2626

27-
private StructuredMapCodec(List<Field<A, ?>> fields, Function<RecordStructure.Container, A> creator) {
27+
private StructuredMapCodec(List<Field<A, ?>> fields, Function<RecordStructure.Container, DataResult<A>> creator) {
2828
this.fields = fields;
2929
this.creator = creator;
3030
}
@@ -33,7 +33,7 @@ public interface Unboxer<Mu extends K1> {
3333
<A> Codec<A> unbox(App<Mu, A> box);
3434
}
3535

36-
public static <A, Mu extends K1> DataResult<MapCodec<A>> of(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, A> creator, Interpreter<Mu> interpreter, Unboxer<Mu> unboxer) {
36+
public static <A, Mu extends K1> DataResult<MapCodec<A>> of(List<RecordStructure.Field<A, ?>> fields, Function<RecordStructure.Container, DataResult<A>> creator, Interpreter<Mu> interpreter, Unboxer<Mu> unboxer) {
3737
var mapCodecFields = new ArrayList<Field<A, ?>>();
3838
for (var field : fields) {
3939
DataResult<MapCodec<A>> result = recordSingleField(field, mapCodecFields, interpreter, unboxer);
@@ -93,12 +93,16 @@ public <T> DataResult<A> decode(DynamicOps<T> ops, MapLike<T> input) {
9393
}
9494
if (isError) {
9595
if (isPartial) {
96-
return DataResult.error(errorMessage, creator.apply(builder.build()), errorLifecycle);
96+
var result = creator.apply(builder.build());
97+
if (result.isError()) {
98+
return DataResult.error(errorMessage, errorLifecycle);
99+
}
100+
return DataResult.error(errorMessage, result.result().orElseThrow(), errorLifecycle);
97101
} else {
98102
return DataResult.error(errorMessage, errorLifecycle);
99103
}
100104
} else {
101-
return DataResult.success(creator.apply(builder.build()));
105+
return creator.apply(builder.build());
102106
}
103107
}
104108

src/main/java/dev/lukebemish/codecextras/structured/reflective/annotations/Annotated.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,5 @@
99
@Retention(RetentionPolicy.RUNTIME)
1010
public @interface Annotated {
1111
Value key();
12-
Value[] value() default {};
13-
String[] stringValue() default {};
14-
int[] intValue() default {};
15-
long[] longValue() default {};
16-
double[] doubleValue() default {};
17-
float[] floatValue() default {};
18-
boolean[] booleanValue() default {};
19-
byte[] byteValue() default {};
20-
short[] shortValue() default {};
21-
char[] charValue() default {};
12+
Value value();
2213
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dev.lukebemish.codecextras.structured.reflective.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target({ElementType.METHOD, ElementType.RECORD_COMPONENT, ElementType.FIELD})
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface Default {
11+
Value value();
12+
}

0 commit comments

Comments
 (0)