Skip to content

Commit 63995f6

Browse files
committed
Fix and test nested side tables.
Nested side tables are unique: - A container whose values are not entities - A container whose values are containers
1 parent cbeaa0c commit 63995f6

File tree

10 files changed

+78
-30
lines changed

10 files changed

+78
-30
lines changed

bosk-jackson/src/main/java/works/bosk/jackson/JsonNodeDriver.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package works.bosk.jackson;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
43
import com.fasterxml.jackson.databind.JsonNode;
54
import com.fasterxml.jackson.databind.ObjectMapper;
65
import java.io.IOException;
@@ -110,11 +109,7 @@ private <T> void doReplacement(NodeInfo nodeInfo, String lastSegment, T newValue
110109

111110
void traceCurrentState(String description) {
112111
if (LOGGER.isTraceEnabled()) {
113-
try {
114-
LOGGER.trace("State {} {}:\n{}", ++updateNumber, description, mapper.writerWithDefaultPrettyPrinter().writeValueAsString(currentRoot));
115-
} catch (JsonProcessingException e) {
116-
throw new IllegalStateException(e);
117-
}
112+
LOGGER.trace("State {} {}:\n{}", ++updateNumber, description, currentRoot.toPrettyString());
118113
}
119114
}
120115

bosk-jackson/src/main/java/works/bosk/jackson/JsonNodeSurgeon.java

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import static java.util.Objects.requireNonNull;
2222
import static works.bosk.jackson.JsonNodeSurgeon.ReplacementStyle.ID_ONLY;
2323
import static works.bosk.jackson.JsonNodeSurgeon.ReplacementStyle.PLAIN;
24-
import static works.bosk.jackson.JsonNodeSurgeon.ReplacementStyle.WRAPPED_ENTITY;
24+
import static works.bosk.jackson.JsonNodeSurgeon.ReplacementStyle.WRAPPED;
2525

2626
/**
2727
* Utilities to find and modify the {@link JsonNode} corresponding to
@@ -56,7 +56,7 @@ record ArrayElement(ArrayNode parent, int elementIndex) implements NodeLocation
5656
* <p>
5757
* (Note that the parent {@link JsonNode} is not always the node corresponding
5858
* to the {@link Reference#enclosingReference enclosing reference}.
59-
* They're two related but different concepts. See {@link ReplacementStyle#WRAPPED_ENTITY WRAPPED_ENTITY}.)
59+
* They're two related but different concepts. See {@link ReplacementStyle#WRAPPED WRAPPED}.)
6060
*/
6161
record NonexistentParent() implements NodeLocation {}
6262
}
@@ -78,8 +78,7 @@ public enum ReplacementStyle {
7878

7979
/**
8080
* Use an {@link ObjectNode} having a single member whose name is the desired
81-
* entity's {@link works.bosk.Entity#id id} and whose value is the serialized
82-
* form of the desired entity.
81+
* last segment of the {@link Reference} pointing at the desired node,
8382
*
8483
* <p>
8584
* Note that this is the style that causes the {@link NodeInfo#valueLocation value location}
@@ -93,7 +92,7 @@ public enum ReplacementStyle {
9392
* something other than {@link NonexistentParent NonexistentParent})
9493
* and will indicate where the wrapper object should be.
9594
*/
96-
WRAPPED_ENTITY,
95+
WRAPPED,
9796
}
9897

9998
/**
@@ -125,8 +124,8 @@ static NodeInfo idOnly(NodeLocation theLocation) {
125124
return new NodeInfo(theLocation, theLocation, ID_ONLY);
126125
}
127126

128-
static NodeInfo wrappedEntity(NodeLocation valueLocation, NodeLocation replacementLocation) {
129-
return new NodeInfo(valueLocation, replacementLocation, WRAPPED_ENTITY);
127+
static NodeInfo wrapped(NodeLocation valueLocation, NodeLocation replacementLocation) {
128+
return new NodeInfo(valueLocation, replacementLocation, WRAPPED);
130129
}
131130
}
132131

@@ -174,30 +173,33 @@ public NodeInfo nodeInfo(JsonNode doc, Reference<?> ref) {
174173
return NodeInfo.idOnly(new ArrayElement(ids, ids.size()));
175174
} else if (Catalog.class.isAssignableFrom(enclosingRef.targetClass())) {
176175
if (parent == null) {
177-
return NodeInfo.wrappedEntity(new NonexistentParent(), new NonexistentParent());
176+
return NodeInfo.wrapped(new NonexistentParent(), new NonexistentParent());
178177
} else {
179178
String entryID = ref.path().lastSegment();
180179
NodeLocation element = findArrayElementWithId(parent, entryID);
181180
JsonNode elementNode = getNode(element, doc);
182181
if (elementNode == null) {
183182
// Doesn't exist yet
184-
return NodeInfo.wrappedEntity (new NonexistentParent(), element);
183+
return NodeInfo.wrapped(new NonexistentParent(), element);
185184
} else {
186-
return NodeInfo.wrappedEntity (getMapEntryValueLocation(elementNode, entryID), element);
185+
return NodeInfo.wrapped(getMapEntryValueLocation(elementNode, entryID), element);
187186
}
188187
}
189188
} else if (SideTable.class.isAssignableFrom(enclosingRef.targetClass())) {
190189
if (parent == null) {
191-
return NodeInfo.wrappedEntity(new NonexistentParent(), new NonexistentParent());
190+
NodeLocation valueLocation = new NonexistentParent();
191+
return NodeInfo.wrapped(valueLocation, new NonexistentParent());
192192
} else {
193193
String entryID = ref.path().lastSegment();
194194
NodeLocation element = findArrayElementWithId(parent.get("valuesById"), entryID);
195195
JsonNode elementNode = getNode(element, doc);
196196
if (elementNode == null) {
197197
// Doesn't exist yet
198-
return NodeInfo.wrappedEntity(new NonexistentParent(), element);
198+
NodeLocation valueLocation = new NonexistentParent();
199+
return NodeInfo.wrapped(valueLocation, element);
199200
} else {
200-
return NodeInfo.wrappedEntity(getMapEntryValueLocation(elementNode, entryID), element);
201+
NodeLocation valueLocation = getMapEntryValueLocation(elementNode, entryID);
202+
return NodeInfo.wrapped(valueLocation, element);
201203
}
202204
}
203205
}
@@ -219,7 +221,7 @@ private static NodeLocation findArrayElementWithId(JsonNode entries, String id)
219221
ObjectNode entryObject = (ObjectNode) entries.get(i);
220222
var properties = entryObject.properties();
221223
if (properties.size() != 1) {
222-
throw new NotYetImplementedException(properties.toString());
224+
throw new NotYetImplementedException("SO MANY PROPERTIES" + properties);
223225
}
224226
Map.Entry<String, JsonNode> entry = properties.iterator().next();
225227
if (id.equals(entry.getKey())) {
@@ -269,11 +271,8 @@ public JsonNode replacementNode(NodeInfo nodeInfo, String lastSegment, Supplier<
269271
return switch (nodeInfo.replacementStyle()) {
270272
case PLAIN -> newValue.get();
271273
case ID_ONLY -> new TextNode(lastSegment);
272-
case WRAPPED_ENTITY -> {
273-
JsonNode entityNode = newValue.get();
274-
String id = entityNode.get("id").textValue();
275-
yield new ObjectNode(JsonNodeFactory.instance, Map.of(id, entityNode));
276-
}
274+
case WRAPPED -> new ObjectNode(JsonNodeFactory.instance)
275+
.set(lastSegment, newValue.get());
277276
};
278277
}
279278

bosk-jackson/src/test/java/works/bosk/jackson/JsonNodeSurgeonTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,14 @@ void catalogEntry() throws IOException, InterruptedException {
152152
JsonNode catalogArray = doc.get("catalog");
153153
JsonNode catalogEntry = catalogArray.get(0);
154154
{
155-
NodeInfo expected = NodeInfo.wrappedEntity(
155+
NodeInfo expected = NodeInfo.wrapped(
156156
new ObjectMember((ObjectNode) catalogEntry, "testEntry"),
157157
new ArrayElement((ArrayNode) catalogArray, 0));
158158
NodeInfo actual = surgeon.nodeInfo(doc, refs.catalogEntry(id));
159159
assertEquals(expected, actual);
160160
}
161161
{
162-
NodeInfo expected = NodeInfo.wrappedEntity(
162+
NodeInfo expected = NodeInfo.wrapped(
163163
new NonexistentParent(),
164164
new ArrayElement((ArrayNode) catalogArray, 1));
165165
NodeInfo actual = surgeon.nodeInfo(doc, refs.catalogEntry(Identifier.from("NONEXISTENT")));
@@ -212,14 +212,14 @@ void sideTableEntry() throws IOException, InterruptedException {
212212
JsonNode valuesById = doc.get("sideTable").get("valuesById");
213213
JsonNode entry = valuesById.get(0);
214214
{
215-
NodeInfo expected = NodeInfo.wrappedEntity(
215+
NodeInfo expected = NodeInfo.wrapped(
216216
new ObjectMember((ObjectNode) entry, "testKey"),
217217
new ArrayElement((ArrayNode) valuesById, 0));
218218
NodeInfo actual = surgeon.nodeInfo(doc, refs.sideTableEntry(id));
219219
assertEquals(expected, actual);
220220
}
221221
{
222-
NodeInfo expected = NodeInfo.wrappedEntity(
222+
NodeInfo expected = NodeInfo.wrapped(
223223
new NonexistentParent(),
224224
new ArrayElement((ArrayNode) valuesById, 1));
225225
NodeInfo actual = surgeon.nodeInfo(doc, refs.sideTableEntry(Identifier.from("NONEXISTENT")));

bosk-mongo/src/test/java/works/bosk/drivers/mongo/AbstractMongoDriverTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public TestEntity initialRootWithEmptyCatalog(Bosk<TestEntity> testEntityBosk) t
122122
Catalog.empty(),
123123
Listing.of(refs.catalog(), entity123),
124124
SideTable.empty(refs.catalog()),
125+
SideTable.empty(refs.catalog()),
125126
TaggedUnion.of(new TestEntity.StringCase(rootID.toString())),
126127
Optional.empty()
127128
);

bosk-mongo/src/test/java/works/bosk/drivers/mongo/MongoDriverConformanceTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ static Stream<ParameterSet> parameters() {
3939
Stream.of(
4040
PandoFormat.oneBigDocument(),
4141
PandoFormat.withGraftPoints("/catalog", "/sideTable"), // Basic
42+
PandoFormat.withGraftPoints("/nestedSideTable"), // Documents are themselves side tables
43+
PandoFormat.withGraftPoints("/nestedSideTable/-x-"), // Graft points are side table entries
4244
PandoFormat.withGraftPoints("/catalog/-x-/sideTable", "/sideTable/-x-/catalog", "/sideTable/-x-/sideTable/-y-/catalog"), // Nesting, parameters
4345
PandoFormat.withGraftPoints("/sideTable/-x-/sideTable/-y-/catalog"), // Multiple parameters in the not-separated part
4446
SEQUOIA

bosk-mongo/src/test/java/works/bosk/drivers/mongo/MongoDriverSpecialTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ public record OptionalEntity(
671671
Optional<Catalog<TestEntity>> catalog,
672672
Optional<Listing<TestEntity>> listing,
673673
Optional<SideTable<TestEntity, TestEntity>> sideTable,
674+
Optional<SideTable<TestEntity, SideTable<TestEntity, TestEntity>>> nestedSideTable,
674675
Optional<TaggedUnion<TestEntity.Variant>> variant,
675676
Optional<TestValues> values
676677
) implements Entity {
@@ -682,6 +683,7 @@ static OptionalEntity withString(Optional<String> string, Bosk<OptionalEntity> b
682683
Optional.of(Catalog.empty()),
683684
Optional.of(Listing.empty(domain)),
684685
Optional.of(SideTable.empty(domain)),
686+
Optional.of(SideTable.empty(domain)),
685687
Optional.of(TaggedUnion.of(new TestEntity.StringCase("stringCase"))),
686688
Optional.empty());
687689
}

bosk-testing/src/main/java/works/bosk/drivers/AbstractDriverTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import works.bosk.Identifier;
1616
import works.bosk.Path;
1717
import works.bosk.Reference;
18+
import works.bosk.SideTable;
1819
import works.bosk.drivers.state.TestEntity;
1920
import works.bosk.exceptions.InvalidTypeException;
21+
import works.bosk.util.Classes;
2022

2123
import static java.lang.Thread.currentThread;
2224
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -74,11 +76,30 @@ protected TestEntity autoInitialize(Reference<TestEntity> ref) {
7476
} else {
7577
autoInitialize(ref.enclosingReference(TestEntity.class));
7678
TestEntity newEntity = emptyEntityAt(ref);
77-
driver.submitConditionalCreation(ref, newEntity);
79+
if (isInNestedSideTable(ref)) {
80+
Reference<SideTable<TestEntity, TestEntity>> outerRef;
81+
try {
82+
outerRef = ref.root().then(Classes.sideTable(TestEntity.class, TestEntity.class), ref.path().truncatedBy(1));
83+
} catch (InvalidTypeException e) {
84+
throw new AssertionError(e);
85+
}
86+
driver.submitConditionalCreation(outerRef, SideTable.of(newEntity.listing().domain(), Identifier.from(ref.path().lastSegment()), newEntity));
87+
} else {
88+
driver.submitConditionalCreation(ref, newEntity);
89+
}
7890
return newEntity;
7991
}
8092
}
8193

94+
private boolean isInNestedSideTable(Reference<TestEntity> ref) {
95+
Path path = ref.path();
96+
if (path.length() < 3) {
97+
return false;
98+
} else {
99+
return path.truncatedBy(2).lastSegment().equals(TestEntity.Fields.nestedSideTable);
100+
}
101+
}
102+
82103
TestEntity emptyEntityAt(Reference<TestEntity> ref) {
83104
CatalogReference<TestEntity> catalogRef;
84105
try {

bosk-testing/src/main/java/works/bosk/drivers/DriverConformanceTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public abstract class DriverConformanceTest extends AbstractDriverTest {
7070
public interface Refs {
7171
@ReferencePath("/id") Reference<Identifier> rootID();
7272
@ReferencePath("/catalog/-id-") Reference<TestEntity> catalogEntry(Identifier id);
73+
@ReferencePath("/nestedSideTable/outer") SideTableReference<TestEntity, TestEntity> outer();
74+
@ReferencePath("/nestedSideTable/outer/-inner-/catalog") CatalogReference<TestEntity> innerCatalog(Identifier id);
7375
@ReferencePath("/values/string") Reference<String> valuesString();
7476
}
7577

@@ -172,6 +174,28 @@ void replaceSideTableDomain(Path enclosingCatalogPath) throws InvalidTypeExcepti
172174
assertCorrectBoskContents();
173175
}
174176

177+
@ParametersByName
178+
void replaceNestedSideTableDomain() throws InvalidTypeException, IOException, InterruptedException {
179+
CatalogReference<TestEntity> catalogRef = initializeBoskWithCatalog(Path.just("catalog"));
180+
driver.flush();
181+
Refs refs = bosk.buildReferences(Refs.class);
182+
Catalog<TestEntity> catalog;
183+
try (var __ = bosk.readContext()) {
184+
catalog = catalogRef.value();
185+
}
186+
SideTable<TestEntity, TestEntity> initialSideTable = SideTable.fromFunction(catalogRef, Stream.of(child1ID, child2ID), catalog::get);
187+
driver.submitReplacement(refs.outer(), initialSideTable);
188+
driver.flush();
189+
assertCorrectBoskContents(); // Correct starting state
190+
191+
// Make a new side table with a different domain but the same contents
192+
SideTable<TestEntity, TestEntity> newSideTable = SideTable.fromEntries(refs.innerCatalog(child1ID), initialSideTable.idEntrySet().stream());
193+
assert initialSideTable.domain() != newSideTable.domain(): "Domains should be different or we're not actually testing a replace operation";
194+
driver.submitReplacement(refs.outer(), newSideTable);
195+
driver.flush();
196+
assertCorrectBoskContents();
197+
}
198+
175199
@ParametersByName
176200
void deleteExisting(Path enclosingCatalogPath) {
177201
CatalogReference<TestEntity> ref = initializeBoskWithCatalog(enclosingCatalogPath);
@@ -599,6 +623,7 @@ static Stream<Path> enclosingCatalogPath() {
599623
Path.just(TestEntity.Fields.catalog),
600624
Path.of(TestEntity.Fields.catalog, AWKWARD_ID, TestEntity.Fields.catalog),
601625
Path.of(TestEntity.Fields.sideTable, AWKWARD_ID, TestEntity.Fields.catalog),
626+
Path.of(TestEntity.Fields.nestedSideTable, "outer", "inner", TestEntity.Fields.catalog),
602627
Path.of(TestEntity.Fields.sideTable, AWKWARD_ID, TestEntity.Fields.catalog, "parent", TestEntity.Fields.catalog),
603628
Path.of(TestEntity.Fields.sideTable, AWKWARD_ID, TestEntity.Fields.sideTable, "parent", TestEntity.Fields.catalog)
604629
);

bosk-testing/src/main/java/works/bosk/drivers/state/TestEntity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public record TestEntity(
2424
Catalog<TestEntity> catalog,
2525
Listing<TestEntity> listing,
2626
SideTable<TestEntity, TestEntity> sideTable,
27+
SideTable<TestEntity, SideTable<TestEntity, TestEntity>> nestedSideTable,
2728
TaggedUnion<Variant> variant,
2829
Optional<TestValues> values
2930
) implements Entity {
@@ -53,6 +54,7 @@ public static TestEntity empty(Identifier id, Reference<Catalog<TestEntity>> cat
5354
Catalog.empty(),
5455
Listing.empty(catalogRef),
5556
SideTable.empty(catalogRef),
57+
SideTable.empty(catalogRef),
5658
TaggedUnion.of(new StringCase("")),
5759
Optional.empty());
5860
}

bosk-testing/src/main/java/works/bosk/drivers/state/UpgradeableEntity.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public record UpgradeableEntity(
1919
Catalog<TestEntity> catalog,
2020
Listing<TestEntity> listing,
2121
SideTable<TestEntity, TestEntity> sideTable,
22+
SideTable<TestEntity, SideTable<TestEntity, TestEntity>> nestedSideTable,
2223
TaggedUnion<TestEntity.Variant> variant,
2324
TestValues values
2425
) implements Entity {

0 commit comments

Comments
 (0)