1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18/* 19 * $Id: XNodeSet.java 469368 2006-10-31 04:41:36Z minchau $ 20 */ 21package org.apache.xpath.objects; 22 23import org.apache.xml.dtm.DTM; 24import org.apache.xml.dtm.DTMIterator; 25import org.apache.xml.dtm.DTMManager; 26import org.apache.xml.utils.XMLString; 27import org.apache.xpath.NodeSetDTM; 28import org.apache.xpath.axes.NodeSequence; 29 30import org.w3c.dom.NodeList; 31import org.w3c.dom.traversal.NodeIterator; 32 33/** 34 * This class represents an XPath nodeset object, and is capable of 35 * converting the nodeset to other types, such as a string. 36 * @xsl.usage general 37 */ 38public class XNodeSet extends NodeSequence 39{ 40 static final long serialVersionUID = 1916026368035639667L; 41 /** 42 * Default constructor for derived objects. 43 */ 44 protected XNodeSet() 45 { 46 } 47 48 /** 49 * Construct a XNodeSet object. 50 * 51 * @param val Value of the XNodeSet object 52 */ 53 public XNodeSet(DTMIterator val) 54 { 55 super(); 56 if(val instanceof XNodeSet) 57 { 58 final XNodeSet nodeSet = (XNodeSet) val; 59 setIter(nodeSet.m_iter); 60 m_dtmMgr = nodeSet.m_dtmMgr; 61 m_last = nodeSet.m_last; 62 // First make sure the DTMIterator val has a cache, 63 // so if it doesn't have one, make one. 64 if(!nodeSet.hasCache()) 65 nodeSet.setShouldCacheNodes(true); 66 67 // Get the cache from val and use it ourselves (we share it). 68 setObject(nodeSet.getIteratorCache()); 69 } 70 else 71 setIter(val); 72 } 73 74 /** 75 * Construct a XNodeSet object. 76 * 77 * @param val Value of the XNodeSet object 78 */ 79 public XNodeSet(XNodeSet val) 80 { 81 super(); 82 setIter(val.m_iter); 83 m_dtmMgr = val.m_dtmMgr; 84 m_last = val.m_last; 85 if(!val.hasCache()) 86 val.setShouldCacheNodes(true); 87 setObject(val.m_obj); 88 } 89 90 91 /** 92 * Construct an empty XNodeSet object. This is used to create a mutable 93 * nodeset to which random nodes may be added. 94 */ 95 public XNodeSet(DTMManager dtmMgr) 96 { 97 this(DTM.NULL,dtmMgr); 98 } 99 100 /** 101 * Construct a XNodeSet object for one node. 102 * 103 * @param n Node to add to the new XNodeSet object 104 */ 105 public XNodeSet(int n, DTMManager dtmMgr) 106 { 107 108 super(new NodeSetDTM(dtmMgr)); 109 m_dtmMgr = dtmMgr; 110 111 if (DTM.NULL != n) 112 { 113 ((NodeSetDTM) m_obj).addNode(n); 114 m_last = 1; 115 } 116 else 117 m_last = 0; 118 } 119 120 /** 121 * Tell that this is a CLASS_NODESET. 122 * 123 * @return type CLASS_NODESET 124 */ 125 public int getType() 126 { 127 return CLASS_NODESET; 128 } 129 130 /** 131 * Given a request type, return the equivalent string. 132 * For diagnostic purposes. 133 * 134 * @return type string "#NODESET" 135 */ 136 public String getTypeString() 137 { 138 return "#NODESET"; 139 } 140 141 /** 142 * Get numeric value of the string conversion from a single node. 143 * 144 * @param n Node to convert 145 * 146 * @return numeric value of the string conversion from a single node. 147 */ 148 public double getNumberFromNode(int n) 149 { 150 XMLString xstr = m_dtmMgr.getDTM(n).getStringValue(n); 151 return xstr.toDouble(); 152 } 153 154 /** 155 * Cast result object to a number. 156 * 157 * @return numeric value of the string conversion from the 158 * next node in the NodeSetDTM, or NAN if no node was found 159 */ 160 public double num() 161 { 162 163 int node = item(0); 164 return (node != DTM.NULL) ? getNumberFromNode(node) : Double.NaN; 165 } 166 167 /** 168 * Cast result object to a number, but allow side effects, such as the 169 * incrementing of an iterator. 170 * 171 * @return numeric value of the string conversion from the 172 * next node in the NodeSetDTM, or NAN if no node was found 173 */ 174 public double numWithSideEffects() 175 { 176 int node = nextNode(); 177 178 return (node != DTM.NULL) ? getNumberFromNode(node) : Double.NaN; 179 } 180 181 182 /** 183 * Cast result object to a boolean. 184 * 185 * @return True if there is a next node in the nodeset 186 */ 187 public boolean bool() 188 { 189 return (item(0) != DTM.NULL); 190 } 191 192 /** 193 * Cast result object to a boolean, but allow side effects, such as the 194 * incrementing of an iterator. 195 * 196 * @return True if there is a next node in the nodeset 197 */ 198 public boolean boolWithSideEffects() 199 { 200 return (nextNode() != DTM.NULL); 201 } 202 203 204 /** 205 * Get the string conversion from a single node. 206 * 207 * @param n Node to convert 208 * 209 * @return the string conversion from a single node. 210 */ 211 public XMLString getStringFromNode(int n) 212 { 213 // %OPT% 214 // I guess we'll have to get a static instance of the DTM manager... 215 if(DTM.NULL != n) 216 { 217 return m_dtmMgr.getDTM(n).getStringValue(n); 218 } 219 else 220 { 221 return org.apache.xpath.objects.XString.EMPTYSTRING; 222 } 223 } 224 225 /** 226 * Directly call the 227 * characters method on the passed ContentHandler for the 228 * string-value. Multiple calls to the 229 * ContentHandler's characters methods may well occur for a single call to 230 * this method. 231 * 232 * @param ch A non-null reference to a ContentHandler. 233 * 234 * @throws org.xml.sax.SAXException 235 */ 236 public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch) 237 throws org.xml.sax.SAXException 238 { 239 int node = item(0); 240 241 if(node != DTM.NULL) 242 { 243 m_dtmMgr.getDTM(node).dispatchCharactersEvents(node, ch, false); 244 } 245 246 } 247 248 /** 249 * Cast result object to an XMLString. 250 * 251 * @return The document fragment node data or the empty string. 252 */ 253 public XMLString xstr() 254 { 255 int node = item(0); 256 return (node != DTM.NULL) ? getStringFromNode(node) : XString.EMPTYSTRING; 257 } 258 259 /** 260 * Cast result object to a string. 261 * 262 * @return The string this wraps or the empty string if null 263 */ 264 public void appendToFsb(org.apache.xml.utils.FastStringBuffer fsb) 265 { 266 XString xstring = (XString)xstr(); 267 xstring.appendToFsb(fsb); 268 } 269 270 271 /** 272 * Cast result object to a string. 273 * 274 * @return the string conversion from the next node in the nodeset 275 * or "" if there is no next node 276 */ 277 public String str() 278 { 279 int node = item(0); 280 return (node != DTM.NULL) ? getStringFromNode(node).toString() : ""; 281 } 282 283 /** 284 * Return a java object that's closest to the representation 285 * that should be handed to an extension. 286 * 287 * @return The object that this class wraps 288 */ 289 public Object object() 290 { 291 if(null == m_obj) 292 return this; 293 else 294 return m_obj; 295 } 296 297 // %REVIEW% 298 // hmmm... 299// /** 300// * Cast result object to a result tree fragment. 301// * 302// * @param support The XPath context to use for the conversion 303// * 304// * @return the nodeset as a result tree fragment. 305// */ 306// public DocumentFragment rtree(XPathContext support) 307// { 308// DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 309// DocumentBuilder db = dbf.newDocumentBuilder(); 310// Document myDoc = db.newDocument(); 311// 312// DocumentFragment docFrag = myDoc.createDocumentFragment(); 313// 314// DTMIterator nl = iter(); 315// int node; 316// 317// while (DTM.NULL != (node = nl.nextNode())) 318// { 319// frag.appendChild(node, true, true); 320// } 321// 322// return frag.getDocument(); 323// } 324 325 /** 326 * Cast result object to a nodelist. 327 * 328 * @return a NodeIterator. 329 * 330 * @throws javax.xml.transform.TransformerException 331 */ 332 public NodeIterator nodeset() throws javax.xml.transform.TransformerException 333 { 334 return new org.apache.xml.dtm.ref.DTMNodeIterator(iter()); 335 } 336 337 /** 338 * Cast result object to a nodelist. 339 * 340 * @return a NodeList. 341 * 342 * @throws javax.xml.transform.TransformerException 343 */ 344 public NodeList nodelist() throws javax.xml.transform.TransformerException 345 { 346 org.apache.xml.dtm.ref.DTMNodeList nodelist = new org.apache.xml.dtm.ref.DTMNodeList(this); 347 // Creating a DTMNodeList has the side-effect that it will create a clone 348 // XNodeSet with cache and run m_iter to the end. You cannot get any node 349 // from m_iter after this call. As a fix, we call SetVector() on the clone's 350 // cache. See Bugzilla 14406. 351 XNodeSet clone = (XNodeSet)nodelist.getDTMIterator(); 352 SetVector(clone.getVector()); 353 return nodelist; 354 } 355 356 357// /** 358// * Return a java object that's closest to the representation 359// * that should be handed to an extension. 360// * 361// * @return The object that this class wraps 362// */ 363// public Object object() 364// { 365// return new org.apache.xml.dtm.ref.DTMNodeList(iter()); 366// } 367 368 /** 369 * Return the iterator without cloning, etc. 370 */ 371 public DTMIterator iterRaw() 372 { 373 return this; 374 } 375 376 public void release(DTMIterator iter) 377 { 378 } 379 380 /** 381 * Cast result object to a nodelist. 382 * 383 * @return The nodeset as a nodelist 384 */ 385 public DTMIterator iter() 386 { 387 try 388 { 389 if(hasCache()) 390 return cloneWithReset(); 391 else 392 return this; // don't bother to clone... won't do any good! 393 } 394 catch (CloneNotSupportedException cnse) 395 { 396 throw new RuntimeException(cnse.getMessage()); 397 } 398 } 399 400 /** 401 * Get a fresh copy of the object. For use with variables. 402 * 403 * @return A fresh nodelist. 404 */ 405 public XObject getFresh() 406 { 407 try 408 { 409 if(hasCache()) 410 return (XObject)cloneWithReset(); 411 else 412 return this; // don't bother to clone... won't do any good! 413 } 414 catch (CloneNotSupportedException cnse) 415 { 416 throw new RuntimeException(cnse.getMessage()); 417 } 418 } 419 420 /** 421 * Cast result object to a mutableNodeset. 422 * 423 * @return The nodeset as a mutableNodeset 424 */ 425 public NodeSetDTM mutableNodeset() 426 { 427 NodeSetDTM mnl; 428 429 if(m_obj instanceof NodeSetDTM) 430 { 431 mnl = (NodeSetDTM) m_obj; 432 } 433 else 434 { 435 mnl = new NodeSetDTM(iter()); 436 setObject(mnl); 437 setCurrentPos(0); 438 } 439 440 return mnl; 441 } 442 443 /** Less than comparator */ 444 static final LessThanComparator S_LT = new LessThanComparator(); 445 446 /** Less than or equal comparator */ 447 static final LessThanOrEqualComparator S_LTE = new LessThanOrEqualComparator(); 448 449 /** Greater than comparator */ 450 static final GreaterThanComparator S_GT = new GreaterThanComparator(); 451 452 /** Greater than or equal comparator */ 453 static final GreaterThanOrEqualComparator S_GTE = 454 new GreaterThanOrEqualComparator(); 455 456 /** Equal comparator */ 457 static final EqualComparator S_EQ = new EqualComparator(); 458 459 /** Not equal comparator */ 460 static final NotEqualComparator S_NEQ = new NotEqualComparator(); 461 462 /** 463 * Tell if one object is less than the other. 464 * 465 * @param obj2 Object to compare this nodeset to 466 * @param comparator Comparator to use 467 * 468 * @return See the comments below for each object type comparison 469 * 470 * @throws javax.xml.transform.TransformerException 471 */ 472 public boolean compare(XObject obj2, Comparator comparator) 473 throws javax.xml.transform.TransformerException 474 { 475 476 boolean result = false; 477 int type = obj2.getType(); 478 479 if (XObject.CLASS_NODESET == type) 480 { 481 // %OPT% This should be XMLString based instead of string based... 482 483 // From http://www.w3.org/TR/xpath: 484 // If both objects to be compared are node-sets, then the comparison 485 // will be true if and only if there is a node in the first node-set 486 // and a node in the second node-set such that the result of performing 487 // the comparison on the string-values of the two nodes is true. 488 // Note this little gem from the draft: 489 // NOTE: If $x is bound to a node-set, then $x="foo" 490 // does not mean the same as not($x!="foo"): the former 491 // is true if and only if some node in $x has the string-value 492 // foo; the latter is true if and only if all nodes in $x have 493 // the string-value foo. 494 DTMIterator list1 = iterRaw(); 495 DTMIterator list2 = ((XNodeSet) obj2).iterRaw(); 496 int node1; 497 java.util.Vector node2Strings = null; 498 499 while (DTM.NULL != (node1 = list1.nextNode())) 500 { 501 XMLString s1 = getStringFromNode(node1); 502 503 if (null == node2Strings) 504 { 505 int node2; 506 507 while (DTM.NULL != (node2 = list2.nextNode())) 508 { 509 XMLString s2 = getStringFromNode(node2); 510 511 if (comparator.compareStrings(s1, s2)) 512 { 513 result = true; 514 515 break; 516 } 517 518 if (null == node2Strings) 519 node2Strings = new java.util.Vector(); 520 521 node2Strings.addElement(s2); 522 } 523 } 524 else 525 { 526 int n = node2Strings.size(); 527 528 for (int i = 0; i < n; i++) 529 { 530 if (comparator.compareStrings(s1, (XMLString)node2Strings.elementAt(i))) 531 { 532 result = true; 533 534 break; 535 } 536 } 537 } 538 } 539 list1.reset(); 540 list2.reset(); 541 } 542 else if (XObject.CLASS_BOOLEAN == type) 543 { 544 545 // From http://www.w3.org/TR/xpath: 546 // If one object to be compared is a node-set and the other is a boolean, 547 // then the comparison will be true if and only if the result of 548 // performing the comparison on the boolean and on the result of 549 // converting the node-set to a boolean using the boolean function 550 // is true. 551 double num1 = bool() ? 1.0 : 0.0; 552 double num2 = obj2.num(); 553 554 result = comparator.compareNumbers(num1, num2); 555 } 556 else if (XObject.CLASS_NUMBER == type) 557 { 558 559 // From http://www.w3.org/TR/xpath: 560 // If one object to be compared is a node-set and the other is a number, 561 // then the comparison will be true if and only if there is a 562 // node in the node-set such that the result of performing the 563 // comparison on the number to be compared and on the result of 564 // converting the string-value of that node to a number using 565 // the number function is true. 566 DTMIterator list1 = iterRaw(); 567 double num2 = obj2.num(); 568 int node; 569 570 while (DTM.NULL != (node = list1.nextNode())) 571 { 572 double num1 = getNumberFromNode(node); 573 574 if (comparator.compareNumbers(num1, num2)) 575 { 576 result = true; 577 578 break; 579 } 580 } 581 list1.reset(); 582 } 583 else if (XObject.CLASS_RTREEFRAG == type) 584 { 585 XMLString s2 = obj2.xstr(); 586 DTMIterator list1 = iterRaw(); 587 int node; 588 589 while (DTM.NULL != (node = list1.nextNode())) 590 { 591 XMLString s1 = getStringFromNode(node); 592 593 if (comparator.compareStrings(s1, s2)) 594 { 595 result = true; 596 597 break; 598 } 599 } 600 list1.reset(); 601 } 602 else if (XObject.CLASS_STRING == type) 603 { 604 605 // From http://www.w3.org/TR/xpath: 606 // If one object to be compared is a node-set and the other is a 607 // string, then the comparison will be true if and only if there 608 // is a node in the node-set such that the result of performing 609 // the comparison on the string-value of the node and the other 610 // string is true. 611 XMLString s2 = obj2.xstr(); 612 DTMIterator list1 = iterRaw(); 613 int node; 614 615 while (DTM.NULL != (node = list1.nextNode())) 616 { 617 XMLString s1 = getStringFromNode(node); 618 if (comparator.compareStrings(s1, s2)) 619 { 620 result = true; 621 622 break; 623 } 624 } 625 list1.reset(); 626 } 627 else 628 { 629 result = comparator.compareNumbers(this.num(), obj2.num()); 630 } 631 632 return result; 633 } 634 635 /** 636 * Tell if one object is less than the other. 637 * 638 * @param obj2 object to compare this nodeset to 639 * 640 * @return see this.compare(...) 641 * 642 * @throws javax.xml.transform.TransformerException 643 */ 644 public boolean lessThan(XObject obj2) throws javax.xml.transform.TransformerException 645 { 646 return compare(obj2, S_LT); 647 } 648 649 /** 650 * Tell if one object is less than or equal to the other. 651 * 652 * @param obj2 object to compare this nodeset to 653 * 654 * @return see this.compare(...) 655 * 656 * @throws javax.xml.transform.TransformerException 657 */ 658 public boolean lessThanOrEqual(XObject obj2) throws javax.xml.transform.TransformerException 659 { 660 return compare(obj2, S_LTE); 661 } 662 663 /** 664 * Tell if one object is less than the other. 665 * 666 * @param obj2 object to compare this nodeset to 667 * 668 * @return see this.compare(...) 669 * 670 * @throws javax.xml.transform.TransformerException 671 */ 672 public boolean greaterThan(XObject obj2) throws javax.xml.transform.TransformerException 673 { 674 return compare(obj2, S_GT); 675 } 676 677 /** 678 * Tell if one object is less than the other. 679 * 680 * @param obj2 object to compare this nodeset to 681 * 682 * @return see this.compare(...) 683 * 684 * @throws javax.xml.transform.TransformerException 685 */ 686 public boolean greaterThanOrEqual(XObject obj2) 687 throws javax.xml.transform.TransformerException 688 { 689 return compare(obj2, S_GTE); 690 } 691 692 /** 693 * Tell if two objects are functionally equal. 694 * 695 * @param obj2 object to compare this nodeset to 696 * 697 * @return see this.compare(...) 698 * 699 * @throws javax.xml.transform.TransformerException 700 */ 701 public boolean equals(XObject obj2) 702 { 703 try 704 { 705 return compare(obj2, S_EQ); 706 } 707 catch(javax.xml.transform.TransformerException te) 708 { 709 throw new org.apache.xml.utils.WrappedRuntimeException(te); 710 } 711 } 712 713 /** 714 * Tell if two objects are functionally not equal. 715 * 716 * @param obj2 object to compare this nodeset to 717 * 718 * @return see this.compare(...) 719 * 720 * @throws javax.xml.transform.TransformerException 721 */ 722 public boolean notEquals(XObject obj2) throws javax.xml.transform.TransformerException 723 { 724 return compare(obj2, S_NEQ); 725 } 726} 727 728/** 729 * compares nodes for various boolean operations. 730 */ 731abstract class Comparator 732{ 733 734 /** 735 * Compare two strings 736 * 737 * 738 * @param s1 First string to compare 739 * @param s2 Second String to compare 740 * 741 * @return Whether the strings are equal or not 742 */ 743 abstract boolean compareStrings(XMLString s1, XMLString s2); 744 745 /** 746 * Compare two numbers 747 * 748 * 749 * @param n1 First number to compare 750 * @param n2 Second number to compare 751 * 752 * @return Whether the numbers are equal or not 753 */ 754 abstract boolean compareNumbers(double n1, double n2); 755} 756 757/** 758 * Compare strings or numbers for less than. 759 */ 760class LessThanComparator extends Comparator 761{ 762 763 /** 764 * Compare two strings for less than. 765 * 766 * 767 * @param s1 First string to compare 768 * @param s2 Second String to compare 769 * 770 * @return True if s1 is less than s2 771 */ 772 boolean compareStrings(XMLString s1, XMLString s2) 773 { 774 return (s1.toDouble() < s2.toDouble()); 775 // return s1.compareTo(s2) < 0; 776 } 777 778 /** 779 * Compare two numbers for less than. 780 * 781 * 782 * @param n1 First number to compare 783 * @param n2 Second number to compare 784 * 785 * @return true if n1 is less than n2 786 */ 787 boolean compareNumbers(double n1, double n2) 788 { 789 return n1 < n2; 790 } 791} 792 793/** 794 * Compare strings or numbers for less than or equal. 795 */ 796class LessThanOrEqualComparator extends Comparator 797{ 798 799 /** 800 * Compare two strings for less than or equal. 801 * 802 * 803 * @param s1 First string to compare 804 * @param s2 Second String to compare 805 * 806 * @return true if s1 is less than or equal to s2 807 */ 808 boolean compareStrings(XMLString s1, XMLString s2) 809 { 810 return (s1.toDouble() <= s2.toDouble()); 811 // return s1.compareTo(s2) <= 0; 812 } 813 814 /** 815 * Compare two numbers for less than or equal. 816 * 817 * 818 * @param n1 First number to compare 819 * @param n2 Second number to compare 820 * 821 * @return true if n1 is less than or equal to n2 822 */ 823 boolean compareNumbers(double n1, double n2) 824 { 825 return n1 <= n2; 826 } 827} 828 829/** 830 * Compare strings or numbers for greater than. 831 */ 832class GreaterThanComparator extends Comparator 833{ 834 835 /** 836 * Compare two strings for greater than. 837 * 838 * 839 * @param s1 First string to compare 840 * @param s2 Second String to compare 841 * 842 * @return true if s1 is greater than s2 843 */ 844 boolean compareStrings(XMLString s1, XMLString s2) 845 { 846 return (s1.toDouble() > s2.toDouble()); 847 // return s1.compareTo(s2) > 0; 848 } 849 850 /** 851 * Compare two numbers for greater than. 852 * 853 * 854 * @param n1 First number to compare 855 * @param n2 Second number to compare 856 * 857 * @return true if n1 is greater than n2 858 */ 859 boolean compareNumbers(double n1, double n2) 860 { 861 return n1 > n2; 862 } 863} 864 865/** 866 * Compare strings or numbers for greater than or equal. 867 */ 868class GreaterThanOrEqualComparator extends Comparator 869{ 870 871 /** 872 * Compare two strings for greater than or equal. 873 * 874 * 875 * @param s1 First string to compare 876 * @param s2 Second String to compare 877 * 878 * @return true if s1 is greater than or equal to s2 879 */ 880 boolean compareStrings(XMLString s1, XMLString s2) 881 { 882 return (s1.toDouble() >= s2.toDouble()); 883 // return s1.compareTo(s2) >= 0; 884 } 885 886 /** 887 * Compare two numbers for greater than or equal. 888 * 889 * 890 * @param n1 First number to compare 891 * @param n2 Second number to compare 892 * 893 * @return true if n1 is greater than or equal to n2 894 */ 895 boolean compareNumbers(double n1, double n2) 896 { 897 return n1 >= n2; 898 } 899} 900 901/** 902 * Compare strings or numbers for equality. 903 */ 904class EqualComparator extends Comparator 905{ 906 907 /** 908 * Compare two strings for equality. 909 * 910 * 911 * @param s1 First string to compare 912 * @param s2 Second String to compare 913 * 914 * @return true if s1 is equal to s2 915 */ 916 boolean compareStrings(XMLString s1, XMLString s2) 917 { 918 return s1.equals(s2); 919 } 920 921 /** 922 * Compare two numbers for equality. 923 * 924 * 925 * @param n1 First number to compare 926 * @param n2 Second number to compare 927 * 928 * @return true if n1 is equal to n2 929 */ 930 boolean compareNumbers(double n1, double n2) 931 { 932 return n1 == n2; 933 } 934} 935 936/** 937 * Compare strings or numbers for non-equality. 938 */ 939class NotEqualComparator extends Comparator 940{ 941 942 /** 943 * Compare two strings for non-equality. 944 * 945 * 946 * @param s1 First string to compare 947 * @param s2 Second String to compare 948 * 949 * @return true if s1 is not equal to s2 950 */ 951 boolean compareStrings(XMLString s1, XMLString s2) 952 { 953 return !s1.equals(s2); 954 } 955 956 /** 957 * Compare two numbers for non-equality. 958 * 959 * 960 * @param n1 First number to compare 961 * @param n2 Second number to compare 962 * 963 * @return true if n1 is not equal to n2 964 */ 965 boolean compareNumbers(double n1, double n2) 966 { 967 return n1 != n2; 968 } 969} 970