// natString.cc - Implementation of java.lang.String native methods. /* Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation This file is part of libgcj. This software is copyrighted work licensed under the terms of the Libgcj License. Please consult the file "LIBGCJ_LICENSE" for details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void unintern (jobject); static jstring* strhash = NULL; static int strhash_count = 0; /* Number of slots used in strhash. */ static int strhash_size = 0; /* Number of slots available in strhash. * Assumed be power of 2! */ // Some defines used by toUpperCase / toLowerCase. #define ESSET 0x00df #define CAPITAL_S 0x0053 #define SMALL_I 0x0069 #define CAPITAL_I_WITH_DOT 0x0130 #define SMALL_DOTLESS_I 0x0131 #define CAPITAL_I 0x0049 #define DELETED_STRING ((jstring)(~0)) #define SET_STRING_IS_INTERNED(STR) /* nothing */ #define UNMASK_PTR(Ptr) (((unsigned long) (Ptr)) & ~0x01) #define MASK_PTR(Ptr) (((unsigned long) (Ptr)) | 0x01) #define PTR_MASKED(Ptr) (((unsigned long) (Ptr)) & 0x01) /* Find a slot where the string with elements DATA, length LEN, and hash HASH should go in the strhash table of interned strings. */ jstring* _Jv_StringFindSlot (jchar* data, jint len, jint hash) { JvSynchronize sync (&StringClass); int start_index = hash & (strhash_size - 1); int deleted_index = -1; int index = start_index; /* step must be non-zero, and relatively prime with strhash_size. */ int step = 8 * hash + 7; for (;;) { jstring* ptr = &strhash[index]; jstring value = (jstring) UNMASK_PTR (*ptr); if (value == NULL) { if (deleted_index >= 0) return (&strhash[deleted_index]); else return ptr; } else if (*ptr == DELETED_STRING) deleted_index = index; else if (value->length() == len && memcmp(JvGetStringChars(value), data, 2*len) == 0) return (ptr); index = (index + step) & (strhash_size - 1); JvAssert (index != start_index); } } /* Calculate a hash code for the string starting at PTR at given LENGTH. This uses the same formula as specified for java.lang.String.hash. */ static jint hashChars (jchar* ptr, jint length) { jchar* limit = ptr + length; jint hash = 0; // Updated specification from // http://www.javasoft.com/docs/books/jls/clarify.html. while (ptr < limit) hash = (31 * hash) + *ptr++; return hash; } jint java::lang::String::hashCode() { return hashChars(JvGetStringChars(this), length()); } jstring* _Jv_StringGetSlot (jstring str) { jchar* data = JvGetStringChars(str); int length = str->length(); return _Jv_StringFindSlot(data, length, hashChars (data, length)); } void java::lang::String::rehash() { JvSynchronize sync (&StringClass); if (strhash == NULL) { strhash_size = 1024; strhash = (jstring *) _Jv_AllocBytesChecked (strhash_size * sizeof (jstring)); memset (strhash, 0, strhash_size * sizeof (jstring)); } else { int i = strhash_size; jstring* ptr = strhash + i; int nsize = strhash_size * 2; jstring *next = (jstring *) _Jv_AllocBytesChecked (nsize * sizeof (jstring)); memset (next, 0, nsize * sizeof (jstring)); while (--i >= 0) { --ptr; if (*ptr == NULL || *ptr == DELETED_STRING) continue; /* This is faster equivalent of * *__JvGetInternSlot(*ptr) = *ptr; */ jstring val = (jstring) UNMASK_PTR (*ptr); jint hash = val->hashCode(); jint index = hash & (nsize - 1); jint step = 8 * hash + 7; for (;;) { if (next[index] == NULL) { next[index] = *ptr; break; } index = (index + step) & (nsize - 1); } } strhash_size = nsize; strhash = next; } } jstring java::lang::String::intern() { JvSynchronize sync (&StringClass); if (4 * strhash_count >= 3 * strhash_size) rehash(); jstring* ptr = _Jv_StringGetSlot(this); if (*ptr != NULL && *ptr != DELETED_STRING) { // See description in unintern() to understand this. *ptr = (jstring) MASK_PTR (*ptr); return (jstring) UNMASK_PTR (*ptr); } jstring str = this->data == this ? this : _Jv_NewString(JvGetStringChars(this), this->length()); SET_STRING_IS_INTERNED(str); strhash_count++; *ptr = str; // When string is GC'd, clear the slot in the hash table. _Jv_RegisterFinalizer ((void *) str, unintern); return str; } /* Called by String fake finalizer. */ static void unintern (jobject obj) { JvSynchronize sync (&StringClass); jstring str = reinterpret_cast (obj); jstring* ptr = _Jv_StringGetSlot(str); if (*ptr == NULL || *ptr == DELETED_STRING) return; // We assume the lowest bit of the pointer is free for our nefarious // manipulations. What we do is set it to `0' (implicitly) when // interning the String. If we subsequently re-intern the same // String, then we set the bit. When finalizing, if the bit is set // then we clear it and re-register the finalizer. We know this is // a safe approach because both the intern() and unintern() acquire // the class lock; this bit can't be manipulated when the lock is // not held. So if we are finalizing and the bit is clear then we // know all references are gone and we can clear the entry in the // hash table. The naive approach of simply clearing the pointer // here fails in the case where a request to intern a new string // with the same contents is made between the time the intern()d // string is found to be unreachable and when the finalizer is // actually run. In this case we could clear a pointer to a valid // string, and future intern() calls for that particular value would // spuriously fail. if (PTR_MASKED (*ptr)) { *ptr = (jstring) UNMASK_PTR (*ptr); _Jv_RegisterFinalizer ((void *) obj, unintern); } else { *ptr = DELETED_STRING; strhash_count--; } } jstring _Jv_NewStringUTF (const char *bytes) { int size = strlen (bytes); unsigned char *p = (unsigned char *) bytes; int length = _Jv_strLengthUtf8 ((char *) p, size); if (length < 0) return NULL; jstring jstr = JvAllocString (length); jchar *chrs = JvGetStringChars (jstr); p = (unsigned char *) bytes; unsigned char *limit = p + size; while (p < limit) *chrs++ = UTF8_GET (p, limit); return jstr; } jstring _Jv_NewStringUtf8Const (Utf8Const* str) { jchar *chrs; jchar buffer[100]; jstring jstr; unsigned char* data = (unsigned char*) str->data; unsigned char* limit = data + str->length; int length = _Jv_strLengthUtf8(str->data, str->length); if (length <= (int) (sizeof(buffer) / sizeof(jchar))) { jstr = NULL; chrs = buffer; } else { jstr = JvAllocString(length); chrs = JvGetStringChars(jstr); } while (data < limit) *chrs++ = UTF8_GET(data, limit); chrs -= length; JvSynchronize sync (&StringClass); if (4 * strhash_count >= 3 * strhash_size) java::lang::String::rehash(); int hash = str->hash; jstring* ptr = _Jv_StringFindSlot (chrs, length, hash); if (*ptr != NULL && *ptr != DELETED_STRING) return (jstring) UNMASK_PTR (*ptr); strhash_count++; if (jstr == NULL) { jstr = JvAllocString(length); chrs = JvGetStringChars(jstr); memcpy (chrs, buffer, sizeof(jchar)*length); } *ptr = jstr; SET_STRING_IS_INTERNED(jstr); return jstr; } jsize _Jv_GetStringUTFLength (jstring string) { jsize len = 0; jchar *ptr = JvGetStringChars (string); jsize i = string->length(); while (--i >= 0) { jchar ch = *ptr++; if (ch > 0 && ch <= 0x7F) len += 1; else if (ch <= 0x7FF) len += 2; else len += 3; } return len; } // Not sure this quite matches GetStringUTFRegion. // null-termination of result? len? throw exception? jsize _Jv_GetStringUTFRegion (jstring str, jsize start, jsize len, char *buf) { jchar *sptr = JvGetStringChars (str) + start; jsize i = len; char *dptr = buf; while (--i >= 0) { jchar ch = *sptr++; if (ch > 0 && ch <= 0x7F) *dptr++ = (char) ch; else if (ch <= 0x7FF) { *dptr++ = (char) (0xC0 + ((ch >> 6) & 0x1F)); *dptr++ = (char) (0x80 + (ch & 0x3F)); } else { *dptr++ = (char) (0xE0 + ((ch >> 12) & 0xF)); *dptr++ = (char) (0x80 + ((ch >> 6) & 0x3F)); *dptr++ = (char) (0x80 + (ch & 0x3F)); } } return dptr - buf; } jstring _Jv_AllocString(jsize len) { jsize sz = sizeof(java::lang::String) + len * sizeof(jchar); jstring obj = (jstring) JvAllocObject(&StringClass, sz); obj->data = obj; obj->boffset = sizeof(java::lang::String); obj->count = len; return obj; } jstring _Jv_NewString(const jchar *chars, jsize len) { jstring str = _Jv_AllocString(len); jchar* data = JvGetStringChars (str); while (--len >= 0) *data++ = *chars++; return str; } jstring _Jv_NewStringLatin1(const char *bytes, jsize len) { jstring str = JvAllocString(len); jchar* data = JvGetStringChars (str); while (--len >= 0) *data++ = *(unsigned char*)bytes++; return str; } void java::lang::String::init () { count = 0; boffset = sizeof(java::lang::String); data = this; } void java::lang::String::init(jcharArray chars, jint offset, jint count, jboolean dont_copy) { if (! chars) throw new NullPointerException; jsize data_size = JvGetArrayLength (chars); if (offset < 0 || count < 0 || offset + count < 0 || offset + count > data_size) throw new StringIndexOutOfBoundsException; jcharArray array; jchar *pdst; if (! dont_copy) { array = JvNewCharArray(count); pdst = elements (array); memcpy (pdst, elements (chars) + offset, count * sizeof (jchar)); } else { JvAssert (offset == 0); array = chars; pdst = elements (array); } data = array; boffset = (char *) pdst - (char *) array; this->count = count; } void java::lang::String::init(jbyteArray ascii, jint hibyte, jint offset, jint count) { if (! ascii) throw new NullPointerException; jsize data_size = JvGetArrayLength (ascii); if (offset < 0 || count < 0 || offset + count < 0 || offset + count > data_size) throw new java::lang::StringIndexOutOfBoundsException; jcharArray array = JvNewCharArray(count); jbyte *psrc = elements (ascii) + offset; jchar *pdst = elements (array); data = array; boffset = (char *) pdst - (char *) array; this->count = count; hibyte = (hibyte & 0xff) << 8; while (-- count >= 0) { *pdst++ = hibyte | (*psrc++ & 0xff); } } void java::lang::String::init (jbyteArray bytes, jint offset, jint count, jstring encoding) { if (! bytes) throw new NullPointerException; jsize data_size = JvGetArrayLength (bytes); if (offset < 0 || count < 0 || offset + count < 0 || offset + count > data_size) throw new StringIndexOutOfBoundsException; jcharArray array = JvNewCharArray (count); gnu::gcj::convert::BytesToUnicode *converter = gnu::gcj::convert::BytesToUnicode::getDecoder(encoding); jint outpos = 0; int avail = count; converter->setInput(bytes, offset, offset+count); while (converter->inpos < converter->inlength) { int done = converter->read(array, outpos, avail); if (done == 0) { jint new_size = 2 * (outpos + avail); jcharArray new_array = JvNewCharArray (new_size); memcpy (elements (new_array), elements (array), outpos * sizeof(jchar)); array = new_array; avail = new_size - outpos; } else { outpos += done; avail -= done; } } this->data = array; this->boffset = (char *) elements (array) - (char *) array; this->count = outpos; } jboolean java::lang::String::equals(jobject anObject) { if (anObject == NULL) return false; if (anObject == this) return true; if (anObject->getClass() != &StringClass) return false; jstring other = (jstring) anObject; if (count != other->count) return false; /* if both are interned, return false. */ jint i = count; jchar *xptr = JvGetStringChars (this); jchar *yptr = JvGetStringChars (other); while (--i >= 0) { if (*xptr++ != *yptr++) return false; } return true; } jchar java::lang::String::charAt(jint i) { if (i < 0 || i >= count) throw new java::lang::StringIndexOutOfBoundsException; return JvGetStringChars(this)[i]; } void java::lang::String::getChars(jint srcBegin, jint srcEnd, jcharArray dst, jint dstBegin) { jint dst_length = JvGetArrayLength (dst); if (srcBegin < 0 || srcBegin > srcEnd || srcEnd > count || dstBegin < 0 || dstBegin + (srcEnd-srcBegin) > dst_length) throw new java::lang::StringIndexOutOfBoundsException; jchar *dPtr = elements (dst) + dstBegin; jchar *sPtr = JvGetStringChars (this) + srcBegin; jint i = srcEnd-srcBegin; while (--i >= 0) *dPtr++ = *sPtr++; } jbyteArray java::lang::String::getBytes (jstring enc) { jint todo = length(); jint buflen = todo; jbyteArray buffer = JvNewByteArray(todo); jint bufpos = 0; jint offset = 0; gnu::gcj::convert::UnicodeToBytes *converter = gnu::gcj::convert::UnicodeToBytes::getEncoder(enc); while (todo > 0) { converter->setOutput(buffer, bufpos); int converted = converter->write(this, offset, todo, NULL); bufpos = converter->count; if (converted == 0) { buflen *= 2; jbyteArray newbuffer = JvNewByteArray(buflen); memcpy (elements (newbuffer), elements (buffer), bufpos); buffer = newbuffer; } else { offset += converted; todo -= converted; } } if (bufpos == buflen) return buffer; jbyteArray result = JvNewByteArray(bufpos); memcpy (elements (result), elements (buffer), bufpos); return result; } void java::lang::String::getBytes(jint srcBegin, jint srcEnd, jbyteArray dst, jint dstBegin) { jint dst_length = JvGetArrayLength (dst); if (srcBegin < 0 || srcBegin > srcEnd || srcEnd > count || dstBegin < 0 || dstBegin + (srcEnd-srcBegin) > dst_length) throw new java::lang::StringIndexOutOfBoundsException; jbyte *dPtr = elements (dst) + dstBegin; jchar *sPtr = JvGetStringChars (this) + srcBegin; jint i = srcEnd-srcBegin; while (--i >= 0) *dPtr++ = (jbyte) *sPtr++; } jcharArray java::lang::String::toCharArray() { jcharArray array = JvNewCharArray(count); jchar *dPtr = elements (array); jchar *sPtr = JvGetStringChars (this); jint i = count; while (--i >= 0) *dPtr++ = *sPtr++; return array; } jboolean java::lang::String::equalsIgnoreCase (jstring anotherString) { if (anotherString == NULL || count != anotherString->count) return false; jchar *tptr = JvGetStringChars (this); jchar *optr = JvGetStringChars (anotherString); jint i = count; while (--i >= 0) { jchar tch = *tptr++; jchar och = *optr++; if (tch != och && (java::lang::Character::toLowerCase (tch) != java::lang::Character::toLowerCase (och)) && (java::lang::Character::toUpperCase (tch) != java::lang::Character::toUpperCase (och))) return false; } return true; } jboolean java::lang::String::regionMatches (jint toffset, jstring other, jint ooffset, jint len) { if (toffset < 0 || ooffset < 0 || toffset + len > count || ooffset + len > other->count) return false; jchar *tptr = JvGetStringChars (this) + toffset; jchar *optr = JvGetStringChars (other) + ooffset; jint i = len; while (--i >= 0) { if (*tptr++ != *optr++) return false; } return true; } jint java::lang::String::compareTo (jstring anotherString) { jchar *tptr = JvGetStringChars (this); jchar *optr = JvGetStringChars (anotherString); jint tlen = this->count; jint olen = anotherString->count; jint i = tlen > olen ? olen : tlen; while (--i >= 0) { jchar tch = *tptr++; jchar och = *optr++; if (tch != och) return (jint) tch - (jint) och; } return tlen - olen; } jboolean java::lang::String::regionMatches (jboolean ignoreCase, jint toffset, jstring other, jint ooffset, jint len) { if (toffset < 0 || ooffset < 0 || toffset + len > count || ooffset + len > other->count) return false; jchar *tptr = JvGetStringChars (this) + toffset; jchar *optr = JvGetStringChars (other) + ooffset; jint i = len; if (ignoreCase) while (--i >= 0) { jchar tch = *tptr++; jchar och = *optr++; if ((java::lang::Character::toLowerCase (tch) != java::lang::Character::toLowerCase (och)) && (java::lang::Character::toUpperCase (tch) != java::lang::Character::toUpperCase (och))) return false; } else while (--i >= 0) { jchar tch = *tptr++; jchar och = *optr++; if (tch != och) return false; } return true; } jboolean java::lang::String::startsWith (jstring prefix, jint toffset) { jint i = prefix->count; if (toffset < 0 || toffset + i > count) return false; jchar *xptr = JvGetStringChars (this) + toffset; jchar *yptr = JvGetStringChars (prefix); while (--i >= 0) { if (*xptr++ != *yptr++) return false; } return true; } jint java::lang::String::indexOf (jint ch, jint fromIndex) { if (fromIndex < 0) fromIndex = 0; jchar *ptr = JvGetStringChars(this); for (;; ++fromIndex) { if (fromIndex >= count) return -1; if (ptr[fromIndex] == ch) return fromIndex; } } jint java::lang::String::indexOf (jstring s, jint fromIndex) { const jchar *const xchars = JvGetStringChars(s); const jchar *const ychars = JvGetStringChars(this) + fromIndex; const int xlength = s->length (); const int ylength = length () - fromIndex; int i = 0; int j = 0; while (i < ylength && j < xlength) { if (xchars[j] != ychars[i]) { i = i - j + 1; j = 0; } else i++, j++; } if (j >= xlength) return fromIndex + i - xlength; else return -1; } jint java::lang::String::lastIndexOf (jint ch, jint fromIndex) { if (fromIndex >= count) fromIndex = count - 1; jchar *ptr = JvGetStringChars(this); for (;; --fromIndex) { if (fromIndex < 0) return -1; if (ptr[fromIndex] == ch) return fromIndex; } } jstring java::lang::String::substring (jint beginIndex, jint endIndex) { if (beginIndex < 0 || endIndex > count || beginIndex > endIndex) throw new StringIndexOutOfBoundsException; if (beginIndex == 0 && endIndex == count) return this; jint newCount = endIndex - beginIndex; if (newCount <= 8) // Optimization, mainly for GC. return JvNewString(JvGetStringChars(this) + beginIndex, newCount); jstring s = new String(); s->data = data; s->count = newCount; s->boffset = boffset + sizeof(jchar) * beginIndex; return s; } jstring java::lang::String::concat(jstring str) { jint str_count = str->count; if (str_count == 0) return this; jstring result = JvAllocString(count + str_count); jchar *dstPtr = JvGetStringChars(result); jchar *srcPtr = JvGetStringChars(this); jint i = count; while (--i >= 0) *dstPtr++ = *srcPtr++; srcPtr = JvGetStringChars(str); i = str->count; while (--i >= 0) *dstPtr++ = *srcPtr++; return result; } jstring java::lang::String::replace (jchar oldChar, jchar newChar) { jint i; jchar* chrs = JvGetStringChars (this); for (i = 0; ; i++) { if (i == count) return this; if (chrs[i] == oldChar) break; } jstring result = JvAllocString (count); jchar *dPtr = JvGetStringChars (result); for (int j = 0; j < i; j++) *dPtr++ = chrs[j]; for (; i < count; i++) { jchar ch = chrs[i]; if (ch == oldChar) ch = newChar; *dPtr++ = ch; } return result; } jstring java::lang::String::toLowerCase (java::util::Locale *locale) { jint i; jchar* chrs = JvGetStringChars(this); jchar ch = 0; bool handle_tr = false; if (locale != NULL) { String *lang = locale->getLanguage (); if (lang->length () == 2 && lang->charAt (0) == 't' && lang->charAt (1) == 'r') handle_tr = true; } for (i = 0; ; i++) { if (i == count) return this; jchar origChar = chrs[i]; if (handle_tr && (origChar == CAPITAL_I || origChar == CAPITAL_I_WITH_DOT)) break; ch = java::lang::Character::toLowerCase(origChar); if (ch != origChar) break; } jstring result = JvAllocString(count); jchar *dPtr = JvGetStringChars (result); for (int j = 0; j < i; j++) *dPtr++ = chrs[j]; *dPtr++ = ch; i++; for (; i < count; i++) { if (handle_tr && chrs[i] == CAPITAL_I) *dPtr++ = SMALL_DOTLESS_I; else if (handle_tr && chrs[i] == CAPITAL_I_WITH_DOT) *dPtr++ = SMALL_I; else *dPtr++ = java::lang::Character::toLowerCase(chrs[i]); } return result; } jstring java::lang::String::toUpperCase (java::util::Locale *locale) { jint i; jchar* chrs = JvGetStringChars(this); jchar ch; // When handling a specific locale there might be special rules. // Currently all existing rules are simply handled inline, as there // are only two and they are documented in the online 1.2 docs. bool handle_esset = locale != NULL; bool handle_tr = false; if (locale != NULL) { String *lang = locale->getLanguage (); if (lang->length () == 2 && lang->charAt (0) == 't' && lang->charAt (1) == 'r') handle_tr = true; } int new_count = count; bool new_string = false; for (i = 0; ; i++) { if (i == count) break; jchar origChar = chrs[i]; if (handle_esset && origChar == ESSET) { ++new_count; new_string = true; } else if (handle_tr && (origChar == SMALL_I || origChar == SMALL_DOTLESS_I)) new_string = true; else { ch = java::lang::Character::toUpperCase(origChar); if (ch != origChar) new_string = true; } if (new_string && ! handle_esset) break; } if (! new_string) return this; jstring result = JvAllocString(new_count); jchar *dPtr = JvGetStringChars (result); for (i = 0; i < count; i++) { if (handle_esset && chrs[i] == ESSET) { *dPtr++ = CAPITAL_S; *dPtr++ = CAPITAL_S; } else if (handle_tr && chrs[i] == SMALL_I) *dPtr++ = CAPITAL_I_WITH_DOT; else if (handle_tr && chrs[i] == SMALL_DOTLESS_I) *dPtr++ = CAPITAL_I; else *dPtr++ = java::lang::Character::toUpperCase(chrs[i]); } return result; } jstring java::lang::String::trim () { jchar* chrs = JvGetStringChars(this); if (count == 0 || (chrs[0] > ' ' && chrs[count-1] > ' ')) return this; jint preTrim = 0; for (;; preTrim++) { if (preTrim == count) return new String(); if (chrs[preTrim] > ' ') break; } jint endTrim = count; while (chrs[endTrim-1] <= ' ') endTrim--; return substring(preTrim, endTrim); } jstring java::lang::String::valueOf(jcharArray data, jint offset, jint count) { jint data_length = JvGetArrayLength (data); if (offset < 0 || count < 0 || offset+count > data_length) throw new java::lang::IndexOutOfBoundsException; jstring result = JvAllocString(count); jchar *sPtr = elements (data) + offset; jchar *dPtr = JvGetStringChars(result); while (--count >= 0) *dPtr++ = *sPtr++; return result; } jstring java::lang::String::valueOf(jchar c) { jstring result = JvAllocString(1); JvGetStringChars (result)[0] = c; return result; }