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