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 }