/* * Copyright 1999-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: XStringForFSB.java,v 1.20 2004/02/17 04:34:38 minchau Exp $ */ package com.sun.org.apache.xpath.internal.objects; import com.sun.org.apache.xalan.internal.res.XSLMessages; import com.sun.org.apache.xml.internal.utils.FastStringBuffer; import com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer; import com.sun.org.apache.xml.internal.utils.XMLString; import com.sun.org.apache.xml.internal.utils.XMLStringFactory; import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; /** * This class will wrap a FastStringBuffer and allow for */ public class XStringForFSB extends XString { /** The start position in the fsb. */ int m_start; /** The length of the string. */ int m_length; /** If the str() function is called, the string will be cached here. */ protected String m_strCache = null; /** cached hash code */ protected int m_hash = 0; /** * Construct a XNodeSet object. * * @param val FastStringBuffer object this will wrap, must be non-null. * @param start The start position in the array. * @param length The number of characters to read from the array. */ public XStringForFSB(FastStringBuffer val, int start, int length) { super(val); m_start = start; m_length = length; if (null == val) throw new IllegalArgumentException( XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FASTSTRINGBUFFER_CANNOT_BE_NULL, null)); } /** * Construct a XNodeSet object. * * @param val String object this will wrap. */ private XStringForFSB(String val) { super(val); throw new IllegalArgumentException( XSLMessages.createXPATHMessage(XPATHErrorResources.ER_FSB_CANNOT_TAKE_STRING, null)); // "XStringForFSB can not take a string for an argument!"); } /** * Cast result object to a string. * * @return The string this wraps or the empty string if null */ public FastStringBuffer fsb() { return ((FastStringBuffer) m_obj); } /** * Cast result object to a string. * * @return The string this wraps or the empty string if null */ public void appendToFsb(com.sun.org.apache.xml.internal.utils.FastStringBuffer fsb) { // %OPT% !!! FSB has to be updated to take partial fsb's for append. fsb.append(str()); } /** * Tell if this object contains a java String object. * * @return true if this XMLString can return a string without creating one. */ public boolean hasString() { return (null != m_strCache); } // /** NEEDSDOC Field strCount */ // public static int strCount = 0; // // /** NEEDSDOC Field xtable */ // static java.util.Hashtable xtable = new java.util.Hashtable(); /** * Since this object is incomplete without the length and the offset, we * have to convert to a string when this function is called. * * @return The java String representation of this object. */ public Object object() { return str(); } /** * Cast result object to a string. * * @return The string this wraps or the empty string if null */ public String str() { if (null == m_strCache) { m_strCache = fsb().getString(m_start, m_length); // strCount++; // // RuntimeException e = new RuntimeException("Bad! Bad!"); // java.io.CharArrayWriter writer = new java.io.CharArrayWriter(); // java.io.PrintWriter pw = new java.io.PrintWriter(writer); // // e.printStackTrace(pw); // // String str = writer.toString(); // // str = str.substring(0, 600); // // if (null == xtable.get(str)) // { // xtable.put(str, str); // System.out.println(str); // } // System.out.println("strCount: " + strCount); // throw e; // e.printStackTrace(); // System.exit(-1); } return m_strCache; } /** * Directly call the * characters method on the passed ContentHandler for the * string-value. Multiple calls to the * ContentHandler's characters methods may well occur for a single call to * this method. * * @param ch A non-null reference to a ContentHandler. * * @throws org.xml.sax.SAXException */ public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch) throws org.xml.sax.SAXException { fsb().sendSAXcharacters(ch, m_start, m_length); } /** * Directly call the * comment method on the passed LexicalHandler for the * string-value. * * @param lh A non-null reference to a LexicalHandler. * * @throws org.xml.sax.SAXException */ public void dispatchAsComment(org.xml.sax.ext.LexicalHandler lh) throws org.xml.sax.SAXException { fsb().sendSAXComment(lh, m_start, m_length); } /** * Returns the length of this string. * * @return the length of the sequence of characters represented by this * object. */ public int length() { return m_length; } /** * Returns the character at the specified index. An index ranges * from 0 to length() - 1. The first character * of the sequence is at index 0, the next at index * 1, and so on, as for array indexing. * * @param index the index of the character. * @return the character at the specified index of this string. * The first character is at index 0. * @exception IndexOutOfBoundsException if the index * argument is negative or not less than the length of this * string. */ public char charAt(int index) { return fsb().charAt(m_start + index); } /** * Copies characters from this string into the destination character * array. * * @param srcBegin index of the first character in the string * to copy. * @param srcEnd index after the last character in the string * to copy. * @param dst the destination array. * @param dstBegin the start offset in the destination array. * @exception IndexOutOfBoundsException If any of the following * is true: * * @exception NullPointerException if dst is null */ public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { // %OPT% Need to call this on FSB when it is implemented. // %UNTESTED% (I don't think anyone calls this yet?) int n = srcEnd - srcBegin; if (n > m_length) n = m_length; if (n > (dst.length - dstBegin)) n = (dst.length - dstBegin); int end = srcBegin + m_start + n; int d = dstBegin; FastStringBuffer fsb = fsb(); for (int i = srcBegin + m_start; i < end; i++) { dst[d++] = fsb.charAt(i); } } /** * Compares this string to the specified object. * The result is true if and only if the argument is not * null and is a String object that represents * the same sequence of characters as this object. * * @param anObject the object to compare this String * against. * * NEEDSDOC @param obj2 * @return true if the String are equal; * false otherwise. * @see java.lang.String#compareTo(java.lang.String) * @see java.lang.String#equalsIgnoreCase(java.lang.String) */ public boolean equals(XMLString obj2) { if (this == obj2) { return true; } int n = m_length; if (n == obj2.length()) { FastStringBuffer fsb = fsb(); int i = m_start; int j = 0; while (n-- != 0) { if (fsb.charAt(i) != obj2.charAt(j)) { return false; } i++; j++; } return true; } return false; } /** * Tell if two objects are functionally equal. * * @param obj2 Object to compare this to * * @return true if the two objects are equal * * @throws javax.xml.transform.TransformerException */ public boolean equals(XObject obj2) { if (this == obj2) { return true; } if(obj2.getType() == XObject.CLASS_NUMBER) return obj2.equals(this); String str = obj2.str(); int n = m_length; if (n == str.length()) { FastStringBuffer fsb = fsb(); int i = m_start; int j = 0; while (n-- != 0) { if (fsb.charAt(i) != str.charAt(j)) { return false; } i++; j++; } return true; } return false; } /** * Tell if two objects are functionally equal. * * @param obj2 Object to compare this to * * NEEDSDOC @param anotherString * * @return true if the two objects are equal * * @throws javax.xml.transform.TransformerException */ public boolean equals(String anotherString) { int n = m_length; if (n == anotherString.length()) { FastStringBuffer fsb = fsb(); int i = m_start; int j = 0; while (n-- != 0) { if (fsb.charAt(i) != anotherString.charAt(j)) { return false; } i++; j++; } return true; } return false; } /** * Compares this string to the specified object. * The result is true if and only if the argument is not * null and is a String object that represents * the same sequence of characters as this object. * * @param anObject the object to compare this String * against. * * NEEDSDOC @param obj2 * @return true if the String are equal; * false otherwise. * @see java.lang.String#compareTo(java.lang.String) * @see java.lang.String#equalsIgnoreCase(java.lang.String) */ public boolean equals(Object obj2) { if (null == obj2) return false; if(obj2 instanceof XNumber) return obj2.equals(this); // In order to handle the 'all' semantics of // nodeset comparisons, we always call the // nodeset function. else if (obj2 instanceof XNodeSet) return obj2.equals(this); else if (obj2 instanceof XStringForFSB) return equals((XMLString) this); else return equals(obj2.toString()); } /** * Compares this String to another String, * ignoring case considerations. Two strings are considered equal * ignoring case if they are of the same length, and corresponding * characters in the two strings are equal ignoring case. * * @param anotherString the String to compare this * String against. * @return true if the argument is not null * and the Strings are equal, * ignoring case; false otherwise. * @see #equals(Object) * @see java.lang.Character#toLowerCase(char) * @see java.lang.Character#toUpperCase(char) */ public boolean equalsIgnoreCase(String anotherString) { return (m_length == anotherString.length()) ? str().equalsIgnoreCase(anotherString) : false; } /** * Compares two strings lexicographically. * * @param anotherString the String to be compared. * * NEEDSDOC @param xstr * @return the value 0 if the argument string is equal to * this string; a value less than 0 if this string * is lexicographically less than the string argument; and a * value greater than 0 if this string is * lexicographically greater than the string argument. * @exception java.lang.NullPointerException if anotherString * is null. */ public int compareTo(XMLString xstr) { int len1 = m_length; int len2 = xstr.length(); int n = Math.min(len1, len2); FastStringBuffer fsb = fsb(); int i = m_start; int j = 0; while (n-- != 0) { char c1 = fsb.charAt(i); char c2 = xstr.charAt(j); if (c1 != c2) { return c1 - c2; } i++; j++; } return len1 - len2; } /** * Compares two strings lexicographically, ignoring case considerations. * This method returns an integer whose sign is that of * this.toUpperCase().toLowerCase().compareTo( * str.toUpperCase().toLowerCase()). *

* Note that this method does not take locale into account, * and will result in an unsatisfactory ordering for certain locales. * The java.text package provides collators to allow * locale-sensitive ordering. * * @param str the String to be compared. * * NEEDSDOC @param xstr * @return a negative integer, zero, or a positive integer as the * the specified String is greater than, equal to, or less * than this String, ignoring case considerations. * @see java.text.Collator#compare(String, String) * @since 1.2 */ public int compareToIgnoreCase(XMLString xstr) { int len1 = m_length; int len2 = xstr.length(); int n = Math.min(len1, len2); FastStringBuffer fsb = fsb(); int i = m_start; int j = 0; while (n-- != 0) { char c1 = Character.toLowerCase(fsb.charAt(i)); char c2 = Character.toLowerCase(xstr.charAt(j)); if (c1 != c2) { return c1 - c2; } i++; j++; } return len1 - len2; } /** * Returns a hashcode for this string. The hashcode for a * String object is computed as *

   * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
   * 
* using int arithmetic, where s[i] is the * ith character of the string, n is the length of * the string, and ^ indicates exponentiation. * (The hash value of the empty string is zero.) * * @return a hash code value for this object. */ public int hashCode() { // Commenting this out because in JDK1.1.8 and VJ++ // we don't match XMLStrings. Defaulting to the super // causes us to create a string, but at this point // this only seems to get called in key processing. // Maybe we can live with it? /* int h = m_hash; if (h == 0) { int off = m_start; int len = m_length; FastStringBuffer fsb = fsb(); for (int i = 0; i < len; i++) { h = 31 * h + fsb.charAt(off); off++; } m_hash = h; } */ return super.hashCode(); // h; } /** * Tests if this string starts with the specified prefix beginning * a specified index. * * @param prefix the prefix. * @param toffset where to begin looking in the string. * @return true if the character sequence represented by the * argument is a prefix of the substring of this object starting * at index toffset; false otherwise. * The result is false if toffset is * negative or greater than the length of this * String object; otherwise the result is the same * as the result of the expression *
   *          this.subString(toffset).startsWith(prefix)
   *          
* @exception java.lang.NullPointerException if prefix is * null. */ public boolean startsWith(XMLString prefix, int toffset) { FastStringBuffer fsb = fsb(); int to = m_start + toffset; int tlim = m_start + m_length; int po = 0; int pc = prefix.length(); // Note: toffset might be near -1>>>1. if ((toffset < 0) || (toffset > m_length - pc)) { return false; } while (--pc >= 0) { if (fsb.charAt(to) != prefix.charAt(po)) { return false; } to++; po++; } return true; } /** * Tests if this string starts with the specified prefix. * * @param prefix the prefix. * @return true if the character sequence represented by the * argument is a prefix of the character sequence represented by * this string; false otherwise. * Note also that true will be returned if the * argument is an empty string or is equal to this * String object as determined by the * {@link #equals(Object)} method. * @exception java.lang.NullPointerException if prefix is * null. * @since JDK1. 0 */ public boolean startsWith(XMLString prefix) { return startsWith(prefix, 0); } /** * Returns the index within this string of the first occurrence of the * specified character. If a character with value ch occurs * in the character sequence represented by this String * object, then the index of the first such occurrence is returned -- * that is, the smallest value k such that: *
   * this.charAt(k) == ch
   * 
* is true. If no such character occurs in this string, * then -1 is returned. * * @param ch a character. * @return the index of the first occurrence of the character in the * character sequence represented by this object, or * -1 if the character does not occur. */ public int indexOf(int ch) { return indexOf(ch, 0); } /** * Returns the index within this string of the first occurrence of the * specified character, starting the search at the specified index. *

* If a character with value ch occurs in the character * sequence represented by this String object at an index * no smaller than fromIndex, then the index of the first * such occurrence is returned--that is, the smallest value k * such that: *

   * (this.charAt(k) == ch) && (k >= fromIndex)
   * 
* is true. If no such character occurs in this string at or after * position fromIndex, then -1 is returned. *

* There is no restriction on the value of fromIndex. If it * is negative, it has the same effect as if it were zero: this entire * string may be searched. If it is greater than the length of this * string, it has the same effect as if it were equal to the length of * this string: -1 is returned. * * @param ch a character. * @param fromIndex the index to start the search from. * @return the index of the first occurrence of the character in the * character sequence represented by this object that is greater * than or equal to fromIndex, or -1 * if the character does not occur. */ public int indexOf(int ch, int fromIndex) { int max = m_start + m_length; FastStringBuffer fsb = fsb(); if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= m_length) { // Note: fromIndex might be near -1>>>1. return -1; } for (int i = m_start + fromIndex; i < max; i++) { if (fsb.charAt(i) == ch) { return i - m_start; } } return -1; } /** * Returns a new string that is a substring of this string. The * substring begins with the character at the specified index and * extends to the end of this string.

* Examples: *

   * "unhappy".substring(2) returns "happy"
   * "Harbison".substring(3) returns "bison"
   * "emptiness".substring(9) returns "" (an empty string)
   * 
* * @param beginIndex the beginning index, inclusive. * @return the specified substring. * @exception IndexOutOfBoundsException if * beginIndex is negative or larger than the * length of this String object. */ public XMLString substring(int beginIndex) { int len = m_length - beginIndex; if (len <= 0) return XString.EMPTYSTRING; else { int start = m_start + beginIndex; return new XStringForFSB(fsb(), start, len); } } /** * Returns a new string that is a substring of this string. The * substring begins at the specified beginIndex and * extends to the character at index endIndex - 1. * Thus the length of the substring is endIndex-beginIndex. * * @param beginIndex the beginning index, inclusive. * @param endIndex the ending index, exclusive. * @return the specified substring. * @exception IndexOutOfBoundsException if the * beginIndex is negative, or * endIndex is larger than the length of * this String object, or * beginIndex is larger than * endIndex. */ public XMLString substring(int beginIndex, int endIndex) { int len = endIndex - beginIndex; if (len > m_length) len = m_length; if (len <= 0) return XString.EMPTYSTRING; else { int start = m_start + beginIndex; return new XStringForFSB(fsb(), start, len); } } /** * Concatenates the specified string to the end of this string. * * @param str the String that is concatenated to the end * of this String. * @return a string that represents the concatenation of this object's * characters followed by the string argument's characters. * @exception java.lang.NullPointerException if str is * null. */ public XMLString concat(String str) { // %OPT% Make an FSB here? return new XString(str().concat(str)); } /** * Removes white space from both ends of this string. * * @return this string, with white space removed from the front and end. */ public XMLString trim() { return fixWhiteSpace(true, true, false); } /** * Returns whether the specified ch conforms to the XML 1.0 definition * of whitespace. Refer to * the definition of S for details. * @param ch Character to check as XML whitespace. * @return =true if ch is XML whitespace; otherwise =false. */ private static boolean isSpace(char ch) { return XMLCharacterRecognizer.isWhiteSpace(ch); // Take the easy way out for now. } /** * Conditionally trim all leading and trailing whitespace in the specified String. * All strings of white space are * replaced by a single space character (#x20), except spaces after punctuation which * receive double spaces if doublePunctuationSpaces is true. * This function may be useful to a formatter, but to get first class * results, the formatter should probably do it's own white space handling * based on the semantics of the formatting object. * * @param trimHead Trim leading whitespace? * @param trimTail Trim trailing whitespace? * @param doublePunctuationSpaces Use double spaces for punctuation? * @return The trimmed string. */ public XMLString fixWhiteSpace(boolean trimHead, boolean trimTail, boolean doublePunctuationSpaces) { int end = m_length + m_start; char[] buf = new char[m_length]; FastStringBuffer fsb = fsb(); boolean edit = false; /* replace S to ' '. and ' '+ -> single ' '. */ int d = 0; boolean pres = false; for (int s = m_start; s < end; s++) { char c = fsb.charAt(s); if (isSpace(c)) { if (!pres) { if (' ' != c) { edit = true; } buf[d++] = ' '; if (doublePunctuationSpaces && (d != 0)) { char prevChar = buf[d - 1]; if (!((prevChar == '.') || (prevChar == '!') || (prevChar == '?'))) { pres = true; } } else { pres = true; } } else { edit = true; pres = true; } } else { buf[d++] = c; pres = false; } } if (trimTail && 1 <= d && ' ' == buf[d - 1]) { edit = true; d--; } int start = 0; if (trimHead && 0 < d && ' ' == buf[0]) { edit = true; start++; } XMLStringFactory xsf = XMLStringFactoryImpl.getFactory(); return edit ? xsf.newstr(buf, start, d - start) : this; } /** * Convert a string to a double -- Allowed input is in fixed * notation ddd.fff. * * %OPT% CHECK PERFORMANCE against generating a Java String and * converting it to double. The advantage of running in native * machine code -- perhaps even microcode, on some systems -- may * more than make up for the cost of allocating and discarding the * additional object. We need to benchmark this. * * %OPT% More importantly, we need to decide whether we _care_ about * the performance of this operation. Does XString.toDouble constitute * any measurable percentage of our typical runtime? I suspect not! * * @return A double value representation of the string, or return Double.NaN * if the string can not be converted. */ public double toDouble() { if(m_length == 0) return Double.NaN; int i; char c; String valueString = fsb().getString(m_start,m_length); // The following are permitted in the Double.valueOf, but not by the XPath spec: // - a plus sign // - The use of e or E to indicate exponents // - trailing f, F, d, or D // See function comments; not sure if this is slower than actually doing the // conversion ourselves (as was before). for (i=0;i '9')) break; } for (;i