Skip to content

Commit

Permalink
Fold MirroringDriver into ReplicaSet.
Browse files Browse the repository at this point in the history
Also simplify ForwardingDriver by giving it just one single downstream
driver. We were never using two or more anyway, and for the zero case,
we've made a new NoOpDriver.
  • Loading branch information
prdoyle committed Aug 18, 2024
1 parent 29c3aab commit ea6a481
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 142 deletions.
46 changes: 16 additions & 30 deletions bosk-core/src/main/java/works/bosk/drivers/ForwardingDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,58 @@

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import works.bosk.BoskDriver;
import works.bosk.Identifier;
import works.bosk.Reference;
import works.bosk.StateTreeNode;
import works.bosk.exceptions.InvalidTypeException;

/**
* Implements all {@link BoskDriver} methods by simply calling the corresponding
* methods on another driver. Useful for overriding one or two methods while leaving
* the rest unchanged.
* <p>
* Unlike {@link ReplicaSet}, this does not automatically fix up the references to
* point to the right bosk. The references must already be from the downstream bosk.
*/
@RequiredArgsConstructor
public class ForwardingDriver<R extends StateTreeNode> implements BoskDriver<R> {
private final Iterable<BoskDriver<R>> downstream;
protected final BoskDriver<R> downstream;

/**
* @return The result of calling <code>initialRoot</code> on the first downstream driver
* that doesn't throw {@link UnsupportedOperationException}. Other exceptions are propagated as-is,
* and abort the initialization immediately.
*/
@Override
public R initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException {
List<UnsupportedOperationException> exceptions = new ArrayList<>();
for (BoskDriver<R> d: downstream) {
try {
return d.initialRoot(rootType);
} catch (UnsupportedOperationException e) {
exceptions.add(e);
}
}

// Oh dear.
UnsupportedOperationException exception = new UnsupportedOperationException("Unable to forward initialRoot request");
exceptions.forEach(exception::addSuppressed);
throw exception;
return downstream.initialRoot(rootType);
}

@Override
public <T> void submitReplacement(Reference<T> target, T newValue) {
downstream.forEach(d -> d.submitReplacement(target, newValue));
downstream.submitReplacement(target, newValue);
}

@Override
public <T> void submitConditionalReplacement(Reference<T> target, T newValue, Reference<Identifier> precondition, Identifier requiredValue) {
downstream.forEach(d -> d.submitConditionalReplacement(target, newValue, precondition, requiredValue));
downstream.submitConditionalReplacement(target, newValue, precondition, requiredValue);
}

@Override
public <T> void submitInitialization(Reference<T> target, T newValue) {
downstream.forEach(d -> d.submitInitialization(target, newValue));
downstream.submitInitialization(target, newValue);
}

@Override
public <T> void submitDeletion(Reference<T> target) {
downstream.forEach(d -> d.submitDeletion(target));
downstream.submitDeletion(target);
}

@Override
public <T> void submitConditionalDeletion(Reference<T> target, Reference<Identifier> precondition, Identifier requiredValue) {
downstream.forEach(d -> d.submitConditionalDeletion(target, precondition, requiredValue));
downstream.submitConditionalDeletion(target, precondition, requiredValue);
}

@Override
public void flush() throws InterruptedException, IOException {
for (BoskDriver<R> d: downstream) {
// Note that exceptions from a downstream flush() will abort this loop
d.flush();
}
downstream.flush();
}

@Override
Expand Down
89 changes: 0 additions & 89 deletions bosk-core/src/main/java/works/bosk/drivers/MirroringDriver.java

This file was deleted.

28 changes: 28 additions & 0 deletions bosk-core/src/main/java/works/bosk/drivers/NoOpDriver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package works.bosk.drivers;

import java.io.IOException;
import java.lang.reflect.Type;
import works.bosk.BoskDriver;
import works.bosk.DriverFactory;
import works.bosk.Identifier;
import works.bosk.Reference;
import works.bosk.StateTreeNode;
import works.bosk.exceptions.InvalidTypeException;

public class NoOpDriver<R extends StateTreeNode> implements BoskDriver<R> {
public static <RR extends StateTreeNode> DriverFactory<RR> factory() {
return (b,d) -> new NoOpDriver<>();
}

@Override
public R initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException {
throw new UnsupportedOperationException();
}

@Override public <T> void submitReplacement(Reference<T> target, T newValue) { }
@Override public <T> void submitConditionalReplacement(Reference<T> target, T newValue, Reference<Identifier> precondition, Identifier requiredValue) { }
@Override public <T> void submitInitialization(Reference<T> target, T newValue) { }
@Override public <T> void submitDeletion(Reference<T> target) { }
@Override public <T> void submitConditionalDeletion(Reference<T> target, Reference<Identifier> precondition, Identifier requiredValue) { }
@Override public void flush() throws IOException, InterruptedException { }
}
65 changes: 60 additions & 5 deletions bosk-core/src/main/java/works/bosk/drivers/ReplicaSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import works.bosk.Bosk;
import works.bosk.BoskDriver;
import works.bosk.DriverFactory;
import works.bosk.Identifier;
Expand All @@ -21,11 +22,21 @@
* Note that this isn't used for true distributed replica sets with one bosk in each JVM process,
* but rather for constructing a local replica set within a single JVM process.
* <p>
* <em>Evolution note</em>: This kind of subsumes the functionality of both {@link ForwardingDriver}
* and {@link MirroringDriver}. Seems like this class could either use or replace those.
* Perhaps {@link ForwardingDriver} should do the {@link Replica#correspondingReference} logic that
* {@link MirroringDriver} does, making the latter unnecessary; and then this class could
* simply be a {@link ForwardingDriver} whose list of downstream drivers is mutable.
* The primary way to use this class is to instantiate a {@code new ReplicaSet()},
* then construct any number of bosks using {@link #driverFactory()}.
* All the resulting bosks will be in the replica set, and more can be added dynamically.
* <p>
* There are also some factory methods that simplify some special use cases.
*
* <ol>
* <li>
* Use {@link #mirroringTo} to mirror changes from a primary bosk to some number
* of secondary ones.
* </li>
* <li>
* Use {@link #redirectingTo} just to get a driver that can accept references to the wrong bosk.
* </li>
* </ol>
*/
public class ReplicaSet<R extends StateTreeNode> {
final Queue<Replica<R>> replicas = new ConcurrentLinkedQueue<>();
Expand All @@ -43,8 +54,52 @@ public DriverFactory<R> driverFactory() {
};
}

/**
* Causes updates to be applied to {@code mirrors} and to the downstream driver.
* <p>
* Assuming the returned factory is only used once, this has the effect of creating
* a fixed replica set to which new replicas can't be added dynamically;
* but the returned factory can be used multiple times.
*/
@SafeVarargs
public static <RR extends StateTreeNode> DriverFactory<RR> mirroringTo(Bosk<RR>... mirrors) {
var replicaSet = new ReplicaSet<RR>();
for (var m: mirrors) {
BoskDriver<RR> downstream = m.driver();
replicaSet.replicas.add(new Replica<>(
m.rootReference(),
new ForwardingDriver<>(downstream) {
@Override
public RR initialRoot(Type rootType) {
throw new UnsupportedOperationException("Don't use initialRoot from " + m);
}

@Override
public String toString() {
return downstream.toString() + " (minus initial state)";
}
}
));
}
return replicaSet.driverFactory();
}

/**
* Causes updates to be applied only to <code>other</code>.
*/
public static <RR extends StateTreeNode> BoskDriver<RR> redirectingTo(Bosk<RR> other) {
// A ReplicaSet with only the one replica
return new ReplicaSet<RR>()
.driverFactory().build(
other,
other.driver()
);
}

final class BroadcastShim implements BoskDriver<R> {
/**
* TODO: should return the current state somehow. For now, I guess it's best to attach all the bosks before submitting any updates.
*
* @return The result of calling <code>initialRoot</code> on the first downstream driver
* that doesn't throw {@link UnsupportedOperationException}. Other exceptions are propagated as-is,
* and abort the initialization immediately.
Expand Down
9 changes: 4 additions & 5 deletions bosk-core/src/test/java/works/bosk/BoskConstructorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
import works.bosk.TypeValidationTest.MutableField;
import works.bosk.TypeValidationTest.SimpleTypes;
import works.bosk.drivers.ForwardingDriver;
import works.bosk.drivers.NoOpDriver;
import works.bosk.exceptions.InvalidTypeException;

import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -37,8 +36,8 @@ void basicProperties_correctValues() {
name,
rootType,
_ -> root,
(b,d)-> {
driver.set(new ForwardingDriver<>(singleton(d)));
(_, d)-> {
driver.set(new ForwardingDriver<>(d));
return driver.get();
});

Expand Down Expand Up @@ -153,7 +152,7 @@ private static void assertDefaultRootThrows(Class<? extends Throwable> expectedT

@NotNull
private static DriverFactory<StateTreeNode> initialRootDriver(InitialRootFunction initialRootFunction) {
return (b,d) -> new ForwardingDriver<StateTreeNode>(emptyList()) {
return (_, _) -> new NoOpDriver<>() {
@Override
public StateTreeNode initialRoot(Type rootType) throws InvalidTypeException, IOException, InterruptedException {
return initialRootFunction.get();
Expand Down
17 changes: 9 additions & 8 deletions bosk-core/src/test/java/works/bosk/DriverStackTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

import org.junit.jupiter.api.Test;
import works.bosk.drivers.ForwardingDriver;
import works.bosk.drivers.NoOpDriver;

import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;

class DriverStackTest {
final BoskDriver<AbstractBoskTest.TestEntity> baseDriver = new ForwardingDriver<>(emptySet());
final BoskDriver<AbstractBoskTest.TestEntity> baseDriver = new NoOpDriver<>();

@Test
void emptyStack_returnsDownstream() {
Expand All @@ -25,8 +24,8 @@ void stackedDrivers_correctOrder() {
);

TestDriver<AbstractBoskTest.TestEntity> firstDriver = (TestDriver<AbstractBoskTest.TestEntity>) stack.build(null, baseDriver);
TestDriver<AbstractBoskTest.TestEntity> secondDriver = (TestDriver<AbstractBoskTest.TestEntity>) firstDriver.downstream;
BoskDriver<AbstractBoskTest.TestEntity> thirdDriver = secondDriver.downstream;
TestDriver<AbstractBoskTest.TestEntity> secondDriver = (TestDriver<AbstractBoskTest.TestEntity>) firstDriver.downstream();
BoskDriver<AbstractBoskTest.TestEntity> thirdDriver = secondDriver.downstream();

assertEquals("first", firstDriver.name);
assertEquals("second", secondDriver.name);
Expand All @@ -35,12 +34,14 @@ void stackedDrivers_correctOrder() {

static class TestDriver<R extends Entity> extends ForwardingDriver<R> {
final String name;
final BoskDriver<R> downstream;

public TestDriver(String name, BoskDriver<R> downstream) {
super(singletonList(downstream));
super(downstream);
this.name = name;
this.downstream = downstream;
}

BoskDriver<R> downstream() {
return downstream;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import org.junit.jupiter.api.BeforeEach;

import static java.util.Collections.singletonList;

public class ForwardingDriverConformanceTest extends DriverConformanceTest {

@BeforeEach
void setupDriverFactory() {
driverFactory = (b,d)-> new ForwardingDriver<>(singletonList(d));
driverFactory = (_, d)-> new ForwardingDriver<>(d);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected void setupBosksAndReferences(DriverFactory<TestEntity> driverFactory)

// This is the bosk we're testing
bosk = new Bosk<TestEntity>(boskName("Test", 1), TestEntity.class, AbstractDriverTest::initialRoot, DriverStack.of(
MirroringDriver.targeting(canonicalBosk),
ReplicaSet.mirroringTo(canonicalBosk),
DriverStateVerifier.wrap(driverFactory, TestEntity.class, AbstractDriverTest::initialRoot)
));
driver = bosk.driver();
Expand Down
Loading

0 comments on commit ea6a481

Please sign in to comment.