Skip to content

Commit 3eca5ba

Browse files
committed
Refactored the code for building pipelines in Metamorph
In the original implementation of Metamorph `Data` and `Collect` objects managed the plumbing of their function pipelines internally (via `AbstractNamedValuePipeHead`). From the perspective of the `MorphBuilder` `Data` and `Collect` objects were connected directly. The fact that the connection was actually done through the function pipeline was hidden. While this approach simplified the `MorphBuilder`, it increased the complexity of the interfaces for connecting the different types of objects in Metamorph. Additionally, functions were required to handle the source parameter in the `receive` method differently compared to the implementations of the method in `Data` or `Collect` objects. Finally, it the half-hidden implementation of the function pipelines makes it very hard to add interceptors (e.g. for logging or debugging) to the pipe system generated from a Metamorph script. This commit tries to solve the short-comings of the original implementation with to changes. First, connections of `Function`, `Data` and `Collect` objects are now handled in the same way and the function pipelines are no longer hidden in the other two objects. Second, the objects are no longer connected to a receiver (via `setNamedValueReceiver`) but the connection is set the other way round using `addNamedValueSource`. While this approach appears slightly less intutive than the original method, it removes the necessity for calling `addNamedValueSource` in addition to `setNamedValueReceiver`. Furthermore it unifies setting of an object as name source or condition source and setting an object as standard data source. This is now all done from the perspective of the receiver. This simplifies the implementation of the relevant methods in the collectors.
1 parent 7931ba0 commit 3eca5ba

26 files changed

+197
-172
lines changed

src/main/java/org/culturegraph/mf/morph/AbstractNamedValuePipeHead.java renamed to src/main/java/org/culturegraph/mf/morph/AbstractNamedValuePipe.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,35 @@
1818
/**
1919
* Base class for {@link NamedValuePipe}s
2020
* @author Markus Michael Geipel
21+
* @author Chistohp Böhme
2122
*
2223
*/
23-
public abstract class AbstractNamedValuePipeHead implements NamedValuePipeHead {
24+
public abstract class AbstractNamedValuePipe implements NamedValuePipe {
2425

2526
private NamedValueReceiver namedValueReceiver;
26-
private NamedValuePipe last = this;
27-
28-
protected final NamedValueReceiver getNamedValueReceiver() {
29-
return namedValueReceiver;
30-
}
3127

3228
@Override
33-
public final <R extends NamedValueReceiver> R setNamedValueReceiver(final R receiver) {
34-
this.namedValueReceiver = receiver;
29+
public final <R extends NamedValueReceiver> R setNamedValueReceiver(
30+
final R receiver) {
31+
32+
namedValueReceiver = receiver;
33+
3534
return receiver;
3635
}
3736

3837
@Override
39-
public final void appendPipe(final NamedValuePipe namedValuePipe) {
40-
if(last==null){
41-
throw new IllegalStateException("NamedValuePipe already finalyzed.");
42-
}
43-
last = last.setNamedValueReceiver(namedValuePipe);
38+
public final void addNamedValueSource(final NamedValueSource namedValueSource) {
39+
namedValueSource.setNamedValueReceiver(this);
40+
41+
onNamedValueSourceAdded(namedValueSource);
4442
}
4543

46-
@Override
47-
public final void endPipe(final NamedValueReceiver lastReceiver) {
48-
last.setNamedValueReceiver(lastReceiver);
49-
last = null;
44+
protected void onNamedValueSourceAdded(final NamedValueSource namedValueSource) {
45+
// Default implementation does nothing
46+
}
47+
48+
protected final NamedValueReceiver getNamedValueReceiver() {
49+
return namedValueReceiver;
5050
}
5151

5252
}

src/main/java/org/culturegraph/mf/morph/Data.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*
2323
* @author Markus Michael Geipel
2424
*/
25-
final class Data extends AbstractNamedValuePipeHead {
25+
final class Data extends AbstractNamedValuePipe {
2626

2727
private String name;
2828

src/main/java/org/culturegraph/mf/morph/Flush.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@ public void receive(final String name, final String value, final NamedValueSourc
3636
listener.flush(recordCount, entityCount);
3737
}
3838

39+
@Override
40+
public void addNamedValueSource(final NamedValueSource namedValueSource) {
41+
// Nothing to do
42+
}
43+
3944
}

src/main/java/org/culturegraph/mf/morph/Metamorph.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
+ "definition is given in brackets.")
4545
@In(StreamReceiver.class)
4646
@Out(StreamReceiver.class)
47-
public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValueReceiver, MultiMap {
47+
public final class Metamorph implements StreamPipe<StreamReceiver>, NamedValuePipe, MultiMap {
4848

4949
public static final String ELSE_KEYWORD = "_else";
5050
public static final char FEEDBACK_CHAR = '@';
@@ -322,4 +322,14 @@ public void registerRecordEndFlush(final FlushListener flushListener) {
322322
recordEndListener.add(flushListener);
323323
}
324324

325+
@Override
326+
public void addNamedValueSource(final NamedValueSource namedValueSource) {
327+
namedValueSource.setNamedValueReceiver(this);
328+
}
329+
330+
@Override
331+
public <R extends NamedValueReceiver> R setNamedValueReceiver(final R receiver) {
332+
throw new UnsupportedOperationException("The Metamorph object cannot act as a NamedValueSender");
333+
}
334+
325335
}

src/main/java/org/culturegraph/mf/morph/MorphBuilder.java

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
* Builds a {@link Metamorph} from an xml description
3333
*
3434
* @author Markus Michael Geipel
35+
* @author Christoph Böhme
36+
*
3537
*/
3638
public final class MorphBuilder extends AbstractMetamorphDomWalker {
3739

@@ -43,15 +45,49 @@ public final class MorphBuilder extends AbstractMetamorphDomWalker {
4345
private static final Pattern OR_PATTERN = Pattern.compile(OR_STRING, Pattern.LITERAL);
4446

4547
private final Metamorph metamorph;
46-
private final Deque<Collect> collectStack;
47-
private Data data;
48-
private boolean setEntityName;
49-
private boolean setCondition;
48+
private final Deque<StackFrame> stack = new LinkedList<StackFrame>();
49+
50+
private static final class StackFrame {
51+
52+
private NamedValuePipe pipe;
53+
private boolean inEntityName;
54+
private boolean inCondition;
55+
56+
public StackFrame(final NamedValuePipe pipe) {
57+
this.pipe = pipe;
58+
}
59+
60+
public void setPipe(final NamedValuePipe pipe) {
61+
this.pipe = pipe;
62+
}
63+
64+
public NamedValuePipe getPipe() {
65+
return pipe;
66+
}
67+
68+
public void setInEntityName(final boolean inEntityName) {
69+
this.inEntityName = inEntityName;
70+
}
71+
72+
public boolean isInEntityName() {
73+
return inEntityName;
74+
}
75+
76+
public void setInCondition(final boolean inCondition) {
77+
this.inCondition = inCondition;
78+
}
79+
80+
public boolean isInCondition() {
81+
return inCondition;
82+
}
83+
84+
}
5085

5186
protected MorphBuilder(final Metamorph metamorph) {
5287
super();
53-
this.collectStack = new LinkedList<Collect>();
88+
5489
this.metamorph = metamorph;
90+
stack.push(new StackFrame(metamorph));
5591
}
5692

5793
@Override
@@ -135,52 +171,55 @@ protected void finish() {
135171

136172
@Override
137173
protected void enterData(final Node dataNode) {
138-
final String source = resolvedAttribute(dataNode, ATTRITBUTE.SOURCE);
139-
data = new Data();
174+
final Data data = new Data();
140175
data.setName(resolvedAttribute(dataNode, ATTRITBUTE.NAME));
176+
177+
final String source = resolvedAttribute(dataNode, ATTRITBUTE.SOURCE);
141178
metamorph.registerNamedValueReceiver(source, data);
142179

143-
if (setEntityName) {
144-
((Entity) collectStack.peek()).setNameSource(data);
145-
setEntityName = false;
146-
}
147-
148-
if (setCondition) {
149-
collectStack.peek().setConditionSource(data);
150-
setCondition = false;
151-
}
180+
stack.push(new StackFrame(data));
152181
}
153182

154183
@Override
155184
protected void exitData(final Node node) {
156-
if (collectStack.isEmpty()) {
157-
data.endPipe(metamorph);
185+
final NamedValuePipe dataPipe = stack.pop().getPipe();
186+
187+
final StackFrame parent = stack.peek();
188+
if (parent.isInEntityName()) {
189+
// Protected xsd schema and by assertion in enterName:
190+
((Entity) parent.getPipe()).setNameSource(dataPipe);
191+
} else if (parent.isInCondition()) {
192+
// Protected xsd schema and by assertion in enterIf:
193+
((ConditionAware) parent.getPipe()).setConditionSource(dataPipe);
158194
} else {
159-
final Collect parent = collectStack.peek();
160-
data.endPipe(parent);
161-
parent.addNamedValueSource(data);
195+
parent.getPipe().addNamedValueSource(dataPipe);
162196
}
163-
data = null;
164197
}
165198

166199
@Override
167200
protected void enterName(final Node nameNode) {
168-
setEntityName = true;
201+
assert stack.peek().getPipe() instanceof Entity :
202+
"statement `name` is only allowed in `entity` statements";
203+
204+
stack.peek().setInEntityName(true);
169205
}
170206

171207
@Override
172208
protected void exitName(final Node nameNode) {
173-
setEntityName = false;
209+
stack.peek().setInEntityName(false);
174210
}
175211

176212
@Override
177213
protected void enterIf(final Node nameNode) {
178-
setCondition = true;
214+
assert stack.peek().getPipe() instanceof ConditionAware :
215+
"statement `if` is not allowed in the current element";
216+
217+
stack.peek().setInCondition(true);
179218
}
180219

181220
@Override
182221
protected void exitIf(final Node nameNode) {
183-
setCondition = false;
222+
stack.peek().setInCondition(false);
184223
}
185224

186225
@Override
@@ -194,34 +233,33 @@ protected void enterCollect(final Node node) {
194233
}
195234
final Collect collect = getCollectFactory().newInstance(node.getLocalName(), attributes, metamorph);
196235

197-
if (setEntityName) {
198-
((Entity) collectStack.peek()).setNameSource(collect);
199-
setEntityName = false;
200-
}
201-
202-
if (setCondition) {
203-
collectStack.peek().setConditionSource(collect);
204-
setCondition = false;
205-
}
206-
207-
collectStack.push(collect);
236+
stack.push(new StackFrame(collect));
208237
}
209238

210239
@Override
211240
protected void exitCollect(final Node node) {
212-
final Collect collect = collectStack.pop();
213-
if (collectStack.isEmpty()) {
214-
collect.endPipe(metamorph);
241+
final NamedValuePipe collectPipe = stack.pop().getPipe();
242+
243+
final StackFrame parent = stack.peek();
244+
245+
if (parent.isInEntityName()) {
246+
// Protected xsd schema and by assertion in enterName:
247+
((Entity) parent.getPipe()).setNameSource(collectPipe);
248+
} else if (parent.isInCondition()) {
249+
// Protected xsd schema and by assertion in enterIf:
250+
((ConditionAware) parent.getPipe()).setConditionSource(collectPipe);
215251
} else {
216-
final Collect parent = collectStack.peek();
217-
parent.addNamedValueSource(collect);
218-
collect.endPipe(parent);
252+
parent.getPipe().addNamedValueSource(collectPipe);
219253
}
254+
220255
// must be set after recursive calls to flush descendants before parent
221256
final String flushWith = resolvedAttribute(node, ATTRITBUTE.FLUSH_WITH);
222257
if (null != flushWith) {
223-
collect.setWaitForFlush(true);
224-
registerFlush(flushWith, collect);
258+
assert collectPipe instanceof Collect :
259+
"Invokations of enterXXX and exitXXX are not properly balanced";
260+
261+
((Collect) collectPipe).setWaitForFlush(true);
262+
registerFlush(flushWith, ((Collect) collectPipe));
225263
}
226264
}
227265

@@ -265,11 +303,10 @@ protected void handleFunction(final Node functionNode) {
265303
final String entryValue = resolvedAttribute(mapEntryNode, ATTRITBUTE.VALUE);
266304
function.putValue(entryName, entryValue);
267305
}
268-
if (data == null) {
269-
collectStack.peek().appendPipe(function);
270-
} else {
271-
data.appendPipe(function);
272-
}
306+
307+
final StackFrame head = stack.peek();
308+
function.addNamedValueSource(head.getPipe());
309+
head.setPipe(function);
273310
}
274311

275312
}

src/main/java/org/culturegraph/mf/morph/NamedValuePipeHead.java

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/main/java/org/culturegraph/mf/morph/NamedValueReceiver.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
* A receiver of named values.
2020
*
2121
* @author Markus Michael Geipel
22+
* @author Christoph Böhme
23+
*
2224
*/
2325
public interface NamedValueReceiver {
2426

2527
void receive(String name, String value, NamedValueSource source, int recordCount, int entityCount);
2628

29+
void addNamedValueSource(final NamedValueSource namedValueSource);
30+
2731
}

src/main/java/org/culturegraph/mf/morph/NamedValueSource.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,25 @@
1616
package org.culturegraph.mf.morph;
1717

1818
/**
19-
* Base interface for all classes in {@link Metamorph} which
20-
* emit name-value-pairs.
19+
* Base interface for all classes in {@link Metamorph} which emit
20+
* name-value-pairs.
2121
*
2222
* @author Markus Michael Geipel
23+
* @author Christoph Böhme
2324
*
2425
*/
2526
public interface NamedValueSource {
2627

28+
/**
29+
* Connects a source of named values to a receiver of named values.
30+
*
31+
* Users should not call this method to connect sources and
32+
* receivers but rather call
33+
* {@link NamedValueReceiver.addNamedValueSource}.
34+
*
35+
* @param receiver
36+
* @return reference to receiver
37+
*/
2738
<R extends NamedValueReceiver> R setNamedValueReceiver(R receiver);
2839

2940
}

0 commit comments

Comments
 (0)