1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.mycore.tools;
20
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.Set;
32 import java.util.TreeMap;
33 import java.util.TreeSet;
34 import java.util.stream.Collectors;
35
36 import javax.xml.stream.XMLInputFactory;
37 import javax.xml.stream.XMLStreamConstants;
38 import javax.xml.stream.XMLStreamException;
39 import javax.xml.stream.XMLStreamReader;
40
41 import org.apache.logging.log4j.LogManager;
42 import org.apache.logging.log4j.Logger;
43 import org.mycore.datamodel.common.MCRLinkTableManager;
44 import org.mycore.datamodel.metadata.MCRObjectID;
45
46 import com.google.common.collect.BiMap;
47 import com.google.common.collect.HashBiMap;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 public class MCRTopologicalSort<T> {
69 private static final Logger LOGGER = LogManager.getLogger(MCRTopologicalSort.class);
70
71
72
73
74
75 Map<Integer, TreeSet<Integer>> edgeSources = new TreeMap<>();
76
77
78
79
80 BiMap<Integer, T> nodes = HashBiMap.create();
81
82 boolean dirty = false;
83
84
85
86
87
88
89
90
91
92
93
94 public static void prepareData(MCRTopologicalSort<String> ts, String[] files, Path dir) {
95 ts.reset();
96
97 String file = null;
98 Map<Integer, List<String>> parentNames = new HashMap<>();
99 XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
100 for (int i = 0; i < files.length; i++) {
101 file = files[i];
102
103 try (BufferedReader br = Files.newBufferedReader(dir.resolve(file))) {
104 XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(br);
105 while (xmlStreamReader.hasNext()) {
106 switch (xmlStreamReader.getEventType()) {
107 case XMLStreamConstants.START_ELEMENT:
108 if (xmlStreamReader.getLocalName().equals("mycoreobject")) {
109 ts.getNodes().forcePut(i, xmlStreamReader
110 .getAttributeValue(null, "ID"));
111 } else {
112 String href = xmlStreamReader
113 .getAttributeValue("http://www.w3.org/1999/xlink", "href");
114 if (xmlStreamReader.getLocalName().equals("parent")) {
115 List<String> dependencyList = parentNames.computeIfAbsent(i,
116 e -> new ArrayList<>());
117 dependencyList.add(
118 href);
119 } else if (xmlStreamReader.getLocalName().equals("relatedItem")) {
120 if (MCRObjectID.isValid(
121 href)) {
122 List<String> dependencyList = parentNames
123 .computeIfAbsent(i, e -> new ArrayList<>());
124 dependencyList.add(
125 href);
126 }
127 } else if (xmlStreamReader.getLocalName().equals("metadata")) {
128 break;
129 }
130 }
131 break;
132
133 case XMLStreamConstants.END_ELEMENT:
134 if (xmlStreamReader.getLocalName().equals("parents")) {
135 break;
136 } else if (xmlStreamReader.getLocalName().equals("relatedItem")) {
137 break;
138 }
139 break;
140 }
141 xmlStreamReader.next();
142 }
143
144 } catch (XMLStreamException | IOException e) {
145 e.printStackTrace();
146 }
147 }
148
149
150 for (int source : parentNames.keySet()) {
151 parentNames.get(source)
152 .stream()
153 .map(ts.getNodes().inverse()::get)
154 .filter(Objects::nonNull)
155 .forEach(target -> ts.addEdge(source, target));
156 }
157 }
158
159
160
161
162
163
164
165 public static void prepareMCRObjects(MCRTopologicalSort<String> ts, String[] mcrids) {
166 ts.reset();
167 for (int i = 0; i < mcrids.length; i++) {
168 ts.getNodes().forcePut(i, mcrids[i]);
169 }
170 for (int i = 0; i < mcrids.length; i++) {
171 Collection<String> parents = MCRLinkTableManager.instance().getDestinationOf(String.valueOf(mcrids[i]),
172 MCRLinkTableManager.ENTRY_TYPE_PARENT);
173 for (String p : parents) {
174 Integer target = ts.getNodes().inverse().get(p);
175 if (target != null) {
176 ts.addEdge(i, target);
177 }
178 }
179 Collection<String> refs = MCRLinkTableManager.instance().getDestinationOf(String.valueOf(mcrids[i]),
180 MCRLinkTableManager.ENTRY_TYPE_REFERENCE);
181 for (String r : refs) {
182 Integer target = ts.getNodes().inverse().get(r);
183 if (target != null) {
184 ts.addEdge(i, target);
185 }
186 }
187 }
188 }
189
190
191
192
193 public void reset() {
194 nodes.clear();
195 edgeSources.clear();
196 dirty = false;
197 }
198
199
200
201
202 public BiMap<Integer, T> getNodes() {
203 return nodes;
204 }
205
206
207
208
209
210
211 public void addNode(T name) {
212 if (!nodes.containsValue(name)) {
213 nodes.put(nodes.size(), name);
214 }
215 }
216
217
218
219
220
221
222
223 public Integer getNodeID(T name) {
224 return nodes.inverse().get(name);
225 }
226
227
228
229
230
231
232 public T getNodeName(Integer id) {
233 return nodes.get(id);
234 }
235
236
237
238
239
240
241 public void addEdge(Integer from, Integer to) {
242 edgeSources.computeIfAbsent(to, k -> new TreeSet<>()).add(from);
243 }
244
245
246
247
248
249
250
251
252
253 public boolean removeEdge(Integer from, Integer to) {
254 TreeSet<Integer> ts = edgeSources.get(to);
255 if (ts != null) {
256 ts.remove(from);
257 if (ts.isEmpty()) {
258 edgeSources.remove(to);
259 return true;
260 } else {
261 return false;
262 }
263 }
264 return true;
265 }
266
267
268
269
270
271
272
273
274
275
276
277 public int[] doTopoSort() {
278 if (dirty) {
279 LOGGER.error(
280 "The data of this instance is inconsistent."
281 + " Please call prepareData() again or start with a new instance!");
282 return null;
283 }
284 dirty = true;
285
286 int[] result = new int[nodes.size()];
287
288 List<Integer> leafs = nodes.keySet()
289 .stream()
290 .filter(i -> !edgeSources.containsKey(i))
291 .sorted()
292 .collect(Collectors.toList());
293 int cursor = result.length - 1;
294
295
296 while (!leafs.isEmpty()) {
297
298 Integer node = leafs.remove(0);
299
300 result[cursor--] = node;
301
302 for (Integer to : new TreeSet<>(edgeSources.keySet())) {
303 Set<Integer> ts = edgeSources.get(to);
304 if (ts != null && ts.contains(node)) {
305
306 if (removeEdge(node, to)) {
307
308 leafs.add(to);
309 }
310 }
311 }
312 }
313
314 if (!edgeSources.isEmpty()) {
315 LOGGER.error("The input contained circular dependencies: \n{}", toString());
316 return null;
317
318 } else {
319 return result;
320 }
321 }
322
323
324
325
326 @Override
327 public String toString() {
328 StringBuilder result = new StringBuilder("[");
329 for (Integer to : edgeSources.keySet()) {
330 for (Integer from : edgeSources.get(to)) {
331 result.append('[').append(nodes.get(from)).append("->").append(nodes.get(to)).append(']');
332 }
333 }
334 result.append(']');
335 return result.toString();
336 }
337 }