Skip to content

Commit 8af6c69

Browse files
authored
Merge pull request #392 from metafacture/378-fixElseNestedWithMoreThanTwoLevels
Correctly output more than two levels in nested pass-through (_elseNested).
2 parents 36d35b1 + 945fb6b commit 8af6c69

File tree

2 files changed

+290
-26
lines changed

2 files changed

+290
-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: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,226 @@ public void issue338_shouldPreserveSameEntitiesInElseNestedSource() {
217217
);
218218
}
219219

220+
@Test
221+
public void issue374_shouldPropagateArrayMarkersInElseNestedSource() {
222+
assertMorph(receiver,
223+
"<rules>" +
224+
" <data source='_elseNested' />" +
225+
"</rules>",
226+
i -> {
227+
i.startRecord("1");
228+
i.startEntity("author[]");
229+
i.startEntity("");
230+
i.literal("@type", "Person");
231+
i.literal("name", "Katja Königstein-Lüdersdorff");
232+
i.endEntity();
233+
i.startEntity("");
234+
i.literal("@type", "Person");
235+
i.literal("name", "Corinna Peters");
236+
i.endEntity();
237+
i.startEntity("");
238+
i.literal("@type", "Person");
239+
i.literal("name", "Oleg Tjulenev");
240+
i.endEntity();
241+
i.startEntity("");
242+
i.literal("@type", "Person");
243+
i.literal("name", "Claudia Vogeler");
244+
i.endEntity();
245+
i.endEntity();
246+
i.endRecord();
247+
},
248+
(o, f) -> {
249+
o.get().startRecord("1");
250+
o.get().startEntity("author[]");
251+
o.get().startEntity("");
252+
o.get().literal("@type", "Person");
253+
o.get().literal("name", "Katja Königstein-Lüdersdorff");
254+
o.get().endEntity();
255+
o.get().startEntity("");
256+
o.get().literal("@type", "Person");
257+
o.get().literal("name", "Corinna Peters");
258+
o.get().endEntity();
259+
o.get().startEntity("");
260+
o.get().literal("@type", "Person");
261+
o.get().literal("name", "Oleg Tjulenev");
262+
o.get().endEntity();
263+
o.get().startEntity("");
264+
o.get().literal("@type", "Person");
265+
o.get().literal("name", "Claudia Vogeler");
266+
f.apply(2).endEntity();
267+
o.get().endRecord();
268+
}
269+
);
270+
}
271+
272+
@Test
273+
public void issue378_shouldOutputMoreThanTwoLevelsInElseNestedSource() {
274+
assertMorph(receiver,
275+
"<rules>" +
276+
" <data source='_elseNested' />" +
277+
"</rules>",
278+
i -> {
279+
i.startRecord("1");
280+
i.startEntity("mods");
281+
i.literal("ID", "duepublico_mods_00074526");
282+
i.startEntity("name");
283+
i.literal("type", "personal");
284+
i.literal("type", "simple");
285+
i.startEntity("displayForm");
286+
i.literal("value", "Armbruster, André");
287+
i.endEntity();
288+
i.startEntity("role");
289+
i.startEntity("roleTerm");
290+
i.literal("authority", "marcrelator");
291+
i.literal("type", "code");
292+
i.literal("value", "aut");
293+
i.endEntity();
294+
i.startEntity("roleTerm");
295+
i.literal("authority", "marcrelator");
296+
i.literal("type", "text");
297+
i.literal("value", "Author");
298+
i.endEntity();
299+
i.endEntity();
300+
i.startEntity("nameIdentifier");
301+
i.literal("type", "gnd");
302+
i.literal("value", "1081830107");
303+
i.endEntity();
304+
i.startEntity("namePart");
305+
i.literal("type", "family");
306+
i.literal("value", "Armbruster");
307+
i.endEntity();
308+
i.startEntity("namePart");
309+
i.literal("type", "given");
310+
i.literal("value", "André");
311+
i.endEntity();
312+
i.endEntity();
313+
i.endEntity();
314+
i.endRecord();
315+
},
316+
(o, f) -> {
317+
o.get().startRecord("1");
318+
o.get().startEntity("mods");
319+
o.get().literal("ID", "duepublico_mods_00074526");
320+
o.get().startEntity("name");
321+
o.get().literal("type", "personal");
322+
o.get().literal("type", "simple");
323+
o.get().startEntity("displayForm");
324+
o.get().literal("value", "Armbruster, André");
325+
o.get().endEntity();
326+
o.get().startEntity("role");
327+
o.get().startEntity("roleTerm");
328+
o.get().literal("authority", "marcrelator");
329+
o.get().literal("type", "code");
330+
o.get().literal("value", "aut");
331+
o.get().endEntity();
332+
o.get().startEntity("roleTerm");
333+
o.get().literal("authority", "marcrelator");
334+
o.get().literal("type", "text");
335+
o.get().literal("value", "Author");
336+
f.apply(2).endEntity();
337+
o.get().startEntity("nameIdentifier");
338+
o.get().literal("type", "gnd");
339+
o.get().literal("value", "1081830107");
340+
o.get().endEntity();
341+
o.get().startEntity("namePart");
342+
o.get().literal("type", "family");
343+
o.get().literal("value", "Armbruster");
344+
o.get().endEntity();
345+
o.get().startEntity("namePart");
346+
o.get().literal("type", "given");
347+
o.get().literal("value", "André");
348+
f.apply(3).endEntity();
349+
o.get().endRecord();
350+
}
351+
);
352+
}
353+
354+
@Test
355+
public void shouldOutputMoreThanTwoLevelsInElseNestedSourceWithModifications() {
356+
assertMorph(receiver,
357+
"<rules>" +
358+
" <entity name='name' flushWith='record'>" +
359+
" <data source='mods.name.displayForm.value' name='displayForm' />" +
360+
" <data source='mods.name.namePart.value' />" +
361+
" </entity>" +
362+
" <data source='_elseNested' />" +
363+
"</rules>",
364+
i -> {
365+
i.startRecord("1");
366+
i.startEntity("mods");
367+
i.literal("ID", "duepublico_mods_00074526");
368+
i.startEntity("name");
369+
i.literal("type", "personal");
370+
i.literal("type", "simple");
371+
i.startEntity("displayForm");
372+
i.literal("value", "Armbruster, André");
373+
i.endEntity();
374+
i.startEntity("role");
375+
i.startEntity("roleTerm");
376+
i.literal("authority", "marcrelator");
377+
i.literal("type", "code");
378+
i.literal("value", "aut");
379+
i.endEntity();
380+
i.startEntity("roleTerm");
381+
i.literal("authority", "marcrelator");
382+
i.literal("type", "text");
383+
i.literal("value", "Author");
384+
i.endEntity();
385+
i.endEntity();
386+
i.startEntity("nameIdentifier");
387+
i.literal("type", "gnd");
388+
i.literal("value", "1081830107");
389+
i.endEntity();
390+
i.startEntity("namePart");
391+
i.literal("type", "family");
392+
i.literal("value", "Armbruster");
393+
i.endEntity();
394+
i.startEntity("namePart");
395+
i.literal("type", "given");
396+
i.literal("value", "André");
397+
i.endEntity();
398+
i.endEntity();
399+
i.endEntity();
400+
i.endRecord();
401+
},
402+
(o, f) -> {
403+
o.get().startRecord("1");
404+
o.get().startEntity("mods");
405+
o.get().literal("ID", "duepublico_mods_00074526");
406+
o.get().startEntity("name");
407+
o.get().literal("type", "personal");
408+
o.get().literal("type", "simple");
409+
o.get().startEntity("role");
410+
o.get().startEntity("roleTerm");
411+
o.get().literal("authority", "marcrelator");
412+
o.get().literal("type", "code");
413+
o.get().literal("value", "aut");
414+
o.get().endEntity();
415+
o.get().startEntity("roleTerm");
416+
o.get().literal("authority", "marcrelator");
417+
o.get().literal("type", "text");
418+
o.get().literal("value", "Author");
419+
f.apply(2).endEntity();
420+
o.get().startEntity("nameIdentifier");
421+
o.get().literal("type", "gnd");
422+
o.get().literal("value", "1081830107");
423+
o.get().endEntity();
424+
o.get().startEntity("namePart");
425+
o.get().literal("type", "family");
426+
o.get().endEntity();
427+
o.get().startEntity("namePart");
428+
o.get().literal("type", "given");
429+
f.apply(3).endEntity();
430+
o.get().startEntity("name");
431+
o.get().literal("displayForm", "Armbruster, André");
432+
o.get().literal("mods.name.namePart.value", "Armbruster");
433+
o.get().literal("mods.name.namePart.value", "André");
434+
o.get().endEntity();
435+
o.get().endRecord();
436+
}
437+
);
438+
}
439+
220440
@Test
221441
public void shouldHandleUnmatchedLiteralsAndEntitiesInElseNestedSource() {
222442
assertMorph(receiver,

0 commit comments

Comments
 (0)