Skip to content

Commit

Permalink
Use sealed types (venasolutions#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
prdoyle authored Feb 18, 2024
2 parents 5a21995 + ee1c8bf commit 1ec93bd
Show file tree
Hide file tree
Showing 21 changed files with 79 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.vena.bosk;

interface AddressableByIdentifier<T> {
sealed interface AddressableByIdentifier<T> permits EnumerableByIdentifier {
/**
* @return The item with the given <code>id</code>, or null if no such item exists.
*/
Expand Down
8 changes: 4 additions & 4 deletions bosk-core/src/main/java/io/vena/bosk/Bosk.java
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,7 @@ private Dereferencer compileVettedPath(Path path) {
}
}

private final class RootRef extends DefiniteReference<R> implements RootReference<R> {
final class RootRef extends DefiniteReference<R> implements RootReference<R> {
public RootRef(Type targetType) {
super(Path.empty(), targetType);
}
Expand Down Expand Up @@ -848,7 +848,7 @@ public <T> T buildReferences(Class<T> refsClass) throws InvalidTypeException {

@Getter
@RequiredArgsConstructor
private abstract class ReferenceImpl<T> implements Reference<T> {
sealed abstract class ReferenceImpl<T> implements Reference<T> {
protected final Path path;
protected final Type targetType;

Expand Down Expand Up @@ -933,7 +933,7 @@ public final boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof @SuppressWarnings({"rawtypes"})Reference other)) {
if (!(obj instanceof Reference<?> other)) {
return false;
}

Expand All @@ -960,7 +960,7 @@ public final String toString() {
/**
* A {@link Reference} with no unbound parameters.
*/
private class DefiniteReference<T> extends ReferenceImpl<T> {
private sealed class DefiniteReference<T> extends ReferenceImpl<T> {
@Getter(lazy = true) private final Dereferencer dereferencer = compileVettedPath(path);

public DefiniteReference(Path path, Type targetType) {
Expand Down
4 changes: 3 additions & 1 deletion bosk-core/src/main/java/io/vena/bosk/CatalogReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
* but avoids throwing {@link InvalidTypeException} from some methods that are known
* to be type-safe, like {@link #then(Identifier) then}.
*/
public interface CatalogReference<E extends Entity> extends Reference<Catalog<E>> {
public sealed interface CatalogReference<E extends Entity>
extends Reference<Catalog<E>>
permits ReferenceUtils.CatalogRef {
/**
* @return {@link Reference} to the catalog entry with the given <code>id</code>.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

import java.util.List;

public interface EnumerableByIdentifier<T> extends AddressableByIdentifier<T> {
sealed public interface EnumerableByIdentifier<T> extends AddressableByIdentifier<T> permits Catalog, SideTable {
List<Identifier> ids();
}
4 changes: 3 additions & 1 deletion bosk-core/src/main/java/io/vena/bosk/ListingReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
* but avoids throwing {@link InvalidTypeException} from some methods that are known
* to be type-safe, like {@link #then(Identifier) then}.
*/
public interface ListingReference<E extends Entity> extends Reference<Listing<E>> {
public sealed interface ListingReference<E extends Entity>
extends Reference<Listing<E>>
permits ReferenceUtils.ListingRef {
/**
* @return {@link Reference} to the {@link ListingEntry} representing the entry with the given id.
*/
Expand Down
8 changes: 7 additions & 1 deletion bosk-core/src/main/java/io/vena/bosk/Reference.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@
*
* @param <T> The type of object being referenced.
*/
public interface Reference<T> {
public sealed interface Reference<T> permits
CatalogReference,
ListingReference,
RootReference,
SideTableReference,
Bosk.ReferenceImpl
{
Path path();

/**
Expand Down
5 changes: 4 additions & 1 deletion bosk-core/src/main/java/io/vena/bosk/RootReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
* A {@link Reference} to the root node of a particular bosk's state tree,
* doubling as the main way to acquire/construct other references.
*/
public interface RootReference<R> extends Reference<R> {
public sealed interface RootReference<R>
extends Reference<R>
permits Bosk.RootRef {

/**
* Dynamically generates an object that can return {@link Reference}s
* as specified by methods annotated with {@link ReferencePath}.
Expand Down
4 changes: 3 additions & 1 deletion bosk-core/src/main/java/io/vena/bosk/SideTableReference.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
* but avoids throwing {@link InvalidTypeException} from some methods that are known
* to be type-safe, like {@link #then(Identifier) then}.
*/
public interface SideTableReference<K extends Entity, V> extends Reference<SideTable<K,V>> {
public sealed interface SideTableReference<K extends Entity, V>
extends Reference<SideTable<K,V>>
permits ReferenceUtils.SideTableRef {
/**
* @return {@link Reference} to the value of the entry whose key has the given <code>id</code>.
*/
Expand Down
24 changes: 0 additions & 24 deletions bosk-core/src/test/java/io/vena/bosk/TypeValidationTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.vena.bosk;

import io.vena.bosk.AbstractBoskTest.AbstractReference;
import io.vena.bosk.annotations.DerivedRecord;
import io.vena.bosk.annotations.DeserializationPath;
import io.vena.bosk.annotations.Enclosing;
Expand Down Expand Up @@ -74,7 +73,6 @@ void testValidRootClasses(Class<?> rootClass) throws InvalidTypeException {
ListValueSubclassWithTwoConstructors.class,
ListValueSubclassWithWrongConstructor.class,
ReferenceToReference.class,
ReferenceWithMutableField.class,
SelfNonReference.class,
SelfWrongType.class,
SelfSubtype.class,
Expand Down Expand Up @@ -158,7 +156,6 @@ public static final class BoskyTypes implements StateTreeNode {
ListValue<String> listValueOfStrings;
ListValue<ValueStruct> listValueOfStructs;
ListValueSubclass listValueSubclass;
ReferenceSubclass referenceSubclass;
}

@Value
Expand All @@ -177,11 +174,6 @@ public static final class ListValueSubclass extends ListValue<String> {
}
}

@RequiredArgsConstructor
public static final class ReferenceSubclass extends AbstractReference<String> {
final String validField;
}

@Getter @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) @RequiredArgsConstructor
public static final class AllowedFieldNames implements StateTreeNode {
int justLetters;
Expand Down Expand Up @@ -750,22 +742,6 @@ public static void testException(InvalidTypeException e) {
}
}

@Getter @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
@RequiredArgsConstructor
public static final class ReferenceWithMutableField implements Entity {
Identifier id;
BadReferenceSubclass ref;

public static void testException(InvalidTypeException e) {
assertThat(e.getMessage(), containsString("ReferenceWithMutableField.ref"));
assertThat(e.getMessage(), containsString("BadReferenceSubclass.mutableField"));
}
}

public static final class BadReferenceSubclass extends AbstractReference<String> {
String mutableField;
}

/**
* Catches a case of over-exuberant memoization we were doing, where we'd
* only validate each class once.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import static io.vena.bosk.drivers.mongo.Formatter.REVISION_ZERO;

@RequiredArgsConstructor
abstract class AbstractFormatDriver<R extends StateTreeNode> implements FormatDriver<R> {
non-sealed abstract class AbstractFormatDriver<R extends StateTreeNode> implements FormatDriver<R> {
final RootReference<R> rootRef;
final Formatter formatter;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

import static java.util.Objects.requireNonNull;

/**
* De-interlaces change stream events associated with different transactions.
*/
class Demultiplexer {
private final Map<TransactionID, List<ChangeStreamDocument<BsonDocument>>> transactionsInProgress = new ConcurrentHashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import org.slf4j.LoggerFactory;

@RequiredArgsConstructor
class DisconnectedDriver<R extends StateTreeNode> implements FormatDriver<R> {
final class DisconnectedDriver<R extends StateTreeNode> implements FormatDriver<R> {
private final Throwable reason;
@Override
public <T> void submitReplacement(Reference<T> target, T newValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
* Implementing {@link #initialRoot} or {@link #refurbish()}
* </li></ol>
*/
interface FormatDriver<R extends StateTreeNode> extends MongoDriver<R> {
sealed interface FormatDriver<R extends StateTreeNode>
extends MongoDriver<R>
permits AbstractFormatDriver, DisconnectedDriver {
void onEvent(ChangeStreamDocument<BsonDocument> event) throws UnprocessableEventException;

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

/**
* Indicates that an error was found in a
* {@link MongoDriverSettings.DatabaseFormat DatabaseFormat} object.
*/
public class FormatMisconfigurationException extends IllegalArgumentException {
public FormatMisconfigurationException(String s) {
super(s);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
* are delegated to a {@link FormatDriver} object that can be swapped out dynamically
* as the database evolves.
*/
class MainDriver<R extends StateTreeNode> implements MongoDriver<R> {
final class MainDriver<R extends StateTreeNode> implements MongoDriver<R> {
private final Bosk<R> bosk;
private final ChangeReceiver receiver;
private final MongoDriverSettings driverSettings;
Expand Down
10 changes: 10 additions & 0 deletions bosk-mongo/src/main/java/io/vena/bosk/drivers/mongo/Manifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
import io.vena.bosk.drivers.mongo.MongoDriverSettings.DatabaseFormat;
import java.util.Optional;

/**
* Defines the format of the manifest document, which is stored in the database
* to describe the database contents.
*/
public record Manifest(
Integer version,
Optional<EmptyNode> sequoia,
Optional<PandoFormat> pando
) implements StateTreeNode {
public Manifest {
if (sequoia.isPresent() == pando.isPresent()) {
throw new IllegalArgumentException("Exactly one format (sequoia or pando) must be specified in manifest");
}
}

public record EmptyNode() implements StateTreeNode { }

public static Manifest forSequoia() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@
import io.vena.bosk.drivers.mongo.status.MongoStatus;
import java.io.IOException;

public interface MongoDriver<R extends StateTreeNode> extends BoskDriver<R> {
/**
* A {@link BoskDriver} that maintains the bosk state in a MongoDB database.
* Multiple bosks, potentially in multiple separate processes,
* can be configured to use the same database, thereby creating a replica set
* with all bosks sharing the same state and receiving updates from each other.
* <p>
*
* For convenience, if the database does not exist at the time of initialization,
* this driver will create it and populate it with the state returned by calling
* {@link BoskDriver#initialRoot} on the downstream driver.
*/
public sealed interface MongoDriver<R extends StateTreeNode>
extends BoskDriver<R>
permits MainDriver, FormatDriver {

/**
* Deserializes and re-serializes the entire bosk contents,
* thus updating the database to match the current serialized format.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Indicates that the database has been found to be in a state that
* could be considered "uninitialized", in the sense that we are permitted
* to respond by automatically initializing the database.
* <p>
* This is not the same as discovering that the database is simply in some unexpected state,
* in which case other exceptions may be thrown and the driver would likely disconnect
* rather than overwrite the database contents.
* Rather, we must have a fairly high degree of certainty that we're ok to start fresh.
*/
class UninitializedCollectionException extends Exception {
public UninitializedCollectionException() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.mongodb.client.model.changestream.OperationType;

/**
* Indicate that no {@link FormatDriver} could cope with a particular
* Indicates that no {@link FormatDriver} could cope with a particular
* change stream event. The framework responds with a (potentially expensive)
* reload operation that avoids attempting to re-process that event;
* in other words, using resume tokens would never be appropriate for these.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package io.vena.bosk.drivers.mongo;

/**
* Indicates that we are unable to interpret the contents of
* the manifest document found in the database.
*/
class UnrecognizedFormatException extends Exception {
public UnrecognizedFormatException(String message) {
super(message);
Expand Down
20 changes: 0 additions & 20 deletions lib-testing/src/main/java/io/vena/bosk/AbstractBoskTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
import io.vena.bosk.annotations.Enclosing;
import io.vena.bosk.annotations.Self;
import io.vena.bosk.exceptions.InvalidTypeException;
import java.lang.reflect.Type;
import java.util.Optional;
import java.util.function.BiConsumer;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.With;
Expand Down Expand Up @@ -127,24 +125,6 @@ public enum TestEnum {
NOT_SO_OK
}

protected static abstract class AbstractReference<T> implements Reference<T> {
@Override public Path path() { return null; }
@Override public Type targetType() { return null; }
@Override public Class<T> targetClass() { return null; }
@Override public T valueIfExists() { return null; }
@Override public void forEachValue(BiConsumer<T, BindingEnvironment> action, BindingEnvironment existingEnvironment) { }

@Override public RootReference<?> root() { return null; }
@Override public <U> Reference<U> then(Class<U> targetClass, String... segments) { return null; }
@Override public <E extends Entity> CatalogReference<E> thenCatalog(Class<E> entryClass, String... segments) { return null; }
@Override public <E extends Entity> ListingReference<E> thenListing(Class<E> entryClass, String... segments) { return null; }
@Override public <K extends Entity, V> SideTableReference<K, V> thenSideTable(Class<K> keyClass, Class<V> valueClass, String... segments) { return null; }
@Override public <TT> Reference<Reference<TT>> thenReference(Class<TT> targetClass, String... segments) { return null; }
@Override public <TT> Reference<TT> enclosingReference(Class<TT> targetClass) { return null; }
@Override public Reference<T> boundBy(BindingEnvironment bindings) { return null; }
@Override public <TT> Reference<TT> truncatedTo(Class<TT> targetClass, int remainingSegments) { return null; }
}

protected static Bosk<TestRoot> setUpBosk(DriverFactory<TestRoot> driverFactory) {
return new Bosk<TestRoot>("Test", TestRoot.class, AbstractRoundTripTest::initialRoot, driverFactory);
}
Expand Down

0 comments on commit 1ec93bd

Please sign in to comment.