/* String.java -- immutable character sequences; the object of string literals
Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package java.lang;
import java.io.UnsupportedEncodingException;
import com.jopdesign.sys.GC;
import annotation.ScopeSafe;
public final class String implements CharSequence {
private static final int MAX_STRING_SIZE = 100;
/**
* Characters which make up the String. Package access is granted for use by
* StringBuffer.
*/
final char[] value;
/** The count is the number of characters in the String. */
final int count;
/**
* Creates an empty String (length 0). Unless you really need a new object,
* consider using <code>""</code> instead. CLCD 1.0
*/
public String() {
this.count = 0;
this.value = new char[MAX_STRING_SIZE];
// value = "".value;
}
/**
* Creates a new String using the byte array. Uses the encoding of the
* platform's default charset, so the resulting string may be longer or
* shorter than the byte array. For more decoding control, use
* {@link java.nio.charset.CharsetDecoder}. The behavior is not specified
* if the decoder encounters invalid characters; this implementation throws
* an Error.
*
* @param data
* byte array to copy
* @throws NullPointerException
* if data is null
* @throws UnsupportedEncodingException
* @throws Error
* if the decoding fails
* @see #String(byte[], int, int)
* @see #String(byte[], int, int, String)
* @since 1.1
*/
public String(byte[] data) throws NullPointerException {
this(data, 0, data.length);
}
/**
* Creates a new String using the portion of the byte array starting at the
* offset and ending at offset + count. Uses the encoding of the platform's
* default charset, so the resulting string may be longer or shorter than
* the byte array. For more decoding control, use
* {@link java.nio.charset.CharsetDecoder}. The behavior is not specified
* if the decoder encounters invalid characters; this implementation throws
* an Error.
*
* @param data
* byte array to copy
* @param offset
* the offset to start at
* @param count
* the number of bytes in the array to use
* @throws
* @throws NullPointerException
* if data is null
* @throws IndexOutOfBoundsException
* if offset or count is incorrect
* @throws Error
* if the decoding fails
* @see #String(byte[], int, int, String)
* @since 1.1
*/
public String(byte[] data, int offset, int count) {
if (data.length - offset < count || data.length > MAX_STRING_SIZE)
throw new StringIndexOutOfBoundsException();
value = new char[MAX_STRING_SIZE];
for (int i = 0; i < count; i++) {// @WCA loop=MAX_STRING_SIZE
this.value[i] = (char) data[i + offset];
}
this.count = count;
}
/**
* Creates a new String using the portion of the byte array starting at the
* offset and ending at offset + count. Uses the specified encoding type to
* decode the byte array, so the resulting string may be longer or shorter
* than the byte array. For more decoding control, use
* {@link java.nio.charset.CharsetDecoder}, and for valid character sets,
* see {@link java.nio.charset.Charset}. The behavior is not specified if
* the decoder encounters invalid characters; this implementation throws an
* Error.
*
* @param data
* byte array to copy
* @param offset
* the offset to start at
* @param count
* the number of bytes in the array to use
* @param encoding
* the name of the encoding to use
* @throws NullPointerException
* if data or encoding is null
* @throws IndexOutOfBoundsException
* if offset or count is incorrect (while unspecified, this is a
* StringIndexOutOfBoundsException)
* @throws UnsupportedEncodingException
* if encoding is not found
* @throws Error
* if the decoding fails
* @since 1.1
*/
public String(byte[] data, int offset, int count, String encoding)
throws UnsupportedEncodingException {
encoding = encoding.toUpperCase();
if (!encoding.equals("ASCII"))
throw new UnsupportedEncodingException();
if (offset < 0)
throw new StringIndexOutOfBoundsException();
if (count < 0)
throw new StringIndexOutOfBoundsException();
// equivalent to: offset + count < 0 || offset + count > data.length
if (data.length - offset < count || data.length > MAX_STRING_SIZE)
throw new StringIndexOutOfBoundsException();
value = new char[MAX_STRING_SIZE];
for (int i = 0; i < count; i++) {// @WCA loop=MAX_STRING_SIZE
this.value[i] = (char) data[i + offset];
}
this.count = count;
}
/**
* Creates a new String using the byte array. Uses the specified encoding
* type to decode the byte array, so the resulting string may be longer or
* shorter than the byte array. For more decoding control, use
* {@link java.nio.charset.CharsetDecoder}, and for valid character sets,
* see {@link java.nio.charset.Charset}. The behavior is not specified if
* the decoder encounters invalid characters; this implementation throws an
* Error.
*
* @param data
* byte array to copy
* @param encoding
* the name of the encoding to use
* @throws NullPointerException
* if data or encoding is null
* @throws UnsupportedEncodingException
* if encoding is not found
* @throws Error
* if the decoding fails
* @see #String(byte[], int, int, String)
* @since 1.1
*/
public String(byte[] data, String encoding)
throws UnsupportedEncodingException {
this(data, 0, data.length, encoding);
}
/**
* Creates a new String using the character sequence of the char array.
* Subsequent changes to data do not affect the String.
*
* @param data
* char array to copy
* @throws NullPointerException
* if data is null
*/
// public String(char[] data) {
// this(data, 0, data.length);
// }
public String(char[] data) {
int count = data.length;
value = new char[MAX_STRING_SIZE];
// value = new char[count];
for (int i = 0; i < count; ++i){
value[i] = data[i];
}
this.count = count;
}
/**
* Creates a new String using the character sequence of a subarray of
* characters. The string starts at offset, and copies count chars.
* Subsequent changes to data do not affect the String.
*
* @param data
* char array to copy
* @param offset
* position (base 0) to start copying out of data
* @param count
* the number of characters from data to copy
* @throws NullPointerException
* if data is null
* @throws IndexOutOfBoundsException
* if (offset < 0 || count < 0 || offset + count < 0
* (overflow) || offset + count > data.length) (while
* unspecified, this is a StringIndexOutOfBoundsException)
*/
public String(char[] data, int offset, int count)
throws IndexOutOfBoundsException {
if (offset < 0)
throw new StringIndexOutOfBoundsException();
if (count < 0)
throw new StringIndexOutOfBoundsException();
// equivalent to: offset + count < 0 || offset + count > data.length
if (data.length - offset < count)
throw new StringIndexOutOfBoundsException();
// value = new char[count];
// VMSystem.arraycopy(data, offset, value, 0, count);
// System.out.println("dbg: String constructor");
// TODO: System.arraycopy produces stack overflow
value = new char[MAX_STRING_SIZE];
for (int i = 0; i < count; i++) {// @WCA loop=MAX_STRING_SIZE
value[i] = data[i + offset];
}
this.count = count;
// System.arraycopy(data, offset, value, 0, count);
}
/**
* Copies the contents of a String to a new String. Since Strings are
* immutable, only a shallow copy is performed.
*
* @param str
* String to copy
* @throws NullPointerException
* if value is null CLDC 1.0
*/
public String(String str) {
value = str.value;
//TODO
this.count = str.count;
}
/**
* Creates a new String using the character sequence represented by the
* StringBuffer. Subsequent changes to buf do not affect the String.
*
* @param buffer
* StringBuffer to copy
* @throws NullPointerException
* if buffer is null
*/
public String(StringBuffer buffer) throws NullPointerException {
synchronized (buffer) {
int count = buffer.length();
// value = new char[count];
// VMSystem.arraycopy(buffer.value, 0, value, 0, count);
// System.arraycopy(buffer.value, 0, value, 0, count);
value = new char[MAX_STRING_SIZE];
for (int i = 0; i < count; i++)// @WCA loop = MAX_STRING_SIZE
value[i] = buffer.value[i];
this.count = count;
}
}
/**
* Private constructor to avoid temporary arrays
*
* @param size
* The number of characters used in this new strig
*/
private String(int size) {
value = new char[MAX_STRING_SIZE];
count = size;
}
/*
* final char[] value;
*
* public String() { value = "".value; }
*
* public String(String str) { value = str.value; }
*
* public String(char[] ca) { int count = ca.length; value = new
* char[count]; for (int i=0; i<count; ++i) value[i] = ca[i]; } public
* String(StringBuffer str) { int count = str.length(); value = new
* char[count]; for (int i=0; i<count; ++i) value[i] = str.value[i]; }
*/
@ScopeSafe
public char charAt(int index) {
if (index < 0 || index >= this.length())
throw new StringIndexOutOfBoundsException(index);
return value[index];
}
@ScopeSafe
public int compareTo(String anotherString) {
if (anotherString == null)
throw new NullPointerException();
int i = (this.length() < anotherString.length()) ? this.length()
: anotherString.length();
int j1 = 0;
int j2 = 0;
while (--i >= 0) {//@WCA loop=MAX_STRING_SIZE
int result = value[j1++] - anotherString.value[j2++];
if (result != 0)
return result;
}
return this.length() - anotherString.length();
}
public String concat(String str) {
if (str == null)
throw new NullPointerException();
if (str.value.length == 0)
return this;
if (value.length == 0)
return str;
/* Additional test needed if String size is limited */
int newSize = this.length() + str.length();
if (newSize > MAX_STRING_SIZE)
throw new StringIndexOutOfBoundsException(newSize);
String s = new String(newSize);
// TODO: inefficient
for (int i = 0; i < this.length(); i++) {// @WCA loop = MAX_STRING_SIZE
s.value[i] = value[i];
}
for (int i2 = 0; i2 < str.length(); i2++) {// @WCA loop = MAX_STRING_SIZE
s.value[i2 + this.length()] = str.value[i2];
}
return s;
}
@ScopeSafe
public boolean endsWith(String suffix) {
if (suffix == null)
throw new NullPointerException();
return regionMatches(false, this.length() - suffix.length(), suffix, 0,
suffix.length());
}
@ScopeSafe
public boolean regionMatches(boolean ignoreCase, int toffset, String other,
int ooffset, int len) {
if (toffset < 0 || ooffset < 0 || toffset + len > this.length()
|| ooffset + len > other.length())
return false;
while (--len >= 0) {// @WCA loop=MAX_STRING_SIZE
char c1 = value[toffset++];
char c2 = other.value[ooffset++];
// Note that checking c1 != c2 is redundant when ignoreCase is true,
// but it avoids method calls.
if (c1 != c2
&& (!ignoreCase || (Character.toLowerCase(c1) != Character
.toLowerCase(c2) && (Character.toUpperCase(c1) != Character
.toUpperCase(c2)))))
return false;
}
return true;
}
@ScopeSafe
public boolean equals(Object anObject) {
if (!(anObject instanceof String))
return false;
String str2 = (String) anObject;
if (value.length != str2.value.length)
return false;
if (value == str2.value)
return true;
int i = value.length;
int x = 0;
int y = 0;
while (--i >= 0)
if (value[x++] != str2.value[y++])
return false;
return true;
}
public byte[] getBytes() {
// XXX - Throw an error here?
// For now, default to the 'safe' encoding.
byte[] bytes = new byte[value.length];
for (int i = 0; i < this.length(); i++) {// @WCA loop = MAX_STRING_SIZE
if (value[i] <= 0xFF) {
bytes[i] = (byte) value[i];
} else {
bytes[i] = (byte) '?';
}
}
return bytes;
}
public byte[] getBytes(String enc) throws UnsupportedEncodingException {
enc = enc.toUpperCase();
if (!enc.equals("ASCII"))
throw new UnsupportedEncodingException(
"at String.getBytes(String encoding)");
return this.getBytes();
}
@ScopeSafe
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0 || srcBegin > srcEnd || srcEnd > value.length)
throw new StringIndexOutOfBoundsException();
/*
* if (srcBegin < 0 || srcBegin > srcEnd || srcEnd > value.length)
* return; // wait for array bound exception!
*/
/*
* System.arraycopy(value, srcBegin + offset, dst, dstBegin, srcEnd -
* srcBegin);
*/
for (int i = 0; i < srcEnd - srcBegin; ++i)
dst[dstBegin + i] = value[srcBegin + i];
}
@ScopeSafe
public int hashCode() {
// Compute the hash code using a local variable to be reentrant.
int hashCode = 0;
int limit = value.length;
for (int i = 0; i < limit; i++)
hashCode = hashCode * 31 + value[i];
return hashCode;
}
@ScopeSafe
public int indexOf(int ch) {
return indexOf(ch, 0);
}
@ScopeSafe
public int indexOf(int ch, int fromIndex) {
if ((char) ch != ch)
return -1;
if (fromIndex < 0)
fromIndex = 0;
int i = fromIndex;
for (; fromIndex < this.length(); fromIndex++){//@WCA loop=MAX_STRING_SIZE
if (value[i++] == ch)
return fromIndex;
}
return -1;
}
@ScopeSafe
public int indexOf(String str) {
return indexOf(str, 0);
}
//
// for CoffeinMarkEmbedded
//
@ScopeSafe
public int indexOf(String str, int fromIndex) {
if (fromIndex < 0)
fromIndex = 0;
int limit = value.length - str.value.length;
for (; fromIndex <= limit; fromIndex++)
if (regionMatches(fromIndex, str, 0, str.value.length))
return fromIndex;
return -1;
}
@ScopeSafe
public int lastIndexOf(int ch) {
return lastIndexOf(ch, value.length - 1);
}
@ScopeSafe
public int lastIndexOf(int ch, int fromIndex) {
if ((char) ch != ch)
return -1;
if (fromIndex >= value.length)
fromIndex = value.length - 1;
int i = fromIndex;
for (; fromIndex >= 0; fromIndex--)
if (value[i--] == ch)
return fromIndex;
return -1;
}
@ScopeSafe
public int length() {
// TODO: Strings known at compile time are treated differently in JOP
if (value.length < MAX_STRING_SIZE) {
return value.length;
} else {
return count;
}
}
public String replace(char oldChar, char newChar) {
if (oldChar == newChar)
return this;
int i = this.length();
int x = -1;
while (--i >= 0){// @WCA loop=MAX_STRING_SIZE
if (value[++x] == oldChar)
break;
}
if (i < 0)
return this;
/* Private constructor avoids temporary array */
String s = new String(this.length());
//TODO: System.arraycopy crashes
//System.arraycopy(value, 0, newStr, 0, value.length);
for (int i2 = 0; i2 < this.length(); i2++) {// @WCA loop=MAX_STRING_SIZE
s.value[i2] = value[i2];
}
s.value[x] = newChar;
while (--i >= 0){// @WCA loop=MAX_STRING_SIZE
if (value[++x] == oldChar){
s.value[x] = newChar;
}
}
return s;
}
@ScopeSafe
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
if (toffset < 0 || ooffset < 0 || toffset + len > value.length
|| ooffset + len > other.value.length)
return false;
while (--len >= 0) {
char c1 = value[toffset++];
char c2 = other.value[ooffset++];
if (c1 != c2)
return false;
}
return true;
}
@ScopeSafe
public boolean startsWith(String prefix, int toffset) {
return regionMatches(false, toffset, prefix, 0, prefix.value.length);
}
@ScopeSafe
public boolean startsWith(String prefix) {
return regionMatches(false, 0, prefix, 0, prefix.value.length);
}
/**
* Creates a substring of this String, starting at a specified index
* and ending at one character before a specified index. This behaves like
* <code>substring(begin, end)</code>.
*
* @param begin index to start substring (inclusive, base 0)
* @param end index to end at (exclusive)
* @return new String which is a substring of this String
* @throws IndexOutOfBoundsException if begin < 0 || end > length()
* || begin > end
* @since 1.4
*/
public CharSequence subSequence(int begin, int end) {
return substring(begin, end);
}
public String substring(int begin) {
return substring(begin, value.length);
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > this.length() || beginIndex > endIndex)
throw new StringIndexOutOfBoundsException();
if (beginIndex == 0 && endIndex == this.length())
return this;
int len = endIndex - beginIndex;
// Package constructor avoids an array copy.
return new String(value, beginIndex, len);
}
public char[] toCharArray() {
char[] copy = new char[this.length()];
// VMSystem.arraycopy(value, offset, copy, 0, count);
//System.arraycopy(value, 0, copy, 0, value.length);
for (int i = 0; i < this.length(); i++) {// @WCA loop=MAX_STRING_SIZE
copy[i] = value[i];
}
return copy;
}
public String toLowerCase() {
/* Private constructor avoids temporary array */
String s = new String(this.length());
for (int i = 0; i < this.length(); i++) {// @WCA loop=MAX_STRING_SIZE
s.value[i] = Character.toLowerCase(this.value[i]);
}
return s;
}
public String toUpperCase() {
/* Private constructor avoids temporary array */
String s = new String(this.length());
for (int i = 0; i < this.length(); i++) {// @WCA loop=MAX_STRING_SIZE
s.value[i] = Character.toUpperCase(this.value[i]);
}
return s;
}
public String toString() {
return this;
}
public String trim() {
int limit = this.length();
if (this.length() == 0
|| (value[0] > '\u0020' && value[limit - 1] > '\u0020'))
return this;
int begin = 0;
do
if (begin == limit)// @WCA loop=MAX_STRING_SIZE
return "";
while (value[begin++] <= '\u0020');
int end = limit;
while (value[--end] <= '\u0020'){// @WCA loop=MAX_STRING_SIZE
;
}
return substring(begin - 1, end + 1);
}
/*
* "true" and "false" strings live in Immortal. If the result of this method
* is assigned to a non-immortal String we have an illegal reference
*/
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
/* Private constructor avoids temporary array */
String s = new String(1);
s.value[0] = c;
return s;
}
public static String valueOf(char[] data) {
return valueOf(data, 0, data.length);
}
public static String valueOf(char[] data, int offset, int count) {
return new String(data, offset, count);
}
public static String valueOf(int i) {
// See Integer to understand why we call the two-arg variant.
return Integer.toString(i, 10);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(Object obj) {
return obj == null ? "null" : obj.toString();
}
/**
* Returns the value array of the given string if it is zero based or a copy
* of it that is zero based (stripping offset and making length equal to
* count). Used for accessing the char[]s of gnu.java.lang.CharData. Package
* private for use in Character.
*/
// what is this used for???
// it's not in the JDK
// static char[] zeroBasedStringValue(String s) {
// char[] value;
//
// if ( s.count == s.value.length)
// value = s.value;
// else {
// int count = s.count;
// value = new char[count];
// // VMSystem.arraycopy(s.value, s.offset, value, 0, count);
// System.arraycopy(s.value, 0, value, 0, count);
//
// }
//
// return value;
// }
// this is needed for the collection classes
public boolean equalsIgnoreCase(String b) {
// avoid NullPointerExceptions
if (b == null)
return false;
return toUpperCase().equals(b.toUpperCase());
}
/** @since 1.5 */
public static String format(String format, Object... args)
{
return format + " String.format() not implemented";
}
/**
* Returns a String that represents the character sequence in the array
* specified.
*
* @param data
* the character array.
* @param offset
* initial offset of the subarray.
* @param count
* length of the subarray.
* @return a <code>String</code> that contains the characters of the
* specified subarray of the character array.
*/
public static String copyValueOf(char data[], int offset, int count) {
// All public String constructors now copy the data.
return new String(data, offset, count);
}
}