1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xpath.objects;
  58. import org.apache.xml.utils.FastStringBuffer;
  59. import org.apache.xml.utils.XMLString;
  60. import org.apache.xml.utils.XMLStringFactory;
  61. import org.apache.xml.utils.XMLCharacterRecognizer;
  62. import org.apache.xpath.res.XPATHErrorResources;
  63. import org.apache.xalan.res.XSLMessages;
  64. import java.util.Locale;
  65. /**
  66. * This class will wrap a FastStringBuffer and allow for
  67. */
  68. public class XStringForFSB extends XString
  69. {
  70. /** The start position in the fsb. */
  71. int m_start;
  72. /** The length of the string. */
  73. int m_length;
  74. /** If the str() function is called, the string will be cached here. */
  75. protected String m_strCache = null;
  76. /** cached hash code */
  77. protected int m_hash = 0;
  78. /**
  79. * Construct a XNodeSet object.
  80. *
  81. * @param val FastStringBuffer object this will wrap, must be non-null.
  82. * @param start The start position in the array.
  83. * @param length The number of characters to read from the array.
  84. */
  85. public XStringForFSB(FastStringBuffer val, int start, int length)
  86. {
  87. super(val);
  88. m_start = start;
  89. m_length = length;
  90. if (null == val)
  91. throw new IllegalArgumentException(
  92. XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FASTSTRINGBUFFER_CANNOT_BE_NULL, null));
  93. }
  94. /**
  95. * Construct a XNodeSet object.
  96. *
  97. * @param val String object this will wrap.
  98. */
  99. private XStringForFSB(String val)
  100. {
  101. super(val);
  102. throw new IllegalArgumentException(
  103. XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FSB_CANNOT_TAKE_STRING, null)); // "XStringForFSB can not take a string for an argument!");
  104. }
  105. /**
  106. * Cast result object to a string.
  107. *
  108. * @return The string this wraps or the empty string if null
  109. */
  110. public FastStringBuffer fsb()
  111. {
  112. return ((FastStringBuffer) m_obj);
  113. }
  114. /**
  115. * Cast result object to a string.
  116. *
  117. * @return The string this wraps or the empty string if null
  118. */
  119. public void appendToFsb(org.apache.xml.utils.FastStringBuffer fsb)
  120. {
  121. // %OPT% !!! FSB has to be updated to take partial fsb's for append.
  122. fsb.append(str());
  123. }
  124. /**
  125. * Tell if this object contains a java String object.
  126. *
  127. * @return true if this XMLString can return a string without creating one.
  128. */
  129. public boolean hasString()
  130. {
  131. return (null != m_strCache);
  132. }
  133. // /** NEEDSDOC Field strCount */
  134. // public static int strCount = 0;
  135. //
  136. // /** NEEDSDOC Field xtable */
  137. // static java.util.Hashtable xtable = new java.util.Hashtable();
  138. /**
  139. * Since this object is incomplete without the length and the offset, we
  140. * have to convert to a string when this function is called.
  141. *
  142. * @return The java String representation of this object.
  143. */
  144. public Object object()
  145. {
  146. return str();
  147. }
  148. /**
  149. * Cast result object to a string.
  150. *
  151. * @return The string this wraps or the empty string if null
  152. */
  153. public String str()
  154. {
  155. if (null == m_strCache)
  156. {
  157. m_strCache = fsb().getString(m_start, m_length);
  158. // strCount++;
  159. //
  160. // RuntimeException e = new RuntimeException("Bad! Bad!");
  161. // java.io.CharArrayWriter writer = new java.io.CharArrayWriter();
  162. // java.io.PrintWriter pw = new java.io.PrintWriter(writer);
  163. //
  164. // e.printStackTrace(pw);
  165. //
  166. // String str = writer.toString();
  167. //
  168. // str = str.substring(0, 600);
  169. //
  170. // if (null == xtable.get(str))
  171. // {
  172. // xtable.put(str, str);
  173. // System.out.println(str);
  174. // }
  175. // System.out.println("strCount: " + strCount);
  176. // throw e;
  177. // e.printStackTrace();
  178. // System.exit(-1);
  179. }
  180. return m_strCache;
  181. }
  182. /**
  183. * Directly call the
  184. * characters method on the passed ContentHandler for the
  185. * string-value. Multiple calls to the
  186. * ContentHandler's characters methods may well occur for a single call to
  187. * this method.
  188. *
  189. * @param ch A non-null reference to a ContentHandler.
  190. *
  191. * @throws org.xml.sax.SAXException
  192. */
  193. public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch)
  194. throws org.xml.sax.SAXException
  195. {
  196. fsb().sendSAXcharacters(ch, m_start, m_length);
  197. }
  198. /**
  199. * Directly call the
  200. * comment method on the passed LexicalHandler for the
  201. * string-value.
  202. *
  203. * @param lh A non-null reference to a LexicalHandler.
  204. *
  205. * @throws org.xml.sax.SAXException
  206. */
  207. public void dispatchAsComment(org.xml.sax.ext.LexicalHandler lh)
  208. throws org.xml.sax.SAXException
  209. {
  210. fsb().sendSAXComment(lh, m_start, m_length);
  211. }
  212. /**
  213. * Returns the length of this string.
  214. *
  215. * @return the length of the sequence of characters represented by this
  216. * object.
  217. */
  218. public int length()
  219. {
  220. return m_length;
  221. }
  222. /**
  223. * Returns the character at the specified index. An index ranges
  224. * from <code>0</code> to <code>length() - 1</code>. The first character
  225. * of the sequence is at index <code>0</code>, the next at index
  226. * <code>1</code>, and so on, as for array indexing.
  227. *
  228. * @param index the index of the character.
  229. * @return the character at the specified index of this string.
  230. * The first character is at index <code>0</code>.
  231. * @exception IndexOutOfBoundsException if the <code>index</code>
  232. * argument is negative or not less than the length of this
  233. * string.
  234. */
  235. public char charAt(int index)
  236. {
  237. return fsb().charAt(m_start + index);
  238. }
  239. /**
  240. * Copies characters from this string into the destination character
  241. * array.
  242. *
  243. * @param srcBegin index of the first character in the string
  244. * to copy.
  245. * @param srcEnd index after the last character in the string
  246. * to copy.
  247. * @param dst the destination array.
  248. * @param dstBegin the start offset in the destination array.
  249. * @exception IndexOutOfBoundsException If any of the following
  250. * is true:
  251. * <ul><li><code>srcBegin</code> is negative.
  252. * <li><code>srcBegin</code> is greater than <code>srcEnd</code>
  253. * <li><code>srcEnd</code> is greater than the length of this
  254. * string
  255. * <li><code>dstBegin</code> is negative
  256. * <li><code>dstBegin+(srcEnd-srcBegin)</code> is larger than
  257. * <code>dst.length</code></ul>
  258. * @exception NullPointerException if <code>dst</code> is <code>null</code>
  259. */
  260. public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
  261. {
  262. // %OPT% Need to call this on FSB when it is implemented.
  263. // %UNTESTED% (I don't think anyone calls this yet?)
  264. int n = srcEnd - srcBegin;
  265. if (n > m_length)
  266. n = m_length;
  267. if (n > (dst.length - dstBegin))
  268. n = (dst.length - dstBegin);
  269. int end = srcBegin + m_start + n;
  270. int d = dstBegin;
  271. FastStringBuffer fsb = fsb();
  272. for (int i = srcBegin + m_start; i < end; i++)
  273. {
  274. dst[d++] = fsb.charAt(i);
  275. }
  276. }
  277. /**
  278. * Compares this string to the specified object.
  279. * The result is <code>true</code> if and only if the argument is not
  280. * <code>null</code> and is a <code>String</code> object that represents
  281. * the same sequence of characters as this object.
  282. *
  283. * @param anObject the object to compare this <code>String</code>
  284. * against.
  285. *
  286. * NEEDSDOC @param obj2
  287. * @return <code>true</code> if the <code>String </code>are equal;
  288. * <code>false</code> otherwise.
  289. * @see java.lang.String#compareTo(java.lang.String)
  290. * @see java.lang.String#equalsIgnoreCase(java.lang.String)
  291. */
  292. public boolean equals(XMLString obj2)
  293. {
  294. if (this == obj2)
  295. {
  296. return true;
  297. }
  298. int n = m_length;
  299. if (n == obj2.length())
  300. {
  301. FastStringBuffer fsb = fsb();
  302. int i = m_start;
  303. int j = 0;
  304. while (n-- != 0)
  305. {
  306. if (fsb.charAt(i) != obj2.charAt(j))
  307. {
  308. return false;
  309. }
  310. i++;
  311. j++;
  312. }
  313. return true;
  314. }
  315. return false;
  316. }
  317. /**
  318. * Tell if two objects are functionally equal.
  319. *
  320. * @param obj2 Object to compare this to
  321. *
  322. * @return true if the two objects are equal
  323. *
  324. * @throws javax.xml.transform.TransformerException
  325. */
  326. public boolean equals(XObject obj2)
  327. {
  328. if (this == obj2)
  329. {
  330. return true;
  331. }
  332. if(obj2.getType() == XObject.CLASS_NUMBER)
  333. return obj2.equals(this);
  334. String str = obj2.str();
  335. int n = m_length;
  336. if (n == str.length())
  337. {
  338. FastStringBuffer fsb = fsb();
  339. int i = m_start;
  340. int j = 0;
  341. while (n-- != 0)
  342. {
  343. if (fsb.charAt(i) != str.charAt(j))
  344. {
  345. return false;
  346. }
  347. i++;
  348. j++;
  349. }
  350. return true;
  351. }
  352. return false;
  353. }
  354. /**
  355. * Tell if two objects are functionally equal.
  356. *
  357. * @param obj2 Object to compare this to
  358. *
  359. * NEEDSDOC @param anotherString
  360. *
  361. * @return true if the two objects are equal
  362. *
  363. * @throws javax.xml.transform.TransformerException
  364. */
  365. public boolean equals(String anotherString)
  366. {
  367. int n = m_length;
  368. if (n == anotherString.length())
  369. {
  370. FastStringBuffer fsb = fsb();
  371. int i = m_start;
  372. int j = 0;
  373. while (n-- != 0)
  374. {
  375. if (fsb.charAt(i) != anotherString.charAt(j))
  376. {
  377. return false;
  378. }
  379. i++;
  380. j++;
  381. }
  382. return true;
  383. }
  384. return false;
  385. }
  386. /**
  387. * Compares this string to the specified object.
  388. * The result is <code>true</code> if and only if the argument is not
  389. * <code>null</code> and is a <code>String</code> object that represents
  390. * the same sequence of characters as this object.
  391. *
  392. * @param anObject the object to compare this <code>String</code>
  393. * against.
  394. *
  395. * NEEDSDOC @param obj2
  396. * @return <code>true</code> if the <code>String </code>are equal;
  397. * <code>false</code> otherwise.
  398. * @see java.lang.String#compareTo(java.lang.String)
  399. * @see java.lang.String#equalsIgnoreCase(java.lang.String)
  400. */
  401. public boolean equals(Object obj2)
  402. {
  403. if (null == obj2)
  404. return false;
  405. if(obj2 instanceof XNumber)
  406. return obj2.equals(this);
  407. // In order to handle the 'all' semantics of
  408. // nodeset comparisons, we always call the
  409. // nodeset function.
  410. else if (obj2 instanceof XNodeSet)
  411. return obj2.equals(this);
  412. else if (obj2 instanceof XStringForFSB)
  413. return equals((XMLString) this);
  414. else
  415. return equals(obj2.toString());
  416. }
  417. /**
  418. * Compares this <code>String</code> to another <code>String</code>,
  419. * ignoring case considerations. Two strings are considered equal
  420. * ignoring case if they are of the same length, and corresponding
  421. * characters in the two strings are equal ignoring case.
  422. *
  423. * @param anotherString the <code>String</code> to compare this
  424. * <code>String</code> against.
  425. * @return <code>true</code> if the argument is not <code>null</code>
  426. * and the <code>String</code>s are equal,
  427. * ignoring case; <code>false</code> otherwise.
  428. * @see #equals(Object)
  429. * @see java.lang.Character#toLowerCase(char)
  430. * @see java.lang.Character#toUpperCase(char)
  431. */
  432. public boolean equalsIgnoreCase(String anotherString)
  433. {
  434. return (m_length == anotherString.length())
  435. ? str().equalsIgnoreCase(anotherString) : false;
  436. }
  437. /**
  438. * Compares two strings lexicographically.
  439. *
  440. * @param anotherString the <code>String</code> to be compared.
  441. *
  442. * NEEDSDOC @param xstr
  443. * @return the value <code>0</code> if the argument string is equal to
  444. * this string; a value less than <code>0</code> if this string
  445. * is lexicographically less than the string argument; and a
  446. * value greater than <code>0</code> if this string is
  447. * lexicographically greater than the string argument.
  448. * @exception java.lang.NullPointerException if <code>anotherString</code>
  449. * is <code>null</code>.
  450. */
  451. public int compareTo(XMLString xstr)
  452. {
  453. int len1 = m_length;
  454. int len2 = xstr.length();
  455. int n = Math.min(len1, len2);
  456. FastStringBuffer fsb = fsb();
  457. int i = m_start;
  458. int j = 0;
  459. while (n-- != 0)
  460. {
  461. char c1 = fsb.charAt(i);
  462. char c2 = xstr.charAt(j);
  463. if (c1 != c2)
  464. {
  465. return c1 - c2;
  466. }
  467. i++;
  468. j++;
  469. }
  470. return len1 - len2;
  471. }
  472. /**
  473. * Compares two strings lexicographically, ignoring case considerations.
  474. * This method returns an integer whose sign is that of
  475. * <code>this.toUpperCase().toLowerCase().compareTo(
  476. * str.toUpperCase().toLowerCase())</code>.
  477. * <p>
  478. * Note that this method does <em>not</em> take locale into account,
  479. * and will result in an unsatisfactory ordering for certain locales.
  480. * The java.text package provides <em>collators</em> to allow
  481. * locale-sensitive ordering.
  482. *
  483. * @param str the <code>String</code> to be compared.
  484. *
  485. * NEEDSDOC @param xstr
  486. * @return a negative integer, zero, or a positive integer as the
  487. * the specified String is greater than, equal to, or less
  488. * than this String, ignoring case considerations.
  489. * @see java.text.Collator#compare(String, String)
  490. * @since 1.2
  491. */
  492. public int compareToIgnoreCase(XMLString xstr)
  493. {
  494. int len1 = m_length;
  495. int len2 = xstr.length();
  496. int n = Math.min(len1, len2);
  497. FastStringBuffer fsb = fsb();
  498. int i = m_start;
  499. int j = 0;
  500. while (n-- != 0)
  501. {
  502. char c1 = Character.toLowerCase(fsb.charAt(i));
  503. char c2 = Character.toLowerCase(xstr.charAt(j));
  504. if (c1 != c2)
  505. {
  506. return c1 - c2;
  507. }
  508. i++;
  509. j++;
  510. }
  511. return len1 - len2;
  512. }
  513. /**
  514. * Returns a hashcode for this string. The hashcode for a
  515. * <code>String</code> object is computed as
  516. * <blockquote><pre>
  517. * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
  518. * </pre></blockquote>
  519. * using <code>int</code> arithmetic, where <code>s[i]</code> is the
  520. * <i>i</i>th character of the string, <code>n</code> is the length of
  521. * the string, and <code>^</code> indicates exponentiation.
  522. * (The hash value of the empty string is zero.)
  523. *
  524. * @return a hash code value for this object.
  525. */
  526. public int hashCode()
  527. {
  528. // Commenting this out because in JDK1.1.8 and VJ++
  529. // we don't match XMLStrings. Defaulting to the super
  530. // causes us to create a string, but at this point
  531. // this only seems to get called in key processing.
  532. // Maybe we can live with it?
  533. /*
  534. int h = m_hash;
  535. if (h == 0)
  536. {
  537. int off = m_start;
  538. int len = m_length;
  539. FastStringBuffer fsb = fsb();
  540. for (int i = 0; i < len; i++)
  541. {
  542. h = 31 * h + fsb.charAt(off);
  543. off++;
  544. }
  545. m_hash = h;
  546. }
  547. */
  548. return super.hashCode(); // h;
  549. }
  550. /**
  551. * Tests if this string starts with the specified prefix beginning
  552. * a specified index.
  553. *
  554. * @param prefix the prefix.
  555. * @param toffset where to begin looking in the string.
  556. * @return <code>true</code> if the character sequence represented by the
  557. * argument is a prefix of the substring of this object starting
  558. * at index <code>toffset</code> <code>false</code> otherwise.
  559. * The result is <code>false</code> if <code>toffset</code> is
  560. * negative or greater than the length of this
  561. * <code>String</code> object; otherwise the result is the same
  562. * as the result of the expression
  563. * <pre>
  564. * this.subString(toffset).startsWith(prefix)
  565. * </pre>
  566. * @exception java.lang.NullPointerException if <code>prefix</code> is
  567. * <code>null</code>.
  568. */
  569. public boolean startsWith(XMLString prefix, int toffset)
  570. {
  571. FastStringBuffer fsb = fsb();
  572. int to = m_start + toffset;
  573. int tlim = m_start + m_length;
  574. int po = 0;
  575. int pc = prefix.length();
  576. // Note: toffset might be near -1>>>1.
  577. if ((toffset < 0) || (toffset > m_length - pc))
  578. {
  579. return false;
  580. }
  581. while (--pc >= 0)
  582. {
  583. if (fsb.charAt(to) != prefix.charAt(po))
  584. {
  585. return false;
  586. }
  587. to++;
  588. po++;
  589. }
  590. return true;
  591. }
  592. /**
  593. * Tests if this string starts with the specified prefix.
  594. *
  595. * @param prefix the prefix.
  596. * @return <code>true</code> if the character sequence represented by the
  597. * argument is a prefix of the character sequence represented by
  598. * this string; <code>false</code> otherwise.
  599. * Note also that <code>true</code> will be returned if the
  600. * argument is an empty string or is equal to this
  601. * <code>String</code> object as determined by the
  602. * {@link #equals(Object)} method.
  603. * @exception java.lang.NullPointerException if <code>prefix</code> is
  604. * <code>null</code>.
  605. * @since JDK1. 0
  606. */
  607. public boolean startsWith(XMLString prefix)
  608. {
  609. return startsWith(prefix, 0);
  610. }
  611. /**
  612. * Returns the index within this string of the first occurrence of the
  613. * specified character. If a character with value <code>ch</code> occurs
  614. * in the character sequence represented by this <code>String</code>
  615. * object, then the index of the first such occurrence is returned --
  616. * that is, the smallest value <i>k</i> such that:
  617. * <blockquote><pre>
  618. * this.charAt(<i>k</i>) == ch
  619. * </pre></blockquote>
  620. * is <code>true</code>. If no such character occurs in this string,
  621. * then <code>-1</code> is returned.
  622. *
  623. * @param ch a character.
  624. * @return the index of the first occurrence of the character in the
  625. * character sequence represented by this object, or
  626. * <code>-1</code> if the character does not occur.
  627. */
  628. public int indexOf(int ch)
  629. {
  630. return indexOf(ch, 0);
  631. }
  632. /**
  633. * Returns the index within this string of the first occurrence of the
  634. * specified character, starting the search at the specified index.
  635. * <p>
  636. * If a character with value <code>ch</code> occurs in the character
  637. * sequence represented by this <code>String</code> object at an index
  638. * no smaller than <code>fromIndex</code>, then the index of the first
  639. * such occurrence is returned--that is, the smallest value <i>k</i>
  640. * such that:
  641. * <blockquote><pre>
  642. * (this.charAt(<i>k</i>) == ch) && (<i>k</i> >= fromIndex)
  643. * </pre></blockquote>
  644. * is true. If no such character occurs in this string at or after
  645. * position <code>fromIndex</code>, then <code>-1</code> is returned.
  646. * <p>
  647. * There is no restriction on the value of <code>fromIndex</code>. If it
  648. * is negative, it has the same effect as if it were zero: this entire
  649. * string may be searched. If it is greater than the length of this
  650. * string, it has the same effect as if it were equal to the length of
  651. * this string: <code>-1</code> is returned.
  652. *
  653. * @param ch a character.
  654. * @param fromIndex the index to start the search from.
  655. * @return the index of the first occurrence of the character in the
  656. * character sequence represented by this object that is greater
  657. * than or equal to <code>fromIndex</code>, or <code>-1</code>
  658. * if the character does not occur.
  659. */
  660. public int indexOf(int ch, int fromIndex)
  661. {
  662. int max = m_start + m_length;
  663. FastStringBuffer fsb = fsb();
  664. if (fromIndex < 0)
  665. {
  666. fromIndex = 0;
  667. }
  668. else if (fromIndex >= m_length)
  669. {
  670. // Note: fromIndex might be near -1>>>1.
  671. return -1;
  672. }
  673. for (int i = m_start + fromIndex; i < max; i++)
  674. {
  675. if (fsb.charAt(i) == ch)
  676. {
  677. return i - m_start;
  678. }
  679. }
  680. return -1;
  681. }
  682. /**
  683. * Returns a new string that is a substring of this string. The
  684. * substring begins with the character at the specified index and
  685. * extends to the end of this string. <p>
  686. * Examples:
  687. * <blockquote><pre>
  688. * "unhappy".substring(2) returns "happy"
  689. * "Harbison".substring(3) returns "bison"
  690. * "emptiness".substring(9) returns "" (an empty string)
  691. * </pre></blockquote>
  692. *
  693. * @param beginIndex the beginning index, inclusive.
  694. * @return the specified substring.
  695. * @exception IndexOutOfBoundsException if
  696. * <code>beginIndex</code> is negative or larger than the
  697. * length of this <code>String</code> object.
  698. */
  699. public XMLString substring(int beginIndex)
  700. {
  701. int len = m_length - beginIndex;
  702. if (len <= 0)
  703. return XString.EMPTYSTRING;
  704. else
  705. {
  706. int start = m_start + beginIndex;
  707. return new XStringForFSB(fsb(), start, len);
  708. }
  709. }
  710. /**
  711. * Returns a new string that is a substring of this string. The
  712. * substring begins at the specified <code>beginIndex</code> and
  713. * extends to the character at index <code>endIndex - 1</code>.
  714. * Thus the length of the substring is <code>endIndex-beginIndex</code>.
  715. *
  716. * @param beginIndex the beginning index, inclusive.
  717. * @param endIndex the ending index, exclusive.
  718. * @return the specified substring.
  719. * @exception IndexOutOfBoundsException if the
  720. * <code>beginIndex</code> is negative, or
  721. * <code>endIndex</code> is larger than the length of
  722. * this <code>String</code> object, or
  723. * <code>beginIndex</code> is larger than
  724. * <code>endIndex</code>.
  725. */
  726. public XMLString substring(int beginIndex, int endIndex)
  727. {
  728. int len = endIndex - beginIndex;
  729. if (len > m_length)
  730. len = m_length;
  731. if (len <= 0)
  732. return XString.EMPTYSTRING;
  733. else
  734. {
  735. int start = m_start + beginIndex;
  736. return new XStringForFSB(fsb(), start, len);
  737. }
  738. }
  739. /**
  740. * Concatenates the specified string to the end of this string.
  741. *
  742. * @param str the <code>String</code> that is concatenated to the end
  743. * of this <code>String</code>.
  744. * @return a string that represents the concatenation of this object's
  745. * characters followed by the string argument's characters.
  746. * @exception java.lang.NullPointerException if <code>str</code> is
  747. * <code>null</code>.
  748. */
  749. public XMLString concat(String str)
  750. {
  751. // %OPT% Make an FSB here?
  752. return new XString(str().concat(str));
  753. }
  754. /**
  755. * Removes white space from both ends of this string.
  756. *
  757. * @return this string, with white space removed from the front and end.
  758. */
  759. public XMLString trim()
  760. {
  761. return fixWhiteSpace(true, true, false);
  762. }
  763. /**
  764. * Returns whether the specified <var>ch</var> conforms to the XML 1.0 definition
  765. * of whitespace. Refer to <A href="http://www.w3.org/TR/1998/REC-xml-19980210#NT-S">
  766. * the definition of <CODE>S</CODE></A> for details.
  767. * @param ch Character to check as XML whitespace.
  768. * @return =true if <var>ch</var> is XML whitespace; otherwise =false.
  769. */
  770. private static boolean isSpace(char ch)
  771. {
  772. return XMLCharacterRecognizer.isWhiteSpace(ch); // Take the easy way out for now.
  773. }
  774. /**
  775. * Conditionally trim all leading and trailing whitespace in the specified String.
  776. * All strings of white space are
  777. * replaced by a single space character (#x20), except spaces after punctuation which
  778. * receive double spaces if doublePunctuationSpaces is true.
  779. * This function may be useful to a formatter, but to get first class
  780. * results, the formatter should probably do it's own white space handling
  781. * based on the semantics of the formatting object.
  782. *
  783. * @param trimHead Trim leading whitespace?
  784. * @param trimTail Trim trailing whitespace?
  785. * @param doublePunctuationSpaces Use double spaces for punctuation?
  786. * @return The trimmed string.
  787. */
  788. public XMLString fixWhiteSpace(boolean trimHead, boolean trimTail,
  789. boolean doublePunctuationSpaces)
  790. {
  791. int end = m_length + m_start;
  792. char[] buf = new char[m_length];
  793. FastStringBuffer fsb = fsb();
  794. boolean edit = false;
  795. /* replace S to ' '. and ' '+ -> single ' '. */
  796. int d = 0;
  797. boolean pres = false;
  798. for (int s = m_start; s < end; s++)
  799. {
  800. char c = fsb.charAt(s);
  801. if (isSpace(c))
  802. {
  803. if (!pres)
  804. {
  805. if (' ' != c)
  806. {
  807. edit = true;
  808. }
  809. buf[d++] = ' ';
  810. if (doublePunctuationSpaces && (d != 0))
  811. {
  812. char prevChar = buf[d - 1];
  813. if (!((prevChar == '.') || (prevChar == '!')
  814. || (prevChar == '?')))
  815. {
  816. pres = true;
  817. }
  818. }
  819. else
  820. {
  821. pres = true;
  822. }
  823. }
  824. else
  825. {
  826. edit = true;
  827. pres = true;
  828. }
  829. }
  830. else
  831. {
  832. buf[d++] = c;
  833. pres = false;
  834. }
  835. }
  836. if (trimTail && 1 <= d && ' ' == buf[d - 1])
  837. {
  838. edit = true;
  839. d--;
  840. }
  841. int start = 0;
  842. if (trimHead && 0 < d && ' ' == buf[0])
  843. {
  844. edit = true;
  845. start++;
  846. }
  847. XMLStringFactory xsf = XMLStringFactoryImpl.getFactory();
  848. return edit ? xsf.newstr(buf, start, d - start) : this;
  849. }
  850. /**
  851. * Convert a string to a double -- Allowed input is in fixed
  852. * notation ddd.fff.
  853. *
  854. * %OPT% CHECK PERFORMANCE against generating a Java String and
  855. * converting it to double. The advantage of running in native
  856. * machine code -- perhaps even microcode, on some systems -- may
  857. * more than make up for the cost of allocating and discarding the
  858. * additional object. We need to benchmark this.
  859. *
  860. * %OPT% More importantly, we need to decide whether we _care_ about
  861. * the performance of this operation. Does XString.toDouble constitute
  862. * any measurable percentage of our typical runtime? I suspect not!
  863. *
  864. * @return A double value representation of the string, or return Double.NaN
  865. * if the string can not be converted. */
  866. public double toDouble()
  867. {
  868. int end = m_length+m_start;
  869. if(0 == end)
  870. return Double.NaN;
  871. int start = m_start;
  872. FastStringBuffer fsb = fsb();
  873. long longResult=0;
  874. boolean isNegative=false;
  875. boolean trailingSpace=false;
  876. int[] digitsFound={0,0}; // intpart,fracpart
  877. int digitType=0; // Index to which kind of digit we're accumulating
  878. double doubleResult;
  879. // Scan past leading whitespace characters
  880. while(start< end &&
  881. XMLCharacterRecognizer.isWhiteSpace( fsb.charAt(start) )
  882. )
  883. ++start;
  884. if (start < end && fsb.charAt(start) == '-')
  885. {
  886. isNegative=true;
  887. start++;
  888. }
  889. // parse the string from left to right converting as an integer.
  890. for (int i = start; i < end; i++)
  891. {
  892. char c = fsb.charAt(i);
  893. if(XMLCharacterRecognizer.isWhiteSpace(c))
  894. {
  895. trailingSpace=true;
  896. break; // Trailing whitespace is ignored
  897. }
  898. else if(trailingSpace)
  899. return Double.NaN; // Nonspace after space is poorly formed
  900. switch(c)
  901. {
  902. case '.':
  903. if(digitType==0)
  904. digitType=1;
  905. else
  906. return Double.NaN; // Second period is error
  907. break;
  908. case '0': // NOT Unicode isDigit(); ASCII digits _only_
  909. case '1':
  910. case '2':
  911. case '3':
  912. case '4':
  913. case '5':
  914. case '6':
  915. case '7':
  916. case '8':
  917. case '9':
  918. longResult = longResult * 10 + (c - '0'); // Accumulate as int
  919. ++digitsFound[digitType]; // Remember scaling
  920. break;
  921. default:
  922. return Double.NaN; // Nonnumeric is error
  923. }
  924. }
  925. if(0 ==digitsFound[0]&& 0==digitsFound[1])
  926. return Double.NaN;
  927. // Convert from scaled integer to floating point. This will come close.
  928. // There's an alternative solution involving Double.longBitsToDouble
  929. // followed by a combined renormalize/scale operation... but I honestly
  930. // think the more straightforward solution comes out to just about
  931. // the same thing.
  932. long scale=1; // AFAIK, java doesn't have an easier 10^n operation
  933. for(int i=digitsFound[1];i>0;--i)
  934. scale*=10;
  935. doubleResult=((double)longResult)/scale;
  936. if(isNegative)
  937. doubleResult *= -1;
  938. return doubleResult;
  939. }
  940. }