1. /*
  2. * Copyright 1999-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.jxpath.ri.model;
  17. import java.util.Locale;
  18. import org.apache.commons.jxpath.JXPathContext;
  19. import org.apache.commons.jxpath.JXPathException;
  20. import org.apache.commons.jxpath.Pointer;
  21. import org.apache.commons.jxpath.ri.Compiler;
  22. import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
  23. import org.apache.commons.jxpath.ri.NamespaceResolver;
  24. import org.apache.commons.jxpath.ri.QName;
  25. import org.apache.commons.jxpath.ri.compiler.NodeNameTest;
  26. import org.apache.commons.jxpath.ri.compiler.NodeTest;
  27. import org.apache.commons.jxpath.ri.compiler.NodeTypeTest;
  28. import org.apache.commons.jxpath.ri.model.beans.NullPointer;
  29. /**
  30. * Common superclass for Pointers of all kinds. A NodePointer maps to
  31. * a deterministic XPath that represents the location of a node in an
  32. * object graph. This XPath uses only simple axes: child, namespace and
  33. * attribute and only simple, context-independent predicates.
  34. *
  35. * @author Dmitri Plotnikov
  36. * @version $Revision: 1.25 $ $Date: 2004/04/01 02:55:32 $
  37. */
  38. public abstract class NodePointer implements Pointer {
  39. public static final int WHOLE_COLLECTION = Integer.MIN_VALUE;
  40. protected int index = WHOLE_COLLECTION;
  41. public static final String UNKNOWN_NAMESPACE = "<<unknown namespace>>";
  42. private boolean attribute = false;
  43. private transient Object rootNode;
  44. private NamespaceResolver namespaceResolver;
  45. /**
  46. * Allocates an entirely new NodePointer by iterating through all installed
  47. * NodePointerFactories until it finds one that can create a pointer.
  48. */
  49. public static NodePointer newNodePointer(
  50. QName name,
  51. Object bean,
  52. Locale locale)
  53. {
  54. NodePointer pointer = null;
  55. if (bean == null) {
  56. pointer = new NullPointer(name, locale);
  57. return pointer;
  58. }
  59. NodePointerFactory[] factories =
  60. JXPathContextReferenceImpl.getNodePointerFactories();
  61. for (int i = 0; i < factories.length; i++) {
  62. pointer = factories[i].createNodePointer(name, bean, locale);
  63. if (pointer != null) {
  64. return pointer;
  65. }
  66. }
  67. throw new JXPathException(
  68. "Could not allocate a NodePointer for object of "
  69. + bean.getClass());
  70. }
  71. /**
  72. * Allocates an new child NodePointer by iterating through all installed
  73. * NodePointerFactories until it finds one that can create a pointer.
  74. */
  75. public static NodePointer newChildNodePointer(
  76. NodePointer parent,
  77. QName name,
  78. Object bean)
  79. {
  80. NodePointerFactory[] factories =
  81. JXPathContextReferenceImpl.getNodePointerFactories();
  82. for (int i = 0; i < factories.length; i++) {
  83. NodePointer pointer =
  84. factories[i].createNodePointer(parent, name, bean);
  85. if (pointer != null) {
  86. return pointer;
  87. }
  88. }
  89. throw new JXPathException(
  90. "Could not allocate a NodePointer for object of "
  91. + bean.getClass());
  92. }
  93. protected NodePointer parent;
  94. protected Locale locale;
  95. // private NamespaceManager namespaceManager;
  96. protected NodePointer(NodePointer parent) {
  97. this.parent = parent;
  98. }
  99. protected NodePointer(NodePointer parent, Locale locale) {
  100. this.parent = parent;
  101. this.locale = locale;
  102. }
  103. public NamespaceResolver getNamespaceResolver() {
  104. if (namespaceResolver == null && parent != null) {
  105. namespaceResolver = parent.getNamespaceResolver();
  106. }
  107. return namespaceResolver;
  108. }
  109. public void setNamespaceResolver(NamespaceResolver namespaceResolver) {
  110. this.namespaceResolver = namespaceResolver;
  111. }
  112. public NodePointer getParent() {
  113. NodePointer pointer = parent;
  114. while (pointer != null && pointer.isContainer()) {
  115. pointer = pointer.getImmediateParentPointer();
  116. }
  117. return pointer;
  118. }
  119. public NodePointer getImmediateParentPointer() {
  120. return parent;
  121. }
  122. /**
  123. * Set to true if the pointer represents the "attribute::" axis.
  124. */
  125. public void setAttribute(boolean attribute) {
  126. this.attribute = attribute;
  127. }
  128. /**
  129. * Returns true if the pointer represents the "attribute::" axis.
  130. */
  131. public boolean isAttribute() {
  132. return attribute;
  133. }
  134. /**
  135. * Returns true if this Pointer has no parent.
  136. */
  137. public boolean isRoot() {
  138. return parent == null;
  139. }
  140. /**
  141. * If true, this node does not have children
  142. */
  143. public abstract boolean isLeaf();
  144. /**
  145. * @deprecated Please use !isContainer()
  146. */
  147. public boolean isNode() {
  148. return !isContainer();
  149. }
  150. /**
  151. * If true, this node is axiliary and can only be used as an intermediate in
  152. * the chain of pointers.
  153. */
  154. public boolean isContainer() {
  155. return false;
  156. }
  157. /**
  158. * If the pointer represents a collection, the index identifies
  159. * an element of that collection. The default value of <code>index</code>
  160. * is <code>WHOLE_COLLECTION</code>, which just means that the pointer
  161. * is not indexed at all.
  162. * Note: the index on NodePointer starts with 0, not 1.
  163. */
  164. public int getIndex() {
  165. return index;
  166. }
  167. public void setIndex(int index) {
  168. this.index = index;
  169. }
  170. /**
  171. * Returns <code>true</code> if the value of the pointer is an array or
  172. * a Collection.
  173. */
  174. public abstract boolean isCollection();
  175. /**
  176. * If the pointer represents a collection (or collection element),
  177. * returns the length of the collection.
  178. * Otherwise returns 1 (even if the value is null).
  179. */
  180. public abstract int getLength();
  181. /**
  182. * By default, returns <code>getNode()</code>, can be overridden to
  183. * return a "canonical" value, like for instance a DOM element should
  184. * return its string value.
  185. */
  186. public Object getValue() {
  187. NodePointer valuePointer = getValuePointer();
  188. if (valuePointer != this) {
  189. return valuePointer.getValue();
  190. }
  191. // Default behavior is to return the same as getNode()
  192. return getNode();
  193. }
  194. /**
  195. * If this pointer manages a transparent container, like a variable,
  196. * this method returns the pointer to the contents.
  197. * Only an auxiliary (non-node) pointer can (and should) return a
  198. * value pointer other than itself.
  199. * Note that you probably don't want to override
  200. * <code>getValuePointer()</code> directly. Override the
  201. * <code>getImmediateValuePointer()</code> method instead. The
  202. * <code>getValuePointer()</code> method is calls
  203. * <code>getImmediateValuePointer()</code> and, if the result is not
  204. * <code>this</code>, invokes <code>getValuePointer()</code> recursively.
  205. * The idea here is to open all nested containers. Let's say we have a
  206. * container within a container within a container. The
  207. * <code>getValuePointer()</code> method should then open all those
  208. * containers and return the pointer to the ultimate contents. It does so
  209. * with the above recursion.
  210. */
  211. public NodePointer getValuePointer() {
  212. NodePointer ivp = getImmediateValuePointer();
  213. if (ivp != this) {
  214. return ivp.getValuePointer();
  215. }
  216. return this;
  217. }
  218. /**
  219. * @see #getValuePointer()
  220. *
  221. * @return NodePointer is either <code>this</code> or a pointer
  222. * for the immediately contained value.
  223. */
  224. public NodePointer getImmediateValuePointer() {
  225. return this;
  226. }
  227. /**
  228. * An actual pointer points to an existing part of an object graph, even
  229. * if it is null. A non-actual pointer represents a part that does not exist
  230. * at all.
  231. * For instance consider the pointer "/address/street".
  232. * If both <em>address</em> and <em>street</em> are not null,
  233. * the pointer is actual.
  234. * If <em>address</em> is not null, but <em>street</em> is null,
  235. * the pointer is still actual.
  236. * If <em>address</em> is null, the pointer is not actual.
  237. * (In JavaBeans) if <em>address</em> is not a property of the root bean,
  238. * a Pointer for this path cannot be obtained at all - actual or otherwise.
  239. */
  240. public boolean isActual() {
  241. if (index == WHOLE_COLLECTION) {
  242. return true;
  243. }
  244. else {
  245. return index >= 0 && index < getLength();
  246. }
  247. }
  248. /**
  249. * Returns the name of this node. Can be null.
  250. */
  251. public abstract QName getName();
  252. /**
  253. * Returns the value represented by the pointer before indexing.
  254. * So, if the node represents an element of a collection, this
  255. * method returns the collection itself.
  256. */
  257. public abstract Object getBaseValue();
  258. /**
  259. * Returns the object the pointer points to; does not convert it
  260. * to a "canonical" type.
  261. *
  262. * @deprecated 1.1 Please use getNode()
  263. */
  264. public Object getNodeValue() {
  265. return getNode();
  266. }
  267. /**
  268. * Returns the object the pointer points to; does not convert it
  269. * to a "canonical" type. Opens containers, properties etc and returns
  270. * the ultimate contents.
  271. */
  272. public Object getNode() {
  273. return getValuePointer().getImmediateNode();
  274. }
  275. public Object getRootNode() {
  276. if (rootNode == null) {
  277. if (parent != null) {
  278. rootNode = parent.getRootNode();
  279. }
  280. else {
  281. rootNode = getImmediateNode();
  282. }
  283. }
  284. return rootNode;
  285. }
  286. /**
  287. * Returns the object the pointer points to; does not convert it
  288. * to a "canonical" type.
  289. */
  290. public abstract Object getImmediateNode();
  291. /**
  292. * Converts the value to the required type and changes the corresponding
  293. * object to that value.
  294. */
  295. public abstract void setValue(Object value);
  296. /**
  297. * Compares two child NodePointers and returns a positive number,
  298. * zero or a positive number according to the order of the pointers.
  299. */
  300. public abstract int compareChildNodePointers(
  301. NodePointer pointer1, NodePointer pointer2);
  302. /**
  303. * Checks if this Pointer matches the supplied NodeTest.
  304. */
  305. public boolean testNode(NodeTest test) {
  306. if (test == null) {
  307. return true;
  308. }
  309. else if (test instanceof NodeNameTest) {
  310. if (isContainer()) {
  311. return false;
  312. }
  313. NodeNameTest nodeNameTest = (NodeNameTest) test;
  314. QName testName = nodeNameTest.getNodeName();
  315. QName nodeName = getName();
  316. if (nodeName == null) {
  317. return false;
  318. }
  319. String testPrefix = testName.getPrefix();
  320. String nodePrefix = nodeName.getPrefix();
  321. if (!equalStrings(testPrefix, nodePrefix)) {
  322. String testNS = getNamespaceURI(testPrefix);
  323. String nodeNS = getNamespaceURI(nodePrefix);
  324. if (!equalStrings(testNS, nodeNS)) {
  325. return false;
  326. }
  327. }
  328. if (nodeNameTest.isWildcard()) {
  329. return true;
  330. }
  331. return testName.getName().equals(nodeName.getName());
  332. }
  333. else if (test instanceof NodeTypeTest) {
  334. if (((NodeTypeTest) test).getNodeType()
  335. == Compiler.NODE_TYPE_NODE) {
  336. return isNode();
  337. }
  338. }
  339. return false;
  340. }
  341. private static boolean equalStrings(String s1, String s2) {
  342. if (s1 == null && s2 != null) {
  343. return false;
  344. }
  345. if (s1 != null && !s1.equals(s2)) {
  346. return false;
  347. }
  348. return true;
  349. }
  350. /**
  351. * Called directly by JXPathContext. Must create path and
  352. * set value.
  353. */
  354. public NodePointer createPath(JXPathContext context, Object value) {
  355. setValue(value);
  356. return this;
  357. }
  358. /**
  359. * Remove the node of the object graph this pointer points to.
  360. */
  361. public void remove() {
  362. // It is a no-op
  363. // System.err.println("REMOVING: " + asPath() + " " + getClass());
  364. // printPointerChain();
  365. }
  366. /**
  367. * Called by a child pointer when it needs to create a parent object.
  368. * Must create an object described by this pointer and return
  369. * a new pointer that properly describes the new object.
  370. */
  371. public NodePointer createPath(JXPathContext context) {
  372. return this;
  373. }
  374. /**
  375. * Called by a child pointer if that child needs to assign the value
  376. * supplied in the createPath(context, value) call to a non-existent
  377. * node. This method may have to expand the collection in order to assign
  378. * the element.
  379. */
  380. public NodePointer createChild(
  381. JXPathContext context,
  382. QName name,
  383. int index,
  384. Object value)
  385. {
  386. throw new JXPathException(
  387. "Cannot create an object for path "
  388. + asPath()
  389. + "/"
  390. + name
  391. + "["
  392. + (index + 1)
  393. + "]"
  394. + ", operation is not allowed for this type of node");
  395. }
  396. /**
  397. * Called by a child pointer when it needs to create a parent object
  398. * for a non-existent collection element. It may have to expand the
  399. * collection, then create an element object and return a new pointer
  400. * describing the newly created element.
  401. */
  402. public NodePointer createChild(
  403. JXPathContext context,
  404. QName name,
  405. int index)
  406. {
  407. throw new JXPathException(
  408. "Cannot create an object for path "
  409. + asPath()
  410. + "/"
  411. + name
  412. + "["
  413. + (index + 1)
  414. + "]"
  415. + ", operation is not allowed for this type of node");
  416. }
  417. /**
  418. * Called to create a non-existing attribute
  419. */
  420. public NodePointer createAttribute(JXPathContext context, QName name) {
  421. throw new JXPathException(
  422. "Cannot create an attribute for path "
  423. + asPath() + "/@" + name
  424. + ", operation is not allowed for this type of node");
  425. }
  426. /**
  427. * If the Pointer has a parent, returns the parent's locale;
  428. * otherwise returns the locale specified when this Pointer
  429. * was created.
  430. */
  431. public Locale getLocale() {
  432. if (locale == null) {
  433. if (parent != null) {
  434. locale = parent.getLocale();
  435. }
  436. }
  437. return locale;
  438. }
  439. /**
  440. * Returns true if the selected locale name starts
  441. * with the specified prefix <i>lang</i>, case-insensitive.
  442. */
  443. public boolean isLanguage(String lang) {
  444. Locale loc = getLocale();
  445. String name = loc.toString().replace('_', '-');
  446. return name.toUpperCase().startsWith(lang.toUpperCase());
  447. }
  448. // /**
  449. // * Installs the supplied manager as the namespace manager for this node
  450. // * pointer. The {@link #getNamespaceURI(String) getNamespaceURI(prefix)}
  451. // * uses this manager to resolve namespace prefixes.
  452. // *
  453. // * @param namespaceManager
  454. // */
  455. // public void setNamespaceManager(NamespaceManager namespaceManager) {
  456. // this.namespaceManager = namespaceManager;
  457. // }
  458. //
  459. // public NamespaceManager getNamespaceManager() {
  460. // if (namespaceManager != null) {
  461. // return namespaceManager;
  462. // }
  463. // if (parent != null) {
  464. // return parent.getNamespaceManager();
  465. // }
  466. // return null;
  467. // }
  468. //
  469. /**
  470. * Returns a NodeIterator that iterates over all children or all children
  471. * that match the given NodeTest, starting with the specified one.
  472. */
  473. public NodeIterator childIterator(
  474. NodeTest test,
  475. boolean reverse,
  476. NodePointer startWith)
  477. {
  478. NodePointer valuePointer = getValuePointer();
  479. if (valuePointer != null && valuePointer != this) {
  480. return valuePointer.childIterator(test, reverse, startWith);
  481. }
  482. return null;
  483. }
  484. /**
  485. * Returns a NodeIterator that iterates over all attributes of the current
  486. * node matching the supplied node name (could have a wildcard).
  487. * May return null if the object does not support the attributes.
  488. */
  489. public NodeIterator attributeIterator(QName qname) {
  490. NodePointer valuePointer = getValuePointer();
  491. if (valuePointer != null && valuePointer != this) {
  492. return valuePointer.attributeIterator(qname);
  493. }
  494. return null;
  495. }
  496. /**
  497. * Returns a NodeIterator that iterates over all namespaces of the value
  498. * currently pointed at.
  499. * May return null if the object does not support the namespaces.
  500. */
  501. public NodeIterator namespaceIterator() {
  502. return null;
  503. }
  504. /**
  505. * Returns a NodePointer for the specified namespace. Will return null
  506. * if namespaces are not supported.
  507. * Will return UNKNOWN_NAMESPACE if there is no such namespace.
  508. */
  509. public NodePointer namespacePointer(String namespace) {
  510. return null;
  511. }
  512. /**
  513. * Decodes a namespace prefix to the corresponding URI.
  514. */
  515. public String getNamespaceURI(String prefix) {
  516. return null;
  517. }
  518. /**
  519. * Returns the namespace URI associated with this Pointer.
  520. */
  521. public String getNamespaceURI() {
  522. return null;
  523. }
  524. /**
  525. * Returns true if the supplied prefix represents the
  526. * default namespace in the context of the current node.
  527. */
  528. protected boolean isDefaultNamespace(String prefix) {
  529. if (prefix == null) {
  530. return true;
  531. }
  532. String namespace = getNamespaceURI(prefix);
  533. if (namespace == null) {
  534. return false; // undefined namespace
  535. }
  536. return namespace.equals(getDefaultNamespaceURI());
  537. }
  538. protected String getDefaultNamespaceURI() {
  539. return null;
  540. }
  541. /**
  542. * Locates a node by ID.
  543. */
  544. public Pointer getPointerByID(JXPathContext context, String id) {
  545. return context.getPointerByID(id);
  546. }
  547. /**
  548. * Locates a node by key and value.
  549. */
  550. public Pointer getPointerByKey(
  551. JXPathContext context,
  552. String key,
  553. String value)
  554. {
  555. return context.getPointerByKey(key, value);
  556. }
  557. /**
  558. * Returns an XPath that maps to this Pointer.
  559. */
  560. public String asPath() {
  561. // If the parent of this node is a container, it is responsible
  562. // for appended this node's part of the path.
  563. if (parent != null && parent.isContainer()) {
  564. return parent.asPath();
  565. }
  566. StringBuffer buffer = new StringBuffer();
  567. if (parent != null) {
  568. buffer.append(parent.asPath());
  569. }
  570. if (buffer.length() == 0
  571. || buffer.charAt(buffer.length() - 1) != '/') {
  572. buffer.append('/');
  573. }
  574. if (attribute) {
  575. buffer.append('@');
  576. }
  577. buffer.append(getName());
  578. if (index != WHOLE_COLLECTION && isCollection()) {
  579. buffer.append('[').append(index + 1).append(']');
  580. }
  581. return buffer.toString();
  582. }
  583. public Object clone() {
  584. try {
  585. NodePointer ptr = (NodePointer) super.clone();
  586. if (parent != null) {
  587. ptr.parent = (NodePointer) parent.clone();
  588. }
  589. return ptr;
  590. }
  591. catch (CloneNotSupportedException ex) {
  592. // Of course it is supported
  593. ex.printStackTrace();
  594. }
  595. return null;
  596. }
  597. public String toString() {
  598. return asPath();
  599. }
  600. public int compareTo(Object object) {
  601. // Let it throw a ClassCastException
  602. NodePointer pointer = (NodePointer) object;
  603. if (parent == pointer.parent) {
  604. if (parent == null) {
  605. return 0;
  606. }
  607. return parent.compareChildNodePointers(this, pointer);
  608. }
  609. // Task 1: find the common parent
  610. int depth1 = 0;
  611. NodePointer p1 = this;
  612. while (p1 != null) {
  613. depth1++;
  614. p1 = p1.parent;
  615. }
  616. int depth2 = 0;
  617. NodePointer p2 = pointer;
  618. while (p2 != null) {
  619. depth2++;
  620. p2 = p2.parent;
  621. }
  622. return compareNodePointers(this, depth1, pointer, depth2);
  623. }
  624. private int compareNodePointers(
  625. NodePointer p1,
  626. int depth1,
  627. NodePointer p2,
  628. int depth2)
  629. {
  630. if (depth1 < depth2) {
  631. int r = compareNodePointers(p1, depth1, p2.parent, depth2 - 1);
  632. if (r != 0) {
  633. return r;
  634. }
  635. return -1;
  636. }
  637. else if (depth1 > depth2) {
  638. int r = compareNodePointers(p1.parent, depth1 - 1, p2, depth2);
  639. if (r != 0) {
  640. return r;
  641. }
  642. return 1;
  643. }
  644. if (p1 == null && p2 == null) {
  645. return 0;
  646. }
  647. if (p1 != null && p1.equals(p2)) {
  648. return 0;
  649. }
  650. if (depth1 == 1) {
  651. throw new JXPathException(
  652. "Cannot compare pointers that do not belong to the same tree: '"
  653. + p1
  654. + "' and '"
  655. + p2
  656. + "'");
  657. }
  658. int r =
  659. compareNodePointers(p1.parent, depth1 - 1, p2.parent, depth2 - 1);
  660. if (r != 0) {
  661. return r;
  662. }
  663. return p1.parent.compareChildNodePointers(p1, p2);
  664. }
  665. /**
  666. * Print internal structure of a pointer for debugging
  667. */
  668. public void printPointerChain() {
  669. printDeep(this, "");
  670. }
  671. private static void printDeep(NodePointer pointer, String indent) {
  672. if (indent.length() == 0) {
  673. System.err.println(
  674. "POINTER: "
  675. + pointer
  676. + "("
  677. + pointer.getClass().getName()
  678. + ")");
  679. }
  680. else {
  681. System.err.println(
  682. indent
  683. + " of "
  684. + pointer
  685. + "("
  686. + pointer.getClass().getName()
  687. + ")");
  688. }
  689. if (pointer.getImmediateParentPointer() != null) {
  690. printDeep(pointer.getImmediateParentPointer(), indent + " ");
  691. }
  692. }
  693. }