1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package eu.ehri.project.persistence.utils;
21
22 import com.google.common.collect.ArrayListMultimap;
23 import com.google.common.collect.ListMultimap;
24 import com.google.common.collect.Lists;
25 import eu.ehri.project.persistence.NestableData;
26
27 import java.util.List;
28 import java.util.function.BiFunction;
29
30
31
32
33 public class DataUtils {
34
35 public static class BundlePathError extends NullPointerException {
36 BundlePathError(String message) {
37 super(message);
38 }
39 }
40
41 static class BundleIndexError extends IndexOutOfBoundsException {
42 BundleIndexError(String message) {
43 super(message);
44 }
45 }
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public static <T, N extends NestableData<N>> T get(N item, String path) {
62 return fetchAttribute(item, NestableDataPath.fromString(path),
63 (subjectNode, subjectPath) -> subjectNode.getDataValue(subjectPath
64 .getTerminus()));
65 }
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 public static <N extends NestableData<N>> N getItem(N item, String path) {
81 return fetchNode(item, NestableDataPath.fromString(path));
82 }
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public static <N extends NestableData<N>> N delete(N item, String path) {
99 return mutateAttribute(item, NestableDataPath.fromString(path),
100 (subject, p) -> subject.removeDataValue(p.getTerminus()));
101 }
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 public static <N extends NestableData<N>> N deleteItem(N item, String path) {
118 return deleteNode(item, NestableDataPath.fromString(path));
119 }
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 public static <T, N extends NestableData<N>> N set(N item, String path, final T value) {
138 return mutateAttribute(item, NestableDataPath.fromString(path),
139 (subject, p) -> subject.withDataValue(p.getTerminus(), value));
140 }
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 public static <N extends NestableData<N>> N setItem(N item, String path, N newItem) {
162 return setNode(item, NestableDataPath.fromString(path), newItem);
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 public static <N extends NestableData<N>> List<N> getRelations(N item, String path) {
179 return fetchAttribute(item, NestableDataPath.fromString(path),
180 (subjectNode, subjectPath) -> Lists.newArrayList(subjectNode.getRelations(subjectPath
181 .getTerminus())));
182 }
183
184
185
186 private static <T, N extends NestableData<N>> T fetchAttribute(N bundle, NestableDataPath path,
187 BiFunction<N, NestableDataPath, T> op) {
188 if (path.isEmpty()) {
189 return op.apply(bundle, path);
190 } else {
191 PathSection section = path.current();
192 if (!bundle.hasRelations(section.getPath()))
193 throw new BundlePathError(String.format(
194 "Relation path '%s' not found", section.getPath()));
195 try {
196 List<N> relations = bundle.getRelations(section.getPath());
197 return fetchAttribute(relations.get(section.getIndex()),
198 path.next(), op);
199 } catch (IndexOutOfBoundsException e) {
200 throw new BundleIndexError(String.format(
201 "Relation index '%s[%s]' not found", path.next(),
202 section.getIndex()));
203 }
204 }
205 }
206
207 private static <N extends NestableData<N>> N fetchNode(N bundle, NestableDataPath path) {
208 if (path.getTerminus() == null) {
209 throw new IllegalArgumentException(
210 "Last component of path must be a valid subtree address.");
211 }
212 if (path.isEmpty()) {
213 return bundle;
214 } else {
215 PathSection section = path.current();
216 if (!bundle.hasRelations(section.getPath()))
217 throw new BundlePathError(String.format(
218 "Relation path '%s' not found", section.getPath()));
219 List<N> relations = bundle.getRelations(section.getPath());
220 try {
221 N next = relations.get(section.getIndex());
222 return fetchNode(next, path.next());
223 } catch (IndexOutOfBoundsException e) {
224 throw new BundleIndexError(String.format(
225 "Relation index '%s[%s]' not found: %s", section.getPath(),
226 section.getIndex(), relations));
227 }
228 }
229 }
230
231 private static <N extends NestableData<N>> N mutateAttribute(N bundle, NestableDataPath path,
232 BiFunction<N, NestableDataPath, N> op) {
233
234
235 if (path.isEmpty()) {
236 return op.apply(bundle, path);
237 } else {
238
239 PathSection section = path.current();
240 if (!bundle.hasRelations(section.getPath()))
241 throw new BundlePathError(String.format(
242 "Relation path '%s' not found", section.getPath()));
243 ListMultimap<String, N> allRelations = ArrayListMultimap
244 .create(bundle.getRelations());
245 try {
246 List<N> relations = Lists.newArrayList(allRelations
247 .removeAll(section.getPath()));
248 N subject = relations.get(section.getIndex());
249 relations.set(section.getIndex(),
250 mutateAttribute(subject, path.next(), op));
251 allRelations.putAll(section.getPath(), relations);
252 return bundle.replaceRelations(allRelations);
253 } catch (IndexOutOfBoundsException e) {
254 throw new BundleIndexError(String.format(
255 "Relation index '%s[%s]' not found", section.getPath(),
256 section.getIndex()));
257 }
258 }
259 }
260
261 private static <N extends NestableData<N>> N setNode(N bundle, NestableDataPath path, N newNode) {
262 if (path.getTerminus() == null) {
263 throw new IllegalArgumentException(
264 "Last component of path must be a valid subtree address.");
265 }
266 if (path.isEmpty())
267 throw new IllegalArgumentException("Path must refer to a nested node.");
268
269 PathSection section = path.current();
270 NestableDataPath next = path.next();
271
272 if (section.getIndex() != -1 && !bundle.hasRelations(section.getPath())) {
273 throw new BundlePathError(String.format(
274 "Relation path '%s' not found", section.getPath()));
275 }
276 ListMultimap<String, N> allRelations = ArrayListMultimap
277 .create(bundle.getRelations());
278 try {
279 List<N> relations = Lists.newArrayList(allRelations
280 .removeAll(section.getPath()));
281 if (next.isEmpty()) {
282
283 if (section.getIndex() == -1) {
284 relations.add(newNode);
285 } else {
286 relations.set(section.getIndex(), newNode);
287 }
288 } else {
289 N subject = relations.get(section.getIndex());
290 relations.set(section.getIndex(),
291 setNode(subject, next, newNode));
292 }
293 allRelations.putAll(section.getPath(), relations);
294 return bundle.replaceRelations(allRelations);
295 } catch (IndexOutOfBoundsException e) {
296 throw new BundleIndexError(String.format(
297 "Relation index '%s[%s]' not found", next.current().getPath(),
298 next.current().getIndex()));
299 }
300 }
301
302 private static <N extends NestableData<N>> N deleteNode(N bundle, NestableDataPath path) {
303 if (path.getTerminus() == null) {
304 throw new IllegalArgumentException(
305 "Last component of path must be a valid subtree address.");
306 }
307 if (path.isEmpty()) {
308 throw new IllegalArgumentException("Path must refer to a nested node.");
309 }
310 PathSection section = path.current();
311 NestableDataPath next = path.next();
312
313 if (!bundle.hasRelations(section.getPath()))
314 throw new BundlePathError(String.format(
315 "Relation path '%s' not found", section.getPath()));
316 ListMultimap<String, N> allRelations = ArrayListMultimap
317 .create(bundle.getRelations());
318 try {
319 List<N> relations = Lists.newArrayList(allRelations
320 .removeAll(section.getPath()));
321 if (next.isEmpty()) {
322 relations.remove(section.getIndex());
323 } else {
324 N subject = relations.get(section.getIndex());
325 relations.set(section.getIndex(),
326 deleteNode(subject, next));
327 }
328 if (!relations.isEmpty())
329 allRelations.putAll(section.getPath(), relations);
330 return bundle.replaceRelations(allRelations);
331 } catch (IndexOutOfBoundsException e) {
332 throw new BundleIndexError(String.format(
333 "Relation index '%s[%s]' not found", section.getPath(),
334 section.getIndex()));
335 }
336 }
337 }