Skip to content

Commit 9570917

Browse files
committed
Support for more types in reflective structure creation, and recursive structures
1 parent d80042a commit 9570917

File tree

12 files changed

+214
-53
lines changed

12 files changed

+214
-53
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package dev.lukebemish.codecextras.structured;
22

3+
import com.google.common.base.Suppliers;
34
import com.mojang.datafixers.kinds.App;
45
import com.mojang.datafixers.kinds.Const;
56
import com.mojang.datafixers.kinds.K1;
67
import com.mojang.datafixers.util.Either;
8+
import com.mojang.datafixers.util.Pair;
79
import com.mojang.datafixers.util.Unit;
810
import com.mojang.serialization.Codec;
911
import com.mojang.serialization.DataResult;
12+
import com.mojang.serialization.DynamicOps;
1013
import com.mojang.serialization.MapCodec;
1114
import dev.lukebemish.codecextras.PartialDispatchedMapCodec;
1215
import dev.lukebemish.codecextras.StringRepresentation;
@@ -191,6 +194,31 @@ public <T> App<Holder.Mu, T> convert(App<MapCodecInterpreter.Holder.Mu, T> input
191194
);
192195
}
193196

197+
@Override
198+
public <A> DataResult<App<Holder.Mu, A>> recursive(Function<Structure<A>, Structure<A>> function) {
199+
var key = Key.<A>create("recursive");
200+
var keyed = Structure.keyed(key);
201+
var complete = function.apply(keyed);
202+
var codec = new Codec<A>() {
203+
private final Holder<A> holder = new Holder<>(this);
204+
private final CodecInterpreter interpreterWithKeys = with(Keys.<Holder.Mu, Object>builder().add(key, holder).build(), Keys2.<ParametricKeyedValue.Mu<Holder.Mu>, K1, K1>builder().build());
205+
private final Supplier<DataResult<Codec<A>>> wrapped = Suppliers.memoize(() ->
206+
complete.interpret(interpreterWithKeys).map(CodecInterpreter::unbox)
207+
);
208+
209+
@Override
210+
public <T> DataResult<T> encode(A input, DynamicOps<T> ops, T prefix) {
211+
return wrapped.get().flatMap(codec -> codec.encode(input, ops, prefix));
212+
}
213+
214+
@Override
215+
public <T> DataResult<Pair<A, T>> decode(DynamicOps<T> ops, T input) {
216+
return wrapped.get().flatMap(codec -> codec.decode(ops, input));
217+
}
218+
};
219+
return DataResult.success(new Holder<>(codec));
220+
}
221+
194222
public static <T> Codec<T> unbox(App<Holder.Mu, T> box) {
195223
return Holder.unbox(box).codec();
196224
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ public <E, A> DataResult<App<Identity.Mu, E>> dispatch(String key, Structure<A>
9999
return DataResult.error(() -> "No default value available for a dispatch");
100100
}
101101

102+
@Override
103+
public <A> DataResult<App<Identity.Mu, A>> recursive(Function<Structure<A>, Structure<A>> function) {
104+
var recursion = new Structure<A>() {
105+
@Override
106+
public <Mu extends K1> DataResult<App<Mu, A>> interpret(Interpreter<Mu> interpreter) {
107+
return DataResult.error(() -> "Detected infinite recursion of default value");
108+
}
109+
};
110+
return function.apply(recursion).interpret(this);
111+
}
112+
102113
@Override
103114
public <K, V> DataResult<App<Identity.Mu, Map<K, V>>> dispatchedMap(Structure<K> keyStructure, Supplier<Set<K>> keys, Function<K, DataResult<Structure<? extends V>>> valueStructures) {
104115
return DataResult.error(() -> "No default value available for a dispatched map");

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public interface Interpreter<Mu extends K1> {
4545

4646
<E, A> DataResult<App<Mu, E>> dispatch(String key, Structure<A> keyStructure, Function<? super E, ? extends DataResult<A>> function, Supplier<Set<A>> keys, Function<A, DataResult<Structure<? extends E>>> structures);
4747

48+
<A> DataResult<App<Mu, A>> recursive(Function<Structure<A>, Structure<A>> function);
49+
4850
default Stream<KeyConsumer<?, Mu>> keyConsumers() {
4951
return Stream.of();
5052
}
@@ -113,4 +115,6 @@ default <A> DataResult<App<Mu,A>> bounded(Structure<A> input, Supplier<Set<A>> v
113115
<L, R> DataResult<App<Mu, Either<L,R>>> xor(App<Mu, L> left, App<Mu, R> right);
114116

115117
<K, V> DataResult<App<Mu, Map<K, V>>> dispatchedMap(Structure<K> keyStructure, Supplier<Set<K>> keys, Function<K, DataResult<Structure<? extends V>>> valueStructures);
118+
119+
116120
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.lukebemish.codecextras.structured;
22

3+
import com.google.common.base.Suppliers;
34
import com.mojang.datafixers.kinds.App;
45
import com.mojang.datafixers.kinds.K1;
56
import com.mojang.datafixers.util.Either;
@@ -120,6 +121,36 @@ public <E, A> DataResult<App<Holder.Mu, E>> dispatch(String key, Structure<A> ke
120121
});
121122
}
122123

124+
@Override
125+
public <A> DataResult<App<Holder.Mu, A>> recursive(Function<Structure<A>, Structure<A>> function) {
126+
var key = Key.<A>create("recursive");
127+
var keyed = Structure.keyed(key);
128+
var complete = function.apply(keyed);
129+
var mapCodec = new MapCodec<A>() {
130+
private final Holder<A> holder = new Holder<>(this);
131+
private final MapCodecInterpreter interpreterWithKeys = with(Keys.<MapCodecInterpreter.Holder.Mu, Object>builder().add(key, holder).build(), Keys2.<ParametricKeyedValue.Mu<MapCodecInterpreter.Holder.Mu>, K1, K1>builder().build());
132+
private final Supplier<DataResult<MapCodec<A>>> wrapped = Suppliers.memoize(() ->
133+
complete.interpret(interpreterWithKeys).map(MapCodecInterpreter::unbox)
134+
);
135+
136+
@Override
137+
public <T> RecordBuilder<T> encode(A input, DynamicOps<T> ops, RecordBuilder<T> prefix) {
138+
return wrapped.get().mapOrElse(c -> c.encode(input, ops, prefix), e -> prefix).withErrorsFrom(wrapped.get());
139+
}
140+
141+
@Override
142+
public <T> DataResult<A> decode(DynamicOps<T> ops, MapLike<T> input) {
143+
return wrapped.get().flatMap(codec -> codec.decode(ops, input));
144+
}
145+
146+
@Override
147+
public <T> Stream<T> keys(DynamicOps<T> ops) {
148+
return wrapped.get().mapOrElse(c -> c.keys(ops), e -> Stream.empty());
149+
}
150+
};
151+
return DataResult.success(new Holder<>(mapCodec));
152+
}
153+
123154
@Override
124155
public <K, V> DataResult<App<Holder.Mu, Map<K, V>>> dispatchedMap(Structure<K> keyStructure, Supplier<Set<K>> keys, Function<K, DataResult<Structure<? extends V>>> valueStructures) {
125156
return DataResult.error(() -> "Cannot make a MapCodec for a dispatched map");

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,15 @@ public <Mu extends K1> DataResult<App<Mu, A>> interpret(Interpreter<Mu> interpre
357357
};
358358
}
359359

360+
static <A> Structure<A> recursive(Function<Structure<A>, Structure<A>> function) {
361+
return new Structure<>() {
362+
@Override
363+
public <Mu extends K1> DataResult<App<Mu, A>> interpret(Interpreter<Mu> interpreter) {
364+
return interpreter.recursive(function);
365+
}
366+
};
367+
}
368+
360369
/**
361370
* Keys provide a way of representing the smallest building blocks of a structure. Interpreters are responsible for
362371
* finding a matching specific representation given a key when interpreting a structure.

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

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package dev.lukebemish.codecextras.structured.reflective;
22

3+
import com.google.common.base.Suppliers;
34
import dev.lukebemish.codecextras.structured.Structure;
45
import dev.lukebemish.codecextras.utility.LayeredServiceLoader;
56
import java.lang.reflect.ParameterizedType;
67
import java.lang.reflect.Type;
78
import java.util.ArrayList;
9+
import java.util.HashMap;
810
import java.util.IdentityHashMap;
911
import java.util.List;
1012
import java.util.Map;
1113
import java.util.Objects;
1214
import java.util.function.Function;
15+
import java.util.function.Supplier;
1316

1417
public interface ReflectiveStructureCreator {
1518
Map<Class<?>, Creator> creators();
@@ -88,8 +91,10 @@ public Instance build() {
8891

8992
private static final LayeredServiceLoader<ReflectiveStructureCreator> SERVICE_LOADER = LayeredServiceLoader.of(ReflectiveStructureCreator.class);
9093

94+
private final Map<Type, Creator> cachedCreators = new HashMap<>();
95+
9196
@SuppressWarnings("unchecked")
92-
public <T> Structure<T> create(Class<T> clazz) {
97+
public synchronized <T> Structure<T> create(Class<T> clazz) {
9398
var caller = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass();
9499
List<ReflectiveStructureCreator> services = LayeredServiceLoader.unique(SERVICE_LOADER.at(ReflectiveStructureCreator.class), SERVICE_LOADER.at(clazz), SERVICE_LOADER.at(caller));
95100
Map<Class<?>, Creator> creatorsMap = new IdentityHashMap<>();
@@ -106,7 +111,10 @@ public <T> Structure<T> create(Class<T> clazz) {
106111
flexibleCreatorsList.addAll(this.flexibleCreators);
107112

108113
flexibleCreatorsList.sort((a, b) -> Integer.compare(b.priority(), a.priority()));
109-
var creator = forType(clazz, creatorsMap, parameterizedCreatorsMap, flexibleCreatorsList);
114+
115+
var recursionCache = new HashMap<Type, Structure<?>>();
116+
117+
var creator = forType(cachedCreators, recursionCache, clazz, creatorsMap, parameterizedCreatorsMap, flexibleCreatorsList);
110118
return (Structure<T>) creator.create();
111119
}
112120
}
@@ -115,60 +123,79 @@ static <T> Structure<T> create(Class<T> clazz) {
115123
return Instance.builder().build().create(clazz);
116124
}
117125

118-
private static Creator forType(Type type, Map<Class<?>, Creator> creatorsMap, Map<Class<?>, ParameterizedCreator> parameterizedCreatorsMap, List<FlexibleCreator> flexibleCreators) {
119-
Class<?> rawType = null;
120-
TypedCreator[] parameterCreators = null;
121-
if (type instanceof ParameterizedType parameterizedType) {
122-
if (parameterizedType.getRawType() instanceof Class<?> clazz) {
123-
rawType = clazz;
124-
var parameters = parameterizedType.getActualTypeArguments();
125-
parameterCreators = new TypedCreator[parameters.length];
126-
if (parameterizedCreatorsMap.containsKey(clazz)) {
127-
for (int i = 0; i < parameters.length; i++) {
128-
var creator = forType(parameters[i], creatorsMap, parameterizedCreatorsMap, flexibleCreators);
129-
var parameterType = parameters[i];
130-
parameterCreators[i] = new TypedCreator() {
131-
@Override
132-
public Structure<?> create() {
133-
return creator.create();
134-
}
135-
136-
@Override
137-
public Type type() {
138-
return parameterType;
139-
}
140-
141-
@Override
142-
public Class<?> rawType() {
143-
if (parameterType instanceof Class<?> clazz) {
144-
return clazz;
145-
} else if (parameterType instanceof ParameterizedType parameterizedType) {
146-
return (Class<?>) parameterizedType.getRawType();
147-
} else {
148-
throw new IllegalArgumentException("Unknown type: " + type);
149-
}
126+
private static Creator forType(Map<Type, Creator> cachedCreators, Map<Type, Structure<?>> recursionCache, Type type, Map<Class<?>, Creator> creatorsMap, Map<Class<?>, ParameterizedCreator> parameterizedCreatorsMap, List<FlexibleCreator> flexibleCreators) {
127+
if (cachedCreators.containsKey(type)) {
128+
return cachedCreators.get(type);
129+
}
130+
if (recursionCache.containsKey(type)) {
131+
var value = recursionCache.get(type);
132+
return () -> value;
133+
}
134+
@SuppressWarnings({"rawtypes", "unchecked"}) Supplier<Structure<?>> full = Suppliers.memoize(() -> Structure.recursive((Function) (Function<Structure, Structure>) (Structure itself) -> {
135+
recursionCache.put(type, itself);
136+
137+
Supplier<Creator> creatorSupplier = () -> {
138+
Class<?> rawType = null;
139+
TypedCreator[] parameterCreators = null;
140+
if (type instanceof ParameterizedType parameterizedType) {
141+
if (parameterizedType.getRawType() instanceof Class<?> clazz) {
142+
rawType = clazz;
143+
var parameters = parameterizedType.getActualTypeArguments();
144+
parameterCreators = new TypedCreator[parameters.length];
145+
if (parameterizedCreatorsMap.containsKey(clazz)) {
146+
for (int i = 0; i < parameters.length; i++) {
147+
var creator = forType(cachedCreators, recursionCache, parameters[i], creatorsMap, parameterizedCreatorsMap, flexibleCreators);
148+
var parameterType = parameters[i];
149+
parameterCreators[i] = new TypedCreator() {
150+
@Override
151+
public Structure<?> create() {
152+
return creator.create();
153+
}
154+
155+
@Override
156+
public Type type() {
157+
return parameterType;
158+
}
159+
160+
@Override
161+
public Class<?> rawType() {
162+
if (parameterType instanceof Class<?> clazz) {
163+
return clazz;
164+
} else if (parameterType instanceof ParameterizedType parameterizedType) {
165+
return (Class<?>) parameterizedType.getRawType();
166+
} else {
167+
throw new IllegalArgumentException("Unknown type: " + type);
168+
}
169+
}
170+
};
150171
}
151-
};
172+
return parameterizedCreatorsMap.get(clazz).creator(parameterCreators);
173+
}
152174
}
153-
return parameterizedCreatorsMap.get(clazz).creator(parameterCreators);
175+
} else if (type instanceof Class<?> clazz) {
176+
rawType = clazz;
177+
parameterCreators = new TypedCreator[0];
178+
var foundCreator = creatorsMap.get(clazz);
179+
if (foundCreator != null) {
180+
return foundCreator;
181+
}
182+
} else {
183+
throw new IllegalArgumentException("Unknown type: " + type);
154184
}
155-
}
156-
} else if (type instanceof Class<?> clazz) {
157-
rawType = clazz;
158-
parameterCreators = new TypedCreator[0];
159-
var foundCreator = creatorsMap.get(clazz);
160-
if (foundCreator != null) {
161-
return foundCreator;
162-
}
163-
} else {
164-
throw new IllegalArgumentException("Unknown type: " + type);
165-
}
166185

167-
for (var flexibleCreator : flexibleCreators) {
168-
if (flexibleCreator.supports(Objects.requireNonNull(rawType), parameterCreators)) {
169-
return flexibleCreator.creator(rawType, parameterCreators, type1 -> forType(type1, creatorsMap, parameterizedCreatorsMap, flexibleCreators).create());
170-
}
171-
}
172-
throw new IllegalArgumentException("No creator found for type: " + type);
186+
for (var flexibleCreator : flexibleCreators) {
187+
if (flexibleCreator.supports(Objects.requireNonNull(rawType), parameterCreators)) {
188+
return flexibleCreator.creator(rawType, parameterCreators, type1 -> forType(cachedCreators, recursionCache, type1, creatorsMap, parameterizedCreatorsMap, flexibleCreators).create());
189+
}
190+
}
191+
throw new IllegalArgumentException("No creator found for type: " + type);
192+
};
193+
var creator = creatorSupplier.get();
194+
var structure = creator.create();
195+
recursionCache.remove(type);
196+
return structure;
197+
}));
198+
cachedCreators.put(type, full::get);
199+
return full::get;
173200
}
174201
}

src/main/java/dev/lukebemish/codecextras/structured/schema/JsonSchemaInterpreter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ public <A> DataResult<App<Holder.Mu, A>> annotate(Structure<A> input, Keys<Ident
227227
return DataResult.success(new Holder<>(schema, definitions));
228228
}
229229

230+
@Override
231+
public <A> DataResult<App<Holder.Mu, A>> recursive(Function<Structure<A>, Structure<A>> function) {
232+
// TODO: implement
233+
return DataResult.error(() -> "Not yet implemented");
234+
}
235+
230236
@Override
231237
public <E, A> DataResult<App<Holder.Mu, E>> dispatch(String key, Structure<A> keyStructure, Function<? super E, ? extends DataResult<A>> function, Supplier<Set<A>> keys, Function<A, DataResult<Structure<? extends E>>> structures) {
232238
return keyStructure.interpret(this).flatMap(keySchemaApp -> {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
@NullMarked
2+
@ApiStatus.Internal
23
package dev.lukebemish.codecextras.utility;
34

5+
import org.jetbrains.annotations.ApiStatus;
46
import org.jspecify.annotations.NullMarked;

src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
exports dev.lukebemish.codecextras.repair;
3232

3333
exports dev.lukebemish.codecextras.structured;
34+
exports dev.lukebemish.codecextras.structured.reflective;
3435
exports dev.lukebemish.codecextras.structured.schema;
3536

3637
exports dev.lukebemish.codecextras.types;

src/minecraft/java/dev/lukebemish/codecextras/minecraft/structured/config/ConfigScreenInterpreter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,12 @@ public <E, A> DataResult<App<ConfigScreenEntry.Mu, E>> dispatch(String key, Stru
10311031
));
10321032
}
10331033

1034+
@Override
1035+
public <A> DataResult<App<ConfigScreenEntry.Mu, A>> recursive(Function<Structure<A>, Structure<A>> function) {
1036+
// TODO: implement
1037+
return DataResult.error(() -> "Not yet implemented");
1038+
}
1039+
10341040
@Override
10351041
public <K, V> DataResult<App<ConfigScreenEntry.Mu, Map<K, V>>> dispatchedMap(Structure<K> keyStructure, Supplier<Set<K>> keys, Function<K, DataResult<Structure<? extends V>>> valueStructures) {
10361042
var keyResult = interpret(keyStructure);

0 commit comments

Comments
 (0)