1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration2.tree;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.atomic.AtomicReference;
28
29 import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
30 import org.apache.commons.lang3.mutable.Mutable;
31 import org.apache.commons.lang3.mutable.MutableObject;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 public class InMemoryNodeModel implements NodeModel<ImmutableNode> {
49
50
51
52 private static final NodeHandler<ImmutableNode> DUMMY_HANDLER = new TreeData(null, Collections.<ImmutableNode, ImmutableNode>emptyMap(),
53 Collections.<ImmutableNode, ImmutableNode>emptyMap(), null, new ReferenceTracker());
54
55
56 private final AtomicReference<TreeData> structure;
57
58
59
60
61 public InMemoryNodeModel() {
62 this(null);
63 }
64
65
66
67
68
69
70
71 public InMemoryNodeModel(final ImmutableNode root) {
72 structure = new AtomicReference<>(createTreeData(initialRootNode(root), null));
73 }
74
75
76
77
78
79
80
81
82
83
84 public ImmutableNode getRootNode() {
85 return getTreeData().getRootNode();
86 }
87
88
89
90
91
92 @Override
93 public NodeHandler<ImmutableNode> getNodeHandler() {
94 return getReferenceNodeHandler();
95 }
96
97 @Override
98 public void addProperty(final String key, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) {
99 addProperty(key, null, values, resolver);
100 }
101
102
103
104
105
106
107
108
109
110
111
112
113 public void addProperty(final String key, final NodeSelector selector, final Iterable<?> values, final NodeKeyResolver<ImmutableNode> resolver) {
114 if (valuesNotEmpty(values)) {
115 updateModel(tx -> {
116 initializeAddTransaction(tx, key, values, resolver);
117 return true;
118 }, selector, resolver);
119 }
120 }
121
122 @Override
123 public void addNodes(final String key, final Collection<? extends ImmutableNode> nodes, final NodeKeyResolver<ImmutableNode> resolver) {
124 addNodes(key, null, nodes, resolver);
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138 public void addNodes(final String key, final NodeSelector selector, final Collection<? extends ImmutableNode> nodes,
139 final NodeKeyResolver<ImmutableNode> resolver) {
140 if (nodes != null && !nodes.isEmpty()) {
141 updateModel(tx -> {
142 final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, tx.getCurrentData());
143 if (results.size() == 1) {
144 if (results.get(0).isAttributeResult()) {
145 throw attributeKeyException(key);
146 }
147 tx.addAddNodesOperation(results.get(0).getNode(), nodes);
148 } else {
149 final NodeAddData<ImmutableNode> addData = resolver.resolveAddKey(tx.getQueryRoot(), key, tx.getCurrentData());
150 if (addData.isAttribute()) {
151 throw attributeKeyException(key);
152 }
153 final ImmutableNode newNode = new ImmutableNode.Builder(nodes.size()).name(addData.getNewNodeName()).addChildren(nodes).create();
154 addNodesByAddData(tx, addData, Collections.singleton(newNode));
155 }
156 return true;
157 }, selector, resolver);
158 }
159 }
160
161 @Override
162 public void setProperty(final String key, final Object value, final NodeKeyResolver<ImmutableNode> resolver) {
163 setProperty(key, null, value, resolver);
164 }
165
166
167
168
169
170
171
172
173
174
175
176
177
178 public void setProperty(final String key, final NodeSelector selector, final Object value, final NodeKeyResolver<ImmutableNode> resolver) {
179 updateModel(tx -> {
180 boolean added = false;
181 final NodeUpdateData<ImmutableNode> updateData = resolver.resolveUpdateKey(tx.getQueryRoot(), key, value, tx.getCurrentData());
182 if (!updateData.getNewValues().isEmpty()) {
183 initializeAddTransaction(tx, key, updateData.getNewValues(), resolver);
184 added = true;
185 }
186 final boolean cleared = initializeClearTransaction(tx, updateData.getRemovedNodes());
187 final boolean updated = initializeUpdateTransaction(tx, updateData.getChangedValues());
188 return added || cleared || updated;
189 }, selector, resolver);
190 }
191
192
193
194
195
196
197 @Override
198 public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
199 return clearTree(key, null, resolver);
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213 public List<QueryResult<ImmutableNode>> clearTree(final String key, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
214 final List<QueryResult<ImmutableNode>> removedElements = new LinkedList<>();
215 updateModel(tx -> {
216 boolean changes = false;
217 final TreeData currentStructure = tx.getCurrentData();
218 final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, currentStructure);
219 removedElements.clear();
220 removedElements.addAll(results);
221 for (final QueryResult<ImmutableNode> result : results) {
222 if (result.isAttributeResult()) {
223 tx.addRemoveAttributeOperation(result.getNode(), result.getAttributeName());
224 } else {
225 if (result.getNode() == currentStructure.getRootNode()) {
226
227 clear(resolver);
228 return false;
229 }
230 tx.addRemoveNodeOperation(currentStructure.getParent(result.getNode()), result.getNode());
231 }
232 changes = true;
233 }
234 return changes;
235 }, selector, resolver);
236
237 return removedElements;
238 }
239
240
241
242
243 @Override
244 public void clearProperty(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
245 clearProperty(key, null, resolver);
246 }
247
248
249
250
251
252
253
254
255
256
257
258 public void clearProperty(final String key, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
259 updateModel(tx -> {
260 final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(tx.getQueryRoot(), key, tx.getCurrentData());
261 return initializeClearTransaction(tx, results);
262 }, selector, resolver);
263 }
264
265
266
267
268
269
270
271 @Override
272 public void clear(final NodeKeyResolver<ImmutableNode> resolver) {
273 final ImmutableNode newRoot = new ImmutableNode.Builder().name(getRootNode().getNodeName()).create();
274 setRootNode(newRoot);
275 }
276
277
278
279
280 @Override
281 public ImmutableNode getInMemoryRepresentation() {
282 return getTreeData().getRootNode();
283 }
284
285
286
287
288
289
290
291
292 @Override
293 public void setRootNode(final ImmutableNode newRoot) {
294 structure.set(createTreeData(initialRootNode(newRoot), structure.get()));
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308 public void replaceRoot(final ImmutableNode newRoot, final NodeKeyResolver<ImmutableNode> resolver) {
309 if (newRoot == null) {
310 throw new IllegalArgumentException("Replaced root node must not be null!");
311 }
312
313 final TreeData current = structure.get();
314
315 final TreeData temp = createTreeDataForRootAndTracker(newRoot, current.getNodeTracker());
316 structure.set(temp.updateNodeTracker(temp.getNodeTracker().update(newRoot, null, resolver, temp)));
317 }
318
319
320
321
322
323
324
325
326
327
328
329
330
331 public void mergeRoot(final ImmutableNode node, final String rootName, final Map<ImmutableNode, ?> references, final Object rootRef,
332 final NodeKeyResolver<ImmutableNode> resolver) {
333 updateModel(tx -> {
334 final TreeData current = tx.getCurrentData();
335 final String newRootName = determineRootName(current.getRootNode(), node, rootName);
336 if (newRootName != null) {
337 tx.addChangeNodeNameOperation(current.getRootNode(), newRootName);
338 }
339 tx.addAddNodesOperation(current.getRootNode(), node.getChildren());
340 tx.addAttributesOperation(current.getRootNode(), node.getAttributes());
341 if (node.getValue() != null) {
342 tx.addChangeNodeValueOperation(current.getRootNode(), node.getValue());
343 }
344 if (references != null) {
345 tx.addNewReferences(references);
346 }
347 if (rootRef != null) {
348 tx.addNewReference(current.getRootNode(), rootRef);
349 }
350 return true;
351 }, null, resolver);
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365 public void trackNode(final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
366 boolean done;
367 do {
368 final TreeData current = structure.get();
369 final NodeTracker newTracker = current.getNodeTracker().trackNode(current.getRootNode(), selector, resolver, current);
370 done = structure.compareAndSet(current, current.updateNodeTracker(newTracker));
371 } while (!done);
372 }
373
374
375
376
377
378
379
380
381
382
383 public Collection<NodeSelector> selectAndTrackNodes(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
384 final Mutable<Collection<NodeSelector>> refSelectors = new MutableObject<>();
385 boolean done;
386 do {
387 final TreeData current = structure.get();
388 final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
389 if (nodes.isEmpty()) {
390 return Collections.emptyList();
391 }
392 done = structure.compareAndSet(current, createSelectorsForTrackedNodes(refSelectors, nodes, current, resolver));
393 } while (!done);
394 return refSelectors.getValue();
395 }
396
397
398
399
400
401
402
403
404
405
406 public Collection<NodeSelector> trackChildNodes(final String key, final NodeKeyResolver<ImmutableNode> resolver) {
407 final Mutable<Collection<NodeSelector>> refSelectors = new MutableObject<>();
408 boolean done;
409 do {
410 refSelectors.setValue(Collections.<NodeSelector>emptyList());
411 final TreeData current = structure.get();
412 final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
413 if (nodes.size() == 1) {
414 final ImmutableNode node = nodes.get(0);
415 done = node.getChildren().isEmpty()
416 || structure.compareAndSet(current, createSelectorsForTrackedNodes(refSelectors, node.getChildren(), current, resolver));
417 } else {
418 done = true;
419 }
420 } while (!done);
421 return refSelectors.getValue();
422 }
423
424
425
426
427
428
429
430
431
432
433
434
435 public NodeSelector trackChildNodeWithCreation(final String key, final String childName, final NodeKeyResolver<ImmutableNode> resolver) {
436 final MutableObject<NodeSelector> refSelector = new MutableObject<>();
437 boolean done;
438
439 do {
440 final TreeData current = structure.get();
441 final List<ImmutableNode> nodes = resolver.resolveNodeKey(current.getRootNode(), key, current);
442 if (nodes.size() != 1) {
443 throw new ConfigurationRuntimeException("Key does not select a single node: " + key);
444 }
445
446 final ImmutableNode parent = nodes.get(0);
447 final TreeData newData = createDataWithTrackedChildNode(current, parent, childName, resolver, refSelector);
448
449 done = structure.compareAndSet(current, newData);
450 } while (!done);
451
452 return refSelector.getValue();
453 }
454
455
456
457
458
459
460
461
462
463
464 public ImmutableNode getTrackedNode(final NodeSelector selector) {
465 return structure.get().getNodeTracker().getTrackedNode(selector);
466 }
467
468
469
470
471
472
473
474
475
476
477
478 public void replaceTrackedNode(final NodeSelector selector, final ImmutableNode newNode) {
479 if (newNode == null) {
480 throw new IllegalArgumentException("Replacement node must not be null!");
481 }
482
483 boolean done;
484 do {
485 final TreeData currentData = structure.get();
486 done = replaceDetachedTrackedNode(currentData, selector, newNode) || replaceActiveTrackedNode(currentData, selector, newNode);
487 } while (!done);
488 }
489
490
491
492
493
494
495
496
497
498
499
500 public NodeHandler<ImmutableNode> getTrackedNodeHandler(final NodeSelector selector) {
501 final TreeData currentData = structure.get();
502 final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
503 return detachedNodeModel != null ? detachedNodeModel.getNodeHandler()
504 : new TrackedNodeHandler(currentData.getNodeTracker().getTrackedNode(selector), currentData);
505 }
506
507
508
509
510
511
512
513
514
515
516
517
518
519 public boolean isTrackedNodeDetached(final NodeSelector selector) {
520 return structure.get().getNodeTracker().isTrackedNodeDetached(selector);
521 }
522
523
524
525
526
527
528
529
530
531 public void untrackNode(final NodeSelector selector) {
532 boolean done;
533 do {
534 final TreeData current = structure.get();
535 final NodeTracker newTracker = current.getNodeTracker().untrackNode(selector);
536 done = structure.compareAndSet(current, current.updateNodeTracker(newTracker));
537 } while (!done);
538 }
539
540
541
542
543
544
545
546 public ReferenceNodeHandler getReferenceNodeHandler() {
547 return getTreeData();
548 }
549
550
551
552
553
554
555 TreeData getTreeData() {
556 return structure.get();
557 }
558
559
560
561
562
563
564
565
566
567 static void updateParentMapping(final Map<ImmutableNode, ImmutableNode> parents, final ImmutableNode root) {
568 NodeTreeWalker.INSTANCE.walkBFS(root, new ConfigurationNodeVisitorAdapter<ImmutableNode>() {
569 @Override
570 public void visitBeforeChildren(final ImmutableNode node, final NodeHandler<ImmutableNode> handler) {
571 node.forEach(c -> parents.put(c, node));
572 }
573 }, DUMMY_HANDLER);
574 }
575
576
577
578
579
580
581
582 static boolean checkIfNodeDefined(final ImmutableNode node) {
583 return node.getValue() != null || !node.getChildren().isEmpty() || !node.getAttributes().isEmpty();
584 }
585
586
587
588
589
590
591
592
593
594 private void initializeAddTransaction(final ModelTransaction tx, final String key, final Iterable<?> values,
595 final NodeKeyResolver<ImmutableNode> resolver) {
596 final NodeAddData<ImmutableNode> addData = resolver.resolveAddKey(tx.getQueryRoot(), key, tx.getCurrentData());
597 if (addData.isAttribute()) {
598 addAttributeProperty(tx, addData, values);
599 } else {
600 addNodeProperty(tx, addData, values);
601 }
602 }
603
604
605
606
607
608
609
610
611 private TreeData createTreeData(final ImmutableNode root, final TreeData current) {
612 final NodeTracker newTracker = current != null ? current.getNodeTracker().detachAllTrackedNodes() : new NodeTracker();
613 return createTreeDataForRootAndTracker(root, newTracker);
614 }
615
616
617
618
619
620
621
622
623
624 private TreeData createTreeDataForRootAndTracker(final ImmutableNode root, final NodeTracker newTracker) {
625 return new TreeData(root, createParentMapping(root), Collections.<ImmutableNode, ImmutableNode>emptyMap(), newTracker, new ReferenceTracker());
626 }
627
628
629
630
631
632
633
634
635 private static void addNodeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) {
636 final Collection<ImmutableNode> newNodes = createNodesToAdd(addData.getNewNodeName(), values);
637 addNodesByAddData(tx, addData, newNodes);
638 }
639
640
641
642
643
644
645
646
647
648 private static void addNodesByAddData(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Collection<ImmutableNode> newNodes) {
649 if (addData.getPathNodes().isEmpty()) {
650 tx.addAddNodesOperation(addData.getParent(), newNodes);
651 } else {
652 final ImmutableNode newChild = createNodeToAddWithPath(addData, newNodes);
653 tx.addAddNodeOperation(addData.getParent(), newChild);
654 }
655 }
656
657
658
659
660
661
662
663
664 private static void addAttributeProperty(final ModelTransaction tx, final NodeAddData<ImmutableNode> addData, final Iterable<?> values) {
665 if (addData.getPathNodes().isEmpty()) {
666 tx.addAttributeOperation(addData.getParent(), addData.getNewNodeName(), values.iterator().next());
667 } else {
668 final int pathNodeCount = addData.getPathNodes().size();
669 final ImmutableNode childWithAttribute = new ImmutableNode.Builder().name(addData.getPathNodes().get(pathNodeCount - 1))
670 .addAttribute(addData.getNewNodeName(), values.iterator().next()).create();
671 final ImmutableNode newChild = pathNodeCount > 1
672 ? createNodeOnPath(addData.getPathNodes().subList(0, pathNodeCount - 1).iterator(), Collections.singleton(childWithAttribute))
673 : childWithAttribute;
674 tx.addAddNodeOperation(addData.getParent(), newChild);
675 }
676 }
677
678
679
680
681
682
683
684
685 private static Collection<ImmutableNode> createNodesToAdd(final String newNodeName, final Iterable<?> values) {
686 final Collection<ImmutableNode> nodes = new LinkedList<>();
687 values.forEach(value -> nodes.add(new ImmutableNode.Builder().name(newNodeName).value(value).create()));
688 return nodes;
689 }
690
691
692
693
694
695
696
697
698
699 private static ImmutableNode createNodeToAddWithPath(final NodeAddData<ImmutableNode> addData, final Collection<ImmutableNode> newNodes) {
700 return createNodeOnPath(addData.getPathNodes().iterator(), newNodes);
701 }
702
703
704
705
706
707
708
709
710
711 private static ImmutableNode createNodeOnPath(final Iterator<String> it, final Collection<ImmutableNode> newNodes) {
712 final String nodeName = it.next();
713 final ImmutableNode.Builder builder;
714 if (it.hasNext()) {
715 builder = new ImmutableNode.Builder(1);
716 builder.addChild(createNodeOnPath(it, newNodes));
717 } else {
718 builder = new ImmutableNode.Builder(newNodes.size());
719 builder.addChildren(newNodes);
720 }
721 return builder.name(nodeName).create();
722 }
723
724
725
726
727
728
729
730
731 private static boolean initializeClearTransaction(final ModelTransaction tx, final Collection<QueryResult<ImmutableNode>> results) {
732 results.forEach(result -> {
733 if (result.isAttributeResult()) {
734 tx.addRemoveAttributeOperation(result.getNode(), result.getAttributeName());
735 } else {
736 tx.addClearNodeValueOperation(result.getNode());
737 }
738 });
739
740 return !results.isEmpty();
741 }
742
743
744
745
746
747
748
749
750 private static boolean initializeUpdateTransaction(final ModelTransaction tx, final Map<QueryResult<ImmutableNode>, Object> changedValues) {
751 changedValues.forEach((k, v) -> {
752 final ImmutableNode node = k.getNode();
753 if (k.isAttributeResult()) {
754 tx.addAttributeOperation(node, k.getAttributeName(), v);
755 } else {
756 tx.addChangeNodeValueOperation(node, v);
757 }
758 });
759
760 return !changedValues.isEmpty();
761 }
762
763
764
765
766
767
768
769
770 private static ImmutableNode initialRootNode(final ImmutableNode providedRoot) {
771 return providedRoot != null ? providedRoot : new ImmutableNode.Builder().create();
772 }
773
774
775
776
777
778
779
780
781
782
783
784 private static String determineRootName(final ImmutableNode rootNode, final ImmutableNode node, final String rootName) {
785 if (rootName != null) {
786 return rootName;
787 }
788 if (rootNode.getNodeName() == null) {
789 return node.getNodeName();
790 }
791 return null;
792 }
793
794
795
796
797
798
799
800
801
802 private Map<ImmutableNode, ImmutableNode> createParentMapping(final ImmutableNode root) {
803 final Map<ImmutableNode, ImmutableNode> parents = new HashMap<>();
804 updateParentMapping(parents, root);
805 return parents;
806 }
807
808
809
810
811
812
813
814
815
816
817 private void updateModel(final TransactionInitializer txInit, final NodeSelector selector, final NodeKeyResolver<ImmutableNode> resolver) {
818 boolean done;
819
820 do {
821 final TreeData currentData = getTreeData();
822 done = executeTransactionOnDetachedTrackedNode(txInit, selector, currentData, resolver)
823 || executeTransactionOnCurrentStructure(txInit, selector, currentData, resolver);
824 } while (!done);
825 }
826
827
828
829
830
831
832
833
834
835
836
837 private boolean executeTransactionOnCurrentStructure(final TransactionInitializer txInit, final NodeSelector selector, final TreeData currentData,
838 final NodeKeyResolver<ImmutableNode> resolver) {
839 final boolean done;
840 final ModelTransaction tx = new ModelTransaction(currentData, selector, resolver);
841 if (!txInit.initTransaction(tx)) {
842 done = true;
843 } else {
844 final TreeData newData = tx.execute();
845 done = structure.compareAndSet(tx.getCurrentData(), newData);
846 }
847 return done;
848 }
849
850
851
852
853
854
855
856
857
858
859
860
861 private boolean executeTransactionOnDetachedTrackedNode(final TransactionInitializer txInit, final NodeSelector selector, final TreeData currentData,
862 final NodeKeyResolver<ImmutableNode> resolver) {
863 if (selector != null) {
864 final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
865 if (detachedNodeModel != null) {
866 detachedNodeModel.updateModel(txInit, null, resolver);
867 return true;
868 }
869 }
870
871 return false;
872 }
873
874
875
876
877
878
879
880
881
882 private boolean replaceDetachedTrackedNode(final TreeData currentData, final NodeSelector selector, final ImmutableNode newNode) {
883 final InMemoryNodeModel detachedNodeModel = currentData.getNodeTracker().getDetachedNodeModel(selector);
884 if (detachedNodeModel != null) {
885 detachedNodeModel.setRootNode(newNode);
886 return true;
887 }
888
889 return false;
890 }
891
892
893
894
895
896
897
898
899
900 private boolean replaceActiveTrackedNode(final TreeData currentData, final NodeSelector selector, final ImmutableNode newNode) {
901 final NodeTracker newTracker = currentData.getNodeTracker().replaceAndDetachTrackedNode(selector, newNode);
902 return structure.compareAndSet(currentData, currentData.updateNodeTracker(newTracker));
903 }
904
905
906
907
908
909
910
911
912
913
914 private static TreeData createSelectorsForTrackedNodes(final Mutable<Collection<NodeSelector>> refSelectors, final List<ImmutableNode> nodes,
915 final TreeData current, final NodeKeyResolver<ImmutableNode> resolver) {
916 final List<NodeSelector> selectors = new ArrayList<>(nodes.size());
917 final Map<ImmutableNode, String> cache = new HashMap<>();
918 nodes.forEach(node -> selectors.add(new NodeSelector(resolver.nodeKey(node, cache, current))));
919 refSelectors.setValue(selectors);
920 final NodeTracker newTracker = current.getNodeTracker().trackNodes(selectors, nodes);
921 return current.updateNodeTracker(newTracker);
922 }
923
924
925
926
927
928
929
930
931
932
933 private static TreeData updateDataWithNewTrackedNode(final TreeData current, final ImmutableNode node, final NodeKeyResolver<ImmutableNode> resolver,
934 final MutableObject<NodeSelector> refSelector) {
935 final NodeSelector selector = new NodeSelector(resolver.nodeKey(node, new HashMap<>(), current));
936 refSelector.setValue(selector);
937 final NodeTracker newTracker = current.getNodeTracker().trackNodes(Collections.singleton(selector), Collections.singleton(node));
938 return current.updateNodeTracker(newTracker);
939 }
940
941
942
943
944
945
946
947
948
949
950
951
952 private static TreeData createDataWithTrackedChildNode(final TreeData current, final ImmutableNode parent, final String childName,
953 final NodeKeyResolver<ImmutableNode> resolver, final MutableObject<NodeSelector> refSelector) {
954 final TreeData newData;
955 final List<ImmutableNode> namedChildren = current.getChildren(parent, childName);
956 if (!namedChildren.isEmpty()) {
957 newData = updateDataWithNewTrackedNode(current, namedChildren.get(0), resolver, refSelector);
958 } else {
959 final ImmutableNode child = new ImmutableNode.Builder().name(childName).create();
960 final ModelTransaction tx = new ModelTransaction(current, null, resolver);
961 tx.addAddNodeOperation(parent, child);
962 newData = updateDataWithNewTrackedNode(tx.execute(), child, resolver, refSelector);
963 }
964 return newData;
965 }
966
967
968
969
970
971
972
973 private static boolean valuesNotEmpty(final Iterable<?> values) {
974 return values.iterator().hasNext();
975 }
976
977
978
979
980
981
982
983
984 private static IllegalArgumentException attributeKeyException(final String key) {
985 return new IllegalArgumentException("New nodes cannot be added to an attribute key: " + key);
986 }
987
988
989
990
991
992
993 private interface TransactionInitializer {
994
995
996
997
998
999
1000
1001
1002 boolean initTransaction(ModelTransaction tx);
1003 }
1004 }