Skip to content

Commit

Permalink
Change bosk root type to StateTreeNode (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
prdoyle authored Jul 18, 2023
2 parents c769146 + 9eeae2d commit 4d747f5
Show file tree
Hide file tree
Showing 29 changed files with 106 additions and 116 deletions.
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,21 @@ The library works particularly well with Java records.
You can define your state tree's root node as follows:

```
import io.vena.bosk.Entity;
import io.vena.bosk.StateTreeNode;
import io.vena.bosk.Identifier;
public record ExampleState (
Identifier id,
// Add fields here as you need them
String name
) implements Entity {}
) implements StateTreeNode {}
```

You can also use classes, especially if you're using Lombok:

```
@Value
@Accessors(fluent = true)
public class ExampleState implements Entity {
Identifier id;
public class ExampleState implements StateTreeNode {
// Add fields here as you need them
String name;
}
Expand All @@ -95,9 +93,9 @@ Now declare your singleton `Bosk` class to house and manage your application sta

```
import io.vena.bosk.Bosk;
import io.vena.bosk.Identifier;
import io.vena.bosk.Path;
import io.vena.bosk.DriverFactory;
import io.vena.bosk.Reference;
import io.vena.bosk.annotations.ReferencePath;
import io.vena.bosk.exceptions.InvalidTypeException;
@Singleton // You can use your framework's dependency injection for this
Expand All @@ -106,7 +104,7 @@ public class ExampleBosk extends Bosk<ExampleState> {
super(
"ExampleBosk",
ExampleState.class,
new ExampleState(Identifier.from("example"), "world"),
defaultRoot(),
driverFactory());
}
Expand All @@ -115,7 +113,11 @@ public class ExampleBosk extends Bosk<ExampleState> {
@ReferencePath("/name") Reference<String> name();
}
public final Refs refs = buildReferences(Refs.class);
public final Refs refs = rootReference().buildReferences(Refs.class);
private static ExampleState defaultRoot() {
return new ExampleState("world");
}
// Start off simple
private static DriverFactory<ExampleState> driverFactory() {
Expand Down Expand Up @@ -173,25 +175,23 @@ import io.vena.bosk.drivers.mongo.MongoDriver;
import io.vena.bosk.drivers.mongo.MongoDriverSettings;
...
private static DriverFactory<ExampleState> driverFactory() {
MongoClientSettings clientSettings = MongoClientSettings.builder()
.build();
private static DriverFactory<ExampleState> driverFactory() {
// Bosk requires certain client settings to provide the required consistency guarantees
MongoClientSettings clientSettings = MongoClientSettings.builder()
.build();
MongoDriverSettings driverSettings = MongoDriverSettings.builder()
.database("ExampleBoskDB") // Bosks using the same name here will share state
.build();
MongoDriverSettings driverSettings = MongoDriverSettings.builder()
.database("exampleDB")
.build();
// For advanced usage, you'll want to inject this object,
// but for getting started, we can just create one here.
BsonPlugin bsonPlugin = new BsonPlugin();
// For advanced usage, you'll want to inject this object,
// but for getting started, we can just create one here.
BsonPlugin bsonPlugin = new BsonPlugin();
return MongoDriver.factory(
clientSettings,
driverSettings,
bsonPlugin);
}
return MongoDriver.factory(
clientSettings,
driverSettings,
bsonPlugin);
}
```

To run this, you'll need a MongoDB replica set.
Expand Down
10 changes: 4 additions & 6 deletions bosk-core/src/main/java/io/vena/bosk/Bosk.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,9 @@
*
* @author pdoyle
*
* @param <R> The type of the root {@link Entity}. <em>Maintenance note:</em> currently,
* there is no need for the root to be an {@link Entity}, because its
* {@link Entity#id() id} is unused. We're considering changing this type bound to be {@link StateTreeNode}.
* @param <R> The type of the state tree's root node
*/
public class Bosk<R extends Entity> {
public class Bosk<R extends StateTreeNode> {
@Getter private final String name;
@Getter private final Identifier instanceID = Identifier.from(randomUUID().toString());
@Getter private final BoskDriver<R> driver;
Expand Down Expand Up @@ -125,7 +123,7 @@ public Bosk(String name, Type rootType, DefaultRootFunction<R> defaultRootFuncti
rawClass(rootType).cast(this.currentRoot);
}

public interface DefaultRootFunction<RR extends Entity> {
public interface DefaultRootFunction<RR extends StateTreeNode> {
RR apply(Bosk<RR> bosk) throws InvalidTypeException;
}

Expand All @@ -137,7 +135,7 @@ public Bosk(String name, Type rootType, R defaultRoot, DriverFactory<R> driverFa
* 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 Entity> BoskDriver<RR> simpleDriver(@SuppressWarnings("unused") Bosk<RR> bosk, BoskDriver<RR> downstream) {
public static <RR extends StateTreeNode> BoskDriver<RR> simpleDriver(@SuppressWarnings("unused") Bosk<RR> bosk, BoskDriver<RR> downstream) {
return downstream;
}

Expand Down
2 changes: 1 addition & 1 deletion bosk-core/src/main/java/io/vena/bosk/BoskDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* @author pdoyle
*/
public interface BoskDriver<R extends Entity> {
public interface BoskDriver<R extends StateTreeNode> {
/**
* Returns the root object the {@link Bosk} should use as its initial state upon
* returning from its constructor.
Expand Down
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 Entity> {
public interface DriverFactory<R extends StateTreeNode> {
BoskDriver<R> build(Bosk<R> bosk, BoskDriver<R> downstream);
}
2 changes: 1 addition & 1 deletion bosk-core/src/main/java/io/vena/bosk/ReferenceBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

class ReferenceBuilder {
@SuppressWarnings({"unchecked","rawtypes"})
static <T, R extends Entity> T buildReferences(Class<T> refsClass, Bosk<R> bosk) throws InvalidTypeException {
static <T, R extends StateTreeNode> T buildReferences(Class<T> refsClass, Bosk<R> bosk) throws InvalidTypeException {
ClassBuilder<T> cb = new ClassBuilder<>(
"REFS_" + refsClass.getSimpleName(),
refsClass,
Expand Down
4 changes: 2 additions & 2 deletions bosk-core/src/main/java/io/vena/bosk/TypeValidation.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public final class TypeValidation {

public static void validateType(Type rootType) throws InvalidTypeException {
Class<?> rootClass = rawClass(rootType);
if (!Entity.class.isAssignableFrom(rootClass)) { // pdoyle - I see this becoming StateTreeNode in our near future
throw new InvalidTypeException("Bosk root type must be an Entity; " + rootClass.getSimpleName() + " is not an Entity");
if (!StateTreeNode.class.isAssignableFrom(rootClass)) {
throw new InvalidTypeException("Bosk root type must be a StateTreeNode; " + rootClass.getSimpleName() + " is not");
}
validateType(rootType, newSetFromMap(new IdentityHashMap<>()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.vena.bosk.drivers;

import io.vena.bosk.BoskDriver;
import io.vena.bosk.Entity;
import io.vena.bosk.Identifier;
import io.vena.bosk.Reference;
import io.vena.bosk.StateTreeNode;
import io.vena.bosk.exceptions.InvalidTypeException;
import java.io.IOException;
import java.lang.reflect.Type;
Expand All @@ -29,11 +29,11 @@
* @author pdoyle
*/
@RequiredArgsConstructor(access = PROTECTED)
public class BufferingDriver<R extends Entity> implements BoskDriver<R> {
public class BufferingDriver<R extends StateTreeNode> implements BoskDriver<R> {
private final BoskDriver<R> downstream;
private final Deque<Consumer<BoskDriver<R>>> updateQueue = new ConcurrentLinkedDeque<>();

public static <RR extends Entity> BufferingDriver<RR> writingTo(BoskDriver<RR> downstream) {
public static <RR extends StateTreeNode> BufferingDriver<RR> writingTo(BoskDriver<RR> downstream) {
return new BufferingDriver<>(downstream);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.vena.bosk.drivers;

import io.vena.bosk.BoskDriver;
import io.vena.bosk.Entity;
import io.vena.bosk.Identifier;
import io.vena.bosk.Reference;
import io.vena.bosk.StateTreeNode;
import io.vena.bosk.exceptions.InvalidTypeException;
import java.io.IOException;
import java.lang.reflect.Type;
Expand All @@ -12,7 +12,7 @@
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class ForwardingDriver<R extends Entity> implements BoskDriver<R> {
public class ForwardingDriver<R extends StateTreeNode> implements BoskDriver<R> {
private final Iterable<BoskDriver<R>> downstream;

/**
Expand Down
22 changes: 11 additions & 11 deletions bosk-core/src/test/java/io/vena/bosk/BoskConstructorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public class BoskConstructorTest {
void basicProperties_correctValues() {
String name = "Name";
Type rootType = SimpleTypes.class;
Entity root = newEntity();
StateTreeNode root = newEntity();

AtomicReference<BoskDriver<Entity>> driver = new AtomicReference<>();
Bosk<Entity> bosk = new Bosk<Entity>(
AtomicReference<BoskDriver<StateTreeNode>> driver = new AtomicReference<>();
Bosk<StateTreeNode> bosk = new Bosk<StateTreeNode>(
name,
rootType,
__ -> root,
Expand Down Expand Up @@ -100,7 +100,7 @@ void mismatchedRootType_throws() {
@Test
void driverInitialRoot_matches() {
SimpleTypes root = newEntity();
Bosk<Entity> bosk = new Bosk<Entity>(
Bosk<StateTreeNode> bosk = new Bosk<StateTreeNode>(
"By value",
SimpleTypes.class,
__ -> {throw new AssertionError("Shouldn't be called");},
Expand All @@ -114,14 +114,14 @@ void driverInitialRoot_matches() {
void defaultRoot_matches() {
SimpleTypes root = newEntity();
{
Bosk<Entity> valueBosk = new Bosk<>("By value", SimpleTypes.class, root, Bosk::simpleDriver);
Bosk<StateTreeNode> valueBosk = new Bosk<>("By value", SimpleTypes.class, root, Bosk::simpleDriver);
try (val __ = valueBosk.readContext()) {
assertSame(root, valueBosk.rootReference().value());
}
}

{
Bosk<Entity> functionBosk = new Bosk<Entity>("By value", SimpleTypes.class, __ -> root, Bosk::simpleDriver);
Bosk<StateTreeNode> functionBosk = new Bosk<StateTreeNode>("By value", SimpleTypes.class, __ -> root, Bosk::simpleDriver);
try (val __ = functionBosk.readContext()) {
assertSame(root, functionBosk.rootReference().value());
}
Expand All @@ -142,7 +142,7 @@ private static void assertInitialRootThrows(Class<? extends Throwable> expectedT
));
}

private static void assertDefaultRootThrows(Class<? extends Throwable> expectedType, DefaultRootFunction<Entity> defaultRootFunction) {
private static void assertDefaultRootThrows(Class<? extends Throwable> expectedType, DefaultRootFunction<StateTreeNode> defaultRootFunction) {
assertThrows(expectedType, () -> new Bosk<>(
"Throw test",
SimpleTypes.class,
Expand All @@ -152,17 +152,17 @@ private static void assertDefaultRootThrows(Class<? extends Throwable> expectedT
}

@NotNull
private static DriverFactory<Entity> initialRootDriver(InitialRootFunction initialRootFunction) {
return (b,d) -> new ForwardingDriver<Entity>(emptyList()) {
private static DriverFactory<StateTreeNode> initialRootDriver(InitialRootFunction initialRootFunction) {
return (b,d) -> new ForwardingDriver<StateTreeNode>(emptyList()) {
@Override
public Entity initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException {
public StateTreeNode initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException {
return initialRootFunction.get();
}
};
}

interface InitialRootFunction {
Entity get() throws InvalidTypeException, IOException, InterruptedException;
StateTreeNode get() throws InvalidTypeException, IOException, InterruptedException;
}

private static SimpleTypes newEntity() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public interface Refs {

@BeforeEach
void initializeBosk() throws InvalidTypeException {
Root initialRoot = new Root(Identifier.from("root"), 1, Catalog.empty());
Root initialRoot = new Root(1, Catalog.empty());
bosk = new Bosk<>(BOSK_NAME, Root.class, initialRoot, Bosk::simpleDriver);
refs = bosk.buildReferences(Refs.class);
Identifier ernieID = Identifier.from("ernie");
Expand All @@ -90,8 +90,7 @@ void initializeBosk() throws InvalidTypeException {

@Getter @With @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) @RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false) @ToString @FieldNameConstants
public static class Root implements Entity {
Identifier id;
public static class Root implements StateTreeNode {
int version;
Catalog<TestEntity> entities;
}
Expand Down Expand Up @@ -265,7 +264,7 @@ class InvalidRoot extends Root {
final String mutableString;

public InvalidRoot(Identifier id, Catalog<TestEntity> entities, String str) {
super(id, 0xdead, entities);
super(0xdead, entities);
this.mutableString = str;
}
}
Expand All @@ -277,7 +276,7 @@ public InvalidRoot(Identifier id, Catalog<TestEntity> entities, String str) {
void testDriver() {
// This doesn't test the operation of the driver; merely that the right driver is returned
AtomicReference<BoskDriver<Root>> driver = new AtomicReference<>();
Bosk<Root> myBosk = new Bosk<>("My bosk", Root.class, new Root(Identifier.unique("root"), 123, Catalog.empty()), (b,d) -> {
Bosk<Root> myBosk = new Bosk<>("My bosk", Root.class, new Root(123, Catalog.empty()), (b,d) -> {
BoskDriver<Root> bd = new ProxyDriver(d);
driver.set(bd);
return bd;
Expand Down
6 changes: 2 additions & 4 deletions bosk-core/src/test/java/io/vena/bosk/TypeValidationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,7 @@ public enum MyEnum {
}

@Getter @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) @RequiredArgsConstructor
public static final class BoskyTypes implements Entity {
Identifier id;
public static final class BoskyTypes implements StateTreeNode {
Reference<SimpleTypes> ref;
Optional<SimpleTypes> optional;
Catalog<SimpleTypes> catalog;
Expand Down Expand Up @@ -184,8 +183,7 @@ public static final class ReferenceSubclass extends AbstractReference<String> {
}

@Getter @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) @RequiredArgsConstructor
public static final class AllowedFieldNames implements Entity {
Identifier id;
public static final class AllowedFieldNames implements StateTreeNode {
int justLetters;
int someNumbers4U2C;
int hereComesAnUnderscore_toldYouSo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.vena.bosk.Path;
import io.vena.bosk.Reference;
import io.vena.bosk.SideTable;
import io.vena.bosk.StateTreeNode;
import io.vena.bosk.TestEntityBuilder;
import io.vena.bosk.exceptions.InvalidTypeException;
import io.vena.bosk.exceptions.NonexistentReferenceException;
Expand Down Expand Up @@ -257,16 +258,16 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
}
};

Class<? extends Entity> rootClass = SimpleEntity.class;
Class<? extends StateTreeNode> rootClass = SimpleEntity.class;
@SuppressWarnings({"unchecked","rawtypes"})
Class<? extends Entity> differentRootClass = (Class)classLoader.loadClass(SimpleEntity.class.getName());
Class<? extends StateTreeNode> differentRootClass = (Class)classLoader.loadClass(SimpleEntity.class.getName());
assertNotSame(rootClass, differentRootClass);

Identifier rootID = Identifier.from("root");
Entity initialRoot = differentRootClass
StateTreeNode initialRoot = differentRootClass
.getConstructor(Identifier.class)
.newInstance(rootID);
Bosk<Entity> differentBosk = new Bosk<>(
Bosk<StateTreeNode> differentBosk = new Bosk<>(
"Different",
differentRootClass,
initialRoot,
Expand All @@ -275,7 +276,7 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
Reference<Identifier> idRef = differentBosk.reference(Identifier.class, Path.parse(
"/id" ));

try (Bosk<Entity>.ReadContext context = differentBosk.readContext()) {
try (Bosk<?>.ReadContext context = differentBosk.readContext()) {
assertSame(rootID, idRef.valueIfExists());
}
}
Expand Down
Loading

0 comments on commit 4d747f5

Please sign in to comment.