Skip to content

Commit

Permalink
Add BoskInfo interface (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
prdoyle authored Feb 18, 2024
2 parents 7ecc2ba + 810daba commit 41a3866
Show file tree
Hide file tree
Showing 21 changed files with 197 additions and 163 deletions.
41 changes: 34 additions & 7 deletions bosk-core/src/main/java/io/vena/bosk/Bosk.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
*
* @param <R> The type of the state tree's root node
*/
public class Bosk<R extends StateTreeNode> {
public class Bosk<R extends StateTreeNode> implements BoskInfo<R> {
@Getter private final String name;
@Getter private final Identifier instanceID = Identifier.from(randomUUID().toString());
@Getter private final BoskDriver<R> driver;
Expand Down Expand Up @@ -111,10 +111,20 @@ public Bosk(String name, Type rootType, DefaultRootFunction<R> defaultRootFuncti
throw new IllegalArgumentException("Invalid root type " + rootType + ": " + e.getMessage(), e);
}

// We do this last because the driver factory is allowed to do such things
// as create References, so it needs the rest of the initialization to
// have completed already.
this.driver = driverFactory.build(this, this.localDriver);
// Rather than pass `this` while it's still under construction,
// pass another object that contains the things that are available
// at this point.
//
UnderConstruction<R> boskInfo = new UnderConstruction<>(
name, instanceID, rootRef, this::registerHooks
);

// We do this as late as possible because the driver factory is allowed
// to do such things as create References, so it needs the rest of the
// initialization to have completed already.
//
this.driver = driverFactory.build(boskInfo, this.localDriver);

try {
this.currentRoot = requireNonNull(driver.initialRoot(rootType));
} catch (InvalidTypeException | IOException | InterruptedException e) {
Expand All @@ -133,11 +143,27 @@ public Bosk(String name, Type rootType, R defaultRoot, DriverFactory<R> driverFa
this(name, rootType, b->defaultRoot, driverFactory);
}

record UnderConstruction<RR extends StateTreeNode>(
String name,
Identifier instanceID,
RootReference<RR> rootReference,
RegisterHooksMethod m
) implements BoskInfo<RR> {
@Override
public void registerHooks(Object receiver) throws InvalidTypeException {
m.registerHooks(receiver);
}
}

private interface RegisterHooksMethod {
void registerHooks(Object receiver) throws InvalidTypeException;
}

/**
* You can use <code>Bosk::simpleDriver</code> as the
* <code>driverFactory</code> if you don't want any additional driver functionality.
*/
public static <RR extends StateTreeNode> BoskDriver<RR> simpleDriver(@SuppressWarnings("unused") Bosk<RR> bosk, BoskDriver<RR> downstream) {
public static <RR extends StateTreeNode> BoskDriver<RR> simpleDriver(@SuppressWarnings("unused") BoskInfo<RR> boskInfo, BoskDriver<RR> downstream) {
return downstream;
}

Expand Down Expand Up @@ -499,6 +525,7 @@ public <T> void registerHook(String name, @NonNull Reference<T> scope, @NonNull
localDriver.triggerEverywhere(reg);
}

@Override
public void registerHooks(Object receiver) throws InvalidTypeException {
HookRegistrar.registerHooks(receiver, this);
}
Expand Down Expand Up @@ -1000,7 +1027,7 @@ public void forEachValue(BiConsumer<T, BindingEnvironment> action, BindingEnviro
* correspond to an {@link Identifier} that can be looked up in an
* object that implements {@link EnumerableByIdentifier}. (We are
* not offering to use reflection to look up object fields by name here.)
*
* <p>
* TODO: This is not currently checked or enforced; it will just cause confusing crashes.
* It should throw {@link InvalidTypeException} at the time the Reference is created.
*/
Expand Down
14 changes: 14 additions & 0 deletions bosk-core/src/main/java/io/vena/bosk/BoskInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.vena.bosk;

import io.vena.bosk.exceptions.InvalidTypeException;

/**
* Provides access to a subset of bosk functionality that is available at the time
* the {@link BoskDriver} is built, before the {@link Bosk} itself is fully initialized.
*/
public interface BoskInfo<R extends StateTreeNode> {
String name();
Identifier instanceID();
RootReference<R> rootReference();
void registerHooks(Object receiver) throws InvalidTypeException;
}
2 changes: 1 addition & 1 deletion bosk-core/src/main/java/io/vena/bosk/DriverFactory.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package io.vena.bosk;

public interface DriverFactory<R extends StateTreeNode> {
BoskDriver<R> build(Bosk<R> bosk, BoskDriver<R> downstream);
BoskDriver<R> build(BoskInfo<R> boskInfo, BoskDriver<R> downstream);
}
4 changes: 2 additions & 2 deletions bosk-core/src/main/java/io/vena/bosk/DriverStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public interface DriverStack<R extends StateTreeNode> extends DriverFactory<R> {
static <RR extends StateTreeNode> DriverStack<RR> of(DriverFactory<RR>...factories) {
return new DriverStack<RR>() {
@Override
public BoskDriver<RR> build(Bosk<RR> bosk, BoskDriver<RR> downstream) {
public BoskDriver<RR> build(BoskInfo<RR> boskInfo, BoskDriver<RR> downstream) {
BoskDriver<RR> result = downstream;
for (int i = factories.length - 1; i >= 0; i--) {
result = factories[i].build(bosk, result);
result = factories[i].build(boskInfo, result);
}
return result;
}
Expand Down
19 changes: 9 additions & 10 deletions bosk-core/src/main/java/io/vena/bosk/SerializationPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,15 @@ public final DeserializationScope entryDeserializationScope(Identifier entryID)

public final DeserializationScope nodeFieldDeserializationScope(Class<?> nodeClass, String fieldName) {
DeserializationPath annotation = infoFor(nodeClass).annotatedParameters_DeserializationPath.get(fieldName);
DeserializationScope outerScope = currentScope.get();
if (annotation == null) {
DeserializationScope outerScope = currentScope.get();
DeserializationScope newScope = new NestedDeserializationScope(
outerScope,
outerScope.path().then(fieldName),
outerScope.bindingEnvironment());
currentScope.set(newScope);
return newScope;
} else {
DeserializationScope outerScope = currentScope.get();
try {
Path path = Path
.parseParameterized(annotation.value())
Expand Down Expand Up @@ -180,13 +179,13 @@ public void close() {
* supplied where possible, such as <code>Optional.empty()</code> and
* {@link Enclosing} references.
*/
public final List<Object> parameterValueList(Class<?> nodeClass, Map<String, Object> parameterValuesByName, LinkedHashMap<String, Parameter> parametersByName, Bosk<?> bosk) {
public final List<Object> parameterValueList(Class<?> nodeClass, Map<String, Object> parameterValuesByName, LinkedHashMap<String, Parameter> parametersByName, BoskInfo<?> boskInfo) {
List<Object> parameterValues = new ArrayList<>();
for (Entry<String, Parameter> entry: parametersByName.entrySet()) {
String name = entry.getKey();
Parameter parameter = entry.getValue();
Class<?> type = parameter.getType();
Reference<?> implicitReference = findImplicitReferenceIfAny(nodeClass, parameter, bosk);
Reference<?> implicitReference = findImplicitReferenceIfAny(nodeClass, parameter, boskInfo);

Object value = parameterValuesByName.remove(name);
if (value == null) {
Expand All @@ -204,7 +203,7 @@ public final List<Object> parameterValueList(Class<?> nodeClass, Map<String, Obj
// If the object is an entry in a Catalog or a key in a SideTable, we can determine its ID
Reference<Object> enclosingRef;
try {
enclosingRef = bosk.rootReference().then(Object.class, path.truncatedBy(1));
enclosingRef = boskInfo.rootReference().then(Object.class, path.truncatedBy(1));
} catch (InvalidTypeException e) {
throw new AssertionError("Non-empty path must have an enclosing reference: " + path, e);
}
Expand Down Expand Up @@ -285,13 +284,13 @@ private <R extends StateTreeNode, T> void initializePolyfills(Reference<T> ref,
}
}

private Reference<?> findImplicitReferenceIfAny(Class<?> nodeClass, Parameter parameter, Bosk<?> bosk) {
private Reference<?> findImplicitReferenceIfAny(Class<?> nodeClass, Parameter parameter, BoskInfo<?> boskInfo) {
if (isSelfReference(nodeClass, parameter)) {
Class<?> targetClass = rawClass(parameterType(parameter.getParameterizedType(), Reference.class, 0));
return selfReference(targetClass, bosk);
return selfReference(targetClass, boskInfo);
} else if (isEnclosingReference(nodeClass, parameter)) {
Class<?> targetClass = rawClass(parameterType(parameter.getParameterizedType(), Reference.class, 0));
Reference<Object> selfRef = selfReference(Object.class, bosk);
Reference<Object> selfRef = selfReference(Object.class, boskInfo);
try {
return selfRef.enclosingReference(targetClass);
} catch (InvalidTypeException e) {
Expand All @@ -305,10 +304,10 @@ private Reference<?> findImplicitReferenceIfAny(Class<?> nodeClass, Parameter pa
}
}

private <T> Reference<T> selfReference(Class<T> targetClass, Bosk<?> bosk) throws AssertionError {
private <T> Reference<T> selfReference(Class<T> targetClass, BoskInfo<?> boskInfo) throws AssertionError {
Path currentPath = currentScope.get().path();
try {
return bosk.rootReference().then(targetClass, currentPath);
return boskInfo.rootReference().then(targetClass, currentPath);
} catch (InvalidTypeException e) {
throw new UnexpectedPathException("currentDeserializationPath should be valid: \"" + currentPath + "\"", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class DiagnosticScopeDriver<R extends StateTreeNode> implements BoskDrive
final Function<BoskDiagnosticContext, DiagnosticScope> scopeSupplier;

public static <RR extends StateTreeNode> DriverFactory<RR> factory(Function<BoskDiagnosticContext, DiagnosticScope> scopeSupplier) {
return (b,d) -> new DiagnosticScopeDriver<>(d, b.diagnosticContext(), scopeSupplier);
return (b,d) -> new DiagnosticScopeDriver<>(d, b.rootReference().diagnosticContext(), scopeSupplier);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class MirroringDriver<R extends StateTreeNode> implements BoskDriver<R> {
* Causes updates to be applied both to <code>mirror</code> and to the downstream driver.
*/
public static <RR extends StateTreeNode> DriverFactory<RR> targeting(Bosk<RR> mirror) {
return (bosk, downstream) -> new ForwardingDriver<>(asList(
return (boskInfo, downstream) -> new ForwardingDriver<>(asList(
new MirroringDriver<>(mirror),
downstream
));
Expand Down
10 changes: 0 additions & 10 deletions bosk-core/src/test/java/io/vena/bosk/BoskConstructorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,6 @@ void defaultRoot_matches() {
}
}

@Test
void readContextDuringDriverFactory_throws() {
assertThrows(IllegalStateException.class, ()->{
new Bosk<>("readContext", SimpleTypes.class, newEntity(), (b,d) -> {
try (val __ = b.readContext()) {}
return d;
});
});
}

////////////////
//
// Helpers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.vena.bosk.Bosk;
import io.vena.bosk.BoskInfo;
import io.vena.bosk.Catalog;
import io.vena.bosk.Entity;
import io.vena.bosk.Phantom;
Expand Down Expand Up @@ -66,7 +66,7 @@ final class JacksonCompiler {
*
* @return a newly compiled {@link CompiledSerDes} for values of the given <code>nodeType</code>.
*/
public <T> CompiledSerDes<T> compiled(JavaType nodeType, Bosk<?> bosk, FieldModerator moderator) {
public <T> CompiledSerDes<T> compiled(JavaType nodeType, BoskInfo<?> boskInfo, FieldModerator moderator) {
try {
// Record that we're compiling this one to avoid infinite recursion
compilationsInProgress.get().addLast(nodeType);
Expand All @@ -89,7 +89,7 @@ public <T> CompiledSerDes<T> compiled(JavaType nodeType, Bosk<?> bosk, FieldMode
// Return a CodecWrapper for the codec
LinkedHashMap<String, Parameter> parametersByName = new LinkedHashMap<>();
parameters.forEach(p -> parametersByName.put(p.getName(), p));
return new CodecWrapper<>(codec, bosk, nodeType, parametersByName, moderator);
return new CodecWrapper<>(codec, boskInfo, nodeType, parametersByName, moderator);
} finally {
Type removed = compilationsInProgress.get().removeLast();
assert removed.equals(nodeType);
Expand Down Expand Up @@ -384,7 +384,7 @@ public void generateFieldWrite(String name, ClassBuilder<Codec> cb, LocalVariabl
@EqualsAndHashCode(callSuper = false)
private class CodecWrapper<T> implements CompiledSerDes<T> {
Codec codec;
Bosk<?> bosk;
BoskInfo<?> boskInfo;
JavaType nodeJavaType;
LinkedHashMap<String, Parameter> parametersByName;
FieldModerator moderator;
Expand All @@ -411,7 +411,7 @@ public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOExcepti
// because we need to tolerate the fields arriving in arbitrary order.
Map<String, Object> valueMap = jacksonPlugin.gatherParameterValuesByName(nodeJavaType, parametersByName, moderator, p, ctxt);

List<Object> parameterValues = jacksonPlugin.parameterValueList(nodeJavaType.getRawClass(), valueMap, parametersByName, bosk);
List<Object> parameterValues = jacksonPlugin.parameterValueList(nodeJavaType.getRawClass(), valueMap, parametersByName, boskInfo);

@SuppressWarnings("unchecked")
T result = (T)codec.instantiateFrom(parameterValues);
Expand Down
34 changes: 17 additions & 17 deletions bosk-jackson/src/main/java/io/vena/bosk/jackson/JacksonPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.vena.bosk.Bosk;
import io.vena.bosk.BoskInfo;
import io.vena.bosk.Catalog;
import io.vena.bosk.Entity;
import io.vena.bosk.Identifier;
Expand Down Expand Up @@ -72,21 +72,21 @@
public final class JacksonPlugin extends SerializationPlugin {
private final JacksonCompiler compiler = new JacksonCompiler(this);

public BoskJacksonModule moduleFor(Bosk<?> bosk) {
public BoskJacksonModule moduleFor(BoskInfo<?> boskInfo) {
return new BoskJacksonModule() {
@Override
public void setupModule(SetupContext context) {
context.addSerializers(new BoskSerializers(bosk));
context.addDeserializers(new BoskDeserializers(bosk));
context.addSerializers(new BoskSerializers(boskInfo));
context.addDeserializers(new BoskDeserializers(boskInfo));
}
};
}

private final class BoskSerializers extends Serializers.Base {
private final Bosk<?> bosk;
private final BoskInfo<?> boskInfo;

public BoskSerializers(Bosk<?> bosk) {
this.bosk = bosk;
public BoskSerializers(BoskInfo<?> boskInfo) {
this.boskInfo = boskInfo;
}

@Override
Expand Down Expand Up @@ -122,7 +122,7 @@ public JsonSerializer<?> findSerializer(SerializationConfig config, JavaType typ
}

private JsonSerializer<Object> derivedRecordSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
return derivedRecordSerDes(type, beanDesc, bosk).serializer(config);
return derivedRecordSerDes(type, beanDesc, boskInfo).serializer(config);
}

private JsonSerializer<Catalog<Entity>> catalogSerializer(SerializationConfig config, BeanDescription beanDesc) {
Expand Down Expand Up @@ -210,7 +210,7 @@ public void serialize(SideTable<Entity, Object> value, JsonGenerator gen, Serial

private JsonSerializer<StateTreeNode> stateTreeNodeSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
StateTreeNodeFieldModerator moderator = new StateTreeNodeFieldModerator(type);
return compiler.<StateTreeNode>compiled(type, bosk, moderator).serializer(config);
return compiler.<StateTreeNode>compiled(type, boskInfo, moderator).serializer(config);
}

private JsonSerializer<MapValue<Object>> mapValueSerializer(SerializationConfig config, BeanDescription beanDesc) {
Expand Down Expand Up @@ -243,10 +243,10 @@ public JsonSerializer<?> findMapSerializer(SerializationConfig config, MapType t
}

private final class BoskDeserializers extends Deserializers.Base {
private final Bosk<?> bosk;
private final BoskInfo<?> boskInfo;

public BoskDeserializers(Bosk<?> bosk) {
this.bosk = bosk;
public BoskDeserializers(BoskInfo<?> boskInfo) {
this.boskInfo = boskInfo;
}

@Override
Expand Down Expand Up @@ -284,7 +284,7 @@ public JsonDeserializer<?> findBeanDeserializer(JavaType type, DeserializationCo
}

private JsonDeserializer<Object> derivedRecordDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) {
return derivedRecordSerDes(type, beanDesc, bosk).deserializer(config);
return derivedRecordSerDes(type, beanDesc, boskInfo).deserializer(config);
}

private JsonDeserializer<Catalog<Entity>> catalogDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) {
Expand Down Expand Up @@ -350,7 +350,7 @@ private JsonDeserializer<Reference<?>> referenceDeserializer(JavaType type, Dese
@Override
public Reference<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return bosk.rootReference().then(Object.class, Path.parse(p.getText()));
return boskInfo.rootReference().then(Object.class, Path.parse(p.getText()));
} catch (InvalidTypeException e) {
throw new UnexpectedPathException(e);
}
Expand Down Expand Up @@ -430,7 +430,7 @@ public SideTable<Entity, Object> deserialize(JsonParser p, DeserializationContex

private JsonDeserializer<StateTreeNode> stateTreeNodeDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) {
StateTreeNodeFieldModerator moderator = new StateTreeNodeFieldModerator(type);
return compiler.<StateTreeNode>compiled(type, bosk, moderator).deserializer(config);
return compiler.<StateTreeNode>compiled(type, boskInfo, moderator).deserializer(config);
}

private JsonDeserializer<ListValue<Object>> listValueDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) {
Expand Down Expand Up @@ -539,7 +539,7 @@ private <V> LinkedHashMap<Identifier, V> readMapEntries(JsonParser p, JsonDeseri
private static final JavaType CATALOG_REF_TYPE = TypeFactory.defaultInstance().constructType(new TypeReference<
Reference<Catalog<?>>>() {});

private <T> CompiledSerDes<T> derivedRecordSerDes(JavaType objType, BeanDescription beanDesc, Bosk<?> bosk) {
private <T> CompiledSerDes<T> derivedRecordSerDes(JavaType objType, BeanDescription beanDesc, BoskInfo<?> boskInfo) {
// Check for special cases
Class<?> objClass = objType.getRawClass();
if (ListValue.class.isAssignableFrom(objClass)) { // TODO: MapValue?
Expand All @@ -555,7 +555,7 @@ private <T> CompiledSerDes<T> derivedRecordSerDes(JavaType objType, BeanDescript

// Default DerivedRecord handling
DerivedRecordFieldModerator moderator = new DerivedRecordFieldModerator(objType);
return compiler.compiled(objType, bosk, moderator);
return compiler.compiled(objType, boskInfo, moderator);
}


Expand Down
Loading

0 comments on commit 41a3866

Please sign in to comment.