/*
* 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:
*
srcBegin
is negative.
* srcBegin
is greater than srcEnd
* srcEnd
is greater than the length of this
* string
* dstBegin
is negative
* dstBegin+(srcEnd-srcBegin)
is larger than
* dst.length
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 String
s 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
*
* using* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] *
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:
* * is* this.charAt(k) == ch *
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:
*
* is true. If no such character occurs in this string at or after * position* (this.charAt(k) == ch) && (k >= fromIndex) *
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: *
* * @param beginIndex the beginning index, inclusive. * @return the specified substring. * @exception IndexOutOfBoundsException if ** "unhappy".substring(2) returns "happy" * "Harbison".substring(3) returns "bison" * "emptiness".substring(9) returns "" (an empty string) *
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