1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.jxpath.ri.model;
19
20 import java.util.HashSet;
21 import java.util.Locale;
22
23 import org.apache.commons.jxpath.AbstractFactory;
24 import org.apache.commons.jxpath.ExceptionHandler;
25 import org.apache.commons.jxpath.JXPathContext;
26 import org.apache.commons.jxpath.JXPathException;
27 import org.apache.commons.jxpath.JXPathNotFoundException;
28 import org.apache.commons.jxpath.NodeSet;
29 import org.apache.commons.jxpath.Pointer;
30 import org.apache.commons.jxpath.ri.Compiler;
31 import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
32 import org.apache.commons.jxpath.ri.NamespaceResolver;
33 import org.apache.commons.jxpath.ri.QName;
34 import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
35 import org.apache.commons.jxpath.ri.compiler.NodeTest;
36 import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
37 import org.apache.commons.jxpath.ri.model.beans.NullPointer;
38
39
40
41
42
43 public abstract class NodePointer implements Pointer {
44
45
46 private static final long serialVersionUID = 8117201322861007777L;
47
48 public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
49
50 public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
51
52
53
54
55
56
57
58
59
60 public static NodePointer newChildNodePointer(final NodePointer parent, final QName qName, final Object bean) {
61 final NodePointerFactory[] factories = JXPathContextReferenceImpl.getNodePointerFactories();
62 for (final NodePointerFactory element : factories) {
63 final NodePointer pointer = element.createNodePointer(parent, qName, bean);
64 if (pointer != null) {
65 return pointer;
66 }
67 }
68 throw new JXPathException("Could not allocate a NodePointer for object of " + bean.getClass());
69 }
70
71
72
73
74
75
76
77
78
79 public static NodePointer newNodePointer(final QName qName, final Object bean, final Locale locale) {
80 NodePointer pointer;
81 if (bean == null) {
82 pointer = new NullPointer(qName, locale);
83 return pointer;
84 }
85 final NodePointerFactory[] factories = JXPathContextReferenceImpl.getNodePointerFactories();
86 for (final NodePointerFactory element : factories) {
87 pointer = element.createNodePointer(qName, bean, locale);
88 if (pointer != null) {
89 return pointer;
90 }
91 }
92 throw new JXPathException("Could not allocate a NodePointer for object of " + bean.getClass());
93 }
94
95
96
97
98
99
100
101 private static void printDeep(final NodePointer pointer, final String indent) {
102 if (indent.length() == 0) {
103 System.err.println("POINTER: " + pointer + "(" + pointer.getClass().getName() + ")");
104 } else {
105 System.err.println(indent + " of " + pointer + "(" + pointer.getClass().getName() + ")");
106 }
107 if (pointer.getImmediateParentPointer() != null) {
108 printDeep(pointer.getImmediateParentPointer(), indent + " ");
109 }
110 }
111
112 private static boolean safeEquals(final Object o1, final Object o2) {
113 return o1 == o2 || o1 != null && o1.equals(o2);
114 }
115
116
117
118
119
120
121
122
123 public static NodePointer verify(final NodePointer nodePointer) {
124 if (!nodePointer.isActual()) {
125
126
127
128
129
130
131 final NodePointer parent = nodePointer.getImmediateParentPointer();
132 if (parent == null || !parent.isContainer() || !parent.isActual()) {
133 throw new JXPathNotFoundException("No value for xpath: " + nodePointer);
134 }
135 }
136 return nodePointer;
137 }
138
139
140 protected int index = WHOLE_COLLECTION;
141
142
143
144
145 private boolean attribute;
146
147
148
149
150 private NamespaceResolver namespaceResolver;
151
152
153
154
155 private ExceptionHandler exceptionHandler;
156
157
158
159
160 private transient Object rootNode;
161
162
163 protected NodePointer parent;
164
165
166 protected Locale locale;
167
168
169
170
171
172
173 protected NodePointer(final NodePointer parent) {
174 this.parent = parent;
175 }
176
177
178
179
180
181
182
183 protected NodePointer(final NodePointer parent, final Locale locale) {
184 this.parent = parent;
185 this.locale = locale;
186 }
187
188
189
190
191
192
193 @Override
194 public String asPath() {
195
196
197 if (parent != null && parent.isContainer()) {
198 return parent.asPath();
199 }
200 final StringBuilder buffer = new StringBuilder();
201 if (parent != null) {
202 buffer.append(parent.asPath());
203 }
204 if (buffer.length() == 0 || buffer.charAt(buffer.length() - 1) != '/') {
205 buffer.append('/');
206 }
207 if (attribute) {
208 buffer.append('@');
209 }
210 buffer.append(getName());
211 if (index != WHOLE_COLLECTION && isCollection()) {
212 buffer.append('[').append(index + 1).append(']');
213 }
214 return buffer.toString();
215 }
216
217
218
219
220
221
222
223
224 public NodeIterator attributeIterator(final QName qname) {
225 final NodePointer valuePointer = getValuePointer();
226 return valuePointer == null || valuePointer == this ? null : valuePointer.attributeIterator(qname);
227 }
228
229
230
231
232
233
234
235
236
237 public NodeIterator childIterator(final NodeTest test, final boolean reverse, final NodePointer startWith) {
238 final NodePointer valuePointer = getValuePointer();
239 return valuePointer == null || valuePointer == this ? null : valuePointer.childIterator(test, reverse, startWith);
240 }
241
242
243
244
245
246
247 @Override
248 public Object clone() {
249 try {
250 final NodePointer ptr = (NodePointer) super.clone();
251 if (parent != null) {
252 ptr.parent = (NodePointer) parent.clone();
253 }
254 return ptr;
255 } catch (final CloneNotSupportedException ex) {
256
257 ex.printStackTrace();
258 }
259 return null;
260 }
261
262
263
264
265
266
267
268
269 public abstract int compareChildNodePointers(NodePointer pointer1, NodePointer pointer2);
270
271
272
273
274
275
276
277
278
279
280 private int compareNodePointers(final NodePointer p1, final int depth1, final NodePointer p2, final int depth2) {
281 if (depth1 < depth2) {
282 final int r = compareNodePointers(p1, depth1, p2.parent, depth2 - 1);
283 return r == 0 ? -1 : r;
284 }
285 if (depth1 > depth2) {
286 final int r = compareNodePointers(p1.parent, depth1 - 1, p2, depth2);
287 return r == 0 ? 1 : r;
288 }
289
290 if (safeEquals(p1, p2)) {
291 return 0;
292 }
293 if (depth1 == 1) {
294 throw new JXPathException("Cannot compare pointers that do not belong to the same tree: '" + p1 + "' and '" + p2 + "'");
295 }
296 final int r = compareNodePointers(p1.parent, depth1 - 1, p2.parent, depth2 - 1);
297 return r == 0 ? p1.parent.compareChildNodePointers(p1, p2) : r;
298 }
299
300 @Override
301 public int compareTo(final Object object) {
302 if (object == this) {
303 return 0;
304 }
305
306 final NodePointer pointer = (NodePointer) object;
307 if (safeEquals(parent, pointer.parent)) {
308 return parent == null ? 0 : parent.compareChildNodePointers(this, pointer);
309 }
310
311 int depth1 = 0;
312 NodePointer p1 = this;
313 final HashSet<NodePointer> parents1 = new HashSet<>();
314 while (p1 != null) {
315 depth1++;
316 p1 = p1.parent;
317 if (p1 != null) {
318 parents1.add(p1);
319 }
320 }
321 boolean commonParentFound = false;
322 int depth2 = 0;
323 NodePointer p2 = pointer;
324 while (p2 != null) {
325 depth2++;
326 p2 = p2.parent;
327 if (parents1.contains(p2)) {
328 commonParentFound = true;
329 }
330 }
331
332 return commonParentFound ? compareNodePointers(this, depth1, pointer, depth2) : 0;
333 }
334
335
336
337
338
339
340
341
342 public NodePointer createAttribute(final JXPathContext context, final QName qName) {
343 throw new JXPathException("Cannot create an attribute for path " + asPath() + "/@" + qName + ", operation is not allowed for this type of node");
344 }
345
346
347
348
349
350
351
352
353
354
355 public NodePointer createChild(final JXPathContext context, final QName qName, final int index) {
356 throw new JXPathException(
357 "Cannot create an object for path " + asPath() + "/" + qName + "[" + (index + 1) + "]" + ", operation is not allowed for this type of node");
358 }
359
360
361
362
363
364
365
366
367
368
369
370 public NodePointer createChild(final JXPathContext context, final QName qName, final int index, final Object value) {
371 throw new JXPathException(
372 "Cannot create an object for path " + asPath() + "/" + qName + "[" + (index + 1) + "]" + ", operation is not allowed for this type of node");
373 }
374
375
376
377
378
379
380
381
382 public NodePointer createPath(final JXPathContext context) {
383 return this;
384 }
385
386
387
388
389
390
391
392
393 public NodePointer createPath(final JXPathContext context, final Object value) {
394 setValue(value);
395 return this;
396 }
397
398
399
400
401
402
403
404 protected String escape(final String string) {
405 final char[] c = { '\'', '"' };
406 final String[] esc = { "'", """ };
407 StringBuilder sb = null;
408 for (int i = 0; sb == null && i < c.length; i++) {
409 if (string.indexOf(c[i]) >= 0) {
410 sb = new StringBuilder(string);
411 }
412 }
413 if (sb == null) {
414 return string;
415 }
416 for (int i = 0; i < c.length; i++) {
417 if (string.indexOf(c[i]) < 0) {
418 continue;
419 }
420 int pos = 0;
421 while (pos < sb.length()) {
422 if (sb.charAt(pos) == c[i]) {
423 sb.replace(pos, pos + 1, esc[i]);
424 pos += esc[i].length();
425 } else {
426 pos++;
427 }
428 }
429 }
430 return sb.toString();
431 }
432
433
434
435
436
437
438
439 protected AbstractFactory getAbstractFactory(final JXPathContext context) {
440 final AbstractFactory factory = context.getFactory();
441 if (factory == null) {
442 throw new JXPathException("Factory is not set on the JXPathContext - cannot create path: " + asPath());
443 }
444 return factory;
445 }
446
447
448
449
450
451
452
453 public abstract Object getBaseValue();
454
455
456
457
458
459
460 protected String getDefaultNamespaceURI() {
461 return null;
462 }
463
464
465
466
467
468
469 public abstract Object getImmediateNode();
470
471
472
473
474
475
476 public NodePointer getImmediateParentPointer() {
477 return parent;
478 }
479
480
481
482
483
484
485
486 public NodePointer getImmediateValuePointer() {
487 return this;
488 }
489
490
491
492
493
494
495
496 public int getIndex() {
497 return index;
498 }
499
500
501
502
503
504
505 public abstract int getLength();
506
507
508
509
510
511
512 public Locale getLocale() {
513 if (locale == null && parent != null) {
514 locale = parent.getLocale();
515 }
516 return locale;
517 }
518
519
520
521
522
523
524 public abstract QName getName();
525
526
527
528
529
530
531 public NamespaceResolver getNamespaceResolver() {
532 if (namespaceResolver == null && parent != null) {
533 namespaceResolver = parent.getNamespaceResolver();
534 }
535 return namespaceResolver;
536 }
537
538
539
540
541
542
543 public String getNamespaceURI() {
544 return null;
545 }
546
547
548
549
550
551
552
553 public String getNamespaceURI(final String prefix) {
554 return null;
555 }
556
557
558
559
560
561
562 @Override
563 public Object getNode() {
564 return getValuePointer().getImmediateNode();
565 }
566
567
568
569
570
571
572
573
574
575 public NodeSet getNodeSetByKey(final JXPathContext context, final String key, final Object value) {
576 return context.getNodeSetByKey(key, value);
577 }
578
579
580
581
582
583
584
585 @Deprecated
586 public Object getNodeValue() {
587 return getNode();
588 }
589
590
591
592
593
594
595 public NodePointer getParent() {
596 NodePointer pointer = parent;
597 while (pointer != null && pointer.isContainer()) {
598 pointer = pointer.getImmediateParentPointer();
599 }
600 return pointer;
601 }
602
603
604
605
606
607
608
609
610 public Pointer getPointerByID(final JXPathContext context, final String id) {
611 return context.getPointerByID(id);
612 }
613
614
615
616
617
618
619
620
621
622 public Pointer getPointerByKey(final JXPathContext context, final String key, final String value) {
623 return context.getPointerByKey(key, value);
624 }
625
626
627
628
629
630
631 @Override
632 public synchronized Object getRootNode() {
633 if (rootNode == null) {
634 rootNode = parent == null ? getImmediateNode() : parent.getRootNode();
635 }
636 return rootNode;
637 }
638
639
640
641
642
643
644 @Override
645 public Object getValue() {
646 final NodePointer valuePointer = getValuePointer();
647 if (valuePointer != this) {
648 return valuePointer.getValue();
649 }
650
651 return getNode();
652 }
653
654
655
656
657
658
659
660
661
662
663
664 public NodePointer getValuePointer() {
665 final NodePointer ivp = getImmediateValuePointer();
666 return ivp == this ? this : ivp.getValuePointer();
667 }
668
669
670
671
672
673
674
675 public void handle(final Throwable t) {
676 handle(t, this);
677 }
678
679
680
681
682
683
684
685
686 public void handle(final Throwable t, final NodePointer originator) {
687 if (exceptionHandler != null) {
688 exceptionHandler.accept(t, originator);
689 return;
690 }
691 if (parent != null) {
692 parent.handle(t, originator);
693 }
694 }
695
696
697
698
699
700
701
702
703
704 public boolean isActual() {
705 return index == WHOLE_COLLECTION || index >= 0 && index < getLength();
706 }
707
708
709
710
711
712
713 public boolean isAttribute() {
714 return attribute;
715 }
716
717
718
719
720
721
722 public abstract boolean isCollection();
723
724
725
726
727
728
729 public boolean isContainer() {
730 return false;
731 }
732
733
734
735
736
737
738
739 protected boolean isDefaultNamespace(final String prefix) {
740 if (prefix == null) {
741 return true;
742 }
743 final String namespace = getNamespaceURI(prefix);
744 return namespace != null && namespace.equals(getDefaultNamespaceURI());
745 }
746
747
748
749
750
751
752
753 public boolean isLanguage(final String lang) {
754 final Locale loc = getLocale();
755 final String name = loc.toString().replace('_', '-');
756 return name.toUpperCase(Locale.ENGLISH).startsWith(lang.toUpperCase(Locale.ENGLISH));
757 }
758
759
760
761
762
763
764 public abstract boolean isLeaf();
765
766
767
768
769
770
771
772 @Deprecated
773 public boolean isNode() {
774 return !isContainer();
775 }
776
777
778
779
780
781
782 public boolean isRoot() {
783 return parent == null;
784 }
785
786
787
788
789
790
791
792 public NodeIterator namespaceIterator() {
793 return null;
794 }
795
796
797
798
799
800
801
802
803 public NodePointer namespacePointer(final String namespace) {
804 return null;
805 }
806
807
808
809
810 public void printPointerChain() {
811 printDeep(this, "");
812 }
813
814
815
816
817 public void remove() {
818
819
820
821 }
822
823
824
825
826
827
828 public void setAttribute(final boolean attribute) {
829 this.attribute = attribute;
830 }
831
832
833
834
835
836
837 public void setExceptionHandler(final ExceptionHandler exceptionHandler) {
838 this.exceptionHandler = exceptionHandler;
839 }
840
841
842
843
844
845
846 public void setIndex(final int index) {
847 this.index = index;
848 }
849
850
851
852
853
854
855 public void setNamespaceResolver(final NamespaceResolver namespaceResolver) {
856 this.namespaceResolver = namespaceResolver;
857 }
858
859
860
861
862
863
864 @Override
865 public abstract void setValue(Object value);
866
867
868
869
870
871
872
873 public boolean testNode(final NodeTest test) {
874 if (test == null) {
875 return true;
876 }
877 if (test instanceof NodeNameTest) {
878 if (isContainer()) {
879 return false;
880 }
881 final NodeNameTest nodeNameTest = (NodeNameTest) test;
882 final QName testName = nodeNameTest.getNodeName();
883 final QName nodeName = getName();
884 if (nodeName == null) {
885 return false;
886 }
887 final String testPrefix = testName.getPrefix();
888 final String nodePrefix = nodeName.getPrefix();
889 if (!safeEquals(testPrefix, nodePrefix)) {
890 final String testNS = getNamespaceURI(testPrefix);
891 final String nodeNS = getNamespaceURI(nodePrefix);
892 if (!safeEquals(testNS, nodeNS)) {
893 return false;
894 }
895 }
896 if (nodeNameTest.isWildcard()) {
897 return true;
898 }
899 return testName.getName().equals(nodeName.getName());
900 }
901 return test instanceof NodeTypeTest && ((NodeTypeTest) test).getNodeType() == Compiler.NODE_TYPE_NODE && isNode();
902 }
903
904 @Override
905 public String toString() {
906 return asPath();
907 }
908 }