Skip to content

Commit 07ae1f4

Browse files
authored
Merge pull request #187 from kit-data-manager/fix-186-handle-loops-in-data-graph
Fix handling of possible loops in data graphs
2 parents 7b0d5af + 9731860 commit 07ae1f4

File tree

4 files changed

+41
-17
lines changed

4 files changed

+41
-17
lines changed

src/main/java/edu/kit/datamanager/ro_crate/reader/RoCrateReader.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -177,22 +177,22 @@ protected Set<String> getDataEntityIds(RootDataEntity root, JsonNode graph) {
177177
if (root == null) { return Set.of(); }
178178
Map<String, Set<String>> network = makeEntityGraph(graph);
179179
Set<String> directDataEntities = new HashSet<>(root.hasPart);
180-
return Stream.concat(
181-
directDataEntities.stream(),
182-
directDataEntities.stream().flatMap(entity -> getDataEntityIdsRecursive(entity, network))
183-
).collect(Collectors.toSet());
184-
}
185180

186-
protected Stream<String> getDataEntityIdsRecursive(
187-
String parent,
188-
Map<String, Set<String>> network
189-
) {
190-
return Stream.concat(
191-
Stream.of(parent),
192-
network.getOrDefault(parent, new HashSet<>()).stream()
193-
.flatMap(s -> getDataEntityIdsRecursive(s, network))
194-
.filter(Objects::nonNull)
195-
);
181+
Stack<String> processingQueue = new Stack<>();
182+
processingQueue.addAll(directDataEntities);
183+
Set<String> result = new HashSet<>();
184+
185+
while (!processingQueue.empty()) {
186+
String currentId = processingQueue.pop();
187+
result.add(currentId);
188+
network.getOrDefault(currentId, new HashSet<>()).stream()
189+
.filter(subId -> !result.contains(subId)) // avoid loops!
190+
.forEach(subId -> {
191+
result.add(subId);
192+
processingQueue.add(subId);
193+
});
194+
}
195+
return result;
196196
}
197197

198198
protected String unpackId(JsonNode node) {

src/test/java/edu/kit/datamanager/ro_crate/crate/ReadAndWriteTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ void testReadCrateWithHasPartHierarchy() {
5353
RoCrateReader reader = new RoCrateReader(new FolderReader());
5454
RoCrate crate = reader.readCrate(ReadAndWriteTest.class.getResource("/crates/hasPartHierarchy").getPath());
5555
assertEquals(1, crate.getAllContextualEntities().size());
56-
assertEquals(4, crate.getAllDataEntities().size());
56+
assertEquals(6, crate.getAllDataEntities().size());
5757
}
5858
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# hasPart/isPartOf Structure in this crate
2+
3+
This documents the hierarchy in this json file for the hasPart property.
4+
"isPartOf" occurences are handled the inverse way (as if it was a hasPart property the other way around).
5+
6+
- ./
7+
- sub-a.file
8+
- sub-b.file
9+
- sub-b-sub-a.file
10+
- looper.file --> sub-b.file
11+
- sub-b-sub-b.file (isPartOf)
12+
- looper-isPartOf.file --> sub-b.file

src/test/resources/crates/hasPartHierarchy/ro-crate-metadata.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,24 @@
4242
},
4343
{
4444
"@id": "sub-b-sub-a.file",
45-
"@type": "File"
45+
"@type": "File",
46+
"hasPart": {"@id": "looper.file"}
4647
},
4748
{
4849
"@id": "sub-b-sub-b.file",
4950
"@type": "File",
5051
"isPartOf": { "@id": "sub-b.file" }
52+
},
53+
{
54+
"@id": "looper.file",
55+
"@type": "File",
56+
"hasPart": {"@id": "sub-b.file"}
57+
},
58+
{
59+
"@id": "looper-isPartOf.file",
60+
"@type": "File",
61+
"hasPart": {"@id": "sub-b.file"},
62+
"isPartOf": {"@id": "sub-b.file"}
5163
}
5264
]
5365
}

0 commit comments

Comments
 (0)