Skip to content

Commit 0dfcec5

Browse files
committed
Working generic records and arrays and structured data elements
1 parent b0a0a83 commit 0dfcec5

File tree

13 files changed

+720
-165
lines changed

13 files changed

+720
-165
lines changed

src/main/java/dev/lukebemish/codecextras/mutable/DataElement.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import java.util.function.Supplier;
77

88
/**
9-
* A holdr for a mutable value.
9+
* A holder for a mutable value.
1010
* @param <T> the type of the value
1111
*/
1212
public interface DataElement<T> {

src/main/java/dev/lukebemish/codecextras/mutable/DataElementType.java

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,32 @@
1818
* @param <D> the type of the holder object
1919
* @param <T> the type of data being retrieved
2020
*/
21-
public interface DataElementType<D, T> {
22-
/**
23-
* {@return a matching {@link DataElement} retrieved from the provided object}
24-
* @param data the object to retrieve the element from
25-
*/
26-
DataElement<T> from(D data);
21+
// TODO: rename to CodecDataElementType in next BC window
22+
public interface DataElementType<D, T> extends GenericDataElementType<D, T> {
2723

2824
/**
2925
* {@return the codec to (de)serialize the element}
3026
*/
3127
Codec<T> codec();
3228

3329
/**
34-
* {@return the name of the data type} Used when encoding; should be unique within a given set of data types.
30+
* @deprecated use the method on {@link GenericDataElementType} instead
31+
* @see GenericDataElementType#cleaner(GenericDataElementType[])
3532
*/
36-
String name();
33+
@Deprecated(forRemoval = true)
34+
static <D> Consumer<D> cleaner(GenericDataElementType<D, ?>... types) {
35+
return GenericDataElementType.cleaner(types);
36+
}
37+
38+
/**
39+
* @deprecated use the method on {@link GenericDataElementType} instead
40+
* @see GenericDataElementType#cleaner(List)
41+
*/
42+
// Being moved to GenericDataElementType
43+
@Deprecated(forRemoval = true)
44+
static <D> Consumer<D> cleaner(List<? extends GenericDataElementType<D, ?>> types) {
45+
return GenericDataElementType.cleaner(types);
46+
}
3747

3848
/**
3949
* {@return a new {@link DataElementType} with the provided name, codec, and getter}
@@ -62,30 +72,6 @@ public String name() {
6272
};
6373
}
6474

65-
/**
66-
* {@return a {@link Consumer} that marks all the provided data elements as clean}
67-
* @param types the data elements to mark as clean
68-
* @param <D> the type of object containing the data elements
69-
*/
70-
@SafeVarargs
71-
static <D> Consumer<D> cleaner(DataElementType<D, ?>... types) {
72-
List<DataElementType<D, ?>> list = List.of(types);
73-
return cleaner(list);
74-
}
75-
76-
/**
77-
* {@return a {@link Consumer} that marks all the provided data elements as clean}
78-
* @param types the data elements to mark as clean
79-
* @param <D> the type of object containing the data elements
80-
*/
81-
static <D> Consumer<D> cleaner(List<? extends DataElementType<D, ?>> types) {
82-
return data -> {
83-
for (var type : types) {
84-
type.from(data).setDirty(false);
85-
}
86-
};
87-
}
88-
8975
/**
9076
* Creates a {@link Codec} for a series of data elements. This codec will encode from an instance of the type that
9177
* holds the data elements, and will decode to a {@link Consumer} that can be applied to an instance of that type to
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dev.lukebemish.codecextras.mutable;
2+
3+
import java.util.List;
4+
import java.util.function.Consumer;
5+
6+
public interface GenericDataElementType<D, T> {
7+
/**
8+
* {@return a matching {@link DataElement} retrieved from the provided object}
9+
* @param data the object to retrieve the element from
10+
*/
11+
DataElement<T> from(D data);
12+
13+
/**
14+
* {@return the name of the data type} Used when encoding; should be unique within a given set of data types.
15+
*/
16+
String name();
17+
18+
/**
19+
* {@return a {@link Consumer } that marks all the provided data elements as clean}
20+
* @param types the data elements to mark as clean
21+
* @param <D> the type of object containing the data elements
22+
*/
23+
@SafeVarargs
24+
static <D> Consumer<D> cleaner(GenericDataElementType<D, ?>... types) {
25+
List<GenericDataElementType<D, ?>> list = List.of(types);
26+
return cleaner(list);
27+
}
28+
29+
/**
30+
* {@return a {@link Consumer} that marks all the provided data elements as clean}
31+
* @param types the data elements to mark as clean
32+
* @param <D> the type of object containing the data elements
33+
*/
34+
static <D> Consumer<D> cleaner(List<? extends GenericDataElementType<D, ?>> types) {
35+
return data -> {
36+
for (var type : types) {
37+
type.from(data).setDirty(false);
38+
}
39+
};
40+
}
41+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package dev.lukebemish.codecextras.structured;
2+
3+
import com.mojang.serialization.DataResult;
4+
import dev.lukebemish.codecextras.Asymmetry;
5+
import dev.lukebemish.codecextras.mutable.DataElement;
6+
import dev.lukebemish.codecextras.mutable.GenericDataElementType;
7+
import java.util.ArrayList;
8+
import java.util.HashMap;
9+
import java.util.IdentityHashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
import java.util.function.Consumer;
14+
import java.util.function.Function;
15+
import java.util.function.Supplier;
16+
17+
public interface StructuredDataElementType<D, T> extends GenericDataElementType<D, T> {
18+
Structure<T> structure();
19+
20+
static <D, T> StructuredDataElementType<D, T> create(String name, Structure<T> structure, Function<D, DataElement<T>> getter) {
21+
return new StructuredDataElementType<>() {
22+
@Override
23+
public Structure<T> structure() {
24+
return structure;
25+
}
26+
27+
@Override
28+
public DataElement<T> from(D data) {
29+
return getter.apply(data);
30+
}
31+
32+
@Override
33+
public String name() {
34+
return name;
35+
}
36+
};
37+
}
38+
39+
@SafeVarargs
40+
static <D> Structure<Asymmetry<Consumer<D>, D>> structure(boolean encodeFull, StructuredDataElementType<D, ?>... elements) {
41+
List<StructuredDataElementType<D, ?>> list = List.of(elements);
42+
return structure(encodeFull, list);
43+
}
44+
45+
static <D> Structure<Asymmetry<Consumer<D>, D>> structure(boolean encodeFull, List<? extends StructuredDataElementType<D, ?>> elements) {
46+
Map<String, StructuredDataElementType<D, ?>> elementTypeMap = new HashMap<>();
47+
48+
for (var element : elements) {
49+
if (elementTypeMap.containsKey(element.name())) {
50+
throw new IllegalArgumentException("Duplicate name for DataElementType: " + element.name());
51+
}
52+
elementTypeMap.put(element.name(), element);
53+
}
54+
55+
return Structure.flatRecord(builder -> {
56+
record Mutation<D, T>(StructuredDataElementType<D, T> elementType, T value) {
57+
public void set(D data) {
58+
elementType.from(data).set(value);
59+
}
60+
61+
static <D, T> RecordStructure.Key<Optional<DataResult<Mutation<D, T>>>> of(boolean encodeFull, StructuredDataElementType<D, T> elementType, RecordStructure<Asymmetry<Consumer<D>, D>> asymmetryBuilder) {
62+
return asymmetryBuilder.addOptional(
63+
elementType.name(),
64+
elementType.structure().flatComapMap(
65+
t -> DataResult.success(new Mutation<>(elementType, t)),
66+
r -> r.map(Mutation::value)
67+
),
68+
asymmetry -> {
69+
DataResult<Optional<Mutation<D, T>>> nested = asymmetry.encoding().map(d ->
70+
elementType.from(d).ifEncodingOrElse(encodeFull, t ->
71+
Optional.of(new Mutation<>(elementType, t)),
72+
Optional::empty
73+
)
74+
);
75+
return nested.mapOrElse(
76+
optional -> optional.map(DataResult::success),
77+
error -> Optional.of(DataResult.error(error.messageSupplier()))
78+
);
79+
}
80+
);
81+
}
82+
}
83+
84+
Map<Object, RecordStructure.Key<? extends Optional<? extends DataResult<? extends Mutation<D, ?>>>>> containerKeys = new IdentityHashMap<>();
85+
List<Object> keysInOrder = new ArrayList<>();
86+
87+
for (var element : elements) {
88+
RecordStructure.Key<? extends Optional<? extends DataResult<? extends Mutation<D, ?>>>> containerKey = Mutation.of(encodeFull, element, builder);
89+
90+
var key = new Object();
91+
containerKeys.put(key, containerKey);
92+
keysInOrder.add(key);
93+
}
94+
95+
return container -> {
96+
Map<Object, Mutation<D, ?>> mutations = new IdentityHashMap<>();
97+
List<Object> foundKeys = new ArrayList<>();
98+
List<Supplier<String>> errors = new ArrayList<>();
99+
for (var key : keysInOrder) {
100+
var containerKey = containerKeys.get(key);
101+
var value = containerKey.apply(container);
102+
value.ifPresent(result -> {
103+
result.ifError(e -> errors.add(e.messageSupplier()));
104+
result.ifSuccess(mutation -> {
105+
mutations.put(key, mutation);
106+
foundKeys.add(key);
107+
});
108+
});
109+
}
110+
Consumer<D> consumer = d -> {
111+
for (var key : foundKeys) {
112+
mutations.get(key).set(d);
113+
}
114+
};
115+
if (errors.isEmpty()) {
116+
return DataResult.success(Asymmetry.ofDecoding(consumer));
117+
} else {
118+
var error = errors.getFirst();
119+
var result = DataResult.<Asymmetry<Consumer<D>, D>>error(error, Asymmetry.ofDecoding(consumer));
120+
for (var e : errors.subList(1, errors.size())) {
121+
result = result.mapError(s -> s + "; " + e.get());
122+
}
123+
return result;
124+
}
125+
};
126+
});
127+
}
128+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package dev.lukebemish.codecextras.structured.reflective;
2+
3+
record ParameterizedTypeResults(Class<?> rawType, ReflectiveStructureCreator.TypedCreator[] parameterCreators) {}

src/main/java/dev/lukebemish/codecextras/structured/reflective/ReflectiveStructureCreator.java

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import dev.lukebemish.codecextras.structured.reflective.systems.Creators;
1313
import dev.lukebemish.codecextras.structured.reflective.systems.FlexibleCreators;
1414
import dev.lukebemish.codecextras.structured.reflective.systems.ParameterizedCreators;
15+
import java.lang.reflect.GenericArrayType;
1516
import java.lang.reflect.ParameterizedType;
1617
import java.lang.reflect.Type;
1718
import java.util.ArrayList;
@@ -198,38 +199,39 @@ private static Structure<?> forType(Map<Type, Structure<?>> cachedCreators, Map<
198199
Supplier<Creators.Creator> creatorSupplier = () -> {
199200
Class<?> rawType = null;
200201
TypedCreator[] parameterCreators = null;
202+
201203
if (type instanceof ParameterizedType parameterizedType) {
202204
if (parameterizedType.getRawType() instanceof Class<?> clazz) {
203205
rawType = clazz;
204206
var parameters = parameterizedType.getActualTypeArguments();
205207
parameterCreators = new TypedCreator[parameters.length];
206-
if (parameterizedCreatorsMap.containsKey(clazz)) {
207-
for (int i = 0; i < parameters.length; i++) {
208-
var structure = forType(cachedCreators, recursionCache, parameters[i], context);
209-
var parameterType = parameters[i];
210-
parameterCreators[i] = new TypedCreator() {
211-
@Override
212-
public Structure<?> create() {
213-
return structure;
214-
}
215-
216-
@Override
217-
public Type type() {
218-
return parameterType;
219-
}
220-
221-
@Override
222-
public Class<?> rawType() {
223-
if (parameterType instanceof Class<?> clazz) {
224-
return clazz;
225-
} else if (parameterType instanceof ParameterizedType parameterizedType) {
226-
return (Class<?>) parameterizedType.getRawType();
227-
} else {
228-
throw new IllegalArgumentException("Unknown type: " + type);
229-
}
208+
for (int i = 0; i < parameters.length; i++) {
209+
var structure = forType(cachedCreators, recursionCache, parameters[i], context);
210+
var parameterType = parameters[i];
211+
parameterCreators[i] = new TypedCreator() {
212+
@Override
213+
public Structure<?> create() {
214+
return structure;
215+
}
216+
217+
@Override
218+
public Type type() {
219+
return parameterType;
220+
}
221+
222+
@Override
223+
public Class<?> rawType() {
224+
if (parameterType instanceof Class<?> clazz) {
225+
return clazz;
226+
} else if (parameterType instanceof ParameterizedType parameterizedType) {
227+
return (Class<?>) parameterizedType.getRawType();
228+
} else {
229+
throw new IllegalArgumentException("Unknown type: " + type);
230230
}
231-
};
232-
}
231+
}
232+
};
233+
}
234+
if (parameterizedCreatorsMap.containsKey(clazz)) {
233235
return parameterizedCreatorsMap.get(clazz).creator(parameterCreators);
234236
}
235237
}
@@ -240,6 +242,10 @@ public Class<?> rawType() {
240242
if (foundCreator != null) {
241243
return foundCreator;
242244
}
245+
} else if (type instanceof GenericArrayType genericArrayType) {
246+
var results = handleGenericArrayType(cachedCreators, recursionCache, type, context, genericArrayType);
247+
rawType = results.rawType().arrayType();
248+
parameterCreators = results.parameterCreators();
243249
} else {
244250
throw new IllegalArgumentException("Unknown type: " + type);
245251
}
@@ -259,4 +265,50 @@ public Class<?> rawType() {
259265
cachedCreators.put(type, full.get());
260266
return full.get();
261267
}
268+
269+
private static ParameterizedTypeResults handleGenericArrayType(Map<Type, Structure<?>> cachedCreators, Map<Type, Structure<?>> recursionCache, Type type, CreationContext context, GenericArrayType genericArrayType) {
270+
TypedCreator[] parameterCreators;
271+
Class<?> rawType;
272+
var componentType = genericArrayType.getGenericComponentType();
273+
if (componentType instanceof Class<?> clazz) {
274+
rawType = clazz;
275+
parameterCreators = new TypedCreator[0];
276+
} else if (componentType instanceof ParameterizedType parameterizedType && parameterizedType.getRawType() instanceof Class<?> clazz) {
277+
rawType = clazz;
278+
parameterCreators = new TypedCreator[parameterizedType.getActualTypeArguments().length];
279+
for (int i = 0; i < parameterizedType.getActualTypeArguments().length; i++) {
280+
var structure = forType(cachedCreators, recursionCache, parameterizedType.getActualTypeArguments()[i], context);
281+
var parameterType = parameterizedType.getActualTypeArguments()[i];
282+
parameterCreators[i] = new TypedCreator() {
283+
@Override
284+
public Structure<?> create() {
285+
return structure;
286+
}
287+
288+
@Override
289+
public Type type() {
290+
return parameterType;
291+
}
292+
293+
@Override
294+
public Class<?> rawType() {
295+
if (parameterType instanceof Class<?> clazz) {
296+
return clazz;
297+
} else if (parameterType instanceof ParameterizedType parameterizedType) {
298+
return (Class<?>) parameterizedType.getRawType();
299+
} else {
300+
throw new IllegalArgumentException("Unknown type: " + type);
301+
}
302+
}
303+
};
304+
}
305+
} else if (componentType instanceof GenericArrayType genericArrayComponentType) {
306+
var results = handleGenericArrayType(cachedCreators, recursionCache, type, context, genericArrayComponentType);
307+
rawType = results.rawType().arrayType();
308+
parameterCreators = results.parameterCreators();
309+
} else {
310+
throw new IllegalArgumentException("Unknown type: " + type);
311+
}
312+
return new ParameterizedTypeResults(rawType, parameterCreators);
313+
}
262314
}

0 commit comments

Comments
 (0)