Skip to content

Commit 4b8c2d7

Browse files
committed
Correctly output more than two levels in nested pass-through (_elseNested).
Keep track of nested entities and open/close them in the appropriate order. Fixes #378. Related to #107, #338.
1 parent 36d35b1 commit 4b8c2d7

File tree

2 files changed

+238
-26
lines changed

2 files changed

+238
-26
lines changed

metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.LinkedList;
5454
import java.util.List;
5555
import java.util.Map;
56+
import java.util.function.Consumer;
5657

5758
/**
5859
* Transforms a data stream sent via the {@link StreamReceiver} interface. Use
@@ -100,8 +101,9 @@ public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValuePi
100101
private MorphErrorHandler errorHandler = new DefaultErrorHandler();
101102
private int recordCount;
102103
private final List<FlushListener> recordEndListener = new ArrayList<>();
104+
105+
private final Deque<EntityEntry> elseNestedEntities = new LinkedList<>();
103106
private boolean elseNested;
104-
private boolean elseNestedEntityStarted;
105107
private String currentLiteralName;
106108

107109
protected Metamorph() {
@@ -239,16 +241,16 @@ protected void registerNamedValueReceiver(final String source, final NamedValueR
239241
@Override
240242
public void startRecord(final String identifier) {
241243
flattener.startRecord(identifier);
244+
elseNestedEntities.clear();
242245
entityCountStack.clear();
243246

244247
entityCount = 0;
245248
currentEntityCount = 0;
249+
entityCountStack.push(Integer.valueOf(entityCount));
246250

247251
++recordCount;
248252
recordCount %= Integer.MAX_VALUE;
249253

250-
entityCountStack.add(Integer.valueOf(entityCount));
251-
252254
final String identifierFinal = identifier;
253255

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

264266
outputStreamReceiver.endRecord();
265-
entityCountStack.removeLast();
266-
if (!entityCountStack.isEmpty()) {
267+
flattener.endRecord();
268+
269+
entityCountStack.pop();
270+
271+
if (!elseNestedEntities.isEmpty() || !entityCountStack.isEmpty()) {
267272
throw new IllegalStateException(ENTITIES_NOT_BALANCED);
268273
}
269-
270-
flattener.endRecord();
271274
}
272275

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

283286
flattener.startEntity(name);
287+
elseNestedEntities.push(new EntityEntry(flattener));
284288
}
285289

286290
@Override
287291
public void endEntity() {
288292
dispatch(flattener.getCurrentPath(), "", getElseSources(), true);
289-
currentEntityCount = entityCountStack.pop().intValue();
290293
flattener.endEntity();
294+
295+
elseNestedEntities.pop();
296+
currentEntityCount = entityCountStack.pop().intValue();
291297
}
292298

293299
@Override
@@ -322,30 +328,38 @@ private void dispatch(final String path, final String value, final List<NamedVal
322328
send(path, value, matchingData);
323329
}
324330
else if (fallbackReceiver != null) {
325-
if (endEntity) {
326-
if (elseNestedEntityStarted) {
327-
outputStreamReceiver.endEntity();
328-
elseNestedEntityStarted = false;
329-
}
330-
}
331-
else {
332-
final String entityName = elseNested ? flattener.getCurrentEntityName() : null;
331+
dispatchFallback(path, endEntity, k -> send(escapeFeedbackChar(k), value, fallbackReceiver));
332+
}
333+
}
333334

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

341-
send(escapeFeedbackChar(currentLiteralName), value, fallbackReceiver);
338+
if (endEntity) {
339+
if (entityEntry != null && entityEntry.getStarted()) {
340+
outputStreamReceiver.endEntity();
341+
}
342+
}
343+
else if (entityEntry != null) {
344+
if (getData(entityEntry.getPath()) == null) {
345+
final Deque<String> entities = new LinkedList<>();
346+
347+
for (final EntityEntry e : elseNestedEntities) {
348+
if (e.getStarted()) {
349+
break;
342350
}
351+
352+
e.setStarted(true);
353+
entities.push(e.getName());
343354
}
344-
else {
345-
send(escapeFeedbackChar(path), value, fallbackReceiver);
346-
}
355+
356+
entities.forEach(outputStreamReceiver::startEntity);
357+
consumer.accept(currentLiteralName);
347358
}
348359
}
360+
else {
361+
consumer.accept(path);
362+
}
349363
}
350364

351365
private List<NamedValueReceiver> getData(final String path) {
@@ -468,4 +482,34 @@ public SourceLocation getSourceLocation() {
468482
return null;
469483
}
470484

485+
private static class EntityEntry {
486+
487+
private final String name;
488+
private final String path;
489+
490+
private boolean started;
491+
492+
EntityEntry(final StreamFlattener flattener) {
493+
name = flattener.getCurrentEntityName();
494+
path = flattener.getCurrentPath();
495+
}
496+
497+
private String getName() {
498+
return name;
499+
}
500+
501+
private String getPath() {
502+
return path;
503+
}
504+
505+
private void setStarted(final boolean started) {
506+
this.started = started;
507+
}
508+
509+
private boolean getStarted() {
510+
return started;
511+
}
512+
513+
}
514+
471515
}

metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,174 @@ public void issue338_shouldPreserveSameEntitiesInElseNestedSource() {
217217
);
218218
}
219219

220+
@Test
221+
public void issue378_shouldOutputMoreThanTwoLevelsInElseNestedSource() {
222+
assertMorph(receiver,
223+
"<rules>" +
224+
" <data source='_elseNested' />" +
225+
"</rules>",
226+
i -> {
227+
i.startRecord("1");
228+
i.startEntity("mods");
229+
i.literal("ID", "duepublico_mods_00074526");
230+
i.startEntity("name");
231+
i.literal("type", "personal");
232+
i.literal("type", "simple");
233+
i.startEntity("displayForm");
234+
i.literal("value", "Armbruster, André");
235+
i.endEntity();
236+
i.startEntity("role");
237+
i.startEntity("roleTerm");
238+
i.literal("authority", "marcrelator");
239+
i.literal("type", "code");
240+
i.literal("value", "aut");
241+
i.endEntity();
242+
i.startEntity("roleTerm");
243+
i.literal("authority", "marcrelator");
244+
i.literal("type", "text");
245+
i.literal("value", "Author");
246+
i.endEntity();
247+
i.endEntity();
248+
i.startEntity("nameIdentifier");
249+
i.literal("type", "gnd");
250+
i.literal("value", "1081830107");
251+
i.endEntity();
252+
i.startEntity("namePart");
253+
i.literal("type", "family");
254+
i.literal("value", "Armbruster");
255+
i.endEntity();
256+
i.startEntity("namePart");
257+
i.literal("type", "given");
258+
i.literal("value", "André");
259+
i.endEntity();
260+
i.endEntity();
261+
i.endEntity();
262+
i.endRecord();
263+
},
264+
(o, f) -> {
265+
o.get().startRecord("1");
266+
o.get().startEntity("mods");
267+
o.get().literal("ID", "duepublico_mods_00074526");
268+
o.get().startEntity("name");
269+
o.get().literal("type", "personal");
270+
o.get().literal("type", "simple");
271+
o.get().startEntity("displayForm");
272+
o.get().literal("value", "Armbruster, André");
273+
o.get().endEntity();
274+
o.get().startEntity("role");
275+
o.get().startEntity("roleTerm");
276+
o.get().literal("authority", "marcrelator");
277+
o.get().literal("type", "code");
278+
o.get().literal("value", "aut");
279+
o.get().endEntity();
280+
o.get().startEntity("roleTerm");
281+
o.get().literal("authority", "marcrelator");
282+
o.get().literal("type", "text");
283+
o.get().literal("value", "Author");
284+
f.apply(2).endEntity();
285+
o.get().startEntity("nameIdentifier");
286+
o.get().literal("type", "gnd");
287+
o.get().literal("value", "1081830107");
288+
o.get().endEntity();
289+
o.get().startEntity("namePart");
290+
o.get().literal("type", "family");
291+
o.get().literal("value", "Armbruster");
292+
o.get().endEntity();
293+
o.get().startEntity("namePart");
294+
o.get().literal("type", "given");
295+
o.get().literal("value", "André");
296+
f.apply(3).endEntity();
297+
o.get().endRecord();
298+
}
299+
);
300+
}
301+
302+
@Test
303+
public void shouldOutputMoreThanTwoLevelsInElseNestedSourceWithModifications() {
304+
assertMorph(receiver,
305+
"<rules>" +
306+
" <entity name='name' flushWith='record'>" +
307+
" <data source='mods.name.displayForm.value' name='displayForm' />" +
308+
" <data source='mods.name.namePart.value' />" +
309+
" </entity>" +
310+
" <data source='_elseNested' />" +
311+
"</rules>",
312+
i -> {
313+
i.startRecord("1");
314+
i.startEntity("mods");
315+
i.literal("ID", "duepublico_mods_00074526");
316+
i.startEntity("name");
317+
i.literal("type", "personal");
318+
i.literal("type", "simple");
319+
i.startEntity("displayForm");
320+
i.literal("value", "Armbruster, André");
321+
i.endEntity();
322+
i.startEntity("role");
323+
i.startEntity("roleTerm");
324+
i.literal("authority", "marcrelator");
325+
i.literal("type", "code");
326+
i.literal("value", "aut");
327+
i.endEntity();
328+
i.startEntity("roleTerm");
329+
i.literal("authority", "marcrelator");
330+
i.literal("type", "text");
331+
i.literal("value", "Author");
332+
i.endEntity();
333+
i.endEntity();
334+
i.startEntity("nameIdentifier");
335+
i.literal("type", "gnd");
336+
i.literal("value", "1081830107");
337+
i.endEntity();
338+
i.startEntity("namePart");
339+
i.literal("type", "family");
340+
i.literal("value", "Armbruster");
341+
i.endEntity();
342+
i.startEntity("namePart");
343+
i.literal("type", "given");
344+
i.literal("value", "André");
345+
i.endEntity();
346+
i.endEntity();
347+
i.endEntity();
348+
i.endRecord();
349+
},
350+
(o, f) -> {
351+
o.get().startRecord("1");
352+
o.get().startEntity("mods");
353+
o.get().literal("ID", "duepublico_mods_00074526");
354+
o.get().startEntity("name");
355+
o.get().literal("type", "personal");
356+
o.get().literal("type", "simple");
357+
o.get().startEntity("role");
358+
o.get().startEntity("roleTerm");
359+
o.get().literal("authority", "marcrelator");
360+
o.get().literal("type", "code");
361+
o.get().literal("value", "aut");
362+
o.get().endEntity();
363+
o.get().startEntity("roleTerm");
364+
o.get().literal("authority", "marcrelator");
365+
o.get().literal("type", "text");
366+
o.get().literal("value", "Author");
367+
f.apply(2).endEntity();
368+
o.get().startEntity("nameIdentifier");
369+
o.get().literal("type", "gnd");
370+
o.get().literal("value", "1081830107");
371+
o.get().endEntity();
372+
o.get().startEntity("namePart");
373+
o.get().literal("type", "family");
374+
o.get().endEntity();
375+
o.get().startEntity("namePart");
376+
o.get().literal("type", "given");
377+
f.apply(3).endEntity();
378+
o.get().startEntity("name");
379+
o.get().literal("displayForm", "Armbruster, André");
380+
o.get().literal("mods.name.namePart.value", "Armbruster");
381+
o.get().literal("mods.name.namePart.value", "André");
382+
o.get().endEntity();
383+
o.get().endRecord();
384+
}
385+
);
386+
}
387+
220388
@Test
221389
public void shouldHandleUnmatchedLiteralsAndEntitiesInElseNestedSource() {
222390
assertMorph(receiver,

0 commit comments

Comments
 (0)