Skip to content

Commit

Permalink
Correctly output more than two levels in nested pass-through (`_elseN…
Browse files Browse the repository at this point in the history
…ested`).

Keep track of nested entities and open/close them in the appropriate order.

Fixes #378.

Related to #107, #338.
  • Loading branch information
blackwinter committed Sep 14, 2021
1 parent 36d35b1 commit 4b8c2d7
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 26 deletions.
96 changes: 70 additions & 26 deletions metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

/**
* Transforms a data stream sent via the {@link StreamReceiver} interface. Use
Expand Down Expand Up @@ -100,8 +101,9 @@ public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValuePi
private MorphErrorHandler errorHandler = new DefaultErrorHandler();
private int recordCount;
private final List<FlushListener> recordEndListener = new ArrayList<>();

private final Deque<EntityEntry> elseNestedEntities = new LinkedList<>();
private boolean elseNested;
private boolean elseNestedEntityStarted;
private String currentLiteralName;

protected Metamorph() {
Expand Down Expand Up @@ -239,16 +241,16 @@ protected void registerNamedValueReceiver(final String source, final NamedValueR
@Override
public void startRecord(final String identifier) {
flattener.startRecord(identifier);
elseNestedEntities.clear();
entityCountStack.clear();

entityCount = 0;
currentEntityCount = 0;
entityCountStack.push(Integer.valueOf(entityCount));

++recordCount;
recordCount %= Integer.MAX_VALUE;

entityCountStack.add(Integer.valueOf(entityCount));

final String identifierFinal = identifier;

outputStreamReceiver.startRecord(identifierFinal);
Expand All @@ -262,12 +264,13 @@ public void endRecord() {
}

outputStreamReceiver.endRecord();
entityCountStack.removeLast();
if (!entityCountStack.isEmpty()) {
flattener.endRecord();

entityCountStack.pop();

if (!elseNestedEntities.isEmpty() || !entityCountStack.isEmpty()) {
throw new IllegalStateException(ENTITIES_NOT_BALANCED);
}

flattener.endRecord();
}

@Override
Expand All @@ -281,13 +284,16 @@ public void startEntity(final String name) {
entityCountStack.push(Integer.valueOf(entityCount));

flattener.startEntity(name);
elseNestedEntities.push(new EntityEntry(flattener));
}

@Override
public void endEntity() {
dispatch(flattener.getCurrentPath(), "", getElseSources(), true);
currentEntityCount = entityCountStack.pop().intValue();
flattener.endEntity();

elseNestedEntities.pop();
currentEntityCount = entityCountStack.pop().intValue();
}

@Override
Expand Down Expand Up @@ -322,30 +328,38 @@ private void dispatch(final String path, final String value, final List<NamedVal
send(path, value, matchingData);
}
else if (fallbackReceiver != null) {
if (endEntity) {
if (elseNestedEntityStarted) {
outputStreamReceiver.endEntity();
elseNestedEntityStarted = false;
}
}
else {
final String entityName = elseNested ? flattener.getCurrentEntityName() : null;
dispatchFallback(path, endEntity, k -> send(escapeFeedbackChar(k), value, fallbackReceiver));
}
}

if (entityName != null) {
if (getData(entityName) == null) {
if (!elseNestedEntityStarted) {
outputStreamReceiver.startEntity(entityName);
elseNestedEntityStarted = true;
}
private void dispatchFallback(final String path, final boolean endEntity, final Consumer<String> consumer) {
final EntityEntry entityEntry = elseNested ? elseNestedEntities.peek() : null;

send(escapeFeedbackChar(currentLiteralName), value, fallbackReceiver);
if (endEntity) {
if (entityEntry != null && entityEntry.getStarted()) {
outputStreamReceiver.endEntity();
}
}
else if (entityEntry != null) {
if (getData(entityEntry.getPath()) == null) {
final Deque<String> entities = new LinkedList<>();

for (final EntityEntry e : elseNestedEntities) {
if (e.getStarted()) {
break;
}

e.setStarted(true);
entities.push(e.getName());
}
else {
send(escapeFeedbackChar(path), value, fallbackReceiver);
}

entities.forEach(outputStreamReceiver::startEntity);
consumer.accept(currentLiteralName);
}
}
else {
consumer.accept(path);
}
}

private List<NamedValueReceiver> getData(final String path) {
Expand Down Expand Up @@ -468,4 +482,34 @@ public SourceLocation getSourceLocation() {
return null;
}

private static class EntityEntry {

private final String name;
private final String path;

private boolean started;

EntityEntry(final StreamFlattener flattener) {
name = flattener.getCurrentEntityName();
path = flattener.getCurrentPath();
}

private String getName() {
return name;
}

private String getPath() {
return path;
}

private void setStarted(final boolean started) {
this.started = started;
}

private boolean getStarted() {
return started;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,174 @@ public void issue338_shouldPreserveSameEntitiesInElseNestedSource() {
);
}

@Test
public void issue378_shouldOutputMoreThanTwoLevelsInElseNestedSource() {
assertMorph(receiver,
"<rules>" +
" <data source='_elseNested' />" +
"</rules>",
i -> {
i.startRecord("1");
i.startEntity("mods");
i.literal("ID", "duepublico_mods_00074526");
i.startEntity("name");
i.literal("type", "personal");
i.literal("type", "simple");
i.startEntity("displayForm");
i.literal("value", "Armbruster, André");
i.endEntity();
i.startEntity("role");
i.startEntity("roleTerm");
i.literal("authority", "marcrelator");
i.literal("type", "code");
i.literal("value", "aut");
i.endEntity();
i.startEntity("roleTerm");
i.literal("authority", "marcrelator");
i.literal("type", "text");
i.literal("value", "Author");
i.endEntity();
i.endEntity();
i.startEntity("nameIdentifier");
i.literal("type", "gnd");
i.literal("value", "1081830107");
i.endEntity();
i.startEntity("namePart");
i.literal("type", "family");
i.literal("value", "Armbruster");
i.endEntity();
i.startEntity("namePart");
i.literal("type", "given");
i.literal("value", "André");
i.endEntity();
i.endEntity();
i.endEntity();
i.endRecord();
},
(o, f) -> {
o.get().startRecord("1");
o.get().startEntity("mods");
o.get().literal("ID", "duepublico_mods_00074526");
o.get().startEntity("name");
o.get().literal("type", "personal");
o.get().literal("type", "simple");
o.get().startEntity("displayForm");
o.get().literal("value", "Armbruster, André");
o.get().endEntity();
o.get().startEntity("role");
o.get().startEntity("roleTerm");
o.get().literal("authority", "marcrelator");
o.get().literal("type", "code");
o.get().literal("value", "aut");
o.get().endEntity();
o.get().startEntity("roleTerm");
o.get().literal("authority", "marcrelator");
o.get().literal("type", "text");
o.get().literal("value", "Author");
f.apply(2).endEntity();
o.get().startEntity("nameIdentifier");
o.get().literal("type", "gnd");
o.get().literal("value", "1081830107");
o.get().endEntity();
o.get().startEntity("namePart");
o.get().literal("type", "family");
o.get().literal("value", "Armbruster");
o.get().endEntity();
o.get().startEntity("namePart");
o.get().literal("type", "given");
o.get().literal("value", "André");
f.apply(3).endEntity();
o.get().endRecord();
}
);
}

@Test
public void shouldOutputMoreThanTwoLevelsInElseNestedSourceWithModifications() {
assertMorph(receiver,
"<rules>" +
" <entity name='name' flushWith='record'>" +
" <data source='mods.name.displayForm.value' name='displayForm' />" +
" <data source='mods.name.namePart.value' />" +
" </entity>" +
" <data source='_elseNested' />" +
"</rules>",
i -> {
i.startRecord("1");
i.startEntity("mods");
i.literal("ID", "duepublico_mods_00074526");
i.startEntity("name");
i.literal("type", "personal");
i.literal("type", "simple");
i.startEntity("displayForm");
i.literal("value", "Armbruster, André");
i.endEntity();
i.startEntity("role");
i.startEntity("roleTerm");
i.literal("authority", "marcrelator");
i.literal("type", "code");
i.literal("value", "aut");
i.endEntity();
i.startEntity("roleTerm");
i.literal("authority", "marcrelator");
i.literal("type", "text");
i.literal("value", "Author");
i.endEntity();
i.endEntity();
i.startEntity("nameIdentifier");
i.literal("type", "gnd");
i.literal("value", "1081830107");
i.endEntity();
i.startEntity("namePart");
i.literal("type", "family");
i.literal("value", "Armbruster");
i.endEntity();
i.startEntity("namePart");
i.literal("type", "given");
i.literal("value", "André");
i.endEntity();
i.endEntity();
i.endEntity();
i.endRecord();
},
(o, f) -> {
o.get().startRecord("1");
o.get().startEntity("mods");
o.get().literal("ID", "duepublico_mods_00074526");
o.get().startEntity("name");
o.get().literal("type", "personal");
o.get().literal("type", "simple");
o.get().startEntity("role");
o.get().startEntity("roleTerm");
o.get().literal("authority", "marcrelator");
o.get().literal("type", "code");
o.get().literal("value", "aut");
o.get().endEntity();
o.get().startEntity("roleTerm");
o.get().literal("authority", "marcrelator");
o.get().literal("type", "text");
o.get().literal("value", "Author");
f.apply(2).endEntity();
o.get().startEntity("nameIdentifier");
o.get().literal("type", "gnd");
o.get().literal("value", "1081830107");
o.get().endEntity();
o.get().startEntity("namePart");
o.get().literal("type", "family");
o.get().endEntity();
o.get().startEntity("namePart");
o.get().literal("type", "given");
f.apply(3).endEntity();
o.get().startEntity("name");
o.get().literal("displayForm", "Armbruster, André");
o.get().literal("mods.name.namePart.value", "Armbruster");
o.get().literal("mods.name.namePart.value", "André");
o.get().endEntity();
o.get().endRecord();
}
);
}

@Test
public void shouldHandleUnmatchedLiteralsAndEntitiesInElseNestedSource() {
assertMorph(receiver,
Expand Down

0 comments on commit 4b8c2d7

Please sign in to comment.