Skip to content

Commit 0a3ff6f

Browse files
committed
Add module for returning the current entity path
The `EntityPathTracker` tracks the path of the current entity and provides access to it.
1 parent c337ce9 commit 0a3ff6f

File tree

2 files changed

+303
-0
lines changed

2 files changed

+303
-0
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2016 Deutsche Nationalbibliothek
3+
*
4+
* Licensed under the Apache License, Version 2.0 the "License";
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.culturegraph.mf.stream.sink;
18+
19+
import java.util.ArrayDeque;
20+
import java.util.Deque;
21+
22+
import org.culturegraph.mf.framework.DefaultStreamReceiver;
23+
24+
/**
25+
* Tracks the <i>path</i> of the current entity. The entity path consists of the
26+
* names of all parent entities of the current entity separated by a separator
27+
* string. For example, the following sequence of events yields the path
28+
* <i>granny.mommy.me</i>:
29+
* <pre>{@literal
30+
* start-record "1"
31+
* start-entity "granny"
32+
* start-entity "mommy"
33+
* start-entity "me"
34+
* }</pre>
35+
*
36+
* <p>The current path is returned from {@link #getCurrentPath()}.
37+
*
38+
* @author Christoph Böhme
39+
* @see org.culturegraph.mf.stream.pipe.StreamFlattener
40+
*/
41+
public class EntityPathTracker extends DefaultStreamReceiver {
42+
43+
public static final String DEFAULT_ENTITY_SEPARATOR = ".";
44+
45+
private final Deque<String> entityStack = new ArrayDeque<String>();
46+
private final StringBuilder currentPath = new StringBuilder();
47+
48+
private String entitySeparator = DEFAULT_ENTITY_SEPARATOR;
49+
50+
/**
51+
* Returns the current entity path.
52+
*
53+
* @return the current entity path or an empty string if not within a record.
54+
*/
55+
public String getCurrentPath() {
56+
return currentPath.toString();
57+
}
58+
59+
/**
60+
* Returns the current entity path with the given literal name appended.
61+
*
62+
* @param literalName the literal name to append to the current entity path.
63+
* Must not be null.
64+
* @return the current entity path with the literal name appended. The {@link
65+
* #getEntitySeparator()} is used to separate both unless no entity was
66+
* received yet in which case only the literal name is returned.
67+
*/
68+
public String getCurrentPathWith(final String literalName) {
69+
if (entityStack.size() == 0) {
70+
return literalName;
71+
}
72+
return getCurrentPath() + entitySeparator + literalName;
73+
}
74+
75+
/**
76+
* Returns the name of the current entity.
77+
*
78+
* @return the name of the current entity or null if not in an entity.
79+
*/
80+
public String getCurrentEntityName() {
81+
return entityStack.peek();
82+
}
83+
84+
public String getEntitySeparator() {
85+
return entitySeparator;
86+
}
87+
88+
/**
89+
* Sets the separator between entity names in the path. The default separator
90+
* is &quot;{@value DEFAULT_ENTITY_SEPARATOR}&quot;.
91+
*
92+
* <p>The separator must not be changed while processing a stream.
93+
*
94+
* @param entitySeparator the new entity separator. Can be empty to join
95+
* entity names without any separator. Multi-character
96+
* separators are also supported. Must not be null.
97+
*/
98+
public void setEntitySeparator(final String entitySeparator) {
99+
this.entitySeparator = entitySeparator;
100+
}
101+
102+
@Override
103+
public void startRecord(final String identifier) {
104+
clearStackAndPath();
105+
}
106+
107+
@Override
108+
public void endRecord() {
109+
clearStackAndPath();
110+
}
111+
112+
@Override
113+
public void startEntity(final String name) {
114+
entityStack.push(name);
115+
appendEntityToPath();
116+
}
117+
118+
@Override
119+
public void endEntity() {
120+
removeEntityFromPath();
121+
entityStack.pop();
122+
}
123+
124+
@Override
125+
public void closeStream() {
126+
clearStackAndPath();
127+
}
128+
129+
@Override
130+
public void resetStream() {
131+
clearStackAndPath();
132+
}
133+
134+
private void clearStackAndPath() {
135+
entityStack.clear();
136+
currentPath.setLength(0);
137+
}
138+
139+
private void appendEntityToPath() {
140+
if (entityStack.size() > 1) {
141+
currentPath.append(entitySeparator);
142+
}
143+
currentPath.append(entityStack.peek());
144+
}
145+
146+
private void removeEntityFromPath() {
147+
final String entityName = entityStack.peek();
148+
final int oldPathLength = currentPath.length();
149+
int lastEntityLength = entityName.length();
150+
if (entityStack.size() > 1) {
151+
lastEntityLength += entitySeparator.length();
152+
}
153+
currentPath.setLength(oldPathLength - lastEntityLength);
154+
}
155+
156+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2016 Deutsche Nationalbibliothek
3+
*
4+
* Licensed under the Apache License, Version 2.0 the "License";
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.culturegraph.mf.stream.sink;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNull;
21+
import static org.junit.Assert.assertTrue;
22+
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
26+
/**
27+
* Tests for class {@link EntityPathTracker}.
28+
*
29+
* @author Christoph Böhme
30+
*/
31+
public class EntityPathTrackerTest {
32+
33+
private EntityPathTracker pathTracker;
34+
35+
@Before
36+
public void initSystemUnderTest() {
37+
pathTracker = new EntityPathTracker();
38+
}
39+
40+
@Test
41+
public void getCurrentPath_shouldReturnEmptyPathIfProcessingHasNotStarted() {
42+
assertTrue(pathTracker.getCurrentPath().isEmpty());
43+
}
44+
45+
@Test
46+
public void getCurrentPath_shouldReturnEmptyPathIfNotInRecord() {
47+
pathTracker.startRecord("1");
48+
pathTracker.endRecord();
49+
assertTrue(pathTracker.getCurrentPath().isEmpty());
50+
}
51+
52+
@Test
53+
public void getCurrentPath_shouldReturnPathToCurrentEntity() {
54+
pathTracker.startRecord("1");
55+
assertEquals("", pathTracker.getCurrentPath());
56+
pathTracker.startEntity("granny");
57+
assertEquals("granny", pathTracker.getCurrentPath());
58+
pathTracker.startEntity("mommy");
59+
assertEquals("granny.mommy", pathTracker.getCurrentPath());
60+
pathTracker.startEntity("me");
61+
assertEquals("granny.mommy.me", pathTracker.getCurrentPath());
62+
pathTracker.endEntity();
63+
assertEquals("granny.mommy", pathTracker.getCurrentPath());
64+
pathTracker.startEntity("my-sister");
65+
assertEquals("granny.mommy.my-sister", pathTracker.getCurrentPath());
66+
pathTracker.endEntity();
67+
assertEquals("granny.mommy", pathTracker.getCurrentPath());
68+
pathTracker.endEntity();
69+
assertEquals("granny", pathTracker.getCurrentPath());
70+
pathTracker.endEntity();
71+
assertEquals("", pathTracker.getCurrentPath());
72+
}
73+
74+
@Test
75+
public void startRecord_shouldResetPath() {
76+
pathTracker.startRecord("1");
77+
pathTracker.startEntity("entity");
78+
assertEquals("entity", pathTracker.getCurrentPath());
79+
80+
pathTracker.startRecord("2");
81+
assertTrue(pathTracker.getCurrentPath().isEmpty());
82+
}
83+
84+
@Test
85+
public void resetStream_shouldResetPath() {
86+
pathTracker.startRecord("1");
87+
pathTracker.startEntity("entity");
88+
assertEquals("entity", pathTracker.getCurrentPath());
89+
90+
pathTracker.resetStream();
91+
assertTrue(pathTracker.getCurrentPath().isEmpty());
92+
}
93+
94+
@Test
95+
public void closeStream_shouldResetPath() {
96+
pathTracker.startRecord("1");
97+
pathTracker.startEntity("entity");
98+
assertEquals("entity", pathTracker.getCurrentPath());
99+
100+
pathTracker.closeStream();
101+
assertTrue(pathTracker.getCurrentPath().isEmpty());
102+
}
103+
104+
@Test
105+
public void getCurrentPathWith_shouldAppendLiteralNameToPath() {
106+
pathTracker.startRecord("1");
107+
pathTracker.startEntity("entity");
108+
109+
assertEquals("entity.literal", pathTracker.getCurrentPathWith("literal"));
110+
}
111+
112+
@Test
113+
public void getCurrentPathWith_shouldReturnOnlyLiteralNameIfNotInEntity() {
114+
pathTracker.startRecord("1");
115+
116+
assertEquals("literal", pathTracker.getCurrentPathWith("literal"));
117+
}
118+
119+
@Test
120+
public void getCurrentEntityName_shouldReturnNullIfProcessingNotStarted() {
121+
assertNull(pathTracker.getCurrentEntityName());
122+
}
123+
124+
@Test
125+
public void getCurrentEntityName_shouldReturnNullIfNotInRecord() {
126+
pathTracker.startRecord("1");
127+
pathTracker.endRecord();
128+
129+
assertNull(pathTracker.getCurrentEntityName());
130+
}
131+
132+
@Test
133+
public void getCurrentEntityName_shouldReturnNameOfCurrentEntity() {
134+
pathTracker.startRecord("1");
135+
assertNull(pathTracker.getCurrentEntityName());
136+
pathTracker.startEntity("grandad");
137+
assertEquals("grandad", pathTracker.getCurrentEntityName());
138+
pathTracker.startEntity("daddy");
139+
assertEquals("daddy", pathTracker.getCurrentEntityName());
140+
pathTracker.endEntity();
141+
assertEquals("grandad", pathTracker.getCurrentEntityName());
142+
pathTracker.endEntity();
143+
assertNull(pathTracker.getCurrentEntityName());
144+
pathTracker.endRecord();
145+
}
146+
147+
}

0 commit comments

Comments
 (0)