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