001: /*
002: * Copyright 2002-2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id$
018: */
019:
020: package org.apache.xpath.domapi;
021:
022: import javax.xml.transform.TransformerException;
023:
024: import org.apache.xpath.XPath;
025: import org.apache.xpath.objects.XObject;
026: import org.apache.xpath.res.XPATHErrorResources;
027: import org.apache.xpath.res.XPATHMessages;
028: import org.w3c.dom.DOMException;
029: import org.w3c.dom.Node;
030: import org.w3c.dom.NodeList;
031: import org.w3c.dom.events.Event;
032: import org.w3c.dom.events.EventListener;
033: import org.w3c.dom.events.EventTarget;
034: import org.w3c.dom.traversal.NodeIterator;
035: import org.w3c.dom.xpath.XPathException;
036: import org.w3c.dom.xpath.XPathResult;
037:
038: /**
039: *
040: * The class provides an implementation XPathResult according
041: * to the DOM L3 XPath Specification, Working Group Note 26 February 2004.
042: *
043: * <p>See also the <a href='http://www.w3.org/TR/2004/NOTE-DOM-Level-3-XPath-20040226'>Document Object Model (DOM) Level 3 XPath Specification</a>.</p>
044: *
045: * <p>The <code>XPathResult</code> interface represents the result of the
046: * evaluation of an XPath expression within the context of a particular
047: * node. Since evaluation of an XPath expression can result in various
048: * result types, this object makes it possible to discover and manipulate
049: * the type and value of the result.</p>
050: *
051: * <p>This implementation wraps an <code>XObject</code>.
052: *
053: * @see org.apache.xpath.objects.XObject
054: * @see org.w3c.dom.xpath.XPathResult
055: *
056: * @xsl.usage internal
057: */
058: class XPathResultImpl implements XPathResult, EventListener {
059:
060: /**
061: * The wrapped XObject
062: */
063: final private XObject m_resultObj;
064:
065: /**
066: * The xpath object that wraps the expression used for this result.
067: */
068: final private XPath m_xpath;
069:
070: /**
071: * This the type specified by the user during construction. Typically
072: * the constructor will be called by org.apache.xpath.XPath.evaluate().
073: */
074: final private short m_resultType;
075:
076: private boolean m_isInvalidIteratorState = false;
077:
078: /**
079: * Only used to attach a mutation event handler when specified
080: * type is an iterator type.
081: */
082: final private Node m_contextNode;
083:
084: /**
085: * The iterator, if this is an iterator type.
086: */
087: private NodeIterator m_iterator = null;;
088:
089: /**
090: * The list, if this is a snapshot type.
091: */
092: private NodeList m_list = null;
093:
094: /**
095: * Constructor for XPathResultImpl.
096: *
097: * For internal use only.
098: */
099: XPathResultImpl(short type, XObject result, Node contextNode,
100: XPath xpath) {
101: // Check that the type is valid
102: if (!isValidType(type)) {
103: String fmsg = XPATHMessages.createXPATHMessage(
104: XPATHErrorResources.ER_INVALID_XPATH_TYPE,
105: new Object[] { new Integer(type) });
106: throw new XPathException(XPathException.TYPE_ERR, fmsg); // Invalid XPath type argument: {0}
107: }
108:
109: // Result object should never be null!
110: if (null == result) {
111: String fmsg = XPATHMessages.createXPATHMessage(
112: XPATHErrorResources.ER_EMPTY_XPATH_RESULT, null);
113: throw new XPathException(
114: XPathException.INVALID_EXPRESSION_ERR, fmsg); // Empty XPath result object
115: }
116:
117: this .m_resultObj = result;
118: this .m_contextNode = contextNode;
119: this .m_xpath = xpath;
120:
121: // If specified result was ANY_TYPE, determine XObject type
122: if (type == ANY_TYPE) {
123: this .m_resultType = getTypeFromXObject(result);
124: } else {
125: this .m_resultType = type;
126: }
127:
128: // If the context node supports DOM Events and the type is one of the iterator
129: // types register this result as an event listener
130: if (((m_resultType == XPathResult.ORDERED_NODE_ITERATOR_TYPE) || (m_resultType == XPathResult.UNORDERED_NODE_ITERATOR_TYPE))) {
131: addEventListener();
132:
133: }// else can we handle iterator types if contextNode doesn't support EventTarget??
134:
135: // If this is an iterator type get the iterator
136: if ((m_resultType == ORDERED_NODE_ITERATOR_TYPE)
137: || (m_resultType == UNORDERED_NODE_ITERATOR_TYPE)
138: || (m_resultType == ANY_UNORDERED_NODE_TYPE)
139: || (m_resultType == FIRST_ORDERED_NODE_TYPE)) {
140:
141: try {
142: m_iterator = m_resultObj.nodeset();
143: } catch (TransformerException te) {
144: // probably not a node type
145: String fmsg = XPATHMessages
146: .createXPATHMessage(
147: XPATHErrorResources.ER_INCOMPATIBLE_TYPES,
148: new Object[] {
149: m_xpath.getPatternString(),
150: getTypeString(getTypeFromXObject(m_resultObj)),
151: getTypeString(m_resultType) });
152: throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The XPathResult of XPath expression {0} has an XPathResultType of {1} which cannot be coerced into the specified XPathResultType of {2}."},
153: }
154:
155: // If user requested ordered nodeset and result is unordered
156: // need to sort...TODO
157: // if ((m_resultType == ORDERED_NODE_ITERATOR_TYPE) &&
158: // (!(((DTMNodeIterator)m_iterator).getDTMIterator().isDocOrdered()))) {
159: //
160: // }
161:
162: // If it's a snapshot type, get the nodelist
163: } else if ((m_resultType == UNORDERED_NODE_SNAPSHOT_TYPE)
164: || (m_resultType == ORDERED_NODE_SNAPSHOT_TYPE)) {
165: try {
166: m_list = m_resultObj.nodelist();
167: } catch (TransformerException te) {
168: // probably not a node type
169: String fmsg = XPATHMessages
170: .createXPATHMessage(
171: XPATHErrorResources.ER_INCOMPATIBLE_TYPES,
172: new Object[] {
173: m_xpath.getPatternString(),
174: getTypeString(getTypeFromXObject(m_resultObj)),
175: getTypeString(m_resultType) });
176: throw new XPathException(XPathException.TYPE_ERR, fmsg); // "The XPathResult of XPath expression {0} has an XPathResultType of {1} which cannot be coerced into the specified XPathResultType of {2}."},
177: }
178: }
179: }
180:
181: /**
182: * @see org.w3c.dom.xpath.XPathResult#getResultType()
183: */
184: public short getResultType() {
185: return m_resultType;
186: }
187:
188: /**
189: * The value of this number result.
190: * @exception XPathException
191: * TYPE_ERR: raised if <code>resultType</code> is not
192: * <code>NUMBER_TYPE</code>.
193: * @see org.w3c.dom.xpath.XPathResult#getNumberValue()
194: */
195: public double getNumberValue() throws XPathException {
196: if (getResultType() != NUMBER_TYPE) {
197: String fmsg = XPATHMessages
198: .createXPATHMessage(
199: XPATHErrorResources.ER_CANT_CONVERT_XPATHRESULTTYPE_TO_NUMBER,
200: new Object[] { m_xpath.getPatternString(),
201: getTypeString(m_resultType) });
202: throw new XPathException(XPathException.TYPE_ERR, fmsg);
203: // "The XPathResult of XPath expression {0} has an XPathResultType of {1} which cannot be converted to a number"
204: } else {
205: try {
206: return m_resultObj.num();
207: } catch (Exception e) {
208: // Type check above should prevent this exception from occurring.
209: throw new XPathException(XPathException.TYPE_ERR, e
210: .getMessage());
211: }
212: }
213: }
214:
215: /**
216: * The value of this string result.
217: * @exception XPathException
218: * TYPE_ERR: raised if <code>resultType</code> is not
219: * <code>STRING_TYPE</code>.
220: *
221: * @see org.w3c.dom.xpath.XPathResult#getStringValue()
222: */
223: public String getStringValue() throws XPathException {
224: if (getResultType() != STRING_TYPE) {
225: String fmsg = XPATHMessages.createXPATHMessage(
226: XPATHErrorResources.ER_CANT_CONVERT_TO_STRING,
227: new Object[] { m_xpath.getPatternString(),
228: m_resultObj.getTypeString() });
229: throw new XPathException(XPathException.TYPE_ERR, fmsg);
230: // "The XPathResult of XPath expression {0} has an XPathResultType of {1} which cannot be converted to a string."
231: } else {
232: try {
233: return m_resultObj.str();
234: } catch (Exception e) {
235: // Type check above should prevent this exception from occurring.
236: throw new XPathException(XPathException.TYPE_ERR, e
237: .getMessage());
238: }
239: }
240: }
241:
242: /**
243: * @see org.w3c.dom.xpath.XPathResult#getBooleanValue()
244: */
245: public boolean getBooleanValue() throws XPathException {
246: if (getResultType() != BOOLEAN_TYPE) {
247: String fmsg = XPATHMessages.createXPATHMessage(
248: XPATHErrorResources.ER_CANT_CONVERT_TO_BOOLEAN,
249: new Object[] { m_xpath.getPatternString(),
250: getTypeString(m_resultType) });
251: throw new XPathException(XPathException.TYPE_ERR, fmsg);
252: // "The XPathResult of XPath expression {0} has an XPathResultType of {1} which cannot be converted to a boolean."
253: } else {
254: try {
255: return m_resultObj.bool();
256: } catch (TransformerException e) {
257: // Type check above should prevent this exception from occurring.
258: throw new XPathException(XPathException.TYPE_ERR, e
259: .getMessage());
260: }
261: }
262: }
263:
264: /**
265: * The value of this single node result, which may be <code>null</code>.
266: * @exception XPathException
267: * TYPE_ERR: raised if <code>resultType</code> is not
268: * <code>ANY_UNORDERED_NODE_TYPE</code> or
269: * <code>FIRST_ORDERED_NODE_TYPE</code>.
270: *
271: * @see org.w3c.dom.xpath.XPathResult#getSingleNodeValue()
272: */
273: public Node getSingleNodeValue() throws XPathException {
274:
275: if ((m_resultType != ANY_UNORDERED_NODE_TYPE)
276: && (m_resultType != FIRST_ORDERED_NODE_TYPE)) {
277: String fmsg = XPATHMessages.createXPATHMessage(
278: XPATHErrorResources.ER_CANT_CONVERT_TO_SINGLENODE,
279: new Object[] { m_xpath.getPatternString(),
280: getTypeString(m_resultType) });
281: throw new XPathException(XPathException.TYPE_ERR, fmsg);
282: // "The XPathResult of XPath expression {0} has an XPathResultType of {1} which cannot be converted to a single node.
283: // This method applies only to types ANY_UNORDERED_NODE_TYPE and FIRST_ORDERED_NODE_TYPE."
284: }
285:
286: NodeIterator result = null;
287: try {
288: result = m_resultObj.nodeset();
289: } catch (TransformerException te) {
290: throw new XPathException(XPathException.TYPE_ERR, te
291: .getMessage());
292: }
293:
294: if (null == result)
295: return null;
296:
297: Node node = result.nextNode();
298:
299: // Wrap "namespace node" in an XPathNamespace
300: if (isNamespaceNode(node)) {
301: return new XPathNamespaceImpl(node);
302: } else {
303: return node;
304: }
305: }
306:
307: /**
308: * @see org.w3c.dom.xpath.XPathResult#getInvalidIteratorState()
309: */
310: public boolean getInvalidIteratorState() {
311: return m_isInvalidIteratorState;
312: }
313:
314: /**
315: * The number of nodes in the result snapshot. Valid values for
316: * snapshotItem indices are <code>0</code> to
317: * <code>snapshotLength-1</code> inclusive.
318: * @exception XPathException
319: * TYPE_ERR: raised if <code>resultType</code> is not
320: * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or
321: * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>.
322: *
323: * @see org.w3c.dom.xpath.XPathResult#getSnapshotLength()
324: */
325: public int getSnapshotLength() throws XPathException {
326:
327: if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE)
328: && (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) {
329: String fmsg = XPATHMessages.createXPATHMessage(
330: XPATHErrorResources.ER_CANT_GET_SNAPSHOT_LENGTH,
331: new Object[] { m_xpath.getPatternString(),
332: getTypeString(m_resultType) });
333: throw new XPathException(XPathException.TYPE_ERR, fmsg);
334: // "The method getSnapshotLength cannot be called on the XPathResult of XPath expression {0} because its XPathResultType is {1}.
335: }
336:
337: return m_list.getLength();
338: }
339:
340: /**
341: * Iterates and returns the next node from the node set or
342: * <code>null</code>if there are no more nodes.
343: * @return Returns the next node.
344: * @exception XPathException
345: * TYPE_ERR: raised if <code>resultType</code> is not
346: * <code>UNORDERED_NODE_ITERATOR_TYPE</code> or
347: * <code>ORDERED_NODE_ITERATOR_TYPE</code>.
348: * @exception DOMException
349: * INVALID_STATE_ERR: The document has been mutated since the result was
350: * returned.
351: * @see org.w3c.dom.xpath.XPathResult#iterateNext()
352: */
353: public Node iterateNext() throws XPathException, DOMException {
354: if ((m_resultType != UNORDERED_NODE_ITERATOR_TYPE)
355: && (m_resultType != ORDERED_NODE_ITERATOR_TYPE)) {
356: String fmsg = XPATHMessages.createXPATHMessage(
357: XPATHErrorResources.ER_NON_ITERATOR_TYPE,
358: new Object[] { m_xpath.getPatternString(),
359: getTypeString(m_resultType) });
360: throw new XPathException(XPathException.TYPE_ERR, fmsg);
361: // "The method iterateNext cannot be called on the XPathResult of XPath expression {0} because its XPathResultType is {1}.
362: // This method applies only to types UNORDERED_NODE_ITERATOR_TYPE and ORDERED_NODE_ITERATOR_TYPE."},
363: }
364:
365: if (getInvalidIteratorState()) {
366: String fmsg = XPATHMessages.createXPATHMessage(
367: XPATHErrorResources.ER_DOC_MUTATED, null);
368: throw new DOMException(DOMException.INVALID_STATE_ERR, fmsg); // Document mutated since result was returned. Iterator is invalid.
369: }
370:
371: Node node = m_iterator.nextNode();
372: if (null == node)
373: removeEventListener(); // JIRA 1673
374: // Wrap "namespace node" in an XPathNamespace
375: if (isNamespaceNode(node)) {
376: return new XPathNamespaceImpl(node);
377: } else {
378: return node;
379: }
380: }
381:
382: /**
383: * Returns the <code>index</code>th item in the snapshot collection. If
384: * <code>index</code> is greater than or equal to the number of nodes in
385: * the list, this method returns <code>null</code>. Unlike the iterator
386: * result, the snapshot does not become invalid, but may not correspond
387: * to the current document if it is mutated.
388: * @param index Index into the snapshot collection.
389: * @return The node at the <code>index</code>th position in the
390: * <code>NodeList</code>, or <code>null</code> if that is not a valid
391: * index.
392: * @exception XPathException
393: * TYPE_ERR: raised if <code>resultType</code> is not
394: * <code>UNORDERED_NODE_SNAPSHOT_TYPE</code> or
395: * <code>ORDERED_NODE_SNAPSHOT_TYPE</code>.
396: *
397: * @see org.w3c.dom.xpath.XPathResult#snapshotItem(int)
398: */
399: public Node snapshotItem(int index) throws XPathException {
400:
401: if ((m_resultType != UNORDERED_NODE_SNAPSHOT_TYPE)
402: && (m_resultType != ORDERED_NODE_SNAPSHOT_TYPE)) {
403: String fmsg = XPATHMessages.createXPATHMessage(
404: XPATHErrorResources.ER_NON_SNAPSHOT_TYPE,
405: new Object[] { m_xpath.getPatternString(),
406: getTypeString(m_resultType) });
407: throw new XPathException(XPathException.TYPE_ERR, fmsg);
408: // "The method snapshotItem cannot be called on the XPathResult of XPath expression {0} because its XPathResultType is {1}.
409: // This method applies only to types UNORDERED_NODE_SNAPSHOT_TYPE and ORDERED_NODE_SNAPSHOT_TYPE."},
410: }
411:
412: Node node = m_list.item(index);
413:
414: // Wrap "namespace node" in an XPathNamespace
415: if (isNamespaceNode(node)) {
416: return new XPathNamespaceImpl(node);
417: } else {
418: return node;
419: }
420: }
421:
422: /**
423: * Check if the specified type is one of the supported types.
424: * @param type The specified type
425: *
426: * @return true If the specified type is supported; otherwise, returns false.
427: */
428: static boolean isValidType(short type) {
429: switch (type) {
430: case ANY_TYPE:
431: case NUMBER_TYPE:
432: case STRING_TYPE:
433: case BOOLEAN_TYPE:
434: case UNORDERED_NODE_ITERATOR_TYPE:
435: case ORDERED_NODE_ITERATOR_TYPE:
436: case UNORDERED_NODE_SNAPSHOT_TYPE:
437: case ORDERED_NODE_SNAPSHOT_TYPE:
438: case ANY_UNORDERED_NODE_TYPE:
439: case FIRST_ORDERED_NODE_TYPE:
440: return true;
441: default:
442: return false;
443: }
444: }
445:
446: /**
447: * @see org.w3c.dom.events.EventListener#handleEvent(Event)
448: */
449: public void handleEvent(Event event) {
450:
451: if (event.getType().equals("DOMSubtreeModified")) {
452: // invalidate the iterator
453: m_isInvalidIteratorState = true;
454:
455: // deregister as a listener to reduce computational load
456: removeEventListener();
457: }
458: }
459:
460: /**
461: * Given a request type, return the equivalent string.
462: * For diagnostic purposes.
463: *
464: * @return type string
465: */
466: private String getTypeString(int type) {
467: switch (type) {
468: case ANY_TYPE:
469: return "ANY_TYPE";
470: case ANY_UNORDERED_NODE_TYPE:
471: return "ANY_UNORDERED_NODE_TYPE";
472: case BOOLEAN_TYPE:
473: return "BOOLEAN";
474: case FIRST_ORDERED_NODE_TYPE:
475: return "FIRST_ORDERED_NODE_TYPE";
476: case NUMBER_TYPE:
477: return "NUMBER_TYPE";
478: case ORDERED_NODE_ITERATOR_TYPE:
479: return "ORDERED_NODE_ITERATOR_TYPE";
480: case ORDERED_NODE_SNAPSHOT_TYPE:
481: return "ORDERED_NODE_SNAPSHOT_TYPE";
482: case STRING_TYPE:
483: return "STRING_TYPE";
484: case UNORDERED_NODE_ITERATOR_TYPE:
485: return "UNORDERED_NODE_ITERATOR_TYPE";
486: case UNORDERED_NODE_SNAPSHOT_TYPE:
487: return "UNORDERED_NODE_SNAPSHOT_TYPE";
488: default:
489: return "#UNKNOWN";
490: }
491: }
492:
493: /**
494: * Given an XObject, determine the corresponding DOM XPath type
495: *
496: * @return type string
497: */
498: private short getTypeFromXObject(XObject object) {
499: switch (object.getType()) {
500: case XObject.CLASS_BOOLEAN:
501: return BOOLEAN_TYPE;
502: case XObject.CLASS_NODESET:
503: return UNORDERED_NODE_ITERATOR_TYPE;
504: case XObject.CLASS_NUMBER:
505: return NUMBER_TYPE;
506: case XObject.CLASS_STRING:
507: return STRING_TYPE;
508: // XPath 2.0 types
509: // case XObject.CLASS_DATE:
510: // case XObject.CLASS_DATETIME:
511: // case XObject.CLASS_DTDURATION:
512: // case XObject.CLASS_GDAY:
513: // case XObject.CLASS_GMONTH:
514: // case XObject.CLASS_GMONTHDAY:
515: // case XObject.CLASS_GYEAR:
516: // case XObject.CLASS_GYEARMONTH:
517: // case XObject.CLASS_TIME:
518: // case XObject.CLASS_YMDURATION: return STRING_TYPE; // treat all date types as strings?
519:
520: case XObject.CLASS_RTREEFRAG:
521: return UNORDERED_NODE_ITERATOR_TYPE;
522: case XObject.CLASS_NULL:
523: return ANY_TYPE; // throw exception ?
524: default:
525: return ANY_TYPE; // throw exception ?
526: }
527:
528: }
529:
530: /**
531: * Given a node, determine if it is a namespace node.
532: *
533: * @param node
534: *
535: * @return boolean Returns true if this is a namespace node; otherwise, returns false.
536: */
537: private boolean isNamespaceNode(Node node) {
538:
539: if ((null != node)
540: && (node.getNodeType() == Node.ATTRIBUTE_NODE)
541: && (node.getNodeName().startsWith("xmlns:") || node
542: .getNodeName().equals("xmlns"))) {
543: return true;
544: } else {
545: return false;
546: }
547: }
548:
549: /**
550: * Add m_contextNode to Event Listner to listen for Mutations Events
551: *
552: */
553: private void addEventListener() {
554: if (m_contextNode instanceof EventTarget)
555: ((EventTarget) m_contextNode).addEventListener(
556: "DOMSubtreeModified", this , true);
557:
558: }
559:
560: /**
561: * Remove m_contextNode to Event Listner to listen for Mutations Events
562: *
563: */
564: private void removeEventListener() {
565: if (m_contextNode instanceof EventTarget)
566: ((EventTarget) m_contextNode).removeEventListener(
567: "DOMSubtreeModified", this , true);
568: }
569:
570: }
|