1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xpath.internal.objects;
23
24import java.util.Locale;
25
26import com.sun.org.apache.xml.internal.dtm.DTM;
27import com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer;
28import com.sun.org.apache.xml.internal.utils.XMLString;
29import com.sun.org.apache.xml.internal.utils.XMLStringFactory;
30import com.sun.org.apache.xpath.internal.ExpressionOwner;
31import com.sun.org.apache.xpath.internal.XPathContext;
32import com.sun.org.apache.xpath.internal.XPathVisitor;
33
34/**
35 * This class represents an XPath string object, and is capable of
36 * converting the string to other types, such as a number.
37 * @xsl.usage general
38 */
39public class XString extends XObject implements XMLString
40{
41    static final long serialVersionUID = 2020470518395094525L;
42
43  /** Empty string XString object */
44  public static final XString EMPTYSTRING = new XString("");
45
46  /**
47   * Construct a XString object.  This constructor exists for derived classes.
48   *
49   * @param val String object this will wrap.
50   */
51  protected XString(Object val)
52  {
53    super(val);
54  }
55
56  /**
57   * Construct a XNodeSet object.
58   *
59   * @param val String object this will wrap.
60   */
61  public XString(String val)
62  {
63    super(val);
64  }
65
66  /**
67   * Tell that this is a CLASS_STRING.
68   *
69   * @return type CLASS_STRING
70   */
71  public int getType()
72  {
73    return CLASS_STRING;
74  }
75
76  /**
77   * Given a request type, return the equivalent string.
78   * For diagnostic purposes.
79   *
80   * @return type string "#STRING"
81   */
82  public String getTypeString()
83  {
84    return "#STRING";
85  }
86
87  /**
88   * Tell if this object contains a java String object.
89   *
90   * @return true if this XMLString can return a string without creating one.
91   */
92  public boolean hasString()
93  {
94    return true;
95  }
96
97  /**
98   * Cast result object to a number.
99   *
100   * @return 0.0 if this string is null, numeric value of this string
101   * or NaN
102   */
103  public double num()
104  {
105    return toDouble();
106  }
107
108  /**
109   * Convert a string to a double -- Allowed input is in fixed
110   * notation ddd.fff.
111   *
112   * @return A double value representation of the string, or return Double.NaN
113   * if the string can not be converted.
114   */
115  public double toDouble()
116  {
117    /* XMLCharacterRecognizer.isWhiteSpace(char c) methods treats the following
118     * characters as white space characters.
119     * ht - horizontal tab, nl - newline , cr - carriage return and sp - space
120     * trim() methods by default also takes care of these white space characters
121     * So trim() method is used to remove leading and trailing white spaces.
122     */
123        XMLString s = trim();
124        double result = Double.NaN;
125        for (int i = 0; i < s.length(); i++)
126        {
127                char c = s.charAt(i);
128    if (c != '-' && c != '.' && ( c < 0X30 || c > 0x39)) {
129            // The character is not a '-' or a '.' or a digit
130            // then return NaN because something is wrong.
131                        return result;
132        }
133        }
134        try
135        {
136                result = Double.parseDouble(s.toString());
137        } catch (NumberFormatException e){}
138
139        return result;
140}
141
142  /**
143   * Cast result object to a boolean.
144   *
145   * @return True if the length of this string object is greater
146   * than 0.
147   */
148  public boolean bool()
149  {
150    return str().length() > 0;
151  }
152
153  /**
154   * Cast result object to a string.
155   *
156   * @return The string this wraps or the empty string if null
157   */
158  public XMLString xstr()
159  {
160    return this;
161  }
162
163  /**
164   * Cast result object to a string.
165   *
166   * @return The string this wraps or the empty string if null
167   */
168  public String str()
169  {
170    return (null != m_obj) ? ((String) m_obj) : "";
171  }
172
173  /**
174   * Cast result object to a result tree fragment.
175   *
176   * @param support Xpath context to use for the conversion
177   *
178   * @return A document fragment with this string as a child node
179   */
180  public int rtf(XPathContext support)
181  {
182
183    DTM frag = support.createDocumentFragment();
184
185    frag.appendTextChild(str());
186
187    return frag.getDocument();
188  }
189
190  /**
191   * Directly call the
192   * characters method on the passed ContentHandler for the
193   * string-value. Multiple calls to the
194   * ContentHandler's characters methods may well occur for a single call to
195   * this method.
196   *
197   * @param ch A non-null reference to a ContentHandler.
198   *
199   * @throws org.xml.sax.SAXException
200   */
201  public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch)
202          throws org.xml.sax.SAXException
203  {
204
205    String str = str();
206
207    ch.characters(str.toCharArray(), 0, str.length());
208  }
209
210  /**
211   * Directly call the
212   * comment method on the passed LexicalHandler for the
213   * string-value.
214   *
215   * @param lh A non-null reference to a LexicalHandler.
216   *
217   * @throws org.xml.sax.SAXException
218   */
219  public void dispatchAsComment(org.xml.sax.ext.LexicalHandler lh)
220          throws org.xml.sax.SAXException
221  {
222
223    String str = str();
224
225    lh.comment(str.toCharArray(), 0, str.length());
226  }
227
228  /**
229   * Returns the length of this string.
230   *
231   * @return  the length of the sequence of characters represented by this
232   *          object.
233   */
234  public int length()
235  {
236    return str().length();
237  }
238
239  /**
240   * Returns the character at the specified index. An index ranges
241   * from <code>0</code> to <code>length() - 1</code>. The first character
242   * of the sequence is at index <code>0</code>, the next at index
243   * <code>1</code>, and so on, as for array indexing.
244   *
245   * @param      index   the index of the character.
246   * @return     the character at the specified index of this string.
247   *             The first character is at index <code>0</code>.
248   * @exception  IndexOutOfBoundsException  if the <code>index</code>
249   *             argument is negative or not less than the length of this
250   *             string.
251   */
252  public char charAt(int index)
253  {
254    return str().charAt(index);
255  }
256
257  /**
258   * Copies characters from this string into the destination character
259   * array.
260   *
261   * @param      srcBegin   index of the first character in the string
262   *                        to copy.
263   * @param      srcEnd     index after the last character in the string
264   *                        to copy.
265   * @param      dst        the destination array.
266   * @param      dstBegin   the start offset in the destination array.
267   * @exception IndexOutOfBoundsException If any of the following
268   *            is true:
269   *            <ul><li><code>srcBegin</code> is negative.
270   *            <li><code>srcBegin</code> is greater than <code>srcEnd</code>
271   *            <li><code>srcEnd</code> is greater than the length of this
272   *                string
273   *            <li><code>dstBegin</code> is negative
274   *            <li><code>dstBegin+(srcEnd-srcBegin)</code> is larger than
275   *                <code>dst.length</code></ul>
276   * @exception NullPointerException if <code>dst</code> is <code>null</code>
277   */
278  public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
279  {
280    str().getChars(srcBegin, srcEnd, dst, dstBegin);
281  }
282
283  /**
284   * Tell if two objects are functionally equal.
285   *
286   * @param obj2 Object to compare this to
287   *
288   * @return true if the two objects are equal
289   *
290   * @throws javax.xml.transform.TransformerException
291   */
292  public boolean equals(XObject obj2)
293  {
294
295    // In order to handle the 'all' semantics of
296    // nodeset comparisons, we always call the
297    // nodeset function.
298    int t = obj2.getType();
299    try
300    {
301            if (XObject.CLASS_NODESET == t)
302              return obj2.equals(this);
303            // If at least one object to be compared is a boolean, then each object
304            // to be compared is converted to a boolean as if by applying the
305            // boolean function.
306            else if(XObject.CLASS_BOOLEAN == t)
307                return obj2.bool() == bool();
308            // Otherwise, if at least one object to be compared is a number, then each object
309            // to be compared is converted to a number as if by applying the number function.
310            else if(XObject.CLASS_NUMBER == t)
311                return obj2.num() == num();
312    }
313    catch(javax.xml.transform.TransformerException te)
314    {
315        throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(te);
316    }
317
318    // Otherwise, both objects to be compared are converted to strings as
319    // if by applying the string function.
320    return xstr().equals(obj2.xstr());
321  }
322
323   /**
324   * Compares this string to the specified <code>String</code>.
325   * The result is <code>true</code> if and only if the argument is not
326   * <code>null</code> and is a <code>String</code> object that represents
327   * the same sequence of characters as this object.
328   *
329   * @param   obj2   the object to compare this <code>String</code> against.
330   * @return  <code>true</code> if the <code>String</code>s are equal;
331   *          <code>false</code> otherwise.
332   * @see     java.lang.String#compareTo(java.lang.String)
333   * @see     java.lang.String#equalsIgnoreCase(java.lang.String)
334   */
335  public boolean equals(String obj2) {
336    return str().equals(obj2);
337  }
338
339  /**
340   * Compares this string to the specified object.
341   * The result is <code>true</code> if and only if the argument is not
342   * <code>null</code> and is a <code>String</code> object that represents
343   * the same sequence of characters as this object.
344   *
345   * @param obj2   the object to compare this <code>String</code>
346   *                     against.
347   * @return  <code>true</code> if the <code>String </code>are equal;
348   *          <code>false</code> otherwise.
349   * @see     java.lang.String#compareTo(java.lang.String)
350   * @see     java.lang.String#equalsIgnoreCase(java.lang.String)
351   */
352  public boolean equals(XMLString obj2)
353  {
354    if (obj2 != null) {
355      if (!obj2.hasString()) {
356        return obj2.equals(str());
357      } else {
358        return str().equals(obj2.toString());
359      }
360    }
361    return false;
362  }
363
364  /**
365   * Compares this string to the specified object.
366   * The result is <code>true</code> if and only if the argument is not
367   * <code>null</code> and is a <code>String</code> object that represents
368   * the same sequence of characters as this object.
369   *
370   * @param   obj2       the object to compare this <code>String</code>
371   *                     against.
372   * @return  <code>true</code> if the <code>String </code>are equal;
373   *          <code>false</code> otherwise.
374   * @see     java.lang.String#compareTo(java.lang.String)
375   * @see     java.lang.String#equalsIgnoreCase(java.lang.String)
376   */
377  public boolean equals(Object obj2)
378  {
379
380    if (null == obj2)
381      return false;
382
383      // In order to handle the 'all' semantics of
384      // nodeset comparisons, we always call the
385      // nodeset function.
386    else if (obj2 instanceof XNodeSet)
387      return obj2.equals(this);
388    else if(obj2 instanceof XNumber)
389        return obj2.equals(this);
390    else
391      return str().equals(obj2.toString());
392  }
393
394  /**
395   * Compares this <code>String</code> to another <code>String</code>,
396   * ignoring case considerations.  Two strings are considered equal
397   * ignoring case if they are of the same length, and corresponding
398   * characters in the two strings are equal ignoring case.
399   *
400   * @param   anotherString   the <code>String</code> to compare this
401   *                          <code>String</code> against.
402   * @return  <code>true</code> if the argument is not <code>null</code>
403   *          and the <code>String</code>s are equal,
404   *          ignoring case; <code>false</code> otherwise.
405   * @see     #equals(Object)
406   * @see     java.lang.Character#toLowerCase(char)
407   * @see java.lang.Character#toUpperCase(char)
408   */
409  public boolean equalsIgnoreCase(String anotherString)
410  {
411    return str().equalsIgnoreCase(anotherString);
412  }
413
414  /**
415   * Compares two strings lexicographically.
416   *
417   * @param   xstr   the <code>String</code> to be compared.
418   *
419   * @return  the value <code>0</code> if the argument string is equal to
420   *          this string; a value less than <code>0</code> if this string
421   *          is lexicographically less than the string argument; and a
422   *          value greater than <code>0</code> if this string is
423   *          lexicographically greater than the string argument.
424   * @exception java.lang.NullPointerException if <code>anotherString</code>
425   *          is <code>null</code>.
426   */
427  public int compareTo(XMLString xstr)
428  {
429
430    int len1 = this.length();
431    int len2 = xstr.length();
432    int n = Math.min(len1, len2);
433    int i = 0;
434    int j = 0;
435
436    while (n-- != 0)
437    {
438      char c1 = this.charAt(i);
439      char c2 = xstr.charAt(j);
440
441      if (c1 != c2)
442      {
443        return c1 - c2;
444      }
445
446      i++;
447      j++;
448    }
449
450    return len1 - len2;
451  }
452
453  /**
454   * Compares two strings lexicographically, ignoring case considerations.
455   * This method returns an integer whose sign is that of
456   * <code>this.toUpperCase().toLowerCase().compareTo(
457   * str.toUpperCase().toLowerCase())</code>.
458   * <p>
459   * Note that this method does <em>not</em> take locale into account,
460   * and will result in an unsatisfactory ordering for certain locales.
461   * The java.text package provides <em>collators</em> to allow
462   * locale-sensitive ordering.
463   *
464   * @param   str   the <code>String</code> to be compared.
465   * @return  a negative integer, zero, or a positive integer as the
466   *          the specified String is greater than, equal to, or less
467   *          than this String, ignoring case considerations.
468   * @see     java.text.Collator#compare(String, String)
469   * @since   1.2
470   */
471  public int compareToIgnoreCase(XMLString str)
472  {
473    // %REVIEW%  Like it says, @since 1.2. Doesn't exist in earlier
474    // versions of Java, hence we can't yet shell out to it. We can implement
475    // it as character-by-character compare, but doing so efficiently
476    // is likely to be (ahem) interesting.
477    //
478    // However, since nobody is actually _using_ this method yet:
479    //    return str().compareToIgnoreCase(str.toString());
480
481    throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException(
482      new java.lang.NoSuchMethodException(
483        "Java 1.2 method, not yet implemented"));
484  }
485
486  /**
487   * Tests if this string starts with the specified prefix beginning
488   * a specified index.
489   *
490   * @param   prefix    the prefix.
491   * @param   toffset   where to begin looking in the string.
492   * @return  <code>true</code> if the character sequence represented by the
493   *          argument is a prefix of the substring of this object starting
494   *          at index <code>toffset</code>; <code>false</code> otherwise.
495   *          The result is <code>false</code> if <code>toffset</code> is
496   *          negative or greater than the length of this
497   *          <code>String</code> object; otherwise the result is the same
498   *          as the result of the expression
499   *          <pre>
500   *          this.subString(toffset).startsWith(prefix)
501   *          </pre>
502   * @exception java.lang.NullPointerException if <code>prefix</code> is
503   *          <code>null</code>.
504   */
505  public boolean startsWith(String prefix, int toffset)
506  {
507    return str().startsWith(prefix, toffset);
508  }
509
510  /**
511   * Tests if this string starts with the specified prefix.
512   *
513   * @param   prefix   the prefix.
514   * @return  <code>true</code> if the character sequence represented by the
515   *          argument is a prefix of the character sequence represented by
516   *          this string; <code>false</code> otherwise.
517   *          Note also that <code>true</code> will be returned if the
518   *          argument is an empty string or is equal to this
519   *          <code>String</code> object as determined by the
520   *          {@link #equals(Object)} method.
521   * @exception java.lang.NullPointerException if <code>prefix</code> is
522   *          <code>null</code>.
523   */
524  public boolean startsWith(String prefix)
525  {
526    return startsWith(prefix, 0);
527  }
528
529  /**
530   * Tests if this string starts with the specified prefix beginning
531   * a specified index.
532   *
533   * @param   prefix    the prefix.
534   * @param   toffset   where to begin looking in the string.
535   * @return  <code>true</code> if the character sequence represented by the
536   *          argument is a prefix of the substring of this object starting
537   *          at index <code>toffset</code>; <code>false</code> otherwise.
538   *          The result is <code>false</code> if <code>toffset</code> is
539   *          negative or greater than the length of this
540   *          <code>String</code> object; otherwise the result is the same
541   *          as the result of the expression
542   *          <pre>
543   *          this.subString(toffset).startsWith(prefix)
544   *          </pre>
545   * @exception java.lang.NullPointerException if <code>prefix</code> is
546   *          <code>null</code>.
547   */
548  public boolean startsWith(XMLString prefix, int toffset)
549  {
550
551    int to = toffset;
552    int tlim = this.length();
553    int po = 0;
554    int pc = prefix.length();
555
556    // Note: toffset might be near -1>>>1.
557    if ((toffset < 0) || (toffset > tlim - pc))
558    {
559      return false;
560    }
561
562    while (--pc >= 0)
563    {
564      if (this.charAt(to) != prefix.charAt(po))
565      {
566        return false;
567      }
568
569      to++;
570      po++;
571    }
572
573    return true;
574  }
575
576  /**
577   * Tests if this string starts with the specified prefix.
578   *
579   * @param   prefix   the prefix.
580   * @return  <code>true</code> if the character sequence represented by the
581   *          argument is a prefix of the character sequence represented by
582   *          this string; <code>false</code> otherwise.
583   *          Note also that <code>true</code> will be returned if the
584   *          argument is an empty string or is equal to this
585   *          <code>String</code> object as determined by the
586   *          {@link #equals(Object)} method.
587   * @exception java.lang.NullPointerException if <code>prefix</code> is
588   *          <code>null</code>.
589   */
590  public boolean startsWith(XMLString prefix)
591  {
592    return startsWith(prefix, 0);
593  }
594
595  /**
596   * Tests if this string ends with the specified suffix.
597   *
598   * @param   suffix   the suffix.
599   * @return  <code>true</code> if the character sequence represented by the
600   *          argument is a suffix of the character sequence represented by
601   *          this object; <code>false</code> otherwise. Note that the
602   *          result will be <code>true</code> if the argument is the
603   *          empty string or is equal to this <code>String</code> object
604   *          as determined by the {@link #equals(Object)} method.
605   * @exception java.lang.NullPointerException if <code>suffix</code> is
606   *          <code>null</code>.
607   */
608  public boolean endsWith(String suffix)
609  {
610    return str().endsWith(suffix);
611  }
612
613  /**
614   * Returns a hashcode for this string. The hashcode for a
615   * <code>String</code> object is computed as
616   * <blockquote><pre>
617   * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
618   * </pre></blockquote>
619   * using <code>int</code> arithmetic, where <code>s[i]</code> is the
620   * <i>i</i>th character of the string, <code>n</code> is the length of
621   * the string, and <code>^</code> indicates exponentiation.
622   * (The hash value of the empty string is zero.)
623   *
624   * @return  a hash code value for this object.
625   */
626  public int hashCode()
627  {
628    return str().hashCode();
629  }
630
631  /**
632   * Returns the index within this string of the first occurrence of the
633   * specified character. If a character with value <code>ch</code> occurs
634   * in the character sequence represented by this <code>String</code>
635   * object, then the index of the first such occurrence is returned --
636   * that is, the smallest value <i>k</i> such that:
637   * <blockquote><pre>
638   * this.charAt(<i>k</i>) == ch
639   * </pre></blockquote>
640   * is <code>true</code>. If no such character occurs in this string,
641   * then <code>-1</code> is returned.
642   *
643   * @param   ch   a character.
644   * @return  the index of the first occurrence of the character in the
645   *          character sequence represented by this object, or
646   *          <code>-1</code> if the character does not occur.
647   */
648  public int indexOf(int ch)
649  {
650    return str().indexOf(ch);
651  }
652
653  /**
654   * Returns the index within this string of the first occurrence of the
655   * specified character, starting the search at the specified index.
656   * <p>
657   * If a character with value <code>ch</code> occurs in the character
658   * sequence represented by this <code>String</code> object at an index
659   * no smaller than <code>fromIndex</code>, then the index of the first
660   * such occurrence is returned--that is, the smallest value <i>k</i>
661   * such that:
662   * <blockquote><pre>
663   * (this.charAt(<i>k</i>) == ch) && (<i>k</i> >= fromIndex)
664   * </pre></blockquote>
665   * is true. If no such character occurs in this string at or after
666   * position <code>fromIndex</code>, then <code>-1</code> is returned.
667   * <p>
668   * There is no restriction on the value of <code>fromIndex</code>. If it
669   * is negative, it has the same effect as if it were zero: this entire
670   * string may be searched. If it is greater than the length of this
671   * string, it has the same effect as if it were equal to the length of
672   * this string: <code>-1</code> is returned.
673   *
674   * @param   ch          a character.
675   * @param   fromIndex   the index to start the search from.
676   * @return  the index of the first occurrence of the character in the
677   *          character sequence represented by this object that is greater
678   *          than or equal to <code>fromIndex</code>, or <code>-1</code>
679   *          if the character does not occur.
680   */
681  public int indexOf(int ch, int fromIndex)
682  {
683    return str().indexOf(ch, fromIndex);
684  }
685
686  /**
687   * Returns the index within this string of the last occurrence of the
688   * specified character. That is, the index returned is the largest
689   * value <i>k</i> such that:
690   * <blockquote><pre>
691   * this.charAt(<i>k</i>) == ch
692   * </pre></blockquote>
693   * is true.
694   * The String is searched backwards starting at the last character.
695   *
696   * @param   ch   a character.
697   * @return  the index of the last occurrence of the character in the
698   *          character sequence represented by this object, or
699   *          <code>-1</code> if the character does not occur.
700   */
701  public int lastIndexOf(int ch)
702  {
703    return str().lastIndexOf(ch);
704  }
705
706  /**
707   * Returns the index within this string of the last occurrence of the
708   * specified character, searching backward starting at the specified
709   * index. That is, the index returned is the largest value <i>k</i>
710   * such that:
711   * <blockquote><pre>
712   * this.charAt(k) == ch) && (k <= fromIndex)
713   * </pre></blockquote>
714   * is true.
715   *
716   * @param   ch          a character.
717   * @param   fromIndex   the index to start the search from. There is no
718   *          restriction on the value of <code>fromIndex</code>. If it is
719   *          greater than or equal to the length of this string, it has
720   *          the same effect as if it were equal to one less than the
721   *          length of this string: this entire string may be searched.
722   *          If it is negative, it has the same effect as if it were -1:
723   *          -1 is returned.
724   * @return  the index of the last occurrence of the character in the
725   *          character sequence represented by this object that is less
726   *          than or equal to <code>fromIndex</code>, or <code>-1</code>
727   *          if the character does not occur before that point.
728   */
729  public int lastIndexOf(int ch, int fromIndex)
730  {
731    return str().lastIndexOf(ch, fromIndex);
732  }
733
734  /**
735   * Returns the index within this string of the first occurrence of the
736   * specified substring. The integer returned is the smallest value
737   * <i>k</i> such that:
738   * <blockquote><pre>
739   * this.startsWith(str, <i>k</i>)
740   * </pre></blockquote>
741   * is <code>true</code>.
742   *
743   * @param   str   any string.
744   * @return  if the string argument occurs as a substring within this
745   *          object, then the index of the first character of the first
746   *          such substring is returned; if it does not occur as a
747   *          substring, <code>-1</code> is returned.
748   * @exception java.lang.NullPointerException if <code>str</code> is
749   *          <code>null</code>.
750   */
751  public int indexOf(String str)
752  {
753    return str().indexOf(str);
754  }
755
756  /**
757   * Returns the index within this string of the first occurrence of the
758   * specified substring. The integer returned is the smallest value
759   * <i>k</i> such that:
760   * <blockquote><pre>
761   * this.startsWith(str, <i>k</i>)
762   * </pre></blockquote>
763   * is <code>true</code>.
764   *
765   * @param   str   any string.
766   * @return  if the string argument occurs as a substring within this
767   *          object, then the index of the first character of the first
768   *          such substring is returned; if it does not occur as a
769   *          substring, <code>-1</code> is returned.
770   * @exception java.lang.NullPointerException if <code>str</code> is
771   *          <code>null</code>.
772   */
773  public int indexOf(XMLString str)
774  {
775    return str().indexOf(str.toString());
776  }
777
778  /**
779   * Returns the index within this string of the first occurrence of the
780   * specified substring, starting at the specified index. The integer
781   * returned is the smallest value <i>k</i> such that:
782   * <blockquote><pre>
783   * this.startsWith(str, <i>k</i>) && (<i>k</i> >= fromIndex)
784   * </pre></blockquote>
785   * is <code>true</code>.
786   * <p>
787   * There is no restriction on the value of <code>fromIndex</code>. If
788   * it is negative, it has the same effect as if it were zero: this entire
789   * string may be searched. If it is greater than the length of this
790   * string, it has the same effect as if it were equal to the length of
791   * this string: <code>-1</code> is returned.
792   *
793   * @param   str         the substring to search for.
794   * @param   fromIndex   the index to start the search from.
795   * @return  If the string argument occurs as a substring within this
796   *          object at a starting index no smaller than
797   *          <code>fromIndex</code>, then the index of the first character
798   *          of the first such substring is returned. If it does not occur
799   *          as a substring starting at <code>fromIndex</code> or beyond,
800   *          <code>-1</code> is returned.
801   * @exception java.lang.NullPointerException if <code>str</code> is
802   *          <code>null</code>
803   */
804  public int indexOf(String str, int fromIndex)
805  {
806    return str().indexOf(str, fromIndex);
807  }
808
809  /**
810   * Returns the index within this string of the rightmost occurrence
811   * of the specified substring.  The rightmost empty string "" is
812   * considered to occur at the index value <code>this.length()</code>.
813   * The returned index is the largest value <i>k</i> such that
814   * <blockquote><pre>
815   * this.startsWith(str, k)
816   * </pre></blockquote>
817   * is true.
818   *
819   * @param   str   the substring to search for.
820   * @return  if the string argument occurs one or more times as a substring
821   *          within this object, then the index of the first character of
822   *          the last such substring is returned. If it does not occur as
823   *          a substring, <code>-1</code> is returned.
824   * @exception java.lang.NullPointerException  if <code>str</code> is
825   *          <code>null</code>.
826   */
827  public int lastIndexOf(String str)
828  {
829    return str().lastIndexOf(str);
830  }
831
832  /**
833   * Returns the index within this string of the last occurrence of
834   * the specified substring.
835   *
836   * @param   str         the substring to search for.
837   * @param   fromIndex   the index to start the search from. There is no
838   *          restriction on the value of fromIndex. If it is greater than
839   *          the length of this string, it has the same effect as if it
840   *          were equal to the length of this string: this entire string
841   *          may be searched. If it is negative, it has the same effect
842   *          as if it were -1: -1 is returned.
843   * @return  If the string argument occurs one or more times as a substring
844   *          within this object at a starting index no greater than
845   *          <code>fromIndex</code>, then the index of the first character of
846   *          the last such substring is returned. If it does not occur as a
847   *          substring starting at <code>fromIndex</code> or earlier,
848   *          <code>-1</code> is returned.
849   * @exception java.lang.NullPointerException if <code>str</code> is
850   *          <code>null</code>.
851   */
852  public int lastIndexOf(String str, int fromIndex)
853  {
854    return str().lastIndexOf(str, fromIndex);
855  }
856
857  /**
858   * Returns a new string that is a substring of this string. The
859   * substring begins with the character at the specified index and
860   * extends to the end of this string. <p>
861   * Examples:
862   * <blockquote><pre>
863   * "unhappy".substring(2) returns "happy"
864   * "Harbison".substring(3) returns "bison"
865   * "emptiness".substring(9) returns "" (an empty string)
866   * </pre></blockquote>
867   *
868   * @param      beginIndex   the beginning index, inclusive.
869   * @return     the specified substring.
870   * @exception  IndexOutOfBoundsException  if
871   *             <code>beginIndex</code> is negative or larger than the
872   *             length of this <code>String</code> object.
873   */
874  public XMLString substring(int beginIndex)
875  {
876    return new XString(str().substring(beginIndex));
877  }
878
879  /**
880   * Returns a new string that is a substring of this string. The
881   * substring begins at the specified <code>beginIndex</code> and
882   * extends to the character at index <code>endIndex - 1</code>.
883   * Thus the length of the substring is <code>endIndex-beginIndex</code>.
884   *
885   * @param      beginIndex   the beginning index, inclusive.
886   * @param      endIndex     the ending index, exclusive.
887   * @return     the specified substring.
888   * @exception  IndexOutOfBoundsException  if the
889   *             <code>beginIndex</code> is negative, or
890   *             <code>endIndex</code> is larger than the length of
891   *             this <code>String</code> object, or
892   *             <code>beginIndex</code> is larger than
893   *             <code>endIndex</code>.
894   */
895  public XMLString substring(int beginIndex, int endIndex)
896  {
897    return new XString(str().substring(beginIndex, endIndex));
898  }
899
900  /**
901   * Concatenates the specified string to the end of this string.
902   *
903   * @param   str   the <code>String</code> that is concatenated to the end
904   *                of this <code>String</code>.
905   * @return  a string that represents the concatenation of this object's
906   *          characters followed by the string argument's characters.
907   * @exception java.lang.NullPointerException if <code>str</code> is
908   *          <code>null</code>.
909   */
910  public XMLString concat(String str)
911  {
912
913    // %REVIEW% Make an FSB here?
914    return new XString(str().concat(str));
915  }
916
917  /**
918   * Converts all of the characters in this <code>String</code> to lower
919   * case using the rules of the given <code>Locale</code>.
920   *
921   * @param locale use the case transformation rules for this locale
922   * @return the String, converted to lowercase.
923   * @see     java.lang.Character#toLowerCase(char)
924   * @see     java.lang.String#toUpperCase(Locale)
925   */
926  public XMLString toLowerCase(Locale locale)
927  {
928    return new XString(str().toLowerCase(locale));
929  }
930
931  /**
932   * Converts all of the characters in this <code>String</code> to lower
933   * case using the rules of the default locale, which is returned
934   * by <code>Locale.getDefault</code>.
935   * <p>
936   *
937   * @return  the string, converted to lowercase.
938   * @see     java.lang.Character#toLowerCase(char)
939   * @see     java.lang.String#toLowerCase(Locale)
940   */
941  public XMLString toLowerCase()
942  {
943    return new XString(str().toLowerCase());
944  }
945
946  /**
947   * Converts all of the characters in this <code>String</code> to upper
948   * case using the rules of the given locale.
949   * @param locale use the case transformation rules for this locale
950   * @return the String, converted to uppercase.
951   * @see     java.lang.Character#toUpperCase(char)
952   * @see     java.lang.String#toLowerCase(Locale)
953   */
954  public XMLString toUpperCase(Locale locale)
955  {
956    return new XString(str().toUpperCase(locale));
957  }
958
959  /**
960   * Converts all of the characters in this <code>String</code> to upper
961   * case using the rules of the default locale, which is returned
962   * by <code>Locale.getDefault</code>.
963   *
964   * <p>
965   * If no character in this string has a different uppercase version,
966   * based on calling the <code>toUpperCase</code> method defined by
967   * <code>Character</code>, then the original string is returned.
968   * <p>
969   * Otherwise, this method creates a new <code>String</code> object
970   * representing a character sequence identical in length to the
971   * character sequence represented by this <code>String</code> object and
972   * with every character equal to the result of applying the method
973   * <code>Character.toUpperCase</code> to the corresponding character of
974   * this <code>String</code> object. <p>
975   * Examples:
976   * <blockquote><pre>
977   * "Fahrvergn&uuml;gen".toUpperCase() returns "FAHRVERGN&Uuml;GEN"
978   * "Visit Ljubinje!".toUpperCase() returns "VISIT LJUBINJE!"
979   * </pre></blockquote>
980   *
981   * @return  the string, converted to uppercase.
982   * @see     java.lang.Character#toUpperCase(char)
983   * @see     java.lang.String#toUpperCase(Locale)
984   */
985  public XMLString toUpperCase()
986  {
987    return new XString(str().toUpperCase());
988  }
989
990  /**
991   * Removes white space from both ends of this string.
992   *
993   * @return  this string, with white space removed from the front and end.
994   */
995  public XMLString trim()
996  {
997    return new XString(str().trim());
998  }
999
1000  /**
1001   * Returns whether the specified <var>ch</var> conforms to the XML 1.0 definition
1002   * of whitespace.  Refer to <A href="http://www.w3.org/TR/1998/REC-xml-19980210#NT-S">
1003   * the definition of <CODE>S</CODE></A> for details.
1004   * @param   ch      Character to check as XML whitespace.
1005   * @return          =true if <var>ch</var> is XML whitespace; otherwise =false.
1006   */
1007  private static boolean isSpace(char ch)
1008  {
1009    return XMLCharacterRecognizer.isWhiteSpace(ch);  // Take the easy way out for now.
1010  }
1011
1012  /**
1013   * Conditionally trim all leading and trailing whitespace in the specified String.
1014   * All strings of white space are
1015   * replaced by a single space character (#x20), except spaces after punctuation which
1016   * receive double spaces if doublePunctuationSpaces is true.
1017   * This function may be useful to a formatter, but to get first class
1018   * results, the formatter should probably do it's own white space handling
1019   * based on the semantics of the formatting object.
1020   *
1021   * @param   trimHead    Trim leading whitespace?
1022   * @param   trimTail    Trim trailing whitespace?
1023   * @param   doublePunctuationSpaces    Use double spaces for punctuation?
1024   * @return              The trimmed string.
1025   */
1026  public XMLString fixWhiteSpace(boolean trimHead, boolean trimTail,
1027                                 boolean doublePunctuationSpaces)
1028  {
1029
1030    // %OPT% !!!!!!!
1031    int len = this.length();
1032    char[] buf = new char[len];
1033
1034    this.getChars(0, len, buf, 0);
1035
1036    boolean edit = false;
1037    int s;
1038
1039    for (s = 0; s < len; s++)
1040    {
1041      if (isSpace(buf[s]))
1042      {
1043        break;
1044      }
1045    }
1046
1047    /* replace S to ' '. and ' '+ -> single ' '. */
1048    int d = s;
1049    boolean pres = false;
1050
1051    for (; s < len; s++)
1052    {
1053      char c = buf[s];
1054
1055      if (isSpace(c))
1056      {
1057        if (!pres)
1058        {
1059          if (' ' != c)
1060          {
1061            edit = true;
1062          }
1063
1064          buf[d++] = ' ';
1065
1066          if (doublePunctuationSpaces && (s != 0))
1067          {
1068            char prevChar = buf[s - 1];
1069
1070            if (!((prevChar == '.') || (prevChar == '!')
1071                  || (prevChar == '?')))
1072            {
1073              pres = true;
1074            }
1075          }
1076          else
1077          {
1078            pres = true;
1079          }
1080        }
1081        else
1082        {
1083          edit = true;
1084          pres = true;
1085        }
1086      }
1087      else
1088      {
1089        buf[d++] = c;
1090        pres = false;
1091      }
1092    }
1093
1094    if (trimTail && 1 <= d && ' ' == buf[d - 1])
1095    {
1096      edit = true;
1097
1098      d--;
1099    }
1100
1101    int start = 0;
1102
1103    if (trimHead && 0 < d && ' ' == buf[0])
1104    {
1105      edit = true;
1106
1107      start++;
1108    }
1109
1110    XMLStringFactory xsf = XMLStringFactoryImpl.getFactory();
1111
1112    return edit ? xsf.newstr(new String(buf, start, d - start)) : this;
1113  }
1114
1115  /**
1116   * @see com.sun.org.apache.xpath.internal.XPathVisitable#callVisitors(ExpressionOwner, XPathVisitor)
1117   */
1118  public void callVisitors(ExpressionOwner owner, XPathVisitor visitor)
1119  {
1120        visitor.visitStringLiteral(owner, this);
1121  }
1122
1123}
1124